lbry-desktop/ui/redux/reducers/comments.js
2021-08-12 10:39:21 +08:00

1062 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: {},
commentsDisabledChannelIds: [],
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,
authorClaimId,
} = action.data;
const commentsDisabledChannelIds = [...state.commentsDisabledChannelIds];
if (disabled) {
if (!commentsDisabledChannelIds.includes(authorClaimId)) {
commentsDisabledChannelIds.push(authorClaimId);
}
const isLoadingByParentId = Object.assign({}, state.isLoadingByParentId);
if (parentId) {
isLoadingByParentId[parentId] = false;
}
return {
...state,
commentsDisabledChannelIds,
isLoading: false,
isLoadingByParentId,
};
} else {
const index = commentsDisabledChannelIds.indexOf(authorClaimId);
if (index > -1) {
commentsDisabledChannelIds.splice(index, 1);
}
}
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);
if (!parentId) {
totalCommentsById[claimId] = totalItems;
topLevelTotalCommentsById[claimId] = totalFilteredItems;
topLevelTotalPagesById[claimId] = totalPages;
} else {
totalRepliesByParentId[parentId] = totalFilteredItems;
isLoadingByParentId[parentId] = false;
}
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);
pushToArrayInObject(topLevelCommentsById, claimId, comment.comment_id);
if (comment.is_pinned) {
pushToArrayInObject(pinnedCommentsById, 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,
commentsDisabledChannelIds,
isLoading: false,
isLoadingByParentId,
};
},
[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 && 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 (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 (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) => {
// TODO: This is incorrect, as it could make 'settingsByChannelId' store
// only 1 channel with other channel's data purged. It works for now
// because the GUI only shows 1 channel's setting at a time, and *always*
// re-fetches to get latest data before displaying. Either rename this to
// 'activeChannelCreatorSettings', or append the new data properly.
return {
...state,
settingsByChannelId: action.data,
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
);