From 624ef0c9b73a7afa10227cfeb8975d19a45a8c74 Mon Sep 17 00:00:00 2001 From: Oleg Silkin Date: Sun, 2 Feb 2020 21:19:07 -0500 Subject: [PATCH] Reducers properly handle create, delete, and list operations for comment replies --- src/redux/actions/comments.js | 11 +++++- src/redux/reducers/comments.js | 65 ++++++++++++++++++++++++++++------ 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/redux/actions/comments.js b/src/redux/actions/comments.js index 5ddd34b..71b94e3 100644 --- a/src/redux/actions/comments.js +++ b/src/redux/actions/comments.js @@ -4,7 +4,13 @@ import Lbry from 'lbry'; import { selectClaimsByUri, selectMyChannelClaims } from 'redux/selectors/claims'; import { doToast } from 'redux/actions/notifications'; -export function doCommentList(uri: string, page: number = 1, pageSize: number = 99999) { +// if parentId is provided, `uri` is ignored +export function doCommentList( + uri: string, + page: number = 1, + pageSize: number = 99999, // while the desktop app doesn't paginate + parentId?: string +) { return (dispatch: Dispatch, getState: GetState) => { const state = getState(); const claim = selectClaimsByUri(state)[uri]; @@ -17,6 +23,7 @@ export function doCommentList(uri: string, page: number = 1, pageSize: number = claim_id: claimId, page, page_size: pageSize, + parentId: parentId, }) .then((result: CommentListResponse) => { const { items: comments } = result; @@ -26,6 +33,7 @@ export function doCommentList(uri: string, page: number = 1, pageSize: number = comments, claimId: claimId, uri: uri, + parentId: parentId, }, }); }) @@ -66,6 +74,7 @@ export function doCommentCreate( data: { comment: result, claimId: claim_id, + parentId: parent_id, }, }); }) diff --git a/src/redux/reducers/comments.js b/src/redux/reducers/comments.js index 46d08d8..84619af 100644 --- a/src/redux/reducers/comments.js +++ b/src/redux/reducers/comments.js @@ -7,6 +7,7 @@ const defaultState: CommentsState = { byId: {}, // ClaimID -> list of comments commentsByUri: {}, // URI -> claimId isLoading: false, + repliesByCommentId: {}, // commentId -> list of commentIds myComments: undefined, }; @@ -23,9 +24,10 @@ export const commentReducer = handleActions( }), [ACTIONS.COMMENT_CREATE_COMPLETED]: (state: CommentsState, action: any): CommentsState => { - const { comment, claimId }: { comment: Comment, claimId: string } = action.data; + const { comment, claimId, parentId } = action.data; const commentById = Object.assign({}, state.commentById); const byId = Object.assign({}, state.byId); + const repliesByCommentId = Object.assign({}, state.repliesByCommentId); const comments = byId[claimId]; const newCommentIds = comments.slice(); @@ -36,6 +38,13 @@ export const commentReducer = handleActions( newCommentIds.unshift(comment.comment_id); byId[claimId] = newCommentIds; + if (parentId) { + const newReplies = repliesByCommentId[parentId] || []; + // unlike regular comments, newest replies should be at the bottom of list + newReplies.push(comment.comment_id); + repliesByCommentId[parentId] = newReplies; + } + return { ...state, commentById, @@ -47,32 +56,50 @@ export const commentReducer = handleActions( [ACTIONS.COMMENT_LIST_STARTED]: state => ({ ...state, isLoading: true }), [ACTIONS.COMMENT_LIST_COMPLETED]: (state: CommentsState, action: any) => { - const { comments, claimId, uri } = action.data; + const { comments, claimId, uri, parentId } = action.data; const commentById = Object.assign({}, state.commentById); const byId = Object.assign({}, state.byId); const commentsByUri = Object.assign({}, state.commentsByUri); + const repliesByCommentId = Object.assign({}, state.repliesByCommentId); 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); + const replyThreads = {}; // map the comment_ids to the new comments for (let i = 0; i < comments.length; i++) { - commentIds[i] = comments[i].comment_id; - commentById[commentIds[i]] = comments[i]; + const comment = comments[i]; + commentIds[i] = comment.comment_id; + commentById[commentIds[i]] = comment; + + if (comment.parent_id) { + if (!(comment.parent_id in replyThreads)) { + replyThreads[comment.parent_id] = []; + } + replyThreads[comment.parent_id].push(comment.comment_id); + } } - byId[claimId] = commentIds; + Object.entries(replyThreads).forEach((parent_id, replyIds) => { + repliesByCommentId[parent_id] = replyIds; + }); + commentsByUri[uri] = claimId; + // don't override the entire list with the replies to one comment + if (parentId == null) { + byId[claimId] = commentIds; + } } return { ...state, byId, commentById, commentsByUri, + repliesByCommentId, isLoading: false, }; }, @@ -89,21 +116,37 @@ export const commentReducer = handleActions( const { comment_id } = action.data; const commentById = Object.assign({}, state.commentById); const byId = Object.assign({}, state.byId); + const repliesByCommentId = Object.assign({}, state.repliesByCommentId); - // to remove the comment and its references - const claimId = commentById[comment_id].claim_id; - for (let i = 0; i < byId[claimId].length; i++) { - if (byId[claimId][i] === comment_id) { - byId[claimId].splice(i, 1); - break; + const comment: Comment = commentById[comment_id]; + + // keep record of comment's existence if it has replies + if (!(comment.comment_id in repliesByCommentId)) { + const claimId = commentById[comment_id].claim_id; + for (let i = 0; i < byId[claimId].length; i++) { + if (byId[claimId][i] === comment_id) { + byId[claimId].splice(i, 1); + break; + } } } + + if (comment.parent_id) { + for (let i = 0; i < repliesByCommentId[comment.parent_id]; i++) { + if (repliesByCommentId[comment.parent_id][i] === comment.comment_id) { + repliesByCommentId[comment.parent_id].splice(i, 1); + break; + } + } + } + delete commentById[comment_id]; return { ...state, commentById, byId, + repliesByCommentId, isLoading: false, }; },