Spaces:
Running
Running
<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} | |