Fix linked-comment scrolling
I think this the best solution so far, at the expense of a slight delay in scrolling if the network call stalls. - Added "fetching by ID" state so that we don't need to use the ugly N-retries method. - `scrollIntoView` doesn't work if the element is already in the viewport, and the `scrollBy` adjustment doesn't take into account the y-position restoration that we perform on certain type of pages. Use `window.scrollTo` instead and taking into account current scroll position.
This commit is contained in:
parent
2cebdc3113
commit
c71b90cecf
7 changed files with 27 additions and 14 deletions
1
flow-typed/Comment.js
vendored
1
flow-typed/Comment.js
vendored
|
@ -43,6 +43,7 @@ declare type CommentsState = {
|
||||||
linkedCommentAncestors: { [string]: Array<string> }, // {"linkedCommentId": ["parentId", "grandParentId", ...]}
|
linkedCommentAncestors: { [string]: Array<string> }, // {"linkedCommentId": ["parentId", "grandParentId", ...]}
|
||||||
pinnedCommentsById: {}, // ClaimId -> array of pinned comment IDs
|
pinnedCommentsById: {}, // ClaimId -> array of pinned comment IDs
|
||||||
isLoading: boolean,
|
isLoading: boolean,
|
||||||
|
isLoadingById: boolean,
|
||||||
isLoadingByParentId: { [string]: boolean },
|
isLoadingByParentId: { [string]: boolean },
|
||||||
myComments: ?Set<string>,
|
myComments: ?Set<string>,
|
||||||
isFetchingReacts: boolean,
|
isFetchingReacts: boolean,
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
makeSelectTopLevelCommentsForUri,
|
makeSelectTopLevelCommentsForUri,
|
||||||
makeSelectTopLevelTotalPagesForUri,
|
makeSelectTopLevelTotalPagesForUri,
|
||||||
selectIsFetchingComments,
|
selectIsFetchingComments,
|
||||||
|
selectIsFetchingCommentsById,
|
||||||
selectIsFetchingReacts,
|
selectIsFetchingReacts,
|
||||||
makeSelectTotalCommentsCountForUri,
|
makeSelectTotalCommentsCountForUri,
|
||||||
selectOthersReactsById,
|
selectOthersReactsById,
|
||||||
|
@ -33,6 +34,7 @@ const select = (state, props) => {
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||||
isFetchingComments: selectIsFetchingComments(state),
|
isFetchingComments: selectIsFetchingComments(state),
|
||||||
|
isFetchingCommentsById: selectIsFetchingCommentsById(state),
|
||||||
isFetchingReacts: selectIsFetchingReacts(state),
|
isFetchingReacts: selectIsFetchingReacts(state),
|
||||||
fetchingChannels: selectFetchingMyChannels(state),
|
fetchingChannels: selectFetchingMyChannels(state),
|
||||||
settingsByChannelId: selectSettingsByChannelId(state),
|
settingsByChannelId: selectSettingsByChannelId(state),
|
||||||
|
|
|
@ -14,14 +14,12 @@ import usePersistedState from 'effects/use-persisted-state';
|
||||||
import { ENABLE_COMMENT_REACTIONS } from 'config';
|
import { ENABLE_COMMENT_REACTIONS } from 'config';
|
||||||
import Empty from 'component/common/empty';
|
import Empty from 'component/common/empty';
|
||||||
import debounce from 'util/debounce';
|
import debounce from 'util/debounce';
|
||||||
|
import useFetched from 'effects/use-fetched';
|
||||||
import { useIsMobile, useIsMediumScreen } from 'effects/use-screensize';
|
import { useIsMobile, useIsMediumScreen } from 'effects/use-screensize';
|
||||||
import { getChannelIdFromClaim } from 'util/claim';
|
import { getChannelIdFromClaim } from 'util/claim';
|
||||||
|
|
||||||
const DEBOUNCE_SCROLL_HANDLER_MS = 200;
|
const DEBOUNCE_SCROLL_HANDLER_MS = 200;
|
||||||
|
|
||||||
// "3" due to 2 separate fetches needed + 1 buffer just in case.
|
|
||||||
const MAX_LINKED_COMMENT_SCROLL_ATTEMPTS = 3;
|
|
||||||
|
|
||||||
function scaleToDevicePixelRatio(value) {
|
function scaleToDevicePixelRatio(value) {
|
||||||
const devicePixelRatio = window.devicePixelRatio || 1.0;
|
const devicePixelRatio = window.devicePixelRatio || 1.0;
|
||||||
if (devicePixelRatio < 1.0) {
|
if (devicePixelRatio < 1.0) {
|
||||||
|
@ -44,6 +42,7 @@ type Props = {
|
||||||
claimIsMine: boolean,
|
claimIsMine: boolean,
|
||||||
myChannels: ?Array<ChannelClaim>,
|
myChannels: ?Array<ChannelClaim>,
|
||||||
isFetchingComments: boolean,
|
isFetchingComments: boolean,
|
||||||
|
isFetchingCommentsById: boolean,
|
||||||
isFetchingReacts: boolean,
|
isFetchingReacts: boolean,
|
||||||
linkedCommentId?: string,
|
linkedCommentId?: string,
|
||||||
totalComments: number,
|
totalComments: number,
|
||||||
|
@ -69,6 +68,7 @@ function CommentList(props: Props) {
|
||||||
claimIsMine,
|
claimIsMine,
|
||||||
myChannels,
|
myChannels,
|
||||||
isFetchingComments,
|
isFetchingComments,
|
||||||
|
isFetchingCommentsById,
|
||||||
isFetchingReacts,
|
isFetchingReacts,
|
||||||
linkedCommentId,
|
linkedCommentId,
|
||||||
totalComments,
|
totalComments,
|
||||||
|
@ -83,15 +83,15 @@ function CommentList(props: Props) {
|
||||||
const DEFAULT_SORT = ENABLE_COMMENT_REACTIONS ? SORT_BY.POPULARITY : SORT_BY.NEWEST;
|
const DEFAULT_SORT = ENABLE_COMMENT_REACTIONS ? SORT_BY.POPULARITY : SORT_BY.NEWEST;
|
||||||
const [sort, setSort] = usePersistedState('comment-sort-by', DEFAULT_SORT);
|
const [sort, setSort] = usePersistedState('comment-sort-by', DEFAULT_SORT);
|
||||||
const [page, setPage] = React.useState(0);
|
const [page, setPage] = React.useState(0);
|
||||||
const [lcScrollAttempts, setLcScrollAttempts] = React.useState(
|
|
||||||
linkedCommentId ? 0 : MAX_LINKED_COMMENT_SCROLL_ATTEMPTS
|
|
||||||
);
|
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const isMediumScreen = useIsMediumScreen();
|
const isMediumScreen = useIsMediumScreen();
|
||||||
const [expandedComments, setExpandedComments] = React.useState(!isMobile && !isMediumScreen);
|
const [expandedComments, setExpandedComments] = React.useState(!isMobile && !isMediumScreen);
|
||||||
const totalFetchedComments = allCommentIds ? allCommentIds.length : 0;
|
const totalFetchedComments = allCommentIds ? allCommentIds.length : 0;
|
||||||
const channelId = getChannelIdFromClaim(claim);
|
const channelId = getChannelIdFromClaim(claim);
|
||||||
const channelSettings = channelId ? settingsByChannelId[channelId] : undefined;
|
const channelSettings = channelId ? settingsByChannelId[channelId] : undefined;
|
||||||
|
const fetchedCommentsOnce = useFetched(isFetchingComments);
|
||||||
|
const fetchedReactsOnce = useFetched(isFetchingReacts);
|
||||||
|
const fetchedLinkedComment = useFetched(isFetchingCommentsById);
|
||||||
|
|
||||||
// Display comments immediately if not fetching reactions
|
// Display comments immediately if not fetching reactions
|
||||||
// If not, wait to show comments until reactions are fetched
|
// If not, wait to show comments until reactions are fetched
|
||||||
|
@ -203,19 +203,19 @@ function CommentList(props: Props) {
|
||||||
|
|
||||||
// Scroll to linked-comment
|
// Scroll to linked-comment
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lcScrollAttempts < MAX_LINKED_COMMENT_SCROLL_ATTEMPTS && readyToDisplayComments && !isFetchingComments) {
|
if (fetchedLinkedComment && fetchedCommentsOnce && fetchedReactsOnce) {
|
||||||
const elems = document.getElementsByClassName(COMMENT_HIGHLIGHTED);
|
const elems = document.getElementsByClassName(COMMENT_HIGHLIGHTED);
|
||||||
if (elems.length > 0) {
|
if (elems.length > 0) {
|
||||||
setLcScrollAttempts(MAX_LINKED_COMMENT_SCROLL_ATTEMPTS);
|
const ROUGH_HEADER_HEIGHT = 125; // @see: --header-height
|
||||||
const linkedComment = elems[0];
|
const linkedComment = elems[0];
|
||||||
linkedComment.scrollIntoView({ block: 'start' });
|
window.scrollTo({
|
||||||
window.scrollBy(0, -125);
|
top: linkedComment.getBoundingClientRect().top + window.scrollY - ROUGH_HEADER_HEIGHT,
|
||||||
} else {
|
left: 0,
|
||||||
setLcScrollAttempts(lcScrollAttempts + 1);
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [fetchedLinkedComment, fetchedCommentsOnce, fetchedReactsOnce]);
|
||||||
}, [readyToDisplayComments, isFetchingComments]); // We just want to respond to these, nothing else.
|
|
||||||
|
|
||||||
// Infinite scroll
|
// Infinite scroll
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -253,6 +253,7 @@ export const COMMENT_LIST_STARTED = 'COMMENT_LIST_STARTED';
|
||||||
export const COMMENT_LIST_COMPLETED = 'COMMENT_LIST_COMPLETED';
|
export const COMMENT_LIST_COMPLETED = 'COMMENT_LIST_COMPLETED';
|
||||||
export const COMMENT_LIST_FAILED = 'COMMENT_LIST_FAILED';
|
export const COMMENT_LIST_FAILED = 'COMMENT_LIST_FAILED';
|
||||||
export const COMMENT_LIST_RESET = 'COMMENT_LIST_RESET';
|
export const COMMENT_LIST_RESET = 'COMMENT_LIST_RESET';
|
||||||
|
export const COMMENT_BY_ID_STARTED = 'COMMENT_BY_ID_STARTED';
|
||||||
export const COMMENT_BY_ID_COMPLETED = 'COMMENT_BY_ID_COMPLETED';
|
export const COMMENT_BY_ID_COMPLETED = 'COMMENT_BY_ID_COMPLETED';
|
||||||
export const COMMENT_CREATE_STARTED = 'COMMENT_CREATE_STARTED';
|
export const COMMENT_CREATE_STARTED = 'COMMENT_CREATE_STARTED';
|
||||||
export const COMMENT_CREATE_COMPLETED = 'COMMENT_CREATE_COMPLETED';
|
export const COMMENT_CREATE_COMPLETED = 'COMMENT_CREATE_COMPLETED';
|
||||||
|
|
|
@ -203,6 +203,10 @@ export function doCommentList(
|
||||||
|
|
||||||
export function doCommentById(commentId: string, toastIfNotFound: boolean = true) {
|
export function doCommentById(commentId: string, toastIfNotFound: boolean = true) {
|
||||||
return (dispatch: Dispatch, getState: GetState) => {
|
return (dispatch: Dispatch, getState: GetState) => {
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.COMMENT_BY_ID_STARTED,
|
||||||
|
});
|
||||||
|
|
||||||
return Comments.comment_by_id({ comment_id: commentId, with_ancestors: true })
|
return Comments.comment_by_id({ comment_id: commentId, with_ancestors: true })
|
||||||
.then((result: CommentByIdResponse) => {
|
.then((result: CommentByIdResponse) => {
|
||||||
const { item, items, ancestors } = result;
|
const { item, items, ancestors } = result;
|
||||||
|
|
|
@ -21,6 +21,7 @@ const defaultState: CommentsState = {
|
||||||
superChatsByUri: {},
|
superChatsByUri: {},
|
||||||
pinnedCommentsById: {}, // ClaimId -> array of pinned comment IDs
|
pinnedCommentsById: {}, // ClaimId -> array of pinned comment IDs
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
isLoadingById: false,
|
||||||
isLoadingByParentId: {},
|
isLoadingByParentId: {},
|
||||||
isCommenting: false,
|
isCommenting: false,
|
||||||
myComments: undefined,
|
myComments: undefined,
|
||||||
|
@ -335,6 +336,8 @@ export default handleActions(
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[ACTIONS.COMMENT_BY_ID_STARTED]: (state) => ({ ...state, isLoadingById: true }),
|
||||||
|
|
||||||
[ACTIONS.COMMENT_BY_ID_COMPLETED]: (state: CommentsState, action: any) => {
|
[ACTIONS.COMMENT_BY_ID_COMPLETED]: (state: CommentsState, action: any) => {
|
||||||
const { comment, ancestors } = action.data;
|
const { comment, ancestors } = action.data;
|
||||||
const claimId = comment.claim_id;
|
const claimId = comment.claim_id;
|
||||||
|
@ -375,6 +378,7 @@ export default handleActions(
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
isLoadingById: false,
|
||||||
topLevelCommentsById,
|
topLevelCommentsById,
|
||||||
topLevelTotalCommentsById,
|
topLevelTotalCommentsById,
|
||||||
topLevelTotalPagesById,
|
topLevelTotalPagesById,
|
||||||
|
|
|
@ -9,6 +9,7 @@ const selectState = (state) => state.comments || {};
|
||||||
|
|
||||||
export const selectCommentsById = createSelector(selectState, (state) => state.commentById || {});
|
export const selectCommentsById = createSelector(selectState, (state) => state.commentById || {});
|
||||||
export const selectIsFetchingComments = createSelector(selectState, (state) => state.isLoading);
|
export const selectIsFetchingComments = createSelector(selectState, (state) => state.isLoading);
|
||||||
|
export const selectIsFetchingCommentsById = createSelector(selectState, (state) => state.isLoadingById);
|
||||||
export const selectIsFetchingCommentsByParentId = createSelector(selectState, (state) => state.isLoadingByParentId);
|
export const selectIsFetchingCommentsByParentId = createSelector(selectState, (state) => state.isLoadingByParentId);
|
||||||
export const selectIsPostingComment = createSelector(selectState, (state) => state.isCommenting);
|
export const selectIsPostingComment = createSelector(selectState, (state) => state.isCommenting);
|
||||||
export const selectIsFetchingReacts = createSelector(selectState, (state) => state.isFetchingReacts);
|
export const selectIsFetchingReacts = createSelector(selectState, (state) => state.isFetchingReacts);
|
||||||
|
|
Loading…
Reference in a new issue