From 6f04bc4210f8c5a80e4c8304aac697c6b73ff9c3 Mon Sep 17 00:00:00 2001 From: Oliver Rose Date: Sun, 18 Oct 2026 20:57:57 -0400 Subject: [PATCH 1/5] refactor: add shared `ReverseScroll` component --- src/lib/components/ReverseScroll.svelte | 171 +++++++++++++++++ src/lib/components/chat/Chat.svelte | 191 ++++--------------- src/lib/components/user/User.svelte | 15 +- src/routes/(main)/whispers/[id]/+page.svelte | 133 ++++++------- 4 files changed, 272 insertions(+), 238 deletions(-) create mode 100644 src/lib/components/ReverseScroll.svelte diff --git a/src/lib/components/ReverseScroll.svelte b/src/lib/components/ReverseScroll.svelte new file mode 100644 index 00000000..dd6a3986 --- /dev/null +++ b/src/lib/components/ReverseScroll.svelte @@ -0,0 +1,171 @@ + + +
{ + observer.observe(element); + + return () => observer.disconnect(); + }} +> + {#if scrollingPaused} +
+ +
+ {/if} + + +
+ + diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index f3fbe87c..a5c3a6f1 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -1,9 +1,4 @@ -
{ - observer.observe(element); - - return () => observer.disconnect(); - }} -> - {#if scrollingPaused} -
- -
- {/if} - - message.id} - onscroll={handleScroll} - bind:this={list} - > - {#snippet children(message, i)} - {#if message.isEvent() || message.isUser()} - {@const prev = chat.messages[i - 1]} - {@const isNewDay = prev && prev.timestamp.getDate() !== message.timestamp.getDate()} - - {#if isNewDay} - - - - {/if} - - {#if message.isEvent()} - - {:else if message.isUser()} - {#if message.event} - - {:else if message.autoMod} - - {:else} - - {/if} - {/if} - - {@const next = chat.messages.at(i + 1)} - {@const nextRecent = next && (next.isEvent() || next.isUser()) && next.recent} - - {#if message === lastRead && next && settings.state["chat.newSeparator"]} - New messages - {/if} + message.id}> + {#snippet children(message, i)} + {#if message.isEvent() || message.isUser()} + {@const prev = chat.messages[i - 1]} + {@const isNewDay = prev && prev.timestamp.getDate() !== message.timestamp.getDate()} + + {#if isNewDay} + + + + {/if} - {#if message.recent && !nextRecent && settings.state["chat.messages.history.separator"]} - Live messages + {#if message.isEvent()} + + {:else if message.isUser()} + {#if message.event} + + {:else if message.autoMod} + + {:else} + {/if} - {:else if message.isComponent()} - {/if} - {/snippet} - -
- + {#if message.recent && !nextRecent && settings.state["chat.messages.history.separator"]} + Live messages + {/if} + {:else if message.isComponent()} + + {/if} + {/snippet} + diff --git a/src/lib/components/user/User.svelte b/src/lib/components/user/User.svelte index a4647f26..136c8037 100644 --- a/src/lib/components/user/User.svelte +++ b/src/lib/components/user/User.svelte @@ -16,6 +16,7 @@ import { settings } from "#lib/settings/index.js"; import Message from "../message/Message.svelte"; + import ReverseScroll from "../ReverseScroll.svelte"; import Popover from "../ui/Popover.svelte"; dayjs.extend(localizedFormat); @@ -221,12 +222,14 @@ {#if history.length} -
- {#each history.toReversed() as message (message.id)} -
- -
- {/each} +
+ message.id}> + {#snippet children(message)} +
+ +
+ {/snippet} +
{/if} {/snippet} diff --git a/src/routes/(main)/whispers/[id]/+page.svelte b/src/routes/(main)/whispers/[id]/+page.svelte index 9ad98d9c..0b97fae1 100644 --- a/src/routes/(main)/whispers/[id]/+page.svelte +++ b/src/routes/(main)/whispers/[id]/+page.svelte @@ -1,21 +1,15 @@
-
- {#each data.whisper.messages as message, i (message.id)} - {@const prev = data.whisper.messages[i - 1]} - {@const isNewDay = - !prev || prev.createdAt.toDateString() !== message.createdAt.toDateString()} - - {#if isNewDay} - - - - {/if} - -
+ + {#key data.whisper} + message.id} + lazy={data.whisper.hasOlder} + onloadmore={() => data.whisper.loadOlder()} > - {message.user.displayName} - -
-
- - {message.user.displayName} - - - + {#snippet children(message, i)} + {@const prev = data.whisper.messages[i - 1]} + {@const isNewDay = + !prev || prev.createdAt.toDateString() !== message.createdAt.toDateString()} + + {#if isNewDay} + + + + {/if} + +
+ {message.user.displayName} + +
+
+ + {message.user.displayName} + + + +
+ +

{message.text}

+
- -

{message.text}

-
-
- {/each} + {/snippet} +
+ {/key}
From b237c2715c5432c21c3bda2c50d349bed94b3f89 Mon Sep 17 00:00:00 2001 From: Oliver Rose Date: Mon, 19 Oct 2026 11:54:59 -0400 Subject: [PATCH 2/5] nit --- src/lib/components/ReverseScroll.svelte | 38 ++----------------------- src/styles/app.css | 36 ++++++++++++++++++++++- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/lib/components/ReverseScroll.svelte b/src/lib/components/ReverseScroll.svelte index dd6a3986..de293fa6 100644 --- a/src/lib/components/ReverseScroll.svelte +++ b/src/lib/components/ReverseScroll.svelte @@ -29,7 +29,7 @@ // Distance from the top of the list at which older items start loading. const LOAD_THRESHOLD = 200; - const { class: className, data, getKey, children, lazy = false, onloadmore }: Props = $props(); + const { class: className, data, getKey, lazy = false, onloadmore, children }: Props = $props(); let list = $state>(); let scrollingPaused = $state(false); @@ -126,7 +126,7 @@ {/if}
- - diff --git a/src/styles/app.css b/src/styles/app.css index 89bd4729..f984cae5 100644 --- a/src/styles/app.css +++ b/src/styles/app.css @@ -4,6 +4,24 @@ @import "./positioning.css"; +@layer properties { + @property --scroll-fade { + syntax: ""; + inherits: false; + initial-value: 0; + } +} + +@keyframes scroll-fade { + from { + --scroll-fade: min(12%, calc(var(--spacing) * 10)); + } + + to { + --scroll-fade: 0; + } +} + @layer { :root { --radius: 0.625rem; @@ -157,7 +175,7 @@ -moz-osx-font-smoothing: grayscale; } - button:hover { + button { cursor: pointer; } @@ -217,4 +235,20 @@ @apply animate-out zoom-out-95 fade-out; } } + + .scroll-fade { + --scroll-fade: 0; + + mask-image: linear-gradient( + to bottom, + #000 0, + #000 calc(100% - var(--scroll-fade)), + transparent 100% + ); + mask-composite: intersect; + mask-repeat: no-repeat; + animation: 1ms ease-in-out scroll-fade both; + animation-timeline: scroll(self y); + animation-range: calc(100% - calc(var(--spacing) * 24)); + } } From ddb86aac894c10358e4ad4c353d617276b15072e Mon Sep 17 00:00:00 2001 From: Oliver Rose Date: Tue, 20 Oct 2026 16:48:18 -0400 Subject: [PATCH 3/5] add prop to show jump btn --- src/lib/components/ReverseScroll.svelte | 13 +++++++++++-- src/lib/components/user/User.svelte | 9 +++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/lib/components/ReverseScroll.svelte b/src/lib/components/ReverseScroll.svelte index de293fa6..9f5e7e73 100644 --- a/src/lib/components/ReverseScroll.svelte +++ b/src/lib/components/ReverseScroll.svelte @@ -11,6 +11,7 @@ interface Props { class?: string; data: T[]; + showJumpButton?: boolean; /** * Whether older items should be loaded with {@linkcode onloadmore} when @@ -29,7 +30,15 @@ // Distance from the top of the list at which older items start loading. const LOAD_THRESHOLD = 200; - const { class: className, data, getKey, lazy = false, onloadmore, children }: Props = $props(); + const { + class: className, + data, + showJumpButton = true, + lazy = false, + getKey, + onloadmore, + children, + }: Props = $props(); let list = $state>(); let scrollingPaused = $state(false); @@ -100,7 +109,7 @@ return () => observer.disconnect(); }} > - {#if scrollingPaused} + {#if showJumpButton && scrollingPaused}