From 09388c5effcd9ac9df134bba2484f3fb9ae3bb68 Mon Sep 17 00:00:00 2001 From: Florian <45694132+flo-bit@users.noreply.github.com> Date: Wed, 4 Jun 2025 15:42:30 +0200 Subject: [PATCH] switch to ids in timeline, removes old spaces --- package-lock.json | 38 +++++- package.json | 1 + src/lib/SpaceSelection.svelte | 2 +- src/lib/components/ChannelButton.svelte | 20 +++- src/lib/components/ChatMessage.svelte | 110 +++++++++++------- src/lib/components/ChatMessageThread.svelte | 19 ++- src/lib/components/Image.svelte | 43 +++++-- src/lib/components/ReplyMessage.svelte | 2 +- src/lib/components/SpaceItem.svelte | 14 ++- src/lib/components/ThreadButton.svelte | 15 ++- src/lib/components/TimelineView.svelte | 16 +-- src/lib/schema.ts | 12 +- src/lib/utils.ts | 30 ++++- src/routes/+page.svelte | 24 +++- src/routes/[spaceId]/+layout.svelte | 6 +- .../channel/[channelId]/+page.svelte | 71 ++++------- .../thread/[threadId]/+page.svelte | 34 +----- 17 files changed, 294 insertions(+), 163 deletions(-) diff --git a/package-lock.json b/package-lock.json index dcfb55e..7af4e3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "jazz-svelte": "^0.14.4", "jazz-tools": "^0.14.4", "paper": "^0.12.18", + "runed": "^0.28.0", "svelte-relative-time": "^0.0.6", "svelte-tiptap": "^2.1.0", "virtua": "^0.41.2", @@ -3075,6 +3076,21 @@ "svelte": "^5.11.0" } }, + "node_modules/bits-ui/node_modules/runed": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.23.4.tgz", + "integrity": "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA==", + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5759,13 +5775,14 @@ } }, "node_modules/runed": { - "version": "0.23.4", - "resolved": "https://registry.npmjs.org/runed/-/runed-0.23.4.tgz", - "integrity": "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.28.0.tgz", + "integrity": "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ==", "funding": [ "https://github.com/sponsors/huntabyte", "https://github.com/sponsors/tglide" ], + "license": "MIT", "dependencies": { "esm-env": "^1.0.0" }, @@ -6145,6 +6162,21 @@ "svelte": "^5.0.0" } }, + "node_modules/svelte-toolbelt/node_modules/runed": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.23.4.tgz", + "integrity": "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA==", + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", diff --git a/package.json b/package.json index 9a852a8..dfc5d9f 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "jazz-svelte": "^0.14.4", "jazz-tools": "^0.14.4", "paper": "^0.12.18", + "runed": "^0.28.0", "svelte-relative-time": "^0.0.6", "svelte-tiptap": "^2.1.0", "virtua": "^0.41.2", diff --git a/src/lib/SpaceSelection.svelte b/src/lib/SpaceSelection.svelte index 0fdb18a..1ef8699 100644 --- a/src/lib/SpaceSelection.svelte +++ b/src/lib/SpaceSelection.svelte @@ -74,7 +74,7 @@ {@const θ = i * angularWidth}
import { base } from '$app/paths'; import { page } from '$app/state'; - import { Channel } from '$lib/schema'; + import { Channel, Message } from '$lib/schema'; import { Button } from '@fuxui/base'; import type { Loaded } from 'jazz-tools'; import { useCurrentRoute } from '$lib/context'; - import { afterNavigate } from '$app/navigation'; + import { afterNavigate, goto } from '$app/navigation'; + import { CoState } from 'jazz-svelte'; const route = useCurrentRoute(); @@ -19,10 +20,20 @@ onclick?: () => void; } = $props(); + let lastMessage = $derived( + new CoState(Message, channel.mainThread?.timeline?.at(-1), { + resolve: { + content: true, + images: true, + reactions: true + } + }) + ); + let isNew = $derived.by(() => { if (!lastReadDate) return channel.mainThread?.timeline?.length !== 0; if (!channel.mainThread?.timeline) return false; - let date = channel.mainThread.timeline[channel.mainThread.timeline.length - 1]?.createdAt; + let date = lastMessage?.current?.createdAt; if (!date) return false; return new Date(lastReadDate) < date; @@ -35,6 +46,9 @@ href="/{route.spaceId}/channel/{channel.id}" class="relative w-full justify-start backdrop-blur-none" {onclick} + onmousedown={() => { + goto(`/${route.spaceId}/channel/${channel.id}`); + }} > ; - previousMessage?: Loaded; + messageId: string; + previousMessageId?: string; me: Loaded; setReplyTo: (message: Loaded) => void; createThread: (message: Loaded) => void; @@ -40,17 +40,39 @@ allowThreadCreation?: boolean; } = $props(); - let profile = $derived(new CoState(MyAppProfile, message._edits.content?.by?.profile?.id)); + let message = $derived( + new CoState(Message, messageId, { + resolve: { + content: true, + images: true, + reactions: true + } + }) + ); + let previousMessage = $derived(new CoState(Message, previousMessageId)); + + let profile = $derived( + new CoState(MyAppProfile, message.current?._edits.content?.by?.profile?.id, { + resolve: { + image: true + } + }) + ); // if the same user and the message was created in the last minute, don't show the border, username or avatar let isSameUser = $derived.by(() => { if (!previousMessage) return false; - if (previousMessage.softDeleted) return false; - if (previousMessage._edits.content?.by?.profile?.id !== message._edits.content?.by?.profile?.id) + if (previousMessage.current?.softDeleted) return false; + if ( + previousMessage.current?._edits.content?.by?.profile?.id !== + message.current?._edits.content?.by?.profile?.id + ) return false; - if (message.replyTo) return false; + if (message.current?.replyTo) return false; return ( - (message.createdAt?.getTime() ?? 0) - (previousMessage?.createdAt?.getTime() ?? 0) < 1000 * 60 + (message.current?.createdAt?.getTime() ?? 0) - + (previousMessage?.current?.createdAt?.getTime() ?? 0) < + 1000 * 60 ); }); @@ -62,13 +84,13 @@ if (!space.current) return false; if (isSpaceAdmin(space.current)) return true; // if is the same user - return message._edits.content?.by?.profile?.id === me.profile?.id; + return message.current?._edits.content?.by?.profile?.id === me.profile?.id; }); let pickerOpen = $state(false); let hovering = $state(false); - function convertReactionsToEmojis(reactions: Loaded[]) { + function convertReactionsToEmojis(reactions: Loaded[] | null | undefined) { if (!reactions) return []; // convert to [emoji, count, user (if current user has reacted with that emoji)] @@ -97,16 +119,16 @@ } function removeReaction(emoji: string) { - let index = message.reactions?.findIndex( + let index = message.current?.reactions?.findIndex( (reaction) => reaction?.emoji === emoji && reaction?._edits.emoji?.by?.profile?.id === me.profile?.id ); if (index === undefined || index < 0) return; - message.reactions?.splice(index, 1); + message.current?.reactions?.splice(index, 1); } function addReaction(emoji: string) { - message.reactions?.push( + message.current?.reactions?.push( Reaction.create( { emoji: emoji @@ -119,25 +141,26 @@ } function softDelete() { - message.softDeleted = true; + if (!message.current) return; + message.current.softDeleted = true; } - let reactions = $derived(convertReactionsToEmojis(message.reactions)); + let reactions = $derived(convertReactionsToEmojis(message.current?.reactions));
- {#if message.replyTo && showReply} - + {#if message.current?.replyTo && showReply} + {/if} - {#if view.highlightedMessage === message.id} + {#if view.highlightedMessage === message.current?.id}
@@ -148,27 +171,33 @@ onpointerover={() => (hovering = true)} onpointerleave={() => (hovering = false)} > - {#if !isSameUser} - + {#if !isSameUser || !message.current} + {:else}
{/if}
- {#if !isSameUser} + {#if !isSameUser || !message.current} - {profile?.current?.name} - + {profile?.current?.name ?? 'Loading...'} + {#if message.current?.createdAt} + + {/if} {/if} - {@html message.content} + {@html message.current?.content ?? 'Loading...'} {#if showReactions} @@ -178,31 +207,34 @@ {#if showMenu} setReplyTo(message)} + setAsReplyTo={() => setReplyTo(message.current)} {hovering} open={pickerOpen} {reactions} {addReaction} {removeReaction} {allowThreadCreation} - createThread={() => createThread(message)} + createThread={() => createThread(message.current)} {softDelete} {canSoftDelete} /> {/if}
- {#if message.images && message.images.length > 0} -
- {#each message.images as image} - + {#if message.current?.images && message.current?.images.length > 0} +
+ {#each message.current?.images as image} + {/each}
{/if} - {#if showThread && message.thread} + {#if showThread && message.current?.thread} {/if}
diff --git a/src/lib/components/ChatMessageThread.svelte b/src/lib/components/ChatMessageThread.svelte index bb87c23..5079770 100644 --- a/src/lib/components/ChatMessageThread.svelte +++ b/src/lib/components/ChatMessageThread.svelte @@ -1,6 +1,6 @@ - +
+ +
diff --git a/src/lib/components/ReplyMessage.svelte b/src/lib/components/ReplyMessage.svelte index 7e51f09..3971c29 100644 --- a/src/lib/components/ReplyMessage.svelte +++ b/src/lib/components/ReplyMessage.svelte @@ -55,7 +55,7 @@ {#if message.current.softDeleted} Message deleted {:else} - + {@html message.current?.content} {/if} diff --git a/src/lib/components/SpaceItem.svelte b/src/lib/components/SpaceItem.svelte index 8775a57..5359d06 100644 --- a/src/lib/components/SpaceItem.svelte +++ b/src/lib/components/SpaceItem.svelte @@ -1,4 +1,5 @@
  • - + { + goto(`/${space.id}`); + }} + >
    {space.emoji || '😀'}
    - + {space.name}
    diff --git a/src/lib/components/ThreadButton.svelte b/src/lib/components/ThreadButton.svelte index bb00f8e..2222e87 100644 --- a/src/lib/components/ThreadButton.svelte +++ b/src/lib/components/ThreadButton.svelte @@ -1,7 +1,7 @@ -
    +
    diff --git a/src/routes/[spaceId]/channel/[channelId]/+page.svelte b/src/routes/[spaceId]/channel/[channelId]/+page.svelte index 69d69b6..9722437 100644 --- a/src/routes/[spaceId]/channel/[channelId]/+page.svelte +++ b/src/routes/[spaceId]/channel/[channelId]/+page.svelte @@ -10,12 +10,13 @@ MyAppAccount, Reaction, Space, - Thread + Thread, + Timeline } from '$lib/schema'; - import { joinSpace, publicGroup } from '$lib/utils'; + import { createMessage, joinSpace, publicGroup } from '$lib/utils'; import { Button, Heading, Input, Modal } from '@fuxui/base'; import { AccountCoState, CoState } from 'jazz-svelte'; - import { co, CoRichText, type Loaded } from 'jazz-tools'; + import { co, CoRichText, z, type Loaded } from 'jazz-tools'; import { view } from '../../view.svelte'; import ChatMessageThread from '$lib/components/ChatMessageThread.svelte'; import TimelineView from '$lib/components/TimelineView.svelte'; @@ -23,16 +24,7 @@ const route = useCurrentRoute(); - let space = $derived( - new CoState(Space, route.spaceId, { - resolve: { - channels: { - $each: true, - $onError: null - } - } - }) - ); + let space = $derived(new CoState(Space, route.spaceId)); // onMount(() => { // setInterval(() => { @@ -55,13 +47,7 @@ new CoState(Channel, route.channelId, { resolve: { mainThread: { - timeline: { - $each: { - reactions: { - $each: true - } - } - } + timeline: true }, subThreads: true } @@ -108,35 +94,13 @@ owner: publicGroup() }); - console.log('submit'); - - console.log('postImages', postImages, postImages.length); - - // add message to channel - const message = Message.create( - { - content: newContent, - createdAt: new Date(), - updatedAt: new Date(), - images: co.list(co.image()).create([...postImages], { - owner: publicGroup() - }), - reactions: co.list(Reaction).create([], { - owner: publicGroup() - }), - replyTo: replyTo?.id, - type: 'message' - }, - { - owner: publicGroup() - } - ); + const message = createMessage(newContent, postImages, replyTo?.id); postImages = []; replyTo = null; - channel.current?.mainThread?.timeline?.push(message); + channel.current?.mainThread?.timeline?.push(message.id); } async function clickJoinSpace() { @@ -183,8 +147,8 @@ const thread = Thread.create( { - name: threadName || 'Unnamed Thread', - timeline: co.list(Message).create([threadMessage], { + name: threadName || 'New Thread', + timeline: Timeline.create([threadMessage.id], { owner: publicGroup() }) }, @@ -199,13 +163,24 @@ createThreadModalOpen = false; } + + // onMount(() => { + // // send a random message every second + // setInterval(() => { + // input = '

    ' + Math.random().toString(36).substring(2, 15) + '

    '; + // handleSubmit(); + + // // log number of messages + // console.log('number of messages', channel.current?.mainThread?.timeline?.length); + // }, 200); + // }); {#if view.active === 'channel'} { threadMessage = message; createThreadModalOpen = true; @@ -246,7 +221,7 @@
    import { afterNavigate } from '$app/navigation'; import ChatInput from '$lib/components/ChatInput.svelte'; - import ChatMessage from '$lib/components/ChatMessage.svelte'; import TimelineView from '$lib/components/TimelineView.svelte'; import { useCurrentRoute } from '$lib/context'; import { LastReadList, Message, MyAppAccount, Reaction, Space, Thread } from '$lib/schema'; - import { joinSpace, publicGroup } from '$lib/utils'; + import { createMessage, joinSpace, publicGroup } from '$lib/utils'; import { AccountCoState, CoState } from 'jazz-svelte'; import { co, CoRichText, type Loaded } from 'jazz-tools'; @@ -32,13 +31,7 @@ let thread = $derived( new CoState(Thread, route.threadId, { resolve: { - timeline: { - $each: { - reactions: { - $each: true - } - } - } + timeline: true } }) ); @@ -84,30 +77,13 @@ }); // add message to channel - const message = Message.create( - { - content: newContent, - createdAt: new Date(), - updatedAt: new Date(), - images: co.list(co.image()).create([...postImages], { - owner: publicGroup() - }), - reactions: co.list(Reaction).create([], { - owner: publicGroup() - }), - replyTo: replyTo?.id, - type: 'message' - }, - { - owner: publicGroup() - } - ); + const message = createMessage(newContent, postImages, replyTo?.id); postImages = []; replyTo = null; - thread.current?.timeline?.push(message); + thread.current?.timeline?.push(message.id); } async function clickJoinSpace() { @@ -143,7 +119,7 @@ {}} -- 2.51.2