This commit is contained in:
Timothy Jaeryang Baek 2024-12-30 23:48:55 -08:00
parent 9d39404e6c
commit 2840ff405b
4 changed files with 167 additions and 42 deletions

View File

@ -1,5 +1,7 @@
<script lang="ts"> <script lang="ts">
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import { Pane, PaneGroup, PaneResizer } from 'paneforge';
import { onDestroy, onMount, tick } from 'svelte'; import { onDestroy, onMount, tick } from 'svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
@ -9,6 +11,9 @@
import Messages from './Messages.svelte'; import Messages from './Messages.svelte';
import MessageInput from './MessageInput.svelte'; import MessageInput from './MessageInput.svelte';
import Navbar from './Navbar.svelte'; import Navbar from './Navbar.svelte';
import Drawer from '../common/Drawer.svelte';
import EllipsisVertical from '../icons/EllipsisVertical.svelte';
import Thread from './Messages/Thread.svelte';
export let id = ''; export let id = '';
@ -20,6 +25,8 @@
let channel = null; let channel = null;
let messages = null; let messages = null;
let threadId = null;
let typingUsers = []; let typingUsers = [];
let typingUsersTimeout = {}; let typingUsersTimeout = {};
@ -150,12 +157,28 @@
}); });
}; };
let mediaQuery;
let largeScreen = false;
onMount(() => { onMount(() => {
if ($chatId) { if ($chatId) {
chatId.set(''); chatId.set('');
} }
$socket?.on('channel-events', channelEventHandler); $socket?.on('channel-events', channelEventHandler);
mediaQuery = window.matchMedia('(min-width: 1024px)');
const handleMediaQuery = async (e) => {
if (e.matches) {
largeScreen = true;
} else {
largeScreen = false;
}
};
mediaQuery.addEventListener('change', handleMediaQuery);
handleMediaQuery(mediaQuery);
}); });
onDestroy(() => { onDestroy(() => {
@ -173,40 +196,98 @@
: ''} w-full max-w-full flex flex-col" : ''} w-full max-w-full flex flex-col"
id="channel-container" id="channel-container"
> >
<Navbar {channel} /> <PaneGroup direction="horizontal" class="w-full h-full">
<Pane defaultSize={50} minSize={50} class="h-full flex flex-col w-full relative">
<Navbar {channel} />
<div class="flex-1 overflow-y-auto"> <div class="flex-1 overflow-y-auto">
{#if channel} {#if channel}
<div <div
class=" pb-2.5 max-w-full z-10 scrollbar-hidden w-full h-full pt-6 flex-1 flex flex-col-reverse overflow-auto" class=" pb-2.5 max-w-full z-10 scrollbar-hidden w-full h-full pt-6 flex-1 flex flex-col-reverse overflow-auto"
id="messages-container" id="messages-container"
bind:this={messagesContainerElement} bind:this={messagesContainerElement}
on:scroll={(e) => { on:scroll={(e) => {
scrollEnd = Math.abs(messagesContainerElement.scrollTop) <= 50; scrollEnd = Math.abs(messagesContainerElement.scrollTop) <= 50;
}} }}
>
{#key id}
<Messages
{channel}
{messages}
{top}
onThread={(id) => {
threadId = id;
}}
onLoad={async () => {
const newMessages = await getChannelMessages(
localStorage.token,
id,
messages.length
);
messages = [...messages, ...newMessages];
if (newMessages.length < 50) {
top = true;
return;
}
}}
/>
{/key}
</div>
{/if}
</div>
<div class=" pb-[1rem]">
<MessageInput
{typingUsers}
{onChange}
onSubmit={submitHandler}
{scrollToBottom}
{scrollEnd}
/>
</div>
</Pane>
{#if !largeScreen}
{#if threadId !== null}
<Drawer
show={threadId !== null}
on:close={() => {
threadId = null;
}}
>
<div class=" {threadId !== null ? ' h-screen w-screen' : 'px-6 py-4'} h-full">
<Thread
{threadId}
{channel}
onClose={() => {
threadId = null;
}}
/>
</div>
</Drawer>
{/if}
{:else if threadId !== null}
<PaneResizer
class="relative flex w-[3px] items-center justify-center bg-background group bg-gray-50 dark:bg-gray-850"
> >
{#key id} <div class="z-10 flex h-7 w-5 items-center justify-center rounded-sm">
<Messages <EllipsisVertical className="size-4 invisible group-hover:visible" />
</div>
</PaneResizer>
<Pane defaultSize={50} minSize={20} class="h-full w-full">
<div class="h-full w-full shadow-xl">
<Thread
{threadId}
{channel} {channel}
{messages} onClose={() => {
{top} threadId = null;
onLoad={async () => {
const newMessages = await getChannelMessages(localStorage.token, id, messages.length);
messages = [...messages, ...newMessages];
if (newMessages.length < 50) {
top = true;
return;
}
}} }}
/> />
{/key} </div>
</div> </Pane>
{/if} {/if}
</div> </PaneGroup>
<div class=" pb-[1rem]">
<MessageInput {typingUsers} {onChange} onSubmit={submitHandler} {scrollToBottom} {scrollEnd} />
</div>
</div> </div>

View File

@ -25,6 +25,7 @@
export let top = false; export let top = false;
export let onLoad: Function = () => {}; export let onLoad: Function = () => {};
export let onThread: Function = () => {};
let messagesLoading = false; let messagesLoading = false;
@ -118,6 +119,9 @@
return null; return null;
}); });
}} }}
onThread={(id) => {
onThread(id);
}}
onReaction={(name) => { onReaction={(name) => {
if ( if (
(message?.reactions ?? []) (message?.reactions ?? [])
@ -127,7 +131,16 @@
) { ) {
messages = messages.map((m) => { messages = messages.map((m) => {
if (m.id === message.id) { if (m.id === message.id) {
m.reactions = m.reactions.filter((reaction) => reaction.name !== name); const reaction = m.reactions.find((reaction) => reaction.name === name);
if (reaction) {
reaction.user_ids = reaction.user_ids.filter((id) => id !== $user.id);
reaction.count = reaction.user_ids.length;
if (reaction.count === 0) {
m.reactions = m.reactions.filter((r) => r.name !== name);
}
}
} }
return m; return m;
}); });

View File

@ -35,6 +35,7 @@
export let onDelete: Function = () => {}; export let onDelete: Function = () => {};
export let onEdit: Function = () => {}; export let onEdit: Function = () => {};
export let onThread: Function = () => {};
export let onReaction: Function = () => {}; export let onReaction: Function = () => {};
let showButtons = false; let showButtons = false;
@ -100,17 +101,18 @@
</Tooltip> </Tooltip>
</ReactionPicker> </ReactionPicker>
<Tooltip content={$i18n.t('Reply in Thread')}> {#if message?.parent_id === null}
<button <Tooltip content={$i18n.t('Reply in Thread')}>
class="hover:bg-gray-100 dark:hover:bg-gray-800 transition rounded-lg p-1" <button
on:click={() => { class="hover:bg-gray-100 dark:hover:bg-gray-800 transition rounded-lg p-1"
edit = true; on:click={() => {
editedContent = message.content; onThread(message.id);
}} }}
> >
<ChatBubbleOvalEllipsis /> <ChatBubbleOvalEllipsis />
</button> </button>
</Tooltip> </Tooltip>
{/if}
<Tooltip content={$i18n.t('Edit')}> <Tooltip content={$i18n.t('Edit')}>
<button <button
@ -288,6 +290,7 @@
].toLowerCase()}.svg" ].toLowerCase()}.svg"
alt={reaction.name} alt={reaction.name}
class=" size-4" class=" size-4"
loading="lazy"
/> />
{:else} {:else}
<div> <div>

View File

@ -0,0 +1,28 @@
<script lang="ts">
import XMark from '$lib/components/icons/XMark.svelte';
export let threadId = null;
export let channel = null;
export let onClose = () => {};
</script>
<div class="flex flex-col w-full h-full bg-gray-50 dark:bg-gray-900 px-3.5 py-3">
<div class="flex items-center justify-between">
<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>
{threadId}
{channel}
</div>