Skip to content

Commit d60cfa3

Browse files
evalstatensarrazin
andauthored
Feature - Scroll to Previous Message (#1440)
* Added "Scroll to Previous Message" button. * update mobile positioning to 50% * fix linting issues in ScrollToPreviousBtn --------- Co-authored-by: Nathan Sarrazin <sarrazin.nathan@gmail.com>
1 parent 3f659ae commit d60cfa3

File tree

3 files changed

+80
-1
lines changed

3 files changed

+80
-1
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<script lang="ts">
2+
import { fade } from "svelte/transition";
3+
import { onDestroy } from "svelte";
4+
import IconChevron from "./icons/IconChevron.svelte";
5+
6+
export let scrollNode: HTMLElement;
7+
export { className as class };
8+
9+
let visible = false;
10+
let className = "";
11+
let observer: ResizeObserver | null = null;
12+
13+
$: if (scrollNode) {
14+
destroy();
15+
16+
if (window.ResizeObserver) {
17+
observer = new ResizeObserver(() => {
18+
updateVisibility();
19+
});
20+
observer.observe(scrollNode);
21+
}
22+
scrollNode.addEventListener("scroll", updateVisibility);
23+
}
24+
25+
function updateVisibility() {
26+
if (!scrollNode) return;
27+
visible =
28+
Math.ceil(scrollNode.scrollTop) + 200 < scrollNode.scrollHeight - scrollNode.clientHeight &&
29+
scrollNode.scrollTop > 200;
30+
}
31+
32+
function scrollToPrevious() {
33+
if (!scrollNode) return;
34+
const messages = scrollNode.querySelectorAll('[id^="message-"]');
35+
const scrollTop = scrollNode.scrollTop;
36+
let previousMessage: Element | null = null;
37+
38+
for (let i = messages.length - 1; i >= 0; i--) {
39+
const messageTop =
40+
messages[i].getBoundingClientRect().top +
41+
scrollTop -
42+
scrollNode.getBoundingClientRect().top;
43+
if (messageTop < scrollTop - 1) {
44+
previousMessage = messages[i];
45+
break;
46+
}
47+
}
48+
49+
if (previousMessage) {
50+
previousMessage.scrollIntoView({ behavior: "smooth", block: "start" });
51+
}
52+
}
53+
54+
function destroy() {
55+
observer?.disconnect();
56+
scrollNode?.removeEventListener("scroll", updateVisibility);
57+
}
58+
59+
onDestroy(destroy);
60+
</script>
61+
62+
{#if visible}
63+
<button
64+
transition:fade={{ duration: 150 }}
65+
on:click={scrollToPrevious}
66+
class="btn absolute flex h-[41px] w-[41px] rounded-full border bg-white shadow-md transition-all hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-700 dark:shadow-gray-950 dark:hover:bg-gray-600 {className}"
67+
>
68+
<IconChevron classNames="rotate-180 mt-[2px]" />
69+
</button>
70+
{/if}

src/lib/components/chat/ChatMessage.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@
230230
{#if message.from === "assistant"}
231231
<div
232232
class="group relative -mb-4 flex items-start justify-start gap-4 pb-4 leading-relaxed"
233+
id="message-assistant-{message.id}"
233234
role="presentation"
234235
on:click={() => (isTapped = !isTapped)}
235236
on:keydown={() => (isTapped = !isTapped)}
@@ -372,6 +373,7 @@
372373
{#if message.from === "user"}
373374
<div
374375
class="group relative w-full items-start justify-start gap-4 max-sm:text-sm"
376+
id="message-user-{message.id}"
375377
role="presentation"
376378
on:click={() => (isTapped = !isTapped)}
377379
on:keydown={() => (isTapped = !isTapped)}

src/lib/components/chat/ChatWindow.svelte

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import AssistantIntroduction from "./AssistantIntroduction.svelte";
2828
import ChatMessage from "./ChatMessage.svelte";
2929
import ScrollToBottomBtn from "../ScrollToBottomBtn.svelte";
30+
import ScrollToPreviousBtn from "../ScrollToPreviousBtn.svelte";
3031
import { browser } from "$app/environment";
3132
import { snapScrollToBottom } from "$lib/actions/snapScrollToBottom";
3233
import SystemPromptModal from "../SystemPromptModal.svelte";
@@ -328,8 +329,14 @@
328329
/>
329330
{/if}
330331
</div>
332+
333+
<ScrollToPreviousBtn
334+
class="fixed right-4 max-md:bottom-[calc(50%+26px)] md:bottom-48 lg:right-10"
335+
scrollNode={chatContainer}
336+
/>
337+
331338
<ScrollToBottomBtn
332-
class="bottom-36 right-4 max-md:hidden lg:right-10"
339+
class="fixed right-4 max-md:bottom-[calc(50%-26px)] md:bottom-36 lg:right-10"
333340
scrollNode={chatContainer}
334341
/>
335342
</div>

0 commit comments

Comments
 (0)