diff --git a/app/javascript/mastodon/features/status/components/detailed_status.tsx b/app/javascript/mastodon/features/status/components/detailed_status.tsx index 4e08635323f..6d432e1f24b 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.tsx +++ b/app/javascript/mastodon/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'; @@ -55,6 +55,8 @@ export const DetailedStatus: React.FC<{ pictureInPicture: any; onToggleHidden?: (status: any) => void; onToggleMediaVisibility?: () => void; + ancestors?: number; + multiColumn?: boolean; }> = ({ status, onOpenMedia, @@ -69,6 +71,8 @@ export const DetailedStatus: React.FC<{ pictureInPicture, onToggleMediaVisibility, onToggleHidden, + ancestors = 0, + multiColumn = false, }) => { const properStatus = status?.get('reblog') ?? status; const [height, setHeight] = useState(0); @@ -123,6 +127,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/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index 7c1f292d15d..ba72ee0c70c 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -164,8 +164,6 @@ class Status extends ImmutablePureComponent { componentDidMount () { attachFullscreenListener(this.onFullScreenChange); - - this._scrollStatusIntoView(); } UNSAFE_componentWillReceiveProps (nextProps) { @@ -487,35 +485,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); @@ -619,6 +593,8 @@ class Status extends ImmutablePureComponent { showMedia={this.state.showMedia} onToggleMediaVisibility={this.handleToggleMediaVisibility} pictureInPicture={pictureInPicture} + ancestors={this.props.ancestorsIds.length} + multiColumn={multiColumn} />