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">
import { toast } from 'svelte-sonner';
import { Pane, PaneGroup, PaneResizer } from 'paneforge';
import { onDestroy, onMount, tick } from 'svelte';
import { goto } from '$app/navigation';
@ -9,6 +11,9 @@
import Messages from './Messages.svelte';
import MessageInput from './MessageInput.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 = '';
@ -20,6 +25,8 @@
let channel = null;
let messages = null;
let threadId = null;
let typingUsers = [];
let typingUsersTimeout = {};
@ -150,12 +157,28 @@
});
};
let mediaQuery;
let largeScreen = false;
onMount(() => {
if ($chatId) {
chatId.set('');
}
$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(() => {
@ -173,40 +196,98 @@
: ''} w-full max-w-full flex flex-col"
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">
{#if channel}
<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"
id="messages-container"
bind:this={messagesContainerElement}
on:scroll={(e) => {
scrollEnd = Math.abs(messagesContainerElement.scrollTop) <= 50;
}}
<div class="flex-1 overflow-y-auto">
{#if channel}
<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"
id="messages-container"
bind:this={messagesContainerElement}
on:scroll={(e) => {
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}
<Messages
<div class="z-10 flex h-7 w-5 items-center justify-center rounded-sm">
<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}
{messages}
{top}
onLoad={async () => {
const newMessages = await getChannelMessages(localStorage.token, id, messages.length);
messages = [...messages, ...newMessages];
if (newMessages.length < 50) {
top = true;
return;
}
onClose={() => {
threadId = null;
}}
/>
{/key}
</div>
</div>
</Pane>
{/if}
</div>
<div class=" pb-[1rem]">
<MessageInput {typingUsers} {onChange} onSubmit={submitHandler} {scrollToBottom} {scrollEnd} />
</div>
</PaneGroup>
</div>

View File

@ -25,6 +25,7 @@
export let top = false;
export let onLoad: Function = () => {};
export let onThread: Function = () => {};
let messagesLoading = false;
@ -118,6 +119,9 @@
return null;
});
}}
onThread={(id) => {
onThread(id);
}}
onReaction={(name) => {
if (
(message?.reactions ?? [])
@ -127,7 +131,16 @@
) {
messages = messages.map((m) => {
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;
});

View File

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