From b65d9679aef8ba26d52ba2eaabe28eb002da8d63 Mon Sep 17 00:00:00 2001 From: Abhinav Raut Date: Wed, 27 May 2026 02:14:23 +0530 Subject: [PATCH] fix: scroll to target message on scrollTo deep-link instead of bottom --- .../conversation/message/MessageList.vue | 35 ++++++------------- .../shared-ui/composables/useStickyScroll.js | 9 ++++- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/frontend/apps/main/src/features/conversation/message/MessageList.vue b/frontend/apps/main/src/features/conversation/message/MessageList.vue index b3a82ce1..3d44c751 100644 --- a/frontend/apps/main/src/features/conversation/message/MessageList.vue +++ b/frontend/apps/main/src/features/conversation/message/MessageList.vue @@ -29,7 +29,7 @@
@@ -95,10 +95,8 @@ const contentEl = ref(null) const emitter = useEmitter() const unReadMessages = ref(0) let currentConversationUUID = '' -let pendingScrollTo = null -const { hasUserScrolled, scrollToBottom, handleScroll } = useStickyScroll(threadEl, contentEl, { - skipAutoScroll: () => !!pendingScrollTo, +const { hasUserScrolled, scrollToBottom, scrollToOffset, handleScroll } = useStickyScroll(threadEl, contentEl, { onArriveBottom: () => { unReadMessages.value = 0 } }) @@ -107,18 +105,14 @@ const handleScrollToBottom = () => { scrollToBottom() } -const scrollToMessage = (messageUUID) => { - if (!messageUUID) return scrollToBottom() - const thread = threadEl.value - const messageEl = thread?.querySelector(`[data-message-uuid="${messageUUID}"]`) - if (!messageEl || !thread) { - scrollToBottom() - return +const vScrollTarget = { + mounted (el, binding) { + if (binding.value !== route.query.scrollTo || !threadEl.value) return + hasUserScrolled.value = true + scrollToOffset(Math.max(0, el.offsetTop - threadEl.value.clientHeight / 3 + el.offsetHeight / 2)) + el.classList.add('highlight-mention') + setTimeout(() => el.classList.remove('highlight-mention'), 2500) } - // Position message at ~1/3 from top of viewport for better visibility - thread.scrollTop = Math.max(0, messageEl.offsetTop - thread.clientHeight / 3 + messageEl.offsetHeight / 2) - messageEl.classList.add('highlight-mention') - setTimeout(() => messageEl.classList.remove('highlight-mention'), 2500) } const newMessageHandler = (data) => { @@ -144,21 +138,14 @@ watch( if (!newUUID || newUUID === currentConversationUUID) return currentConversationUUID = newUUID unReadMessages.value = 0 - pendingScrollTo = route.query.scrollTo || null - hasUserScrolled.value = !!pendingScrollTo + hasUserScrolled.value = !!route.query.scrollTo } ) watch( () => conversationStore.conversationMessages.length, (newLen, oldLen) => { - if (pendingScrollTo && newLen > 0) { - const target = pendingScrollTo - pendingScrollTo = null - nextTick(() => scrollToMessage(target)) - return - } - if (oldLen === 0 && newLen > 0) { + if (oldLen === 0 && newLen > 0 && !route.query.scrollTo) { hasUserScrolled.value = false nextTick(scrollToBottom) } diff --git a/frontend/shared-ui/composables/useStickyScroll.js b/frontend/shared-ui/composables/useStickyScroll.js index 35fd9159..491511ae 100644 --- a/frontend/shared-ui/composables/useStickyScroll.js +++ b/frontend/shared-ui/composables/useStickyScroll.js @@ -22,6 +22,13 @@ export function useStickyScroll (scrollEl, contentEl, options = {}) { el.scrollTop = el.scrollHeight } + const scrollToOffset = (top) => { + const el = scrollEl.value + if (!el) return + isProgrammaticScroll = true + el.scrollTop = top + } + const handleScroll = () => { if (isProgrammaticScroll) { isProgrammaticScroll = false @@ -50,5 +57,5 @@ export function useStickyScroll (scrollEl, contentEl, options = {}) { if (resizeObserver) resizeObserver.disconnect() }) - return { hasUserScrolled, scrollToBottom, handleScroll } + return { hasUserScrolled, scrollToBottom, scrollToOffset, handleScroll } }