<script lang="ts"> import { goto } from '$app/navigation'; import { socket, user } from '$lib/stores'; import { getChannelThreadMessages, sendMessage } from '$lib/apis/channels'; import XMark from '$lib/components/icons/XMark.svelte'; import MessageInput from './MessageInput.svelte'; import Messages from './Messages.svelte'; import { onDestroy, onMount, tick } from 'svelte'; import { toast } from 'svelte-sonner'; export let threadId = null; export let channel = null; export let onClose = () => {}; let messages = null; let top = false; let typingUsers = []; let typingUsersTimeout = {}; let messagesContainerElement = null; $: if (threadId) { initHandler(); } const scrollToBottom = () => { messagesContainerElement.scrollTop = messagesContainerElement.scrollHeight; }; const initHandler = async () => { messages = null; top = false; typingUsers = []; typingUsersTimeout = {}; if (channel) { messages = await getChannelThreadMessages(localStorage.token, channel.id, threadId); if (messages.length < 50) { top = true; } await tick(); scrollToBottom(); } else { goto('/'); } }; const channelEventHandler = async (event) => { console.log(event); if (event.channel_id === channel.id) { const type = event?.data?.type ?? null; const data = event?.data?.data ?? null; if (type === 'message') { if ((data?.parent_id ?? null) === threadId) { if (messages) { messages = [data, ...messages]; if (typingUsers.find((user) => user.id === event.user.id)) { typingUsers = typingUsers.filter((user) => user.id !== event.user.id); } } } } else if (type === 'message:update') { if (messages) { const idx = messages.findIndex((message) => message.id === data.id); if (idx !== -1) { messages[idx] = data; } } } else if (type === 'message:delete') { if (messages) { messages = messages.filter((message) => message.id !== data.id); } } else if (type.includes('message:reaction')) { if (messages) { const idx = messages.findIndex((message) => message.id === data.id); if (idx !== -1) { messages[idx] = data; } } } else if (type === 'typing' && event.message_id === threadId) { if (event.user.id === $user.id) { return; } typingUsers = data.typing ? [ ...typingUsers, ...(typingUsers.find((user) => user.id === event.user.id) ? [] : [ { id: event.user.id, name: event.user.name } ]) ] : typingUsers.filter((user) => user.id !== event.user.id); if (typingUsersTimeout[event.user.id]) { clearTimeout(typingUsersTimeout[event.user.id]); } typingUsersTimeout[event.user.id] = setTimeout(() => { typingUsers = typingUsers.filter((user) => user.id !== event.user.id); }, 5000); } } }; const submitHandler = async ({ content, data }) => { if (!content) { return; } const res = await sendMessage(localStorage.token, channel.id, { parent_id: threadId, content: content, data: data }).catch((error) => { toast.error(error); return null; }); }; const onChange = async () => { $socket?.emit('channel-events', { channel_id: channel.id, message_id: threadId, data: { type: 'typing', data: { typing: true } } }); }; onMount(() => { $socket?.on('channel-events', channelEventHandler); }); onDestroy(() => { $socket?.off('channel-events', channelEventHandler); }); </script> {#if channel} <div class="flex flex-col w-full h-full bg-gray-50 dark:bg-gray-850"> <div class="flex items-center justify-between px-3.5 pt-3"> <div class=" font-medium text-lg">Thread</div> <div> <button class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300 p-2" on:click={() => { onClose(); }} > <XMark /> </button> </div> </div> <div class=" max-h-full w-full overflow-y-auto pt-3" bind:this={messagesContainerElement}> <Messages id={threadId} {channel} {messages} {top} thread={true} onLoad={async () => { const newMessages = await getChannelThreadMessages( localStorage.token, channel.id, threadId, messages.length ); messages = [...messages, ...newMessages]; if (newMessages.length < 50) { top = true; return; } }} /> <div class=" pb-[1rem]"> <MessageInput id={threadId} {typingUsers} {onChange} onSubmit={submitHandler} /> </div> </div> </div> {/if}