feat: Implement chat pagination, participant single page with mutations, and fix user link substitutions

This commit is contained in:
Daan Meijer
2026-06-17 22:55:11 +02:00
parent 11df4ef55c
commit ed16d5dcda
12 changed files with 268 additions and 43 deletions
+40 -12
View File
@@ -1,15 +1,15 @@
<script setup lang="ts">
import { ref, computed } from 'vue';
import { useForm, usePage } from '@inertiajs/vue3';
import { useForm, usePage, router } from '@inertiajs/vue3';
import { useEcho, echoIsConfigured, configureEcho } from '@laravel/echo-vue';
import { route } from 'ziggy-js';
import { Paperclip, Info } from '@lucide/vue';
import { ref, computed } from 'vue';
import { route } from 'ziggy-js';
const props = withDefaults(
defineProps<{
chat: {
id: number;
messages: Array<{
messages?: Array<{
id: number;
user: { id: number; name: string } | null;
content: string;
@@ -33,12 +33,36 @@ const props = withDefaults(
} | null;
}>;
dynamicId: number;
initialMessages: {
data: Array<any>;
next_page_url: string | null;
};
}>(),
{
participants: () => [],
}
);
const messages = ref(props.initialMessages.data.reverse());
const nextPageUrl = ref(props.initialMessages.next_page_url);
function loadMoreMessages() {
if (!nextPageUrl.value) {
return;
}
router.get(nextPageUrl.value, {}, {
preserveState: true,
preserveScroll: true,
only: ['messages'],
onSuccess: (page) => {
const newMessages = page.props.messages as { data: Array<any>; next_page_url: string | null };
messages.value = [...newMessages.data.reverse(), ...messages.value];
nextPageUrl.value = newMessages.next_page_url;
},
});
}
if (!echoIsConfigured()) {
configureEcho({
broadcaster: 'reverb',
@@ -63,11 +87,12 @@ const form = useForm({
});
useEcho(`chats.${props.chat.id}`, 'MessageSent', (e: any) => {
props.chat.messages.push(e.message);
messages.value.push(e.message);
});
const participantsById = computed(() => {
return props.participants.reduce(
const list = props.participants || [];
return list.reduce(
(acc, p) => {
acc[p.id] = p;
return acc;
@@ -130,7 +155,7 @@ function parseMessageContent(message: {
/[-\/\\^$*+?.()|[\]{}]/g,
'\\$&',
);
const nameRegex = new RegExp(`"${escapedName}"`, 'g');
content = content.replace(
nameRegex,
@@ -197,17 +222,20 @@ function closeLightbox() {
<div class="c-chat">
<h4 class="c-chat__title">Chat</h4>
<div class="c-chat__list">
<div v-if="nextPageUrl" class="c-chat__load-more">
<button @click="loadMoreMessages" class="c-chat__load-more-btn">
Load More
</button>
</div>
<div
v-for="message in chat.messages"
v-for="message in messages"
:key="message.id"
:class="[
'c-chat__message',
{
'c-chat__message--system': message.user === null,
'c-chat__message--own': isOwnMessage(message.user?.id),
'c-chat__message--other': message.user !== null && !isOwnMessage(
message.user?.id,
),
'c-chat__message--other': message.user !== null && !isOwnMessage(message.user?.id)
},
]"
>
@@ -276,7 +304,7 @@ function closeLightbox() {
</div>
</template>
</div>
<div v-if="chat.messages.length === 0" class="c-chat__empty">
<div v-if="messages.length === 0" class="c-chat__empty">
No messages yet.
</div>
</div>
+31
View File
@@ -0,0 +1,31 @@
<script setup lang="ts">
import { computed } from 'vue';
import { Link } from '@inertiajs/vue3';
import { route } from 'ziggy-js';
const props = defineProps<{
message: {
id: number;
user: { id: number; name: string } | null;
content: string;
created_at: string;
media?: Array<{
id: number;
url: string;
file_name: string;
mime_type: string;
}>;
};
}>();
const processedContent = computed(() => {
return props.message.content.replace(/<user:(\d+)>/g, (match, userId) => {
// This is a placeholder for a more robust user lookup
return `<a href="${route('users.show', userId)}" class="text-blue-500 hover:underline">@user${userId}</a>`;
});
});
</script>
<template>
<div v-html="processedContent"></div>
</template>
+10 -1
View File
@@ -1,5 +1,9 @@
<script setup lang="ts">
import { Link } from '@inertiajs/vue3';
import { route } from 'ziggy-js';
defineProps<{
dynamicId: number;
participants: Array<{
id: number;
name: string;
@@ -19,7 +23,12 @@ defineProps<{
:key="participant.id"
class="c-participants-list__item"
>
{{ participant.pivot.display_name ?? participant.name }}
<Link
:href="route('dynamics.users.show', [dynamicId, participant.id])"
class="block"
>
{{ participant.pivot.display_name ?? participant.name }}
</Link>
</li>
</ul>
</div>