From ba9bdc77aae9cd2e4059d3118e3627e9138229bf Mon Sep 17 00:00:00 2001 From: Florian <45694132+flo-bit@users.noreply.github.com> Date: Sat, 24 May 2025 02:07:12 +0200 Subject: [PATCH] add replies --- src/hooks.server.ts | 12 ++ src/lib/components/ChannelButton.svelte | 1 - src/lib/components/ChatInput.svelte | 83 ++++++++++ src/lib/components/ChatMessage.svelte | 149 ++++++------------ src/lib/components/ChatMessageMenu.svelte | 79 ++++++++++ src/lib/components/ChatReactions.svelte | 50 ++++++ src/lib/components/MessageInput.svelte | 0 src/lib/components/ReplyMessage.svelte | 36 +++++ .../components/layout/sidebar/Sidebar.svelte | 2 +- .../rich-text-editor/RichTextEditor.svelte | 2 - src/lib/context.ts | 18 +++ src/routes/[spaceId]/+layout.svelte | 11 ++ src/routes/[spaceId]/+page.svelte | 59 ++++--- .../channel/[channelId]/+page.svelte | 87 ++++------ 14 files changed, 400 insertions(+), 189 deletions(-) create mode 100644 src/hooks.server.ts create mode 100644 src/lib/components/ChatInput.svelte create mode 100644 src/lib/components/ChatMessageMenu.svelte create mode 100644 src/lib/components/ChatReactions.svelte delete mode 100644 src/lib/components/MessageInput.svelte create mode 100644 src/lib/components/ReplyMessage.svelte create mode 100644 src/lib/context.ts diff --git a/src/hooks.server.ts b/src/hooks.server.ts new file mode 100644 index 0000000..205e631 --- /dev/null +++ b/src/hooks.server.ts @@ -0,0 +1,12 @@ +// hooks.server.ts +import type { Handle } from '@sveltejs/kit'; + +export const handle: Handle = async ({ event, resolve }) => { + // Suppress well-known Chrome DevTools requests + + if (event.url.pathname.startsWith('/.well-known/appspecific/com.chrome.devtools')) { + return new Response(null, { status: 204 }); // Return empty response with 204 No Content + } + + return await resolve(event); +}; diff --git a/src/lib/components/ChannelButton.svelte b/src/lib/components/ChannelButton.svelte index 2a76fda..48b899c 100644 --- a/src/lib/components/ChannelButton.svelte +++ b/src/lib/components/ChannelButton.svelte @@ -19,7 +19,6 @@ let isNew = $derived.by(() => { if (!lastReadDate) return true; - console.log('hello'); if (!channel.mainThread?.timeline) return false; let date = channel.mainThread.timeline[channel.mainThread.timeline.length - 1]?.createdAt; if (!date) return false; diff --git a/src/lib/components/ChatInput.svelte b/src/lib/components/ChatInput.svelte new file mode 100644 index 0000000..ed21eae --- /dev/null +++ b/src/lib/components/ChatInput.svelte @@ -0,0 +1,83 @@ + + +
+ {#if me?.root?.joinedSpaces?.some((space) => space?.id === spaceId)} + {#if replyTo} +
+
+ + + + + replying to + + {profile?.current?.name} + +
+ + +
+ {/if} + + + + {:else} +
+ +
+ {/if} +
diff --git a/src/lib/components/ChatMessage.svelte b/src/lib/components/ChatMessage.svelte index fc95143..ea60dcc 100644 --- a/src/lib/components/ChatMessage.svelte +++ b/src/lib/components/ChatMessage.svelte @@ -1,20 +1,24 @@ + +{#if hovering || open} +
+
+ + { + open = false; + + if (reactions.find((r) => r.emoji === emoji.unicode)?.user) { + removeReaction(emoji.unicode); + } else { + addReaction(emoji.unicode); + } + }} + bind:open + > + {#snippet child({ props })} + + {/snippet} + +
+
+{/if} diff --git a/src/lib/components/ChatReactions.svelte b/src/lib/components/ChatReactions.svelte new file mode 100644 index 0000000..032dc3f --- /dev/null +++ b/src/lib/components/ChatReactions.svelte @@ -0,0 +1,50 @@ + + +{#if reactions.length > 0} +
+ {#each reactions as reaction} + { + return reaction.user; + }, + (pressed) => { + if (pressed) { + addReaction(reaction.emoji); + } else { + removeReaction(reaction.emoji); + } + } + } + class="text-base-600 dark:text-base-400 text-sm" + size="sm" + > + {reaction.emoji} + {#if reaction.count > 1} + + {reaction.count} + + {/if} + + {/each} +
+{/if} diff --git a/src/lib/components/MessageInput.svelte b/src/lib/components/MessageInput.svelte deleted file mode 100644 index e69de29..0000000 diff --git a/src/lib/components/ReplyMessage.svelte b/src/lib/components/ReplyMessage.svelte new file mode 100644 index 0000000..16d7007 --- /dev/null +++ b/src/lib/components/ReplyMessage.svelte @@ -0,0 +1,36 @@ + + +{#if message.current} +
+ + + + + {#if profile?.current?.imageUrl} + + {/if} + + + {profile?.current?.name} + + + {@html message.current?.content} + +
+{/if} diff --git a/src/lib/components/layout/sidebar/Sidebar.svelte b/src/lib/components/layout/sidebar/Sidebar.svelte index da2e06a..40118ee 100644 --- a/src/lib/components/layout/sidebar/Sidebar.svelte +++ b/src/lib/components/layout/sidebar/Sidebar.svelte @@ -21,7 +21,7 @@ id="mobile-menu" class={cn( 'mobileMenuPopover border-base-200 bg-white/70 dark:border-base-300/10 dark:bg-base-950/50 my-2 ml-0 overflow-y-hidden rounded-r-2xl', - 'inset-auto left-0 top-0 block h-[calc(100dvh-1rem)] w-72 border shadow-lg lg:hidden backdrop-blur-xl', + 'inset-auto -left-1 top-0 block h-[calc(100dvh-1rem)] w-72 border shadow-lg lg:hidden backdrop-blur-xl', className, mobileClasses )} diff --git a/src/lib/components/rich-text-editor/RichTextEditor.svelte b/src/lib/components/rich-text-editor/RichTextEditor.svelte index 2ac6b18..c97b406 100644 --- a/src/lib/components/rich-text-editor/RichTextEditor.svelte +++ b/src/lib/components/rich-text-editor/RichTextEditor.svelte @@ -83,7 +83,6 @@ // } // }), initKeyboardShortcutHandler({ onEnter: onEnter }), - ]; editor = new Editor({ @@ -199,7 +198,6 @@
diff --git a/src/lib/context.ts b/src/lib/context.ts new file mode 100644 index 0000000..eb67f88 --- /dev/null +++ b/src/lib/context.ts @@ -0,0 +1,18 @@ +import type { Loaded } from 'jazz-tools'; +import { getContext } from 'svelte'; +import type { MyAppAccount, Space } from './schema'; + +export function useMe(): { current: Loaded } | undefined { + const me = getContext('me'); + return me as { current: Loaded }; +} + +export function useSpace(): { current: Loaded } | undefined { + const space = getContext('space'); + return space as { current: Loaded }; +} + +export function useSpaceId(): string { + const spaceId = getContext('spaceId'); + return spaceId as string; +} diff --git a/src/routes/[spaceId]/+layout.svelte b/src/routes/[spaceId]/+layout.svelte index 976c9fc..0e0afdd 100644 --- a/src/routes/[spaceId]/+layout.svelte +++ b/src/routes/[spaceId]/+layout.svelte @@ -22,6 +22,7 @@ import SpaceSelection from '$lib/SpaceSelection.svelte'; import ThemeSelectDropdown from '$lib/components/ThemeSelectDropdown.svelte'; import ChannelButton from '$lib/components/ChannelButton.svelte'; + import { setContext } from 'svelte'; let spaceId: string = $state(page.params.spaceId); @@ -43,6 +44,16 @@ } }); + setContext('me', me); + setContext('space', space); + setContext('spaceId', spaceId); + + $effect(() => { + setContext('space', space); + setContext('spaceId', spaceId); + setContext('me', me); + }); + afterNavigate(() => { spaceId = page.params.spaceId; }); diff --git a/src/routes/[spaceId]/+page.svelte b/src/routes/[spaceId]/+page.svelte index 75030be..4b5f285 100644 --- a/src/routes/[spaceId]/+page.svelte +++ b/src/routes/[spaceId]/+page.svelte @@ -3,54 +3,53 @@ import { base } from '$app/paths'; import { page } from '$app/state'; import ChannelButton from '$lib/components/ChannelButton.svelte'; + import { useMe, useSpace, useSpaceId } from '$lib/context'; import { MyAppAccount, Space } from '$lib/schema'; import { Button, Heading, Paragraph, Subheading } from '@fuxui/base'; import { AccountCoState, CoState } from 'jazz-svelte'; + import { getContext } from 'svelte'; - let spaceId: string = $state(page.params.spaceId); - - const me = new AccountCoState(MyAppAccount, { - resolve: { - profile: true, - root: true - } - }); - - let space = $derived( - new CoState(Space, spaceId, { - resolve: { - channels: { - $each: true, - $onError: null - } - } - }) - ); - - afterNavigate(() => { - spaceId = page.params.spaceId; - }); + // let spaceId: string = $state(page.params.spaceId); + + const me = useMe(); + const space = useSpace(); + const spaceId = useSpaceId(); + + // let space = $derived( + // new CoState(Space, spaceId, { + // resolve: { + // channels: { + // $each: true, + // $onError: null + // } + // } + // }) + // ); + + // afterNavigate(() => { + // spaceId = page.params.spaceId; + // }); - {#if space.current?.emoji} - {space.current?.emoji} + {#if space?.current?.emoji} + {space?.current?.emoji} {/if} - {space.current?.name} + {space?.current?.name} -{#if space.current?.description} +{#if space?.current?.description} About - {space.current?.description} + {space?.current?.description} {/if} Channels
- {#each space.current?.channels ?? [] as channel} + {#each space?.current?.channels ?? [] as channel} {#if channel} - + {/if} {/each}
diff --git a/src/routes/[spaceId]/channel/[channelId]/+page.svelte b/src/routes/[spaceId]/channel/[channelId]/+page.svelte index 2d82e41..7d05420 100644 --- a/src/routes/[spaceId]/channel/[channelId]/+page.svelte +++ b/src/routes/[spaceId]/channel/[channelId]/+page.svelte @@ -2,36 +2,23 @@ import { afterNavigate } from '$app/navigation'; import { base } from '$app/paths'; import { page } from '$app/state'; + import ChatInput from '$lib/components/ChatInput.svelte'; import ChatMessage from '$lib/components/ChatMessage.svelte'; import Container from '$lib/components/Container.svelte'; import { RichTextEditor } from '$lib/components/rich-text-editor'; + import { useMe, useSpace, useSpaceId } from '$lib/context'; import { Channel, LastReadList, Message, MyAppAccount, Reaction, Space } from '$lib/schema'; import { joinSpace, publicGroup } from '$lib/utils'; import { Button, Prose } from '@fuxui/base'; import { AccountCoState, CoState } from 'jazz-svelte'; - import { co, CoRichText } from 'jazz-tools'; + import { co, CoRichText, type Loaded } from 'jazz-tools'; import { onMount } from 'svelte'; let channelId = $state(page.params.channelId); - let spaceId = $state(page.params.spaceId); - const me = new AccountCoState(MyAppAccount, { - resolve: { - profile: true, - root: true - } - }); - - let space = $derived( - new CoState(Space, spaceId, { - resolve: { - channels: { - $each: true, - $onError: null - } - } - }) - ); + const me = useMe(); + const space = useSpace(); + const spaceId = useSpaceId(); let channel = $derived( new CoState(Channel, channelId, { @@ -40,9 +27,7 @@ timeline: { $each: { reactions: { - $each: { - emoji: true - } + $each: true } } } @@ -54,22 +39,18 @@ afterNavigate(() => { channelId = page.params.channelId; - spaceId = page.params.spaceId; - - console.log('afterNavigate', space.current); - console.log('afterNavigate', channel.current); setLastRead(); }); + function setLastRead() { - if(!me.current) { + if(!me?.current?.root) { console.log('no account'); return; } - if (me.current?.root.lastRead) { + if (me?.current?.root?.lastRead) { me.current.root.lastRead[channelId] = new Date(); - console.log('set last read', me.current.root.lastRead); } else { me.current.root.lastRead = LastReadList.create({ [channelId]: new Date() @@ -81,7 +62,6 @@ let count = $state(channel.current?.mainThread?.timeline?.length ?? 0); $effect(() => { - console.log('count', count); let newCount = channel.current?.mainThread?.timeline?.length ?? 0; if (count < newCount) { count = newCount; @@ -91,12 +71,11 @@ }); function handleSubmit() { - console.log('input', input); let newContent = new CoRichText({ text: input, owner: publicGroup() }); - console.log('newContent', newContent); + // add message to channel const message = Message.create( { @@ -109,6 +88,7 @@ reactions: co.list(Reaction).create([], { owner: publicGroup() }), + replyTo: replyTo?.id, type: 'message' }, { @@ -116,14 +96,14 @@ } ); - channel.current?.mainThread?.timeline?.push(message); + replyTo = null; - // clear message + channel.current?.mainThread?.timeline?.push(message); } async function clickJoinSpace() { - console.log('clickJoinSpace', space.current); - let toJoinSpace = space.current; + console.log('clickJoinSpace', space?.current); + let toJoinSpace = space?.current; if (!toJoinSpace) { toJoinSpace = await Space.load(spaceId); } @@ -134,18 +114,23 @@ } joinSpace(toJoinSpace); - me.current?.root.joinedSpaces?.unshift(toJoinSpace); + me?.current?.root?.joinedSpaces?.unshift(toJoinSpace); console.log('joined space', toJoinSpace); } let input = $state(''); + let replyTo = $state | null>(null); + + function setReplyTo(message: Loaded) { + replyTo = message; + }
{#each channel.current?.mainThread?.timeline ?? [] as message, index} {#if message} - + {/if} {/each} {#if channel.current?.mainThread?.timeline?.length === 0} @@ -155,20 +140,12 @@ {/if}
-
- {#if me.current?.root.joinedSpaces?.some((space) => space?.id === spaceId)} - - - - {:else} -
- -
- {/if} -
+ + \ No newline at end of file -- 2.51.2