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:
parent
5f55603fb2
commit
4688b4bf58
6 changed files with 101 additions and 74 deletions
|
@ -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),
|
||||
|
|
|
@ -28,6 +28,7 @@ function scaleToDevicePixelRatio(value) {
|
|||
|
||||
type Props = {
|
||||
allCommentIds: any,
|
||||
pinnedComments: Array<Comment>,
|
||||
topLevelComments: Array<Comment>,
|
||||
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 (
|
||||
<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
|
||||
useEffect(() => {
|
||||
if (page === 0) {
|
||||
|
@ -223,8 +253,6 @@ function CommentList(props: Props) {
|
|||
topLevelTotalPages,
|
||||
]);
|
||||
|
||||
const displayedComments = readyToDisplayComments ? topLevelComments : [];
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={
|
||||
|
@ -295,33 +323,8 @@ function CommentList(props: Props) {
|
|||
})}
|
||||
ref={commentRef}
|
||||
>
|
||||
{topLevelComments &&
|
||||
displayedComments &&
|
||||
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}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{readyToDisplayComments && pinnedComments && getCommentElems(pinnedComments)}
|
||||
{readyToDisplayComments && topLevelComments && getCommentElems(topLevelComments)}
|
||||
</ul>
|
||||
|
||||
{isMobile && (
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -19,11 +19,11 @@ type Props = {
|
|||
doCommentSocketDisconnect: (string) => void,
|
||||
doCommentList: (string, string, number, number) => void,
|
||||
comments: Array<Comment>,
|
||||
pinnedComments: Array<Comment>,
|
||||
fetchingComments: boolean,
|
||||
doSuperChatList: (string) => void,
|
||||
superChats: Array<Comment>,
|
||||
myChannels: ?Array<ChannelClaim>,
|
||||
pinnedCommentsById: { [claimId: string]: Array<string> },
|
||||
};
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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() : []
|
||||
|
|
Loading…
Reference in a new issue