From b5edf601f49b648d3dab6aa36e96c2827bc0ceff Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Fri, 4 Sep 2026 16:45:10 +0300 Subject: [PATCH 01/14] more unused deps --- package.json | 2 -- yarn.lock | 38 -------------------------------------- 2 files changed, 40 deletions(-) diff --git a/package.json b/package.json index 674828e26..29ef7bf5a 100644 --- a/package.json +++ b/package.json @@ -67,8 +67,6 @@ "@vue/devtools-api": "8.2.0", "@vue/test-utils": "2.5.0", "autoprefixer": "10.5.4", - "chalk": "6.0.0", - "cross-spawn": "7.0.6", "iso-639-1": "3.1.6", "playwright": "1.61.0", "postcss": "8.5.28", diff --git a/yarn.lock b/yarn.lock index 741639b8f..606d9d438 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1499,11 +1499,6 @@ chai@^6.2.2: resolved "https://registry.yarnpkg.com/chai/-/chai-6.2.2.tgz#ae41b52c9aca87734505362717f3255facda360e" integrity sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg== -chalk@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-6.0.0.tgz#a3bae843bc8454f41ef66ef3fd584dcf34dd805d" - integrity sha512-2uNTXIuTTxk7ciZgAU1BQcgnchcG0xXnrs6jzkQfj9SsRa9M2s5zE8WT96hS6KmG4MzWHSrvH43DF1m4XRkrFg== - chokidar@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-5.0.0.tgz#949c126a9238a80792be9a0265934f098af369a5" @@ -1608,15 +1603,6 @@ cropperjs@2.2.0: "@cropper/elements" "^2.2.0" "@cropper/utils" "^2.2.0" -cross-spawn@7.0.6: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - crossws@^0.4.12: version "0.4.12" resolved "https://registry.yarnpkg.com/crossws/-/crossws-0.4.12.tgz#3be6300b1e5a06db08e890e72eb1c7f6c809c9af" @@ -2617,11 +2603,6 @@ path-exists@^4.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - path-scurry@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.2.tgz#6be0d0ee02a10d9e0de7a98bae65e182c9061f85" @@ -3083,18 +3064,6 @@ setprototypeof@~1.2.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - siginfo@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" @@ -3603,13 +3572,6 @@ which@^1.3.1: dependencies: isexe "^2.0.0" -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - why-is-node-running@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" From d0366b24c1fadee1c557f2ed08e633ff6d0aa686 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Fri, 4 Sep 2026 17:31:47 +0300 Subject: [PATCH 02/14] try to use vue-virtual-scroller for timeline --- src/components/timeline/timeline.js | 69 +-------------------------- src/components/timeline/timeline.scss | 4 ++ src/components/timeline/timeline.vue | 40 ++++++++++------ 3 files changed, 31 insertions(+), 82 deletions(-) diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 7600e1765..1f4486647 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -38,7 +38,6 @@ const Timeline = { showScrollTop: false, paused: false, unfocused: false, - virtualScrollIndex: 0, blockingClicks: false, } }, @@ -102,20 +101,7 @@ const Timeline = { } }, statusesToDisplay() { - if (!this.virtualScrollingEnabled) { - return new Set(this.filteredVisibleStatuses.map(({ id }) => id)) - } - - const amount = this.timeline.visibleStatusIds.size - const statusesPerSide = Math.ceil(Math.max(3, window.innerHeight / 80)) - const min = Math.max(0, this.virtualScrollIndex - statusesPerSide) - const max = Math.min(amount, this.virtualScrollIndex + statusesPerSide) - return new Set( - this.filteredVisibleStatuses.slice(min, max).map(({ id }) => id), - ) - }, - virtualScrollingEnabled() { - return useMergedConfigStore().mergedConfig.virtualScrolling + return new Set(this.filteredVisibleStatuses.map(({ id }) => id)) }, ...mapState(useInterfaceStore, { mobileLayout: (store) => store.layoutType === 'mobile', @@ -135,7 +121,6 @@ const Timeline = { } window.addEventListener('keydown', this.handleShortKey) window.addEventListener('scroll', this.handleScroll) - setTimeout(this.determineVisibleStatuses, 250) }, unmounted() { this.timelineChange(null, this.timelineRef) @@ -193,54 +178,6 @@ const Timeline = { 1000, this, ), - determineVisibleStatuses() { - if (!this.$refs.timeline) return - if (!this.virtualScrollingEnabled) return - - const statuses = this.$refs.timeline.children - if (statuses.length === 0) return - const cappedScrollIndex = Math.max( - 0, - Math.min(this.virtualScrollIndex, statuses.length - 1), - ) - - const height = Math.max(document.body.offsetHeight, window.pageYOffset) - - const centerOfScreen = window.pageYOffset + window.innerHeight * 0.5 - - // Start from approximating the index of some visible status by using the - // the center of the screen on the timeline. - let approxIndex = Math.min( - Math.floor(statuses.length * (centerOfScreen / height)), - statuses.length - 1, - ) - let err = statuses[approxIndex].getBoundingClientRect().y - - // if we have a previous scroll index that can be used, test if it's - // closer than the previous approximation, use it if so - - const virtualScrollIndexY = - statuses[cappedScrollIndex].getBoundingClientRect().y - if (Math.abs(err) > virtualScrollIndexY) { - approxIndex = cappedScrollIndex - err = virtualScrollIndexY - } - - // if the status is too far from viewport, check the next/previous ones if - // they happen to be better - while (err < -20 && approxIndex < statuses.length - 1) { - err += statuses[approxIndex].offsetHeight - approxIndex++ - } - while (err > window.innerHeight + 100 && approxIndex > 0) { - approxIndex-- - err -= statuses[approxIndex].offsetHeight - } - - // this status is now the center point for virtual scrolling and visible - // statuses will be nearby statuses before and after it - this.virtualScrollIndex = approxIndex - }, scrollLoad() { // TODO simplify this logic const bodyBRect = document.body.getBoundingClientRect() @@ -253,7 +190,6 @@ const Timeline = { } }, handleScroll: throttle(function (e) { - this.determineVisibleStatuses() this.scrollLoad(e) }, 200), handleVisibilityChange() { @@ -264,9 +200,6 @@ const Timeline = { timelineRef(newTimeline, oldTimeline) { this.timelineChange(newTimeline, oldTimeline) }, - filteredVisibleStatuses() { - this.determineVisibleStatuses() - }, newStatusCount(count) { if (!useMergedConfigStore().mergedConfig.streaming) { return diff --git a/src/components/timeline/timeline.scss b/src/components/timeline/timeline.scss index 984129bf4..364882232 100644 --- a/src/components/timeline/timeline.scss +++ b/src/components/timeline/timeline.scss @@ -4,6 +4,10 @@ backdrop-filter: none; } + .vue-recycle-scroller.timeline { + overflow: visible; + } + .timeline-placeholder { background: var(--background); display: flex; diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 5b34551bc..7ee4bcfc5 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -77,23 +77,35 @@ />
-
- -
+ + +
-
-
- + + -
-
+ @goto="setFocused" + @toggle-expanded="toggleExpanded" + @suspendable-state-change="onStatusSuspendStateChange" + @height-change="updateVirtualHeight" + /> + + +
diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index be95f8698..d7ae9cd10 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -12,7 +12,7 @@ :focused="focused === statusId" :in-profile="inProfile" :profile-user-id="profileUserId" - class="conversation-status conversation-status-treeview status-fadein panel-body" + class="conversation-status conversation-status-treeview panel-body" :simple-tree="simple" :thread-display-status="threadDisplayStatus[statusId]" diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 7ee4bcfc5..a0087a68d 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -80,24 +80,21 @@ - diff --git a/src/components/draft/draft.vue b/src/components/draft/draft.vue index a644eb6d7..6c266fd27 100644 --- a/src/components/draft/draft.vue +++ b/src/components/draft/draft.vue @@ -13,7 +13,7 @@ - - - + + +

