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 a6f9e6c91..b434592f3 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'; @@ -38,12 +38,12 @@ export default function LivestreamComments(props: Props) { doCommentSocketConnect, doCommentSocketDisconnect, comments: commentsByChronologicalOrder, + pinnedComments, doCommentList, fetchingComments, doSuperChatList, myChannels, superChats: superChatsByTipAmount, - pinnedCommentsById, } = props; let superChatsFiatAmount, superChatsTotalAmount; @@ -57,11 +57,7 @@ export default function LivestreamComments(props: Props) { const discussionElement = document.querySelector('.livestream__comments'); - 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; function restoreScrollPos() { if (discussionElement) { 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() : []