feat: Implement chat pagination, participant single page with mutations, and fix user link substitutions
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user