Livestream: stop pinned comments from appearing as latest #6888

Merged
infinite-persistence merged 1 commit from ip/livestream.pin.fix into master 2021-08-17 18:09:55 +02:00
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';
@ -39,12 +39,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;
@ -60,11 +60,7 @@ export default function LivestreamComments(props: Props) {
const discussionElement = document.querySelector('.livestream__comments'); const discussionElement = document.querySelector('.livestream__comments');
const commentElement = document.querySelector('.livestream-comment'); const commentElement = document.querySelector('.livestream-comment');
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]);
}
React.useEffect(() => { React.useEffect(() => {
if (claimId) { if (claimId) {

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,45 +616,50 @@ 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) {
const index = topLevelCommentsById[claimId].indexOf(pinnedComment.comment_id); if (topLevelCommentsById[claimId]) {
if (index > -1) { const index = topLevelCommentsById[claimId].indexOf(pinnedComment.comment_id);
topLevelCommentsById[claimId].splice(index, 1); 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] = [];
} }
} else {
topLevelCommentsById[claimId] = [];
}
if (unpin) { if (pinnedCommentsById[claimId]) {
// Without the sort score, I have no idea where to put it. Just const index = pinnedCommentsById[claimId].indexOf(pinnedComment.comment_id);
// dump it at the bottom. Users can refresh if they want it back to if (index > -1) {
// the correct sorted position. pinnedCommentsById[claimId].splice(index, 1);
topLevelCommentsById[claimId].push(pinnedComment.comment_id);
} else {
topLevelCommentsById[claimId].unshift(pinnedComment.comment_id);
pinnedCommentsById[claimId].unshift(pinnedComment.comment_id);
} }
} else {
pinnedCommentsById[claimId] = [];
}
if (commentById[pinnedComment.comment_id]) { if (unpin) {
// Commentron's `comment.Pin` response places the creator's credentials // Without the sort score, I have no idea where to put it. Just
// in the 'channel_*' fields, which doesn't make sense. Maybe it is to // dump it at the top. Users can refresh if they want it back to
// show who signed/pinned it, but even if so, it shouldn't overload // the correct sorted position.
// these variables which are already used by existing comment data structure. topLevelCommentsById[claimId].unshift(pinnedComment.comment_id);
// Ensure we don't override the existing/correct values, but fallback } else {
// to whatever was given. pinnedCommentsById[claimId].unshift(pinnedComment.comment_id);
const { channel_id, channel_name, channel_url } = commentById[pinnedComment.comment_id]; }
commentById[pinnedComment.comment_id] = {
...pinnedComment, if (commentById[pinnedComment.comment_id]) {
channel_id: channel_id || pinnedComment.channel_id, // Commentron's `comment.Pin` response places the creator's credentials
channel_name: channel_name || pinnedComment.channel_name, // in the 'channel_*' fields, which doesn't make sense. Maybe it is to
channel_url: channel_url || pinnedComment.channel_url, // show who signed/pinned it, but even if so, it shouldn't overload
}; // these variables which are already used by existing comment data structure.
} else { // Ensure we don't override the existing/correct values, but fallback
commentById[pinnedComment.comment_id] = pinnedComment; // 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;
} }
} }

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() : []