Fix linked-comment scrolling (again)

## Issue
Now that we batch-resolve the comment authors before displaying the comments, the linked-comment scrolling logic didn't work well with nested replies.

## Change
Previously, I didn't want to put the logic at the lowest level (`Comment`) because it was hard for the child to know whether to scroll or not. For example, we don't want to scroll when user changes the comment filters or presses the Refresh Comments button.

Relented and moved the logic to `Comment`, and pass a flag via `window` (I know this is frowned upon by some) to indicate whether a scrolling is needed.

This is probably more efficient overall as we don't need to scan the DOM, and with minimal delay as we scroll immediately after the linked-comment is mounted.

## Known issues
In markdown posts with lots of images, a layout shift due to delayed inline-image fetching can cause the scrolling to be inaccurate. This should be fixed by reserving space for markdown post images.
This commit is contained in:
infinite-persistence 2021-10-16 13:34:06 +08:00
parent 0b0f2848da
commit 91be939c19
No known key found for this signature in database
GPG key ID: B9C3252EDC3D0AA0
2 changed files with 21 additions and 19 deletions

View file

@ -117,6 +117,7 @@ function Comment(props: Props) {
location: { pathname, search },
} = useHistory();
const isLinkedComment = linkedCommentId && linkedCommentId === commentId;
const isInLinkedCommentChain =
linkedCommentId &&
linkedCommentAncestors[linkedCommentId] &&
@ -205,6 +206,18 @@ function Comment(props: Props) {
replace(`${pathname}?${urlParams.toString()}`);
}
const linkedCommentRef = React.useCallback((node) => {
if (node !== null && window.pendingLinkedCommentScroll) {
const ROUGH_HEADER_HEIGHT = 125; // @see: --header-height
delete window.pendingLinkedCommentScroll;
window.scrollTo({
top: node.getBoundingClientRect().top + window.scrollY - ROUGH_HEADER_HEIGHT,
left: 0,
behavior: 'smooth',
});
}
}, []);
return (
<li
className={classnames('comment', {
@ -215,8 +228,9 @@ function Comment(props: Props) {
id={commentId}
>
<div
ref={isLinkedComment ? linkedCommentRef : undefined}
className={classnames('comment__content', {
[COMMENT_HIGHLIGHTED]: linkedCommentId && linkedCommentId === commentId,
[COMMENT_HIGHLIGHTED]: isLinkedComment,
'comment--slimed': slimedToDeath && !displayDeadComment,
})}
>

View file

@ -1,5 +1,4 @@
// @flow
import { COMMENT_HIGHLIGHTED } from 'constants/classnames';
import { COMMENT_PAGE_SIZE_TOP_LEVEL, SORT_BY } from 'constants/comment';
import { ENABLE_COMMENT_REACTIONS } from 'config';
import { getChannelIdFromClaim } from 'util/claim';
@ -15,7 +14,6 @@ import debounce from 'util/debounce';
import Empty from 'component/common/empty';
import React, { useEffect } from 'react';
import Spinner from 'component/spinner';
import useFetched from 'effects/use-fetched';
import usePersistedState from 'effects/use-persisted-state';
const DEBOUNCE_SCROLL_HANDLER_MS = 200;
@ -68,7 +66,6 @@ function CommentList(props: Props) {
claimIsMine,
myChannels,
isFetchingComments,
isFetchingCommentsById,
isFetchingReacts,
linkedCommentId,
totalComments,
@ -93,9 +90,6 @@ function CommentList(props: Props) {
const [sort, setSort] = usePersistedState('comment-sort-by', DEFAULT_SORT);
const [page, setPage] = React.useState(0);
const [commentsToDisplay, setCommentsToDisplay] = React.useState(topLevelComments);
const fetchedCommentsOnce = useFetched(isFetchingComments);
const fetchedReactsOnce = useFetched(isFetchingReacts);
const fetchedLinkedComment = useFetched(isFetchingCommentsById);
const hasDefaultExpansion = commentsAreExpanded || desktopView;
const [expandedComments, setExpandedComments] = React.useState(hasDefaultExpansion);
const totalFetchedComments = allCommentIds ? allCommentIds.length : 0;
@ -176,19 +170,13 @@ function CommentList(props: Props) {
// Scroll to linked-comment
useEffect(() => {
if (fetchedLinkedComment && fetchedCommentsOnce && fetchedReactsOnce) {
const elems = document.getElementsByClassName(COMMENT_HIGHLIGHTED);
if (elems.length > 0) {
const ROUGH_HEADER_HEIGHT = 125; // @see: --header-height
const linkedComment = elems[0];
window.scrollTo({
top: linkedComment.getBoundingClientRect().top + window.scrollY - ROUGH_HEADER_HEIGHT,
left: 0,
behavior: 'smooth',
});
}
if (linkedCommentId) {
window.pendingLinkedCommentScroll = true;
} else {
delete window.pendingLinkedCommentScroll;
}
}, [fetchedLinkedComment, fetchedCommentsOnce, fetchedReactsOnce]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Infinite scroll
useEffect(() => {