From ecd42e244ad911d02b94cca3763d7317f76df9b6 Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Mon, 16 Aug 2021 19:03:01 +0800 Subject: [PATCH] Livestream: stop pinned comments from appearing as latest ## Ticket 6879: Previously pinned livestream comments show as latest ## Issue `comment.List` will always display the pinned comment first, hence the problem when the chat is refreshed. ## Approach Completely split pinned comments from top-level comments in the Reducer, and the let the GUI (e.g. regular comments, livestream comments) decide how they want to display it. For the case of livestream, there is no need to repeat the pinned comments in the regular chat area, since there is a dedicated area on top. --- ui/component/commentsList/index.js | 2 + ui/component/commentsList/view.jsx | 61 +++++++++--------- ui/component/livestreamComments/index.js | 4 +- ui/component/livestreamComments/view.jsx | 10 +-- ui/redux/reducers/comments.js | 78 +++++++++++++----------- ui/redux/selectors/comments.js | 20 ++++++ 6 files changed, 101 insertions(+), 74 deletions(-) diff --git a/ui/component/commentsList/index.js b/ui/component/commentsList/index.js index 5d54af205..e56b6ca6d 100644 --- a/ui/component/commentsList/index.js +++ b/ui/component/commentsList/index.js @@ -15,6 +15,7 @@ import { selectMyReactionsByCommentId, makeSelectCommentIdsForUri, selectSettingsByChannelId, + makeSelectPinnedCommentsForUri, } from 'redux/selectors/comments'; import { doCommentReset, doCommentList, doCommentById, doCommentReactList } from 'redux/actions/comments'; import { selectActiveChannelClaim } from 'redux/selectors/app'; @@ -25,6 +26,7 @@ const select = (state, props) => { return { myChannels: selectMyChannelClaims(state), allCommentIds: makeSelectCommentIdsForUri(props.uri)(state), + pinnedComments: makeSelectPinnedCommentsForUri(props.uri)(state), topLevelComments: makeSelectTopLevelCommentsForUri(props.uri)(state), topLevelTotalPages: makeSelectTopLevelTotalPagesForUri(props.uri)(state), totalComments: makeSelectTotalCommentsCountForUri(props.uri)(state), diff --git a/ui/component/commentsList/view.jsx b/ui/component/commentsList/view.jsx index 6e750bd3d..ae3ee5b12 100644 --- a/ui/component/commentsList/view.jsx +++ b/ui/component/commentsList/view.jsx @@ -28,6 +28,7 @@ function scaleToDevicePixelRatio(value) { type Props = { allCommentIds: any, + pinnedComments: Array, topLevelComments: Array, topLevelTotalPages: number, fetchTopLevelComments: (string, number, number, number) => void, @@ -57,6 +58,7 @@ function CommentList(props: Props) { fetchReacts, resetComments, uri, + pinnedComments, topLevelComments, topLevelTotalPages, claim, @@ -111,6 +113,34 @@ function CommentList(props: Props) { } } + function getCommentElems(comments) { + return comments.map((comment) => { + return ( + + ); + }); + } + // Reset comments useEffect(() => { if (page === 0) { @@ -223,8 +253,6 @@ function CommentList(props: Props) { topLevelTotalPages, ]); - const displayedComments = readyToDisplayComments ? topLevelComments : []; - return ( - {topLevelComments && - displayedComments && - displayedComments.map((comment) => { - return ( - - ); - })} + {readyToDisplayComments && pinnedComments && getCommentElems(pinnedComments)} + {readyToDisplayComments && topLevelComments && getCommentElems(topLevelComments)} {isMobile && ( diff --git a/ui/component/livestreamComments/index.js b/ui/component/livestreamComments/index.js index 97cac2e75..10ee56c06 100644 --- a/ui/component/livestreamComments/index.js +++ b/ui/component/livestreamComments/index.js @@ -3,22 +3,22 @@ import { makeSelectClaimForUri, selectMyChannelClaims } from 'lbry-redux'; import { doCommentSocketConnect, doCommentSocketDisconnect } from 'redux/actions/websocket'; import { doCommentList, doSuperChatList } from 'redux/actions/comments'; import { - selectPinnedCommentsById, makeSelectTopLevelCommentsForUri, selectIsFetchingComments, makeSelectSuperChatsForUri, makeSelectSuperChatTotalAmountForUri, + makeSelectPinnedCommentsForUri, } from 'redux/selectors/comments'; import LivestreamComments from './view'; const select = (state, props) => ({ claim: makeSelectClaimForUri(props.uri)(state), comments: makeSelectTopLevelCommentsForUri(props.uri)(state).slice(0, 75), + pinnedComments: makeSelectPinnedCommentsForUri(props.uri)(state), fetchingComments: selectIsFetchingComments(state), superChats: makeSelectSuperChatsForUri(props.uri)(state), superChatsTotalAmount: makeSelectSuperChatTotalAmountForUri(props.uri)(state), myChannels: selectMyChannelClaims(state), - pinnedCommentsById: selectPinnedCommentsById(state), }); export default connect(select, { diff --git a/ui/component/livestreamComments/view.jsx b/ui/component/livestreamComments/view.jsx index 76aeda7a5..c653cd339 100644 --- a/ui/component/livestreamComments/view.jsx +++ b/ui/component/livestreamComments/view.jsx @@ -19,11 +19,11 @@ type Props = { doCommentSocketDisconnect: (string) => void, doCommentList: (string, string, number, number) => void, comments: Array, + pinnedComments: Array, fetchingComments: boolean, doSuperChatList: (string) => void, superChats: Array, myChannels: ?Array, - pinnedCommentsById: { [claimId: string]: Array }, }; const VIEW_MODE_CHAT = 'view_chat'; @@ -39,12 +39,12 @@ export default function LivestreamComments(props: Props) { doCommentSocketConnect, doCommentSocketDisconnect, comments: commentsByChronologicalOrder, + pinnedComments, doCommentList, fetchingComments, doSuperChatList, myChannels, superChats: superChatsByTipAmount, - pinnedCommentsById, } = props; let superChatsFiatAmount, superChatsTotalAmount; @@ -60,11 +60,7 @@ export default function LivestreamComments(props: Props) { const discussionElement = document.querySelector('.livestream__comments'); const commentElement = document.querySelector('.livestream-comment'); - let pinnedComment; - const pinnedCommentIds = (claimId && pinnedCommentsById[claimId]) || []; - if (pinnedCommentIds.length > 0) { - pinnedComment = commentsByChronologicalOrder.find((c) => c.comment_id === pinnedCommentIds[0]); - } + const pinnedComment = pinnedComments.length > 0 ? pinnedComments[0] : null; React.useEffect(() => { if (claimId) { diff --git a/ui/redux/reducers/comments.js b/ui/redux/reducers/comments.js index 4e539e67c..30a558fe7 100644 --- a/ui/redux/reducers/comments.js +++ b/ui/redux/reducers/comments.js @@ -301,9 +301,10 @@ export default handleActions( for (let i = 0; i < comments.length; ++i) { const comment = comments[i]; commonUpdateAction(comment, commentById, commentIds, i); - pushToArrayInObject(topLevelCommentsById, claimId, comment.comment_id); if (comment.is_pinned) { pushToArrayInObject(pinnedCommentsById, claimId, comment.comment_id); + } else { + pushToArrayInObject(topLevelCommentsById, claimId, comment.comment_id); } } } @@ -615,45 +616,50 @@ export default handleActions( const topLevelCommentsById = Object.assign({}, state.topLevelCommentsById); const pinnedCommentsById = Object.assign({}, state.pinnedCommentsById); - if (pinnedComment && topLevelCommentsById[claimId]) { - const index = topLevelCommentsById[claimId].indexOf(pinnedComment.comment_id); - if (index > -1) { - topLevelCommentsById[claimId].splice(index, 1); - - if (pinnedCommentsById[claimId]) { - // Remove here so that the 'unshift' below will be a unique entry. - pinnedCommentsById[claimId] = pinnedCommentsById[claimId].filter((x) => x !== pinnedComment.comment_id); - } else { - pinnedCommentsById[claimId] = []; + if (pinnedComment) { + if (topLevelCommentsById[claimId]) { + const index = topLevelCommentsById[claimId].indexOf(pinnedComment.comment_id); + if (index > -1) { + topLevelCommentsById[claimId].splice(index, 1); } + } else { + topLevelCommentsById[claimId] = []; + } - if (unpin) { - // Without the sort score, I have no idea where to put it. Just - // dump it at the bottom. Users can refresh if they want it back to - // the correct sorted position. - topLevelCommentsById[claimId].push(pinnedComment.comment_id); - } else { - topLevelCommentsById[claimId].unshift(pinnedComment.comment_id); - pinnedCommentsById[claimId].unshift(pinnedComment.comment_id); + if (pinnedCommentsById[claimId]) { + const index = pinnedCommentsById[claimId].indexOf(pinnedComment.comment_id); + if (index > -1) { + pinnedCommentsById[claimId].splice(index, 1); } + } else { + pinnedCommentsById[claimId] = []; + } - if (commentById[pinnedComment.comment_id]) { - // Commentron's `comment.Pin` response places the creator's credentials - // in the 'channel_*' fields, which doesn't make sense. Maybe it is to - // show who signed/pinned it, but even if so, it shouldn't overload - // these variables which are already used by existing comment data structure. - // Ensure we don't override the existing/correct values, but fallback - // to whatever was given. - const { channel_id, channel_name, channel_url } = commentById[pinnedComment.comment_id]; - commentById[pinnedComment.comment_id] = { - ...pinnedComment, - channel_id: channel_id || pinnedComment.channel_id, - channel_name: channel_name || pinnedComment.channel_name, - channel_url: channel_url || pinnedComment.channel_url, - }; - } else { - commentById[pinnedComment.comment_id] = pinnedComment; - } + if (unpin) { + // Without the sort score, I have no idea where to put it. Just + // dump it at the top. Users can refresh if they want it back to + // the correct sorted position. + topLevelCommentsById[claimId].unshift(pinnedComment.comment_id); + } else { + pinnedCommentsById[claimId].unshift(pinnedComment.comment_id); + } + + if (commentById[pinnedComment.comment_id]) { + // Commentron's `comment.Pin` response places the creator's credentials + // in the 'channel_*' fields, which doesn't make sense. Maybe it is to + // show who signed/pinned it, but even if so, it shouldn't overload + // these variables which are already used by existing comment data structure. + // Ensure we don't override the existing/correct values, but fallback + // to whatever was given. + const { channel_id, channel_name, channel_url } = commentById[pinnedComment.comment_id]; + commentById[pinnedComment.comment_id] = { + ...pinnedComment, + channel_id: channel_id || pinnedComment.channel_id, + channel_name: channel_name || pinnedComment.channel_name, + channel_url: channel_url || pinnedComment.channel_url, + }; + } else { + commentById[pinnedComment.comment_id] = pinnedComment; } } diff --git a/ui/redux/selectors/comments.js b/ui/redux/selectors/comments.js index a724a27f3..e1f53738c 100644 --- a/ui/redux/selectors/comments.js +++ b/ui/redux/selectors/comments.js @@ -13,7 +13,27 @@ export const selectIsFetchingCommentsByParentId = createSelector(selectState, (s export const selectIsPostingComment = createSelector(selectState, (state) => state.isCommenting); export const selectIsFetchingReacts = createSelector(selectState, (state) => state.isFetchingReacts); export const selectOthersReactsById = createSelector(selectState, (state) => state.othersReactsByCommentId); + export const selectPinnedCommentsById = createSelector(selectState, (state) => state.pinnedCommentsById); +export const makeSelectPinnedCommentsForUri = (uri: string) => + createSelector( + selectCommentsByUri, + selectCommentsById, + selectPinnedCommentsById, + (byUri, byId, pinnedCommentsById) => { + const claimId = byUri[uri]; + const pinnedCommentIds = pinnedCommentsById && pinnedCommentsById[claimId]; + const pinnedComments = []; + + if (pinnedCommentIds) { + pinnedCommentIds.forEach((commentId) => { + pinnedComments.push(byId[commentId]); + }); + } + + return pinnedComments; + } + ); export const selectModerationBlockList = createSelector(selectState, (state) => state.moderationBlockList ? state.moderationBlockList.reverse() : [] -- 2.45.3