From 8c725777ed2b7143ed48b9096f73c9ad2fe32dbf Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 18 Nov 2025 17:20:50 +0100 Subject: [PATCH] [Glitch] Fix scroll-to-status in threaded view being unreliable Port 6ccd9c2f1f0b8dd26465de6aa54a6b56d6cd09ae to glitch-soc Signed-off-by: Claire --- .../status/components/detailed_status.tsx | 30 ++++++++++++++++++- .../flavours/glitch/features/status/index.jsx | 29 ++---------------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.tsx b/app/javascript/flavours/glitch/features/status/components/detailed_status.tsx index 87d88510fa..f8a87ac210 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.tsx +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.tsx @@ -4,7 +4,7 @@ @typescript-eslint/no-unsafe-assignment */ import type { CSSProperties } from 'react'; -import { useState, useRef, useCallback } from 'react'; +import { useState, useRef, useCallback, useEffect } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -57,6 +57,8 @@ export const DetailedStatus: React.FC<{ pictureInPicture: any; onToggleHidden?: (status: any) => void; onToggleMediaVisibility?: () => void; + ancestors?: number; + multiColumn?: boolean; expanded: boolean; }> = ({ status, @@ -72,6 +74,8 @@ export const DetailedStatus: React.FC<{ pictureInPicture, onToggleMediaVisibility, onToggleHidden, + ancestors = 0, + multiColumn = false, expanded, }) => { const properStatus = status?.get('reblog') ?? status; @@ -136,6 +140,30 @@ export const DetailedStatus: React.FC<{ if (onTranslate) onTranslate(status); }, [onTranslate, status]); + // The component is managed and will change if the status changes + // Ancestors can increase when loading a thread, in which case we want to scroll, + // or decrease if a post is deleted, in which case we don't want to mess with it + const previousAncestors = useRef(-1); + useEffect(() => { + if (nodeRef.current && previousAncestors.current < ancestors) { + nodeRef.current.scrollIntoView(true); + + // In the single-column interface, `scrollIntoView` will put the post behind the header, so compensate for that. + if (!multiColumn) { + const offset = document + .querySelector('.column-header__wrapper') + ?.getBoundingClientRect().bottom; + + if (offset) { + const scrollingElement = document.scrollingElement ?? document.body; + scrollingElement.scrollBy(0, -offset); + } + } + } + + previousAncestors.current = ancestors; + }, [ancestors, multiColumn]); + if (!properStatus) { return null; } diff --git a/app/javascript/flavours/glitch/features/status/index.jsx b/app/javascript/flavours/glitch/features/status/index.jsx index 1c33cb21d0..c921bfad50 100644 --- a/app/javascript/flavours/glitch/features/status/index.jsx +++ b/app/javascript/flavours/glitch/features/status/index.jsx @@ -162,7 +162,6 @@ class Status extends ImmutablePureComponent { componentDidMount () { attachFullscreenListener(this.onFullScreenChange); this.props.dispatch(fetchStatus(this.props.params.statusId, { forceFetch: true })); - this._scrollStatusIntoView(); } static getDerivedStateFromProps(props, state) { @@ -512,35 +511,11 @@ class Status extends ImmutablePureComponent { this.statusNode = c; }; - _scrollStatusIntoView () { - const { status, multiColumn } = this.props; - - if (status) { - requestIdleCallback(() => { - this.statusNode?.scrollIntoView(true); - - // In the single-column interface, `scrollIntoView` will put the post behind the header, - // so compensate for that. - if (!multiColumn) { - const offset = document.querySelector('.column-header__wrapper')?.getBoundingClientRect()?.bottom; - if (offset) { - const scrollingElement = document.scrollingElement || document.body; - scrollingElement.scrollBy(0, -offset); - } - } - }); - } - } - componentDidUpdate (prevProps) { - const { status, ancestorsIds, descendantsIds } = this.props; + const { status, descendantsIds } = this.props; const isSameStatus = status && (prevProps.status?.get('id') === status.get('id')); - if (status && (ancestorsIds.length > prevProps.ancestorsIds.length || !isSameStatus)) { - this._scrollStatusIntoView(); - } - // Only highlight replies after the initial load if (prevProps.descendantsIds.length && isSameStatus) { const newRepliesIds = difference(descendantsIds, prevProps.descendantsIds); @@ -653,6 +628,8 @@ class Status extends ImmutablePureComponent { showMedia={this.state.showMedia} onToggleMediaVisibility={this.handleToggleMediaVisibility} pictureInPicture={pictureInPicture} + ancestors={this.props.ancestorsIds.length} + multiColumn={multiColumn} />