lbry-desktop/ui/redux/reducers/comments.js
infinite-persistence 4688b4bf58
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.
2021-08-17 12:09:55 -04:00

1066 lines
38 KiB
JavaScript

// @flow
import * as ACTIONS from 'constants/action_types';
import { handleActions } from 'util/redux-utils';
import { BLOCK_LEVEL } from 'constants/comment';
import { isURIEqual } from 'lbry-redux';
const defaultState: CommentsState = {
commentById: {}, // commentId -> Comment
byId: {}, // ClaimID -> list of fetched comment IDs.
totalCommentsById: {}, // ClaimId -> ultimate total (including replies) in commentron.
repliesByParentId: {}, // ParentCommentID -> list of fetched replies.
totalRepliesByParentId: {}, // ParentCommentID -> total replies in commentron.
topLevelCommentsById: {}, // ClaimID -> list of fetched top level comments.
topLevelTotalPagesById: {}, // ClaimID -> total number of top-level pages in commentron. Based on COMMENT_PAGE_SIZE_TOP_LEVEL.
topLevelTotalCommentsById: {}, // ClaimID -> total top level comments in commentron.
// TODO:
// Remove commentsByUri
// It is not needed and doesn't provide anything but confusion
commentsByUri: {}, // URI -> claimId
linkedCommentAncestors: {}, // {"linkedCommentId": ["parentId", "grandParentId", ...]}
superChatsByUri: {},
pinnedCommentsById: {}, // ClaimId -> array of pinned comment IDs
isLoading: false,
isLoadingByParentId: {},
isCommenting: false,
myComments: undefined,
isFetchingReacts: false,
pendingCommentReactions: [],
typesReacting: [],
myReactsByCommentId: undefined,
othersReactsByCommentId: undefined,
moderationBlockList: undefined,
adminBlockList: undefined,
moderatorBlockList: undefined,
moderatorBlockListDelegatorsMap: {},
fetchingModerationBlockList: false,
moderationDelegatesById: {},
fetchingModerationDelegates: false,
moderationDelegatorsById: {},
fetchingModerationDelegators: false,
blockingByUri: {},
unBlockingByUri: {},
togglingForDelegatorMap: {},
settingsByChannelId: {}, // ChannelId -> PerChannelSettings
fetchingSettings: false,
fetchingBlockedWords: false,
};
function pushToArrayInObject(obj, key, valueToPush) {
if (!obj[key]) {
obj[key] = [valueToPush];
} else if (!obj[key].includes(valueToPush)) {
obj[key].push(valueToPush);
}
}
export default handleActions(
{
[ACTIONS.COMMENT_CREATE_STARTED]: (state: CommentsState, action: any): CommentsState => ({
...state,
isCommenting: true,
}),
[ACTIONS.COMMENT_CREATE_FAILED]: (state: CommentsState, action: any) => ({
...state,
isCommenting: false,
}),
[ACTIONS.COMMENT_CREATE_COMPLETED]: (state: CommentsState, action: any): CommentsState => {
const {
comment,
claimId,
uri,
livestream,
}: { comment: Comment, claimId: string, uri: string, livestream: boolean } = action.data;
const commentById = Object.assign({}, state.commentById);
const byId = Object.assign({}, state.byId);
const totalCommentsById = Object.assign({}, state.totalCommentsById);
const topLevelCommentsById = Object.assign({}, state.topLevelCommentsById); // was byId {ClaimId -> [commentIds...]}
const repliesByParentId = Object.assign({}, state.repliesByParentId); // {ParentCommentID -> [commentIds...] } list of reply comments
const totalRepliesByParentId = Object.assign({}, state.totalRepliesByParentId);
const commentsByUri = Object.assign({}, state.commentsByUri);
const comments = byId[claimId] || [];
const newCommentIds = comments.slice();
// If it was created during a livestream, let the websocket handler perform the state update
if (!livestream) {
// add the comment by its ID
commentById[comment.comment_id] = comment;
// push the comment_id to the top of ID list
newCommentIds.unshift(comment.comment_id);
byId[claimId] = newCommentIds;
if (totalCommentsById[claimId]) {
totalCommentsById[claimId] += 1;
}
if (comment['parent_id']) {
if (!repliesByParentId[comment.parent_id]) {
repliesByParentId[comment.parent_id] = [comment.comment_id];
} else {
repliesByParentId[comment.parent_id].unshift(comment.comment_id);
}
if (!totalRepliesByParentId[comment.parent_id]) {
totalRepliesByParentId[comment.parent_id] = 1;
} else {
totalRepliesByParentId[comment.parent_id] += 1;
}
// Update the parent's "replies" value
if (commentById[comment.parent_id]) {
commentById[comment.parent_id].replies = (commentById[comment.parent_id].replies || 0) + 1;
}
} else {
if (!topLevelCommentsById[claimId]) {
commentsByUri[uri] = claimId;
topLevelCommentsById[claimId] = [comment.comment_id];
} else {
topLevelCommentsById[claimId].unshift(comment.comment_id);
}
}
}
return {
...state,
topLevelCommentsById,
repliesByParentId,
totalRepliesByParentId,
commentById,
byId,
totalCommentsById,
commentsByUri,
isLoading: false,
isCommenting: false,
};
},
[ACTIONS.COMMENT_REACTION_LIST_STARTED]: (state: CommentsState, action: any): CommentsState => ({
...state,
isFetchingReacts: true,
}),
[ACTIONS.COMMENT_REACTION_LIST_FAILED]: (state: CommentsState, action: any) => ({
...state,
isFetchingReacts: false,
}),
[ACTIONS.COMMENT_REACT_FAILED]: (state: CommentsState, action: any): CommentsState => {
const commentReaction = action.data; // String: reactionHash + type
const newReactingTypes = new Set(state.pendingCommentReactions);
newReactingTypes.delete(commentReaction);
return {
...state,
pendingCommentReactions: Array.from(newReactingTypes),
};
},
[ACTIONS.COMMENT_REACT_STARTED]: (state: CommentsState, action: any): CommentsState => {
const commentReaction = action.data;
const newReactingTypes = new Set(state.pendingCommentReactions);
newReactingTypes.add(commentReaction);
return {
...state,
pendingCommentReactions: Array.from(newReactingTypes),
};
},
[ACTIONS.COMMENT_REACT_COMPLETED]: (state: CommentsState, action: any): CommentsState => {
const commentReaction = action.data; // String: reactionHash + type
const newReactingTypes = new Set(state.pendingCommentReactions);
newReactingTypes.delete(commentReaction);
return {
...state,
pendingCommentReactions: Array.from(newReactingTypes),
};
},
[ACTIONS.COMMENT_REACTION_LIST_COMPLETED]: (state: CommentsState, action: any): CommentsState => {
const { myReactions, othersReactions, channelId, commentIds } = action.data;
const myReacts = Object.assign({}, state.myReactsByCommentId);
const othersReacts = Object.assign({}, state.othersReactsByCommentId);
const myReactionsEntries = myReactions ? Object.entries(myReactions) : [];
const othersReactionsEntries = othersReactions ? Object.entries(othersReactions) : [];
if (myReactionsEntries.length > 0) {
myReactionsEntries.forEach(([commentId, reactions]) => {
const key = channelId ? `${commentId}:${channelId}` : commentId;
myReacts[key] = Object.entries(reactions).reduce((acc, [name, count]) => {
if (count === 1) {
acc.push(name);
}
return acc;
}, []);
});
} else {
commentIds.forEach((commentId) => {
const key = channelId ? `${commentId}:${channelId}` : commentId;
myReacts[key] = [];
});
}
if (othersReactionsEntries.length > 0) {
othersReactionsEntries.forEach(([commentId, reactions]) => {
const key = channelId ? `${commentId}:${channelId}` : commentId;
othersReacts[key] = reactions;
});
} else {
commentIds.forEach((commentId) => {
const key = channelId ? `${commentId}:${channelId}` : commentId;
othersReacts[key] = {};
});
}
return {
...state,
isFetchingReacts: false,
myReactsByCommentId: myReacts,
othersReactsByCommentId: othersReacts,
};
},
[ACTIONS.COMMENT_LIST_STARTED]: (state, action: any) => {
const { parentId } = action.data;
const isLoadingByParentId = Object.assign({}, state.isLoadingByParentId);
if (parentId) {
isLoadingByParentId[parentId] = true;
}
return {
...state,
isLoading: true,
isLoadingByParentId,
};
},
[ACTIONS.COMMENT_LIST_COMPLETED]: (state: CommentsState, action: any) => {
const {
comments,
parentId,
totalItems,
totalFilteredItems,
totalPages,
claimId,
uri,
disabled,
commenterClaimId,
} = action.data;
const commentById = Object.assign({}, state.commentById);
const byId = Object.assign({}, state.byId);
const topLevelCommentsById = Object.assign({}, state.topLevelCommentsById); // was byId {ClaimId -> [commentIds...]}
const topLevelTotalCommentsById = Object.assign({}, state.topLevelTotalCommentsById);
const topLevelTotalPagesById = Object.assign({}, state.topLevelTotalPagesById);
const commentsByUri = Object.assign({}, state.commentsByUri);
const repliesByParentId = Object.assign({}, state.repliesByParentId);
const totalCommentsById = Object.assign({}, state.totalCommentsById);
const pinnedCommentsById = Object.assign({}, state.pinnedCommentsById);
const totalRepliesByParentId = Object.assign({}, state.totalRepliesByParentId);
const isLoadingByParentId = Object.assign({}, state.isLoadingByParentId);
const settingsByChannelId = Object.assign({}, state.settingsByChannelId);
settingsByChannelId[commenterClaimId] = {
...(settingsByChannelId[commenterClaimId] || {}),
comments_enabled: !disabled,
};
if (parentId) {
isLoadingByParentId[parentId] = false;
}
if (!disabled) {
if (parentId) {
totalRepliesByParentId[parentId] = totalFilteredItems;
} else {
totalCommentsById[claimId] = totalItems;
topLevelTotalCommentsById[claimId] = totalFilteredItems;
topLevelTotalPagesById[claimId] = totalPages;
}
const commonUpdateAction = (comment, commentById, commentIds, index) => {
// map the comment_ids to the new comments
commentById[comment.comment_id] = comment;
commentIds[index] = comment.comment_id;
};
if (comments) {
// we use an Array to preserve order of listing
// in reality this doesn't matter and we can just
// sort comments by their timestamp
const commentIds = Array(comments.length);
// --- Top-level comments ---
if (!parentId) {
for (let i = 0; i < comments.length; ++i) {
const comment = comments[i];
commonUpdateAction(comment, commentById, commentIds, i);
if (comment.is_pinned) {
pushToArrayInObject(pinnedCommentsById, claimId, comment.comment_id);
} else {
pushToArrayInObject(topLevelCommentsById, claimId, comment.comment_id);
}
}
}
// --- Replies ---
else {
for (let i = 0; i < comments.length; ++i) {
const comment = comments[i];
commonUpdateAction(comment, commentById, commentIds, i);
pushToArrayInObject(repliesByParentId, parentId, comment.comment_id);
}
}
byId[claimId] ? byId[claimId].push(...commentIds) : (byId[claimId] = commentIds);
commentsByUri[uri] = claimId;
}
}
return {
...state,
topLevelCommentsById,
topLevelTotalCommentsById,
topLevelTotalPagesById,
repliesByParentId,
totalCommentsById,
pinnedCommentsById,
totalRepliesByParentId,
byId,
commentById,
commentsByUri,
isLoading: false,
isLoadingByParentId,
settingsByChannelId,
};
},
[ACTIONS.COMMENT_BY_ID_COMPLETED]: (state: CommentsState, action: any) => {
const { comment, ancestors } = action.data;
const claimId = comment.claim_id;
const commentById = Object.assign({}, state.commentById);
const byId = Object.assign({}, state.byId);
const topLevelCommentsById = Object.assign({}, state.topLevelCommentsById); // was byId {ClaimId -> [commentIds...]}
const topLevelTotalCommentsById = Object.assign({}, state.topLevelTotalCommentsById);
const topLevelTotalPagesById = Object.assign({}, state.topLevelTotalPagesById);
const repliesByParentId = Object.assign({}, state.repliesByParentId);
const linkedCommentAncestors = Object.assign({}, state.linkedCommentAncestors);
const updateStore = (comment, commentById, byId, repliesByParentId, topLevelCommentsById) => {
// 'comment.ByID' doesn't populate 'replies'. We should have at least 1
// at the moment, and the correct value will populated by 'comment.List'.
commentById[comment.comment_id] = { ...comment, replies: 1 };
byId[claimId] ? byId[claimId].unshift(comment.comment_id) : (byId[claimId] = [comment.comment_id]);
const parentId = comment.parent_id;
if (comment.parent_id) {
pushToArrayInObject(repliesByParentId, parentId, comment.comment_id);
} else {
pushToArrayInObject(topLevelCommentsById, claimId, comment.comment_id);
}
};
updateStore(comment, commentById, byId, repliesByParentId, topLevelCommentsById);
if (ancestors) {
ancestors.forEach((ancestor) => {
updateStore(ancestor, commentById, byId, repliesByParentId, topLevelCommentsById);
pushToArrayInObject(linkedCommentAncestors, comment.comment_id, ancestor.comment_id);
});
}
return {
...state,
topLevelCommentsById,
topLevelTotalCommentsById,
topLevelTotalPagesById,
repliesByParentId,
byId,
commentById,
linkedCommentAncestors,
};
},
[ACTIONS.COMMENT_SUPER_CHAT_LIST_FAILED]: (state: CommentsState, action: any) => ({
...state,
isLoading: false,
}),
[ACTIONS.COMMENT_SUPER_CHAT_LIST_STARTED]: (state) => ({ ...state, isLoading: true }),
[ACTIONS.COMMENT_SUPER_CHAT_LIST_COMPLETED]: (state: CommentsState, action: any) => {
const { comments, totalAmount, uri } = action.data;
return {
...state,
superChatsByUri: {
...state.superChatsByUri,
[uri]: {
comments,
totalAmount,
},
},
isLoading: false,
};
},
[ACTIONS.COMMENT_LIST_FAILED]: (state: CommentsState, action: any) => ({
...state,
isLoading: false,
}),
[ACTIONS.COMMENT_LIST_RESET]: (state: CommentsState, action: any) => {
const { claimId } = action.data;
const byId = Object.assign({}, state.byId);
const totalCommentsById = Object.assign({}, state.totalCommentsById);
const topLevelCommentsById = Object.assign({}, state.topLevelCommentsById); // was byId {ClaimId -> [commentIds...]}
const topLevelTotalCommentsById = Object.assign({}, state.topLevelTotalCommentsById);
const topLevelTotalPagesById = Object.assign({}, state.topLevelTotalPagesById);
const myReacts = Object.assign({}, state.myReactsByCommentId);
const othersReacts = Object.assign({}, state.othersReactsByCommentId);
function deleteReacts(reactObj, commentIdsToRemove) {
if (commentIdsToRemove && commentIdsToRemove.length > 0) {
let reactionKeys = Object.keys(reactObj);
reactionKeys.forEach((rk) => {
const colonIndex = rk.indexOf(':');
const commentId = colonIndex === -1 ? rk : rk.substring(0, colonIndex);
if (commentIdsToRemove.includes(commentId)) {
delete reactObj[rk];
}
});
}
}
deleteReacts(myReacts, byId[claimId]);
deleteReacts(othersReacts, byId[claimId]);
delete byId[claimId];
delete totalCommentsById[claimId];
delete topLevelCommentsById[claimId];
delete topLevelTotalCommentsById[claimId];
delete topLevelTotalPagesById[claimId];
return {
...state,
byId,
totalCommentsById,
topLevelCommentsById,
topLevelTotalCommentsById,
topLevelTotalPagesById,
myReactsByCommentId: myReacts,
othersReactsByCommentId: othersReacts,
};
},
[ACTIONS.COMMENT_RECEIVED]: (state: CommentsState, action: any) => {
const { uri, claimId, comment } = action.data;
const commentsByUri = Object.assign({}, state.commentsByUri);
const commentsByClaimId = Object.assign({}, state.byId);
const allCommentsById = Object.assign({}, state.commentById);
const topLevelCommentsById = Object.assign({}, state.topLevelCommentsById);
const superChatsByUri = Object.assign({}, state.superChatsByUri);
const commentsForId = topLevelCommentsById[claimId];
allCommentsById[comment.comment_id] = comment;
commentsByUri[uri] = claimId;
if (commentsForId) {
const newCommentsForId = commentsForId.slice();
const commentExists = newCommentsForId.includes(comment.comment_id);
if (!commentExists) {
newCommentsForId.unshift(comment.comment_id);
}
topLevelCommentsById[claimId] = newCommentsForId;
} else {
topLevelCommentsById[claimId] = [comment.comment_id];
}
// We don't care to keep existing lower level comments since this is just for livestreams
commentsByClaimId[claimId] = topLevelCommentsById[claimId];
if (comment.support_amount > 0) {
const superChatForUri = superChatsByUri[uri];
const superChatCommentsForUri = superChatForUri && superChatForUri.comments;
let sortedSuperChatComments = [];
let hasAddedNewComment = false;
if (superChatCommentsForUri && superChatCommentsForUri.length > 0) {
// Go for the entire length of superChatCommentsForUri since a comment will be added to this list
for (var i = 0; i < superChatCommentsForUri.length; i++) {
const existingSuperChat = superChatCommentsForUri[i];
if (existingSuperChat.support_amount < comment.support_amount && !hasAddedNewComment) {
hasAddedNewComment = true;
sortedSuperChatComments.push(comment);
sortedSuperChatComments.push(existingSuperChat);
} else {
sortedSuperChatComments.push(existingSuperChat);
}
// If the new superchat hasn't been added yet, it must be the smallest superchat in the list
if (
i === superChatCommentsForUri.length - 1 &&
sortedSuperChatComments.length === superChatCommentsForUri.length
) {
sortedSuperChatComments.push(comment);
}
}
superChatsByUri[uri].comments = sortedSuperChatComments;
superChatsByUri[uri].totalAmount += comment.support_amount;
} else {
superChatsByUri[uri] = { comments: [comment], totalAmount: comment.support_amount };
}
}
return {
...state,
byId: commentsByClaimId,
commentById: allCommentsById,
commentsByUri,
topLevelCommentsById,
superChatsByUri,
};
},
[ACTIONS.COMMENT_ABANDON_STARTED]: (state: CommentsState, action: any) => ({
...state,
isLoading: true,
}),
[ACTIONS.COMMENT_ABANDON_COMPLETED]: (state: CommentsState, action: any) => {
const { comment_id } = action.data;
const commentById = Object.assign({}, state.commentById);
const byId = Object.assign({}, state.byId);
const repliesByParentId = Object.assign({}, state.repliesByParentId); // {ParentCommentID -> [commentIds...] } list of reply comments
const totalRepliesByParentId = Object.assign({}, state.totalRepliesByParentId);
const totalCommentsById = Object.assign({}, state.totalCommentsById);
const comment = commentById[comment_id];
// to remove the comment and its references
const claimId = comment.claim_id;
for (let i = 0; i < byId[claimId].length; i++) {
if (byId[claimId][i] === comment_id) {
byId[claimId].splice(i, 1);
break;
}
}
// Update replies
if (comment['parent_id'] && repliesByParentId[comment.parent_id]) {
const index = repliesByParentId[comment.parent_id].indexOf(comment.comment_id);
if (index > -1) {
repliesByParentId[comment.parent_id].splice(index, 1);
if (commentById[comment.parent_id]) {
commentById[comment.parent_id].replies = Math.max(0, (commentById[comment.parent_id].replies || 0) - 1);
}
if (totalRepliesByParentId[comment.parent_id]) {
totalRepliesByParentId[comment.parent_id] = Math.max(0, totalRepliesByParentId[comment.parent_id] - 1);
}
}
}
if (totalCommentsById[claimId]) {
totalCommentsById[claimId] = Math.max(0, totalCommentsById[claimId] - 1);
}
delete commentById[comment_id];
return {
...state,
commentById,
byId,
totalCommentsById,
repliesByParentId,
totalRepliesByParentId,
isLoading: false,
};
},
[ACTIONS.COMMENT_ABANDON_FAILED]: (state: CommentsState, action: any) => ({
...state,
isCommenting: false,
}),
[ACTIONS.COMMENT_UPDATE_STARTED]: (state: CommentsState, action: any) => ({
...state,
isCommenting: true,
}),
[ACTIONS.COMMENT_UPDATE_COMPLETED]: (state: CommentsState, action: any) => {
const { comment } = action.data;
const commentById = Object.assign({}, state.commentById);
commentById[comment.comment_id] = comment;
return {
...state,
commentById,
isCommenting: false,
};
},
[ACTIONS.COMMENT_UPDATE_FAILED]: (state: CommentsState, action: any) => ({
...state,
isCmmenting: false,
}),
[ACTIONS.COMMENT_PIN_COMPLETED]: (state: CommentsState, action: any) => {
const { pinnedComment, claimId, unpin } = action.data;
const commentById = Object.assign({}, state.commentById);
const topLevelCommentsById = Object.assign({}, state.topLevelCommentsById);
const pinnedCommentsById = Object.assign({}, state.pinnedCommentsById);
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 (pinnedCommentsById[claimId]) {
const index = pinnedCommentsById[claimId].indexOf(pinnedComment.comment_id);
if (index > -1) {
pinnedCommentsById[claimId].splice(index, 1);
}
} else {
pinnedCommentsById[claimId] = [];
}
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;
}
}
return {
...state,
commentById,
topLevelCommentsById,
pinnedCommentsById,
};
},
[ACTIONS.COMMENT_MODERATION_BLOCK_LIST_STARTED]: (state: CommentsState, action: any) => ({
...state,
fetchingModerationBlockList: true,
}),
[ACTIONS.COMMENT_MODERATION_BLOCK_LIST_COMPLETED]: (state: CommentsState, action: any) => {
const { personalBlockList, adminBlockList, moderatorBlockList, moderatorBlockListDelegatorsMap } = action.data;
return {
...state,
moderationBlockList: personalBlockList,
adminBlockList: adminBlockList,
moderatorBlockList: moderatorBlockList,
moderatorBlockListDelegatorsMap: moderatorBlockListDelegatorsMap,
fetchingModerationBlockList: false,
};
},
[ACTIONS.COMMENT_MODERATION_BLOCK_LIST_FAILED]: (state: CommentsState, action: any) => ({
...state,
fetchingModerationBlockList: false,
}),
[ACTIONS.COMMENT_MODERATION_BLOCK_STARTED]: (state: CommentsState, action: any) => {
const { blockedUri, creatorUri, blockLevel } = action.data;
switch (blockLevel) {
default:
case BLOCK_LEVEL.SELF:
case BLOCK_LEVEL.ADMIN:
return {
...state,
blockingByUri: {
...state.blockingByUri,
[blockedUri]: true,
},
};
case BLOCK_LEVEL.MODERATOR:
const newMap = Object.assign({}, state.togglingForDelegatorMap);
const togglingDelegatorsForBlockedUri = newMap[blockedUri];
if (togglingDelegatorsForBlockedUri) {
if (!togglingDelegatorsForBlockedUri.includes(creatorUri)) {
togglingDelegatorsForBlockedUri.push(creatorUri);
}
} else {
newMap[blockedUri] = [creatorUri];
}
return {
...state,
togglingForDelegatorMap: newMap,
};
}
},
[ACTIONS.COMMENT_MODERATION_UN_BLOCK_STARTED]: (state: CommentsState, action: any) => {
const { blockedUri, creatorUri, blockLevel } = action.data;
switch (blockLevel) {
default:
case BLOCK_LEVEL.SELF:
case BLOCK_LEVEL.ADMIN:
return {
...state,
unBlockingByUri: {
...state.unBlockingByUri,
[blockedUri]: true,
},
};
case BLOCK_LEVEL.MODERATOR:
const newMap = Object.assign({}, state.togglingForDelegatorMap);
const togglingDelegatorsForBlockedUri = newMap[blockedUri];
if (togglingDelegatorsForBlockedUri) {
if (!togglingDelegatorsForBlockedUri.includes(creatorUri)) {
togglingDelegatorsForBlockedUri.push(creatorUri);
}
} else {
newMap[blockedUri] = [creatorUri];
}
return {
...state,
togglingForDelegatorMap: newMap,
};
}
},
[ACTIONS.COMMENT_MODERATION_BLOCK_FAILED]: (state: CommentsState, action: any) => {
const { blockedUri, creatorUri, blockLevel } = action.data;
switch (blockLevel) {
default:
case BLOCK_LEVEL.SELF:
case BLOCK_LEVEL.ADMIN:
return {
...state,
blockingByUri: {
...state.blockingByUri,
[blockedUri]: false,
},
};
case BLOCK_LEVEL.MODERATOR:
const newMap = Object.assign({}, state.togglingForDelegatorMap);
const togglingDelegatorsForBlockedUri = newMap[blockedUri];
if (togglingDelegatorsForBlockedUri) {
newMap[blockedUri] = togglingDelegatorsForBlockedUri.filter((x) => x !== creatorUri);
}
return {
...state,
togglingForDelegatorMap: newMap,
};
}
},
[ACTIONS.COMMENT_MODERATION_UN_BLOCK_FAILED]: (state: CommentsState, action: any) => {
const { blockedUri, creatorUri, blockLevel } = action.data;
switch (blockLevel) {
default:
case BLOCK_LEVEL.SELF:
case BLOCK_LEVEL.ADMIN:
return {
...state,
unBlockingByUri: {
...state.unBlockingByUri,
[blockedUri]: false,
},
};
case BLOCK_LEVEL.MODERATOR:
const newMap = Object.assign({}, state.togglingForDelegatorMap);
const togglingDelegatorsForBlockedUri = newMap[blockedUri];
if (togglingDelegatorsForBlockedUri) {
newMap[blockedUri] = togglingDelegatorsForBlockedUri.filter((x) => x !== creatorUri);
}
return {
...state,
togglingForDelegatorMap: newMap,
};
}
},
[ACTIONS.COMMENT_MODERATION_BLOCK_COMPLETE]: (state: CommentsState, action: any) => {
const { blockedUri, creatorUri, blockLevel } = action.data;
const commentById = Object.assign({}, state.commentById);
const blockingByUri = Object.assign({}, state.blockingByUri);
for (const commentId in commentById) {
const comment = commentById[commentId];
if (isURIEqual(blockedUri, comment.channel_url)) {
delete commentById[comment.comment_id];
}
}
switch (blockLevel) {
case BLOCK_LEVEL.SELF: {
const blockList = state.moderationBlockList || [];
const newBlockList = blockList.slice();
newBlockList.push(blockedUri);
delete blockingByUri[blockedUri];
return {
...state,
commentById,
blockingByUri,
moderationBlockList: newBlockList,
};
}
case BLOCK_LEVEL.MODERATOR: {
const blockList = state.moderatorBlockList || [];
const newBlockList = blockList.slice();
// Update main block list
if (!newBlockList.includes(blockedUri)) {
newBlockList.push(blockedUri);
}
// Update list of delegators
const moderatorBlockListDelegatorsMap = Object.assign({}, state.moderatorBlockListDelegatorsMap);
const delegatorUrisForBlockedUri = moderatorBlockListDelegatorsMap[blockedUri];
if (delegatorUrisForBlockedUri) {
if (!delegatorUrisForBlockedUri.includes(creatorUri)) {
delegatorUrisForBlockedUri.push(creatorUri);
}
} else {
moderatorBlockListDelegatorsMap[blockedUri] = [creatorUri];
}
// Remove "toggling" flag
const togglingMap = Object.assign({}, state.togglingForDelegatorMap);
const togglingDelegatorsForBlockedUri = togglingMap[blockedUri];
if (togglingDelegatorsForBlockedUri) {
togglingMap[blockedUri] = togglingDelegatorsForBlockedUri.filter((x) => x !== creatorUri);
}
return {
...state,
commentById,
moderatorBlockList: newBlockList,
moderatorBlockListDelegatorsMap,
togglingForDelegatorMap: togglingMap,
};
}
case BLOCK_LEVEL.ADMIN:
const blockList = state.adminBlockList || [];
const newBlockList = blockList.slice();
newBlockList.push(blockedUri);
delete blockingByUri[blockedUri];
return {
...state,
commentById,
blockingByUri,
adminBlockList: newBlockList,
};
}
},
[ACTIONS.COMMENT_MODERATION_UN_BLOCK_COMPLETE]: (state: CommentsState, action: any) => {
const { blockedUri, creatorUri, blockLevel } = action.data;
const unBlockingByUri = Object.assign(state.unBlockingByUri, {});
switch (blockLevel) {
case BLOCK_LEVEL.SELF: {
const blockList = state.moderationBlockList || [];
delete unBlockingByUri[blockedUri];
return {
...state,
unBlockingByUri,
moderationBlockList: blockList.slice().filter((uri) => uri !== blockedUri),
};
}
case BLOCK_LEVEL.ADMIN: {
const blockList = state.adminBlockList || [];
delete unBlockingByUri[blockedUri];
return {
...state,
unBlockingByUri,
adminBlockList: blockList.slice().filter((uri) => uri !== blockedUri),
};
}
case BLOCK_LEVEL.MODERATOR: {
const blockList = state.moderatorBlockList || [];
const newBlockList = blockList.slice();
const togglingMap = Object.assign({}, state.togglingForDelegatorMap);
const moderatorBlockListDelegatorsMap = Object.assign({}, state.moderatorBlockListDelegatorsMap);
const delegatorUrisForBlockedUri = moderatorBlockListDelegatorsMap[blockedUri];
if (delegatorUrisForBlockedUri) {
const index = delegatorUrisForBlockedUri.indexOf(creatorUri);
if (index > -1) {
// Remove from delegators list
delegatorUrisForBlockedUri.splice(index, 1);
// // Remove blocked entry if it was removed for all delegators
// if (delegatorUrisForBlockedUri.length === 0) {
// delete moderatorBlockListDelegatorsMap[blockedUri];
// newBlockList = newBlockList.filter((uri) => uri !== blockedUri);
// }
// Remove from "toggling" flag
const togglingDelegatorsForBlockedUri = togglingMap[blockedUri];
if (togglingDelegatorsForBlockedUri) {
togglingMap[blockedUri] = togglingDelegatorsForBlockedUri.filter((x) => x !== creatorUri);
}
}
}
return {
...state,
moderatorBlockList: newBlockList,
togglingForDelegatorMap: togglingMap,
};
}
}
},
[ACTIONS.COMMENT_FETCH_MODERATION_DELEGATES_STARTED]: (state: CommentsState, action: any) => ({
...state,
fetchingModerationDelegates: true,
}),
[ACTIONS.COMMENT_FETCH_MODERATION_DELEGATES_FAILED]: (state: CommentsState, action: any) => ({
...state,
fetchingModerationDelegates: false,
}),
[ACTIONS.COMMENT_FETCH_MODERATION_DELEGATES_COMPLETED]: (state: CommentsState, action: any) => {
const moderationDelegatesById = Object.assign({}, state.moderationDelegatesById);
if (action.data.delegates) {
moderationDelegatesById[action.data.id] = action.data.delegates.map((delegate) => {
return {
channelId: delegate.channel_id,
channelName: delegate.channel_name,
};
});
} else {
moderationDelegatesById[action.data.id] = [];
}
return {
...state,
fetchingModerationDelegates: false,
moderationDelegatesById: moderationDelegatesById,
};
},
[ACTIONS.COMMENT_MODERATION_AM_I_LIST_STARTED]: (state: CommentsState, action: any) => ({
...state,
fetchingModerationDelegators: true,
}),
[ACTIONS.COMMENT_MODERATION_AM_I_LIST_FAILED]: (state: CommentsState, action: any) => ({
...state,
fetchingModerationDelegators: true,
}),
[ACTIONS.COMMENT_MODERATION_AM_I_LIST_COMPLETED]: (state: CommentsState, action: any) => {
return {
...state,
fetchingModerationDelegators: true,
moderationDelegatorsById: action.data,
};
},
[ACTIONS.COMMENT_FETCH_SETTINGS_STARTED]: (state: CommentsState, action: any) => ({
...state,
fetchingSettings: true,
}),
[ACTIONS.COMMENT_FETCH_SETTINGS_FAILED]: (state: CommentsState, action: any) => ({
...state,
fetchingSettings: false,
}),
[ACTIONS.COMMENT_FETCH_SETTINGS_COMPLETED]: (state: CommentsState, action: any) => {
const { channelId, settings, partialUpdate } = action.data;
const settingsByChannelId = Object.assign({}, state.settingsByChannelId);
if (partialUpdate) {
settingsByChannelId[channelId] = {
// The existing may contain additional Creator Settings (e.g. 'words')
...(settingsByChannelId[channelId] || {}),
// Spread new settings.
...settings,
};
} else {
settingsByChannelId[channelId] = settings;
if (settings.words) {
settingsByChannelId[channelId].words = settings.words.split(',');
}
}
return {
...state,
settingsByChannelId,
fetchingSettings: false,
};
},
[ACTIONS.COMMENT_FETCH_BLOCKED_WORDS_STARTED]: (state: CommentsState, action: any) => ({
...state,
fetchingBlockedWords: true,
}),
[ACTIONS.COMMENT_FETCH_BLOCKED_WORDS_FAILED]: (state: CommentsState, action: any) => ({
...state,
fetchingBlockedWords: false,
}),
[ACTIONS.COMMENT_FETCH_BLOCKED_WORDS_COMPLETED]: (state: CommentsState, action: any) => {
const blockedWordsByChannelId = action.data;
const settingsByChannelId = Object.assign({}, state.settingsByChannelId);
// blockedWordsByChannelId: {string: [string]}
Object.entries(blockedWordsByChannelId).forEach((x) => {
const channelId = x[0];
if (!settingsByChannelId[channelId]) {
settingsByChannelId[channelId] = {};
}
settingsByChannelId[channelId].words = x[1];
});
return {
...state,
settingsByChannelId,
fetchingBlockedWords: false,
};
},
},
defaultState
);