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