From b7943cac84e217a72a528a43a9fd0ba7238037da Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 10 Sep 2026 19:10:01 +0300 Subject: [PATCH] scroll. --- src/components/conversation/conversation.js | 18 +++-------- src/components/conversation/conversation.vue | 2 ++ src/composables/useConversation.js | 2 ++ .../useTreeConversationTopology.js | 2 +- src/composables/useVirtualScrolling.js | 32 ++++++++++++++----- 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index b77ead529..863e4f61e 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -133,6 +133,7 @@ export default { // # Main things const { currentStatus, + mainStatus, conversation, replies, getReplies, @@ -201,7 +202,7 @@ export default { const isLinearView = computed(() => displayStyle.value !== 'tree') const linearElement = useTemplateRef('linear') const linearScrollCompensation = computed( - () => isLinearView.value && isExpanded.value, + () => isLinearView.value, ) const { heightChart: heightChartLinear, @@ -214,20 +215,11 @@ export default { body: linearElement, scrollPositionInstance: scroller, scrollCompensation: linearScrollCompensation, - anchorId: currentStatus.id, + anchorId: mainStatus.value?.id, + anchorRepeatId: currentStatus.value?.id, getPlaceholderHeight, }) - watch( - expanded, - async (value) => { - pauseWatchers() - await tryScrollTo(currentStatus.value.id) - resumeWatchers() - }, - { flush: 'post' }, - ) - // # Tree style stuff const isTreeView = computed(() => displayStyle.value === 'tree') const { @@ -241,7 +233,7 @@ export default { const ancestorsElement = useTemplateRef('ancestors') const treeScrollCompensation = computed( - () => isTreeView.value && isExpanded.value, + () => isTreeView.value, ) const { heightChart: heightChartAncestors, diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 8518565a9..8d4c4f967 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -107,6 +107,8 @@ :focused="focused === element.item.id" conversation-rank="ancestor" :data-status-id="element.id" + :data-vs-height="element.height" + :data-vs-top="element.top" @goto="setFocused" @dive="diveIntoStatus(element.item.id)" diff --git a/src/composables/useConversation.js b/src/composables/useConversation.js index 989e12c61..a0cc960cc 100644 --- a/src/composables/useConversation.js +++ b/src/composables/useConversation.js @@ -22,6 +22,7 @@ export function useConversation(statusId, expanded) { const loadError = ref(null) const currentStatus = computed(() => getStatusObject(statusId.value)) + const mainStatus = computed(() => currentStatus.value?.retweeted_status ?? currentStatus.value) const sortById = (a, b) => { const idA = a.type === 'retweet' ? a.retweeted_status.id : a.id @@ -115,6 +116,7 @@ export function useConversation(statusId, expanded) { return { currentStatus, + mainStatus, conversation, replies, getReplies, diff --git a/src/composables/useTreeConversationTopology.js b/src/composables/useTreeConversationTopology.js index b2ce7e23d..6e7e4fb00 100644 --- a/src/composables/useTreeConversationTopology.js +++ b/src/composables/useTreeConversationTopology.js @@ -26,7 +26,7 @@ export function useTreeConversationTopology(conversation, replies, current) { if (!result.has(id)) { result.set(id, new Set()) } - if (irid) { + if (irid && conversation.value.length !== 1) { // Setting parent for current item result.get(id).add(irid) } diff --git a/src/composables/useVirtualScrolling.js b/src/composables/useVirtualScrolling.js index e08d70304..bb1f11903 100644 --- a/src/composables/useVirtualScrolling.js +++ b/src/composables/useVirtualScrolling.js @@ -1,4 +1,4 @@ -import { computed, ref, toValue, watch } from 'vue' +import { computed, ref, toValue, watch, nextTick } from 'vue' import { useWindowSize } from 'src/composables/useWindowSize.js' @@ -12,6 +12,7 @@ export function useVirtualScrolling({ // ID of anchor element, used for scroll compensation. // Omitting it makes last element the anchor anchorId, + anchorRepeatId, // whether to use scroll compensation when elements above anchor change scrollCompensation, // Placeholder height specification. Must be a function. @@ -69,27 +70,41 @@ export function useVirtualScrolling({ } = scrollPositionInstance watch(heightChart, async (newVal, oldVal) => { if (!toValue(scrollCompensation)) return - if (scrollInProgress.value) return + if (newVal.length === 0 && oldVal.length === 0) return pauseWatchers() // If we're not given an achor, treat last element as one const getAnchoredEl = (list) => - anchorId?.value - ? list.find(({ id }) => id === anchorId?.value) + toValue(anchorId) + ? list.find(({ id }) => id === toValue(anchorId) || id === toValue(anchorRepeatId)) : list[list.length - 1] const oldElement = getAnchoredEl(oldVal) const newElement = getAnchoredEl(newVal) - const oldOffset = oldElement?.top ?? 0 - const newOffset = newElement?.top ?? 0 - const diff = newOffset - oldOffset // Positive = down, Negative = up + const diff = (() => { + if (oldElement && newElement) { + // Generic shifting + const oldOffset = toValue(anchorId) ? oldElement.top : (oldElement.top + oldElement.height) + const newOffset = toValue(anchorId) ? newElement.top : (newElement.top + newElement.height) + return newOffset - oldOffset + } else if (!oldElement && newElement) { + // Expansion + return newElement.top + newElement.height + } else if (oldElement && !newElement) { + // Collapsing + return 0 - oldElement.top - oldElement.height + } else { + throw new Error("Somehow both new and old elements are missing, this shouldn't happen") + } + })() if (diff !== 0) { // Scroll by amount offset changed to keep it in view topScrollBoundary.value += diff bottomScrollBoundary.value += diff - scrollBy(0, diff) + await nextTick() + await scrollBy(0, diff) } resumeWatchers() @@ -212,5 +227,6 @@ export function useVirtualScrolling({ updateVirtualHeight, pauseWatchers, resumeWatchers, + updateBoundaries, } }