Date: Tue, 8 Sep 2026 17:21:06 +0300 Subject: [PATCH 07/14] stop using vue-virtual-scroller --- src/components/conversation/conversation.js | 1 + src/components/conversation/conversation.vue | 46 ++++++++------------ 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index eb759a712..92c93eade 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -426,6 +426,7 @@ export default { // # Main things status, statusReplies, + getReplies, conversation, // # Conversation Expansion diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 18ddce63e..03bcfef86 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -149,39 +149,29 @@ @suspendable-state-change="onStatusSuspendStateChange" /> - - - + @goto="setFocused" + @toggle-expanded="toggleExpanded" + @suspendable-state-change="onStatusSuspendStateChange" + /> + +
Date: Tue, 8 Sep 2026 17:44:16 +0300 Subject: [PATCH 08/14] restored virtual scrolling things --- src/components/conversation/conversation.js | 33 +++++++++++++++++++- src/components/conversation/conversation.vue | 6 ++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 92c93eade..bbb55cd04 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -1,6 +1,15 @@ import { get } from 'lodash-es' import { storeToRefs } from 'pinia' -import { computed, nextTick, provide, ref, toRefs, watch } from 'vue' +import { + computed, + nextTick, + onMounted, + provide, + ref, + toRefs, + useTemplateRef, + watch, +} from 'vue' import { useRouter } from 'vue-router' import ChatMessageList from 'src/components/chat_message_list/chat_message_list.vue' @@ -251,7 +260,12 @@ export default { } // # Virtual scrolling stuff + const body = useTemplateRef('body') const { virtualHidden } = toRefs(props) + const virtualHeight = ref(120) + const hiddenStyle = computed(() => ({ + height: this.virtualHeight + 'px', + })) const unsuspendibleIds = ref(new Set()) const suspendable = computed(() => unsuspendibleIds.value.size === 0) const hide = computed(() => virtualHidden.value && suspendable.value) @@ -262,6 +276,21 @@ export default { unsuspendibleIds.value.delete(id) } } + const updateVirtualHeight = () => { + if (hide) return // no updates when not rendering + if (!status.value) return // not loaded yet + nextTick(() => { + virtualHeight.value = body.value.getBoundingClientRect().height + emit('update:virtualHeight', { + id: status.value.id, + height: virtualHeight.value, + top: body.value.clientTop, + }) + }) + } + onMounted(() => { + updateVirtualHeight() + }) // # Misc UI things const firstStatus = computed(() => conversation.value[0]) @@ -437,7 +466,9 @@ export default { // # Virtual scrolling stuff hide, onStatusSuspendStateChange, + updateVirtualHeight, virtualHidden, + hiddenStyle, // # Misc UI things getStatusClasses, diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 03bcfef86..cebb9a10d 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -1,6 +1,7 @@