174 lines
4.6 KiB
TypeScript
174 lines
4.6 KiB
TypeScript
import { Node, Comment, CommentMode } from '@app/common/types';
|
|
|
|
import {
|
|
COMMENTS_SET,
|
|
COMMENTS_SET_ACTION,
|
|
COMMENT_MODE_SET,
|
|
COMMENT_MODE_SET_ACTION,
|
|
COMMENTS_APPEND_ACTION,
|
|
COMMENTS_APPEND,
|
|
COMMENTS_EDIT_ACTION,
|
|
COMMENTS_EDIT,
|
|
COMMENTS_PATCH,
|
|
COMMENTS_PATCH_ACTION,
|
|
} from './types';
|
|
import { getPinnedComments } from './utils';
|
|
import { cmpRef } from '@app/utils/cmpRef';
|
|
|
|
export const topComments = (
|
|
state: (Comment['id'])[] = [],
|
|
action: COMMENTS_SET_ACTION | COMMENTS_APPEND_ACTION
|
|
): (Comment['id'])[] => {
|
|
switch (action.type) {
|
|
case COMMENTS_SET: {
|
|
return cmpRef(state, action.comments.map(x => x.comment.id));
|
|
}
|
|
case COMMENTS_APPEND: {
|
|
if (action.comment.pid) return state;
|
|
return [action.comment.id, ...state];
|
|
}
|
|
default:
|
|
return state;
|
|
}
|
|
};
|
|
|
|
const reduceChildIds = (
|
|
c: Record<Comment['id'], (Comment['id'])[]>,
|
|
x: Node
|
|
): Record<Comment['id'], (Comment['id'])[]> => {
|
|
if (!x.replies) return c;
|
|
if (!c[x.comment.id]) {
|
|
c[x.comment.id] = [];
|
|
}
|
|
for (const reply of x.replies) {
|
|
c[x.comment.id].push(reply.comment.id);
|
|
if (reply.replies) {
|
|
reduceChildIds(c, reply);
|
|
}
|
|
}
|
|
|
|
return c;
|
|
};
|
|
|
|
export const childComments = (
|
|
state: Record<Comment['id'], (Comment['id'])[]> = {},
|
|
action: COMMENTS_SET_ACTION | COMMENTS_APPEND_ACTION
|
|
): Record<Comment['id'], (Comment['id'])[]> => {
|
|
switch (action.type) {
|
|
case COMMENTS_SET: {
|
|
return action.comments.reduce<Record<Comment['id'], (Comment['id'])[]>>(reduceChildIds, {});
|
|
}
|
|
case COMMENTS_APPEND: {
|
|
if (!action.comment.pid) return state;
|
|
return { ...state, [action.comment.pid]: [action.comment.id, ...(state[action.comment.pid] || [])] };
|
|
}
|
|
default:
|
|
return state;
|
|
}
|
|
};
|
|
|
|
const cmpComment = (a: Comment | undefined, b: Comment): Comment => {
|
|
if (!a) return b;
|
|
if (a.id !== b.id) return b;
|
|
if (!a.edit) {
|
|
if (!b.edit) return a;
|
|
return b;
|
|
}
|
|
if (!b.edit) return b;
|
|
if (a.edit.time !== b.edit.time) return b;
|
|
return a;
|
|
};
|
|
|
|
const reduceComments = (c: Record<Comment['id'], Comment>, x: Node): Record<Comment['id'], Comment> => {
|
|
c[x.comment.id] = cmpComment(c[x.comment.id], x.comment);
|
|
if (x.replies) {
|
|
x.replies.reduce(reduceComments, c);
|
|
}
|
|
return c;
|
|
};
|
|
|
|
export const comments = (
|
|
state: Record<Comment['id'], Comment> = {},
|
|
action: COMMENTS_SET_ACTION | COMMENTS_APPEND_ACTION | COMMENTS_EDIT_ACTION | COMMENTS_PATCH_ACTION
|
|
): Record<Comment['id'], Comment> => {
|
|
switch (action.type) {
|
|
case COMMENTS_SET: {
|
|
return action.comments.reduce<Record<Comment['id'], Comment>>(reduceComments, { ...state });
|
|
}
|
|
case COMMENTS_APPEND:
|
|
case COMMENTS_EDIT: {
|
|
return { ...state, [action.comment.id]: action.comment };
|
|
}
|
|
case COMMENTS_PATCH: {
|
|
let newState = state;
|
|
let changed = false;
|
|
const editObject = { summary: '', time: new Date().toISOString() };
|
|
for (const id of action.ids) {
|
|
if (!state.hasOwnProperty(id)) continue;
|
|
if (!changed) {
|
|
changed = true;
|
|
newState = { ...newState };
|
|
}
|
|
newState[id] = { ...newState[id], edit: editObject, ...action.patch };
|
|
}
|
|
return newState;
|
|
}
|
|
default:
|
|
return state;
|
|
}
|
|
};
|
|
|
|
export type ActiveCommentState = null | { id: Comment['id']; state: CommentMode };
|
|
|
|
export const activeComment = (
|
|
state: ActiveCommentState = null,
|
|
action: COMMENT_MODE_SET_ACTION
|
|
): ActiveCommentState => {
|
|
switch (action.type) {
|
|
case COMMENT_MODE_SET: {
|
|
return action.mode;
|
|
}
|
|
default:
|
|
return state;
|
|
}
|
|
};
|
|
|
|
export const pinnedComments = (
|
|
state: (Comment['id'])[] = [],
|
|
action: COMMENTS_SET_ACTION | COMMENTS_EDIT_ACTION | COMMENTS_PATCH_ACTION
|
|
): (Comment['id'])[] => {
|
|
switch (action.type) {
|
|
case COMMENTS_SET: {
|
|
return getPinnedComments(action.comments).map(x => x.id);
|
|
}
|
|
case COMMENTS_EDIT: {
|
|
const index = state.indexOf(action.comment.id);
|
|
if (!action.comment.pin) {
|
|
if (index === -1) return state;
|
|
const newState = [...state];
|
|
newState.splice(index, 1);
|
|
return newState;
|
|
}
|
|
if (index !== -1) return state;
|
|
return [...state, action.comment.id];
|
|
}
|
|
case COMMENTS_PATCH: {
|
|
if (!action.patch.hasOwnProperty('pin')) return state;
|
|
if (!action.patch.pin) {
|
|
return state.filter(x => action.ids.indexOf(x) === -1);
|
|
}
|
|
return [...state, ...action.ids].reduce<(Comment['id'])[]>((c, x) => {
|
|
if (c.indexOf(x) === -1) {
|
|
c.push(x);
|
|
}
|
|
return c;
|
|
}, []);
|
|
}
|
|
|
|
default:
|
|
return state;
|
|
}
|
|
};
|
|
|
|
export default { topComments, childComments, comments, activeComment, pinnedComments };
|