Livestream: stop pinned comments from appearing as latest (#6888)

## 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.
This commit is contained in:
infinite-persistence 2021-08-17 09:09:55 -07:00 committed by GitHub
parent 5f55603fb2
commit 4688b4bf58
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 101 additions and 74 deletions

View file

@ -15,6 +15,7 @@ import {
selectMyReactionsByCommentId, selectMyReactionsByCommentId,
makeSelectCommentIdsForUri, makeSelectCommentIdsForUri,
selectSettingsByChannelId, selectSettingsByChannelId,
makeSelectPinnedCommentsForUri,
} from 'redux/selectors/comments'; } from 'redux/selectors/comments';
import { doCommentReset, doCommentList, doCommentById, doCommentReactList } from 'redux/actions/comments'; import { doCommentReset, doCommentList, doCommentById, doCommentReactList } from 'redux/actions/comments';
import { selectActiveChannelClaim } from 'redux/selectors/app'; import { selectActiveChannelClaim } from 'redux/selectors/app';
@ -25,6 +26,7 @@ const select = (state, props) => {
return { return {
myChannels: selectMyChannelClaims(state), myChannels: selectMyChannelClaims(state),
allCommentIds: makeSelectCommentIdsForUri(props.uri)(state), allCommentIds: makeSelectCommentIdsForUri(props.uri)(state),
pinnedComments: makeSelectPinnedCommentsForUri(props.uri)(state),
topLevelComments: makeSelectTopLevelCommentsForUri(props.uri)(state), topLevelComments: makeSelectTopLevelCommentsForUri(props.uri)(state),
topLevelTotalPages: makeSelectTopLevelTotalPagesForUri(props.uri)(state), topLevelTotalPages: makeSelectTopLevelTotalPagesForUri(props.uri)(state),
totalComments: makeSelectTotalCommentsCountForUri(props.uri)(state), totalComments: makeSelectTotalCommentsCountForUri(props.uri)(state),

View file

@ -28,6 +28,7 @@ function scaleToDevicePixelRatio(value) {
type Props = { type Props = {
allCommentIds: any, allCommentIds: any,
pinnedComments: Array<Comment>,
topLevelComments: Array<Comment>, topLevelComments: Array<Comment>,
topLevelTotalPages: number, topLevelTotalPages: number,
fetchTopLevelComments: (string, number, number, number) => void, fetchTopLevelComments: (string, number, number, number) => void,
@ -57,6 +58,7 @@ function CommentList(props: Props) {
fetchReacts, fetchReacts,
resetComments, resetComments,
uri, uri,
pinnedComments,
topLevelComments, topLevelComments,
topLevelTotalPages, topLevelTotalPages,
claim, claim,
@ -111,6 +113,34 @@ function CommentList(props: Props) {
} }
} }
function getCommentElems(comments) {
return comments.map((comment) => {
return (
<CommentView
isTopLevel
threadDepth={3}
key={comment.comment_id}
uri={uri}
authorUri={comment.channel_url}
author={comment.channel_name}
claimId={comment.claim_id}
commentId={comment.comment_id}
message={comment.comment}
timePosted={comment.timestamp * 1000}
claimIsMine={claimIsMine}
commentIsMine={comment.channel_id && isMyComment(comment.channel_id)}
linkedCommentId={linkedCommentId}
isPinned={comment.is_pinned}
supportAmount={comment.support_amount}
numDirectReplies={comment.replies}
isModerator={comment.is_moderator}
isGlobalMod={comment.is_global_mod}
isFiat={comment.is_fiat}
/>
);
});
}
// Reset comments // Reset comments
useEffect(() => { useEffect(() => {
if (page === 0) { if (page === 0) {
@ -223,8 +253,6 @@ function CommentList(props: Props) {
topLevelTotalPages, topLevelTotalPages,
]); ]);
const displayedComments = readyToDisplayComments ? topLevelComments : [];
return ( return (
<Card <Card
title={ title={
@ -295,33 +323,8 @@ function CommentList(props: Props) {
})} })}
ref={commentRef} ref={commentRef}
> >
{topLevelComments && {readyToDisplayComments && pinnedComments && getCommentElems(pinnedComments)}
displayedComments && {readyToDisplayComments && topLevelComments && getCommentElems(topLevelComments)}
displayedComments.map((comment) => {
return (
<CommentView
isTopLevel
threadDepth={3}
key={comment.comment_id}
uri={uri}
authorUri={comment.channel_url}
author={comment.channel_name}
claimId={comment.claim_id}
commentId={comment.comment_id}
message={comment.comment}
timePosted={comment.timestamp * 1000}
claimIsMine={claimIsMine}
commentIsMine={comment.channel_id && isMyComment(comment.channel_id)}
linkedCommentId={linkedCommentId}
isPinned={comment.is_pinned}
supportAmount={comment.support_amount}
numDirectReplies={comment.replies}
isModerator={comment.is_moderator}
isGlobalMod={comment.is_global_mod}
isFiat={comment.is_fiat}
/>
);
})}
</ul> </ul>
{isMobile && ( {isMobile && (

View file

@ -3,22 +3,22 @@ import { makeSelectClaimForUri, selectMyChannelClaims } from 'lbry-redux';
import { doCommentSocketConnect, doCommentSocketDisconnect } from 'redux/actions/websocket'; import { doCommentSocketConnect, doCommentSocketDisconnect } from 'redux/actions/websocket';
import { doCommentList, doSuperChatList } from 'redux/actions/comments'; import { doCommentList, doSuperChatList } from 'redux/actions/comments';
import { import {
selectPinnedCommentsById,
makeSelectTopLevelCommentsForUri, makeSelectTopLevelCommentsForUri,
selectIsFetchingComments, selectIsFetchingComments,
makeSelectSuperChatsForUri, makeSelectSuperChatsForUri,
makeSelectSuperChatTotalAmountForUri, makeSelectSuperChatTotalAmountForUri,
makeSelectPinnedCommentsForUri,
} from 'redux/selectors/comments'; } from 'redux/selectors/comments';
import LivestreamComments from './view'; import LivestreamComments from './view';
const select = (state, props) => ({ const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state), claim: makeSelectClaimForUri(props.uri)(state),
comments: makeSelectTopLevelCommentsForUri(props.uri)(state).slice(0, 75), comments: makeSelectTopLevelCommentsForUri(props.uri)(state).slice(0, 75),
pinnedComments: makeSelectPinnedCommentsForUri(props.uri)(state),
fetchingComments: selectIsFetchingComments(state), fetchingComments: selectIsFetchingComments(state),
superChats: makeSelectSuperChatsForUri(props.uri)(state), superChats: makeSelectSuperChatsForUri(props.uri)(state),
superChatsTotalAmount: makeSelectSuperChatTotalAmountForUri(props.uri)(state), superChatsTotalAmount: makeSelectSuperChatTotalAmountForUri(props.uri)(state),
myChannels: selectMyChannelClaims(state), myChannels: selectMyChannelClaims(state),
pinnedCommentsById: selectPinnedCommentsById(state),
}); });
export default connect(select, { export default connect(select, {

View file

@ -19,11 +19,11 @@ type Props = {
doCommentSocketDisconnect: (string) => void, doCommentSocketDisconnect: (string) => void,
doCommentList: (string, string, number, number) => void, doCommentList: (string, string, number, number) => void,
comments: Array<Comment>, comments: Array<Comment>,
pinnedComments: Array<Comment>,
fetchingComments: boolean, fetchingComments: boolean,
doSuperChatList: (string) => void, doSuperChatList: (string) => void,
superChats: Array<Comment>, superChats: Array<Comment>,
myChannels: ?Array<ChannelClaim>, myChannels: ?Array<ChannelClaim>,
pinnedCommentsById: { [claimId: string]: Array<string> },
}; };
const VIEW_MODE_CHAT = 'view_chat'; const VIEW_MODE_CHAT = 'view_chat';
@ -38,12 +38,12 @@ export default function LivestreamComments(props: Props) {
doCommentSocketConnect, doCommentSocketConnect,
doCommentSocketDisconnect, doCommentSocketDisconnect,
comments: commentsByChronologicalOrder, comments: commentsByChronologicalOrder,
pinnedComments,
doCommentList, doCommentList,
fetchingComments, fetchingComments,
doSuperChatList, doSuperChatList,
myChannels, myChannels,
superChats: superChatsByTipAmount, superChats: superChatsByTipAmount,
pinnedCommentsById,
} = props; } = props;
let superChatsFiatAmount, superChatsTotalAmount; let superChatsFiatAmount, superChatsTotalAmount;
@ -57,11 +57,7 @@ export default function LivestreamComments(props: Props) {
const discussionElement = document.querySelector('.livestream__comments'); const discussionElement = document.querySelector('.livestream__comments');
let pinnedComment; const pinnedComment = pinnedComments.length > 0 ? pinnedComments[0] : null;
const pinnedCommentIds = (claimId && pinnedCommentsById[claimId]) || [];
if (pinnedCommentIds.length > 0) {
pinnedComment = commentsByChronologicalOrder.find((c) => c.comment_id === pinnedCommentIds[0]);
}
function restoreScrollPos() { function restoreScrollPos() {
if (discussionElement) { if (discussionElement) {

View file

@ -301,9 +301,10 @@ export default handleActions(
for (let i = 0; i < comments.length; ++i) { for (let i = 0; i < comments.length; ++i) {
const comment = comments[i]; const comment = comments[i];
commonUpdateAction(comment, commentById, commentIds, i); commonUpdateAction(comment, commentById, commentIds, i);
pushToArrayInObject(topLevelCommentsById, claimId, comment.comment_id);
if (comment.is_pinned) { if (comment.is_pinned) {
pushToArrayInObject(pinnedCommentsById, claimId, comment.comment_id); pushToArrayInObject(pinnedCommentsById, claimId, comment.comment_id);
} else {
pushToArrayInObject(topLevelCommentsById, claimId, comment.comment_id);
} }
} }
} }
@ -615,25 +616,31 @@ export default handleActions(
const topLevelCommentsById = Object.assign({}, state.topLevelCommentsById); const topLevelCommentsById = Object.assign({}, state.topLevelCommentsById);
const pinnedCommentsById = Object.assign({}, state.pinnedCommentsById); const pinnedCommentsById = Object.assign({}, state.pinnedCommentsById);
if (pinnedComment && topLevelCommentsById[claimId]) { if (pinnedComment) {
if (topLevelCommentsById[claimId]) {
const index = topLevelCommentsById[claimId].indexOf(pinnedComment.comment_id); const index = topLevelCommentsById[claimId].indexOf(pinnedComment.comment_id);
if (index > -1) { if (index > -1) {
topLevelCommentsById[claimId].splice(index, 1); topLevelCommentsById[claimId].splice(index, 1);
}
} else {
topLevelCommentsById[claimId] = [];
}
if (pinnedCommentsById[claimId]) { if (pinnedCommentsById[claimId]) {
// Remove here so that the 'unshift' below will be a unique entry. const index = pinnedCommentsById[claimId].indexOf(pinnedComment.comment_id);
pinnedCommentsById[claimId] = pinnedCommentsById[claimId].filter((x) => x !== pinnedComment.comment_id); if (index > -1) {
pinnedCommentsById[claimId].splice(index, 1);
}
} else { } else {
pinnedCommentsById[claimId] = []; pinnedCommentsById[claimId] = [];
} }
if (unpin) { if (unpin) {
// Without the sort score, I have no idea where to put it. Just // 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 // dump it at the top. Users can refresh if they want it back to
// the correct sorted position. // the correct sorted position.
topLevelCommentsById[claimId].push(pinnedComment.comment_id);
} else {
topLevelCommentsById[claimId].unshift(pinnedComment.comment_id); topLevelCommentsById[claimId].unshift(pinnedComment.comment_id);
} else {
pinnedCommentsById[claimId].unshift(pinnedComment.comment_id); pinnedCommentsById[claimId].unshift(pinnedComment.comment_id);
} }
@ -655,7 +662,6 @@ export default handleActions(
commentById[pinnedComment.comment_id] = pinnedComment; commentById[pinnedComment.comment_id] = pinnedComment;
} }
} }
}
return { return {
...state, ...state,

View file

@ -13,7 +13,27 @@ export const selectIsFetchingCommentsByParentId = createSelector(selectState, (s
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);
export const selectOthersReactsById = createSelector(selectState, (state) => state.othersReactsByCommentId); export const selectOthersReactsById = createSelector(selectState, (state) => state.othersReactsByCommentId);
export const selectPinnedCommentsById = createSelector(selectState, (state) => state.pinnedCommentsById); 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) => export const selectModerationBlockList = createSelector(selectState, (state) =>
state.moderationBlockList ? state.moderationBlockList.reverse() : [] state.moderationBlockList ? state.moderationBlockList.reverse() : []