1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-25 12:56:41 +07:00

node: refactored sagas

This commit is contained in:
Fedor Katurov 2021-03-03 11:10:28 +07:00
parent be7829f130
commit 5102c738b3
6 changed files with 318 additions and 324 deletions

View file

@ -31,9 +31,9 @@ export const API = {
RELATED: (id: INode['id']) => `/node/${id}/related`, RELATED: (id: INode['id']) => `/node/${id}/related`,
UPDATE_TAGS: (id: INode['id']) => `/node/${id}/tags`, UPDATE_TAGS: (id: INode['id']) => `/node/${id}/tags`,
POST_LIKE: (id: INode['id']) => `/node/${id}/like`, POST_LIKE: (id: INode['id']) => `/node/${id}/like`,
POST_STAR: (id: INode['id']) => `/node/${id}/heroic`, POST_HEROIC: (id: INode['id']) => `/node/${id}/heroic`,
POST_LOCK: (id: INode['id']) => `/node/${id}/lock`, POST_LOCK: (id: INode['id']) => `/node/${id}/lock`,
POST_LOCK_COMMENT: (id: INode['id'], comment_id: IComment['id']) => LOCK_COMMENT: (id: INode['id'], comment_id: IComment['id']) =>
`/node/${id}/comment/${comment_id}/lock`, `/node/${id}/comment/${comment_id}/lock`,
SET_CELL_VIEW: (id: INode['id']) => `/node/${id}/cell-view`, SET_CELL_VIEW: (id: INode['id']) => `/node/${id}/cell-view`,
}, },

View file

@ -55,11 +55,6 @@ export const nodePostLocalComment = (
type: NODE_ACTIONS.POST_COMMENT, type: NODE_ACTIONS.POST_COMMENT,
}); });
export const nodeCancelCommentEdit = (id: number) => ({
id,
type: NODE_ACTIONS.CANCEL_COMMENT_EDIT,
});
export const nodeSetSendingComment = (is_sending_comment: boolean) => ({ export const nodeSetSendingComment = (is_sending_comment: boolean) => ({
is_sending_comment, is_sending_comment,
type: NODE_ACTIONS.SET_SENDING_COMMENT, type: NODE_ACTIONS.SET_SENDING_COMMENT,

View file

@ -1,22 +1,43 @@
import { api, configWithToken, resultMiddleware, errorMiddleware, cleanResult } from '~/utils/api'; import { api, cleanResult, configWithToken, errorMiddleware, resultMiddleware } from '~/utils/api';
import { INode, IResultWithStatus, IComment } from '../types'; import { IComment, INode, IResultWithStatus } from '../types';
import { API } from '~/constants/api'; import { API } from '~/constants/api';
import { nodeUpdateTags, nodeLike, nodeStar, nodeLock, nodeLockComment } from './actions';
import { INodeState } from './reducer';
import { COMMENTS_DISPLAY } from './constants'; import { COMMENTS_DISPLAY } from './constants';
import { GetNodeDiffRequest, GetNodeDiffResult } from '~/redux/node/types'; import {
ApiGetNodeRelatedRequest,
ApiGetNodeRelatedResult,
ApiGetNodeRequest,
ApiGetNodeResult,
ApiLockCommentRequest,
ApiLockcommentResult,
ApiLockNodeRequest,
ApiLockNodeResult,
ApiPostCommentRequest,
ApiPostCommentResult,
ApiPostNodeHeroicRequest,
ApiPostNodeHeroicResponse,
ApiPostNodeLikeRequest,
ApiPostNodeLikeResult,
ApiPostNodeTagsRequest,
ApiPostNodeTagsResult,
GetNodeDiffRequest,
GetNodeDiffResult,
} from '~/redux/node/types';
export const postNode = ({ export type ApiPostNodeRequest = { node: INode };
access, export type ApiPostNodeResult = {
node,
}: {
access: string;
node: INode; node: INode;
}): Promise<IResultWithStatus<INode>> => errors: Record<string, string>;
api };
.post(API.NODE.SAVE, node, configWithToken(access))
.then(resultMiddleware) export type ApiGetNodeCommentsRequest = {
.catch(errorMiddleware); id: number;
take?: number;
skip?: number;
};
export type ApiGetNodeCommentsResponse = { comments: IComment[]; comment_count: number };
export const apiPostNode = ({ node }: ApiPostNodeRequest) =>
api.post<ApiPostNodeResult>(API.NODE.SAVE, node).then(cleanResult);
export const getNodes = ({ export const getNodes = ({
from, from,
@ -53,113 +74,41 @@ export const getNodeDiff = ({
}) })
.then(cleanResult); .then(cleanResult);
export const getNode = ({ export const apiGetNode = ({ id }: ApiGetNodeRequest) =>
id, api.get<ApiGetNodeResult>(API.NODE.GET_NODE(id)).then(cleanResult);
access,
}: {
id: string | number;
access: string;
}): Promise<IResultWithStatus<{ nodes: INode[] }>> =>
api.get(API.NODE.GET_NODE(id), configWithToken(access)).then(cleanResult);
export const postNodeComment = ({ export const apiPostComment = ({ id, data }: ApiPostCommentRequest) =>
id, api.post<ApiPostCommentResult>(API.NODE.COMMENT(id), data).then(cleanResult);
data,
access,
}: {
access: string;
id: number;
data: IComment;
}): Promise<IResultWithStatus<{ comment: Comment }>> =>
api
.post(API.NODE.COMMENT(id), data, configWithToken(access))
.then(resultMiddleware)
.catch(errorMiddleware);
export const getNodeComments = ({ export const apiGetNodeComments = ({
id, id,
access,
take = COMMENTS_DISPLAY, take = COMMENTS_DISPLAY,
skip = 0, skip = 0,
}: { }: ApiGetNodeCommentsRequest) =>
id: number;
access: string;
take?: number;
skip?: number;
}): Promise<IResultWithStatus<{ comments: IComment[]; comment_count: number }>> =>
api api
.get(API.NODE.COMMENT(id), configWithToken(access, { params: { take, skip } })) .get<ApiGetNodeCommentsResponse>(API.NODE.COMMENT(id), { params: { take, skip } })
.then(resultMiddleware) .then(cleanResult);
.catch(errorMiddleware);
export const getNodeRelated = ({ export const apiGetNodeRelated = ({ id }: ApiGetNodeRelatedRequest) =>
id, api.get<ApiGetNodeRelatedResult>(API.NODE.RELATED(id)).then(cleanResult);
access,
}: {
id: number;
access: string;
}): Promise<IResultWithStatus<{ related: INodeState['related'] }>> =>
api
.get(API.NODE.RELATED(id), configWithToken(access))
.then(resultMiddleware)
.catch(errorMiddleware);
export const updateNodeTags = ({ export const apiPostNodeTags = ({ id, tags }: ApiPostNodeTagsRequest) =>
id,
tags,
access,
}: ReturnType<typeof nodeUpdateTags> & { access: string }): Promise<IResultWithStatus<{
node: INode;
}>> =>
api api
.post(API.NODE.UPDATE_TAGS(id), { tags }, configWithToken(access)) .post<ApiPostNodeTagsResult>(API.NODE.UPDATE_TAGS(id), { tags })
.then(resultMiddleware) .then(cleanResult);
.catch(errorMiddleware);
export const postNodeLike = ({ export const apiPostNodeLike = ({ id }: ApiPostNodeLikeRequest) =>
id, api.post<ApiPostNodeLikeResult>(API.NODE.POST_LIKE(id)).then(cleanResult);
access,
}: ReturnType<typeof nodeLike> & { access: string }): Promise<IResultWithStatus<{
is_liked: INode['is_liked'];
}>> =>
api
.post(API.NODE.POST_LIKE(id), {}, configWithToken(access))
.then(resultMiddleware)
.catch(errorMiddleware);
export const postNodeStar = ({ export const apiPostNodeHeroic = ({ id }: ApiPostNodeHeroicRequest) =>
id, api.post<ApiPostNodeHeroicResponse>(API.NODE.POST_HEROIC(id)).then(cleanResult);
access,
}: ReturnType<typeof nodeStar> & { access: string }): Promise<IResultWithStatus<{
is_liked: INode['is_liked'];
}>> =>
api
.post(API.NODE.POST_STAR(id), {}, configWithToken(access))
.then(resultMiddleware)
.catch(errorMiddleware);
export const postNodeLock = ({ export const apiLockNode = ({ id, is_locked }: ApiLockNodeRequest) =>
id,
is_locked,
access,
}: ReturnType<typeof nodeLock> & { access: string }): Promise<IResultWithStatus<{
deleted_at: INode['deleted_at'];
}>> =>
api api
.post(API.NODE.POST_LOCK(id), { is_locked }, configWithToken(access)) .post<ApiLockNodeResult>(API.NODE.POST_LOCK(id), { is_locked })
.then(resultMiddleware) .then(cleanResult);
.catch(errorMiddleware);
export const postNodeLockComment = ({ export const apiLockComment = ({ id, is_locked, current }: ApiLockCommentRequest) =>
id,
is_locked,
current,
access,
}: ReturnType<typeof nodeLockComment> & {
access: string;
current: INode['id'];
}): Promise<IResultWithStatus<{ deleted_at: INode['deleted_at'] }>> =>
api api
.post(API.NODE.POST_LOCK_COMMENT(current, id), { is_locked }, configWithToken(access)) .post<ApiLockcommentResult>(API.NODE.LOCK_COMMENT(current, id), { is_locked })
.then(resultMiddleware) .then(cleanResult);
.catch(errorMiddleware);

View file

@ -29,7 +29,6 @@ export const NODE_ACTIONS = {
LOCK: `${prefix}LOCK`, LOCK: `${prefix}LOCK`,
LOCK_COMMENT: `${prefix}LOCK_COMMENT`, LOCK_COMMENT: `${prefix}LOCK_COMMENT`,
EDIT_COMMENT: `${prefix}EDIT_COMMENT`, EDIT_COMMENT: `${prefix}EDIT_COMMENT`,
CANCEL_COMMENT_EDIT: `${prefix}CANCEL_COMMENT_EDIT`,
CREATE: `${prefix}CREATE`, CREATE: `${prefix}CREATE`,
LOAD_MORE_COMMENTS: `${prefix}LOAD_MORE_COMMENTS`, LOAD_MORE_COMMENTS: `${prefix}LOAD_MORE_COMMENTS`,

View file

@ -1,6 +1,5 @@
import { all, call, delay, put, select, takeLatest, takeLeading } from 'redux-saga/effects'; import { all, call, put, select, takeLatest, takeLeading } from 'redux-saga/effects';
import { push } from 'connected-react-router'; import { push } from 'connected-react-router';
import { omit } from 'ramda';
import { import {
COMMENTS_DISPLAY, COMMENTS_DISPLAY,
@ -10,10 +9,8 @@ import {
NODE_EDITOR_DATA, NODE_EDITOR_DATA,
} from './constants'; } from './constants';
import { import {
nodeCancelCommentEdit,
nodeCreate, nodeCreate,
nodeEdit, nodeEdit,
nodeEditComment,
nodeGotoNode, nodeGotoNode,
nodeLike, nodeLike,
nodeLoadNode, nodeLoadNode,
@ -34,35 +31,33 @@ import {
nodeUpdateTags, nodeUpdateTags,
} from './actions'; } from './actions';
import { import {
getNode, apiGetNode,
getNodeComments, apiGetNodeComments,
getNodeRelated, apiGetNodeRelated,
postNode, apiLockComment,
postNodeComment, apiLockNode,
postNodeLike, apiPostComment,
postNodeLock, apiPostNode,
postNodeLockComment, apiPostNodeHeroic,
postNodeStar, apiPostNodeLike,
updateNodeTags, apiPostNodeTags,
} from './api'; } from './api';
import { wrap } from '../auth/sagas';
import { flowSetNodes, flowSetUpdated } from '../flow/actions'; import { flowSetNodes, flowSetUpdated } from '../flow/actions';
import { ERRORS } from '~/constants/errors'; import { ERRORS } from '~/constants/errors';
import { modalSetShown, modalShowDialog } from '../modal/actions'; import { modalSetShown, modalShowDialog } from '../modal/actions';
import { selectFlow, selectFlowNodes } from '../flow/selectors'; import { selectFlow, selectFlowNodes } from '../flow/selectors';
import { URLS } from '~/constants/urls'; import { URLS } from '~/constants/urls';
import { selectNode } from './selectors'; import { selectNode } from './selectors';
import { INode, IResultWithStatus, Unwrap } from '../types'; import { Unwrap } from '../types';
import { NODE_EDITOR_DIALOGS } from '~/constants/dialogs'; import { NODE_EDITOR_DIALOGS } from '~/constants/dialogs';
import { DIALOGS } from '~/redux/modal/constants'; import { DIALOGS } from '~/redux/modal/constants';
import { INodeState } from './reducer';
import { IFlowState } from '../flow/reducer';
export function* updateNodeEverywhere(node) { export function* updateNodeEverywhere(node) {
const { const {
current: { id }, current: { id },
}: INodeState = yield select(selectNode); }: ReturnType<typeof selectNode> = yield select(selectNode);
const flow_nodes: IFlowState['nodes'] = yield select(selectFlowNodes);
const flow_nodes: ReturnType<typeof selectFlowNodes> = yield select(selectFlowNodes);
if (id === node.id) { if (id === node.id) {
yield put(nodeSetCurrent(node)); yield put(nodeSetCurrent(node));
@ -78,22 +73,17 @@ export function* updateNodeEverywhere(node) {
} }
function* onNodeSave({ node }: ReturnType<typeof nodeSave>) { function* onNodeSave({ node }: ReturnType<typeof nodeSave>) {
try {
yield put(nodeSetSaveErrors({})); yield put(nodeSetSaveErrors({}));
const { const { errors, node: result }: Unwrap<typeof apiPostNode> = yield call(apiPostNode, { node });
error,
data: { errors, node: result },
} = yield call(wrap, postNode, { node });
if (errors && Object.values(errors).length > 0) { if (errors && Object.values(errors).length > 0) {
return yield put(nodeSetSaveErrors(errors)); yield put(nodeSetSaveErrors(errors));
return;
} }
if (error || !result || !result.id) { const nodes: ReturnType<typeof selectFlowNodes> = yield select(selectFlowNodes);
return yield put(nodeSetSaveErrors({ error: error || ERRORS.CANT_SAVE_NODE }));
}
const nodes = yield select(selectFlowNodes);
const updated_flow_nodes = node.id const updated_flow_nodes = node.id
? nodes.map(item => (item.id === result.id ? result : item)) ? nodes.map(item => (item.id === result.id ? result : item))
: [result, ...nodes]; : [result, ...nodes];
@ -107,6 +97,9 @@ function* onNodeSave({ node }: ReturnType<typeof nodeSave>) {
} }
return yield put(modalSetShown(false)); return yield put(modalSetShown(false));
} catch (error) {
yield put(nodeSetSaveErrors({ error: error || ERRORS.CANT_SAVE_NODE }));
}
} }
function* onNodeGoto({ id, node_type }: ReturnType<typeof nodeGotoNode>) { function* onNodeGoto({ id, node_type }: ReturnType<typeof nodeGotoNode>) {
@ -114,16 +107,21 @@ function* onNodeGoto({ id, node_type }: ReturnType<typeof nodeGotoNode>) {
yield put(nodeLoadNode(id)); yield put(nodeLoadNode(id));
yield put(nodeSetCommentData(0, { ...EMPTY_COMMENT })); yield put(nodeSetCommentData(0, { ...EMPTY_COMMENT }));
yield put(nodeSetRelated(null)); yield put(nodeSetRelated({ albums: {}, similar: [] }));
} }
function* onNodeLoadMoreComments() { function* onNodeLoadMoreComments() {
try {
const { const {
current: { id }, current: { id },
comments, comments,
}: ReturnType<typeof selectNode> = yield select(selectNode); }: ReturnType<typeof selectNode> = yield select(selectNode);
const { data, error }: Unwrap<typeof getNodeComments> = yield call(wrap, getNodeComments, { if (!id) {
return;
}
const data: Unwrap<typeof apiGetNodeComments> = yield call(apiGetNodeComments, {
id, id,
take: COMMENTS_DISPLAY, take: COMMENTS_DISPLAY,
skip: comments.length, skip: comments.length,
@ -131,7 +129,7 @@ function* onNodeLoadMoreComments() {
const current: ReturnType<typeof selectNode> = yield select(selectNode); const current: ReturnType<typeof selectNode> = yield select(selectNode);
if (!data || error || current.current.id != id) { if (!data || current.current.id != id) {
return; return;
} }
@ -141,36 +139,33 @@ function* onNodeLoadMoreComments() {
comment_count: data.comment_count, comment_count: data.comment_count,
}) })
); );
} catch (error) {}
} }
function* onNodeLoad({ id, order = 'ASC' }: ReturnType<typeof nodeLoadNode>) { function* onNodeLoad({ id }: ReturnType<typeof nodeLoadNode>) {
// Get node body
try {
yield put(nodeSetLoading(true)); yield put(nodeSetLoading(true));
yield put(nodeSetLoadingComments(true)); yield put(nodeSetLoadingComments(true));
const { const { node }: Unwrap<typeof apiGetNode> = yield call(apiGetNode, { id });
data: { node, error },
} = yield call(wrap, getNode, { id });
if (error || !node || !node.id) {
yield put(push(URLS.ERRORS.NOT_FOUND));
yield put(nodeSetLoading(false));
return;
}
yield put(nodeSetCurrent(node)); yield put(nodeSetCurrent(node));
yield put(nodeSetLoading(false)); yield put(nodeSetLoading(false));
} catch (error) {
yield put(push(URLS.ERRORS.NOT_FOUND));
yield put(nodeSetLoading(false));
}
const { // Comments and related
comments: { try {
data: { comments, comment_count }, const [{ comments, comment_count }, { related }]: [
}, Unwrap<typeof apiGetNodeComments>,
related: { Unwrap<typeof apiGetNodeRelated>
data: { related }, ] = yield all([
}, call(apiGetNodeComments, { id, take: COMMENTS_DISPLAY, skip: 0 }),
} = yield all({ call(apiGetNodeRelated, { id }),
comments: call(wrap, getNodeComments, { id, take: COMMENTS_DISPLAY, skip: 0 }), ]);
related: call(wrap, getNodeRelated, { id }),
});
yield put( yield put(
nodeSet({ nodeSet({
@ -178,33 +173,29 @@ function* onNodeLoad({ id, order = 'ASC' }: ReturnType<typeof nodeLoadNode>) {
comment_count, comment_count,
related, related,
is_loading_comments: false, is_loading_comments: false,
comment_data: { 0: { ...EMPTY_COMMENT } },
}) })
); );
} catch {}
// Remove current node from recently updated
const { updated } = yield select(selectFlow); const { updated } = yield select(selectFlow);
if (updated.some(item => item.id === id)) { if (updated.some(item => item.id === id)) {
yield put(flowSetUpdated(updated.filter(item => item.id !== id))); yield put(flowSetUpdated(updated.filter(item => item.id !== id)));
} }
return;
} }
function* onPostComment({ nodeId, comment, callback }: ReturnType<typeof nodePostLocalComment>) { function* onPostComment({ nodeId, comment, callback }: ReturnType<typeof nodePostLocalComment>) {
const { data, error }: Unwrap<typeof postNodeComment> = yield call(wrap, postNodeComment, { try {
const data: Unwrap<typeof apiPostComment> = yield call(apiPostComment, {
data: comment, data: comment,
id: nodeId, id: nodeId,
}); });
if (error || !data.comment) {
return callback(error);
}
const { current }: ReturnType<typeof selectNode> = yield select(selectNode); const { current }: ReturnType<typeof selectNode> = yield select(selectNode);
if (current?.id === nodeId) { if (current?.id === nodeId) {
const { comments } = yield select(selectNode); const { comments }: ReturnType<typeof selectNode> = yield select(selectNode);
if (!comment.id) { if (!comment.id) {
yield put(nodeSetComments([data.comment, ...comments])); yield put(nodeSetComments([data.comment, ...comments]));
@ -218,30 +209,18 @@ function* onPostComment({ nodeId, comment, callback }: ReturnType<typeof nodePos
callback(); callback();
} }
} catch (error) {
return callback(error);
} }
function* onCancelCommentEdit({ id }: ReturnType<typeof nodeCancelCommentEdit>) {
const { comment_data } = yield select(selectNode);
yield put(
nodeSet({
comment_data: omit([id.toString()], comment_data),
})
);
} }
function* onUpdateTags({ id, tags }: ReturnType<typeof nodeUpdateTags>) { function* onUpdateTags({ id, tags }: ReturnType<typeof nodeUpdateTags>) {
yield delay(100); try {
const { node }: Unwrap<typeof apiPostNodeTags> = yield call(apiPostNodeTags, { id, tags });
const { const { current }: ReturnType<typeof selectNode> = yield select(selectNode);
data: { node },
}: IResultWithStatus<{ node: INode }> = yield call(wrap, updateNodeTags, { id, tags });
const { current } = yield select(selectNode);
if (!node || !node.id || node.id !== current.id) return; if (!node || !node.id || node.id !== current.id) return;
yield put(nodeSetTags(node.tags)); yield put(nodeSetTags(node.tags));
} catch {}
} }
function* onCreateSaga({ node_type: type }: ReturnType<typeof nodeCreate>) { function* onCreateSaga({ node_type: type }: ReturnType<typeof nodeCreate>) {
@ -252,42 +231,49 @@ function* onCreateSaga({ node_type: type }: ReturnType<typeof nodeCreate>) {
} }
function* onEditSaga({ id }: ReturnType<typeof nodeEdit>) { function* onEditSaga({ id }: ReturnType<typeof nodeEdit>) {
try {
if (!id) {
return;
}
yield put(modalShowDialog(DIALOGS.LOADING)); yield put(modalShowDialog(DIALOGS.LOADING));
const { const { node }: Unwrap<typeof apiGetNode> = yield call(apiGetNode, { id });
data: { node },
error,
} = yield call(wrap, getNode, { id });
if (error || !node || !node.type || !NODE_EDITOR_DIALOGS[node.type]) if (!NODE_EDITOR_DIALOGS[node?.type]) {
return yield put(modalSetShown(false)); throw new Error('Unknown node type');
}
yield put(nodeSetEditor(node)); yield put(nodeSetEditor(node));
yield put(modalShowDialog(NODE_EDITOR_DIALOGS[node.type])); yield put(modalShowDialog(NODE_EDITOR_DIALOGS[node.type]));
} catch (error) {
return true; yield put(modalSetShown(false));
}
} }
function* onLikeSaga({ id }: ReturnType<typeof nodeLike>) { function* onLikeSaga({ id }: ReturnType<typeof nodeLike>) {
const { try {
current, const { current }: ReturnType<typeof selectNode> = yield select(selectNode);
current: { is_liked, like_count },
} = yield select(selectNode); const count = current.like_count || 0;
yield call(updateNodeEverywhere, { yield call(updateNodeEverywhere, {
...current, ...current,
is_liked: !is_liked, is_liked: !current.is_liked,
like_count: is_liked ? Math.max(like_count - 1, 0) : like_count + 1, like_count: current.is_liked ? Math.max(count - 1, 0) : count + 1,
}); });
const { data, error } = yield call(wrap, postNodeLike, { id }); const data: Unwrap<typeof apiPostNodeLike> = yield call(apiPostNodeLike, { id });
if (!error || data.is_liked === !is_liked) return; // ok and matches yield call(updateNodeEverywhere, {
...current,
yield call(updateNodeEverywhere, { ...current, is_liked, like_count }); is_liked: data.is_liked,
});
} catch {}
} }
function* onStarSaga({ id }: ReturnType<typeof nodeLike>) { function* onStarSaga({ id }: ReturnType<typeof nodeLike>) {
try {
const { const {
current, current,
current: { is_heroic }, current: { is_heroic },
@ -295,53 +281,68 @@ function* onStarSaga({ id }: ReturnType<typeof nodeLike>) {
yield call(updateNodeEverywhere, { ...current, is_heroic: !is_heroic }); yield call(updateNodeEverywhere, { ...current, is_heroic: !is_heroic });
const { data, error } = yield call(wrap, postNodeStar, { id }); const data: Unwrap<typeof apiPostNodeHeroic> = yield call(apiPostNodeHeroic, { id });
if (!error || data.is_heroic === !is_heroic) return; // ok and matches yield call(updateNodeEverywhere, { ...current, is_heroic: data.is_heroic });
} catch {}
yield call(updateNodeEverywhere, { ...current, is_heroic });
} }
function* onLockSaga({ id, is_locked }: ReturnType<typeof nodeLock>) { function* onLockSaga({ id, is_locked }: ReturnType<typeof nodeLock>) {
const { const { current }: ReturnType<typeof selectNode> = yield select(selectNode);
current,
current: { deleted_at },
} = yield select(selectNode);
try {
yield call(updateNodeEverywhere, { yield call(updateNodeEverywhere, {
...current, ...current,
deleted_at: is_locked ? new Date().toISOString() : null, deleted_at: is_locked ? new Date().toISOString() : null,
}); });
const { error } = yield call(wrap, postNodeLock, { id, is_locked }); const data: Unwrap<typeof apiLockNode> = yield call(apiLockNode, { id, is_locked });
if (error) return yield call(updateNodeEverywhere, { ...current, deleted_at }); yield call(updateNodeEverywhere, {
...current,
deleted_at: data.deleted_at || undefined,
});
} catch {
yield call(updateNodeEverywhere, { ...current, deleted_at: current.deleted_at });
}
} }
function* onLockCommentSaga({ id, is_locked }: ReturnType<typeof nodeLockComment>) { function* onLockCommentSaga({ id, is_locked }: ReturnType<typeof nodeLockComment>) {
const { current, comments } = yield select(selectNode); const { current, comments }: ReturnType<typeof selectNode> = yield select(selectNode);
try {
yield put( yield put(
nodeSetComments( nodeSetComments(
comments.map(comment => comments.map(comment =>
comment.id === id comment.id === id
? { ...comment, deleted_at: is_locked ? new Date().toISOString() : null } ? { ...comment, deleted_at: is_locked ? new Date().toISOString() : undefined }
: comment : comment
) )
) )
); );
yield call(wrap, postNodeLockComment, { current: current.id, id, is_locked }); const data: Unwrap<typeof apiLockComment> = yield call(apiLockComment, {
current: current.id,
id,
is_locked,
});
yield put(
nodeSetComments(
comments.map(comment =>
comment.id === id ? { ...comment, deleted_at: data.deleted_at || undefined } : comment
)
)
);
} catch {
yield put(
nodeSetComments(
comments.map(comment =>
comment.id === id ? { ...comment, deleted_at: current.deleted_at } : comment
)
)
);
} }
function* onEditCommentSaga({ id }: ReturnType<typeof nodeEditComment>) {
const { comments } = yield select(selectNode);
const comment = comments.find(item => item.id === id);
if (!comment) return;
yield put(nodeSetCommentData(id, { ...EMPTY_COMMENT, ...comment }));
} }
export default function* nodeSaga() { export default function* nodeSaga() {
@ -349,7 +350,6 @@ export default function* nodeSaga() {
yield takeLatest(NODE_ACTIONS.GOTO_NODE, onNodeGoto); yield takeLatest(NODE_ACTIONS.GOTO_NODE, onNodeGoto);
yield takeLatest(NODE_ACTIONS.LOAD_NODE, onNodeLoad); yield takeLatest(NODE_ACTIONS.LOAD_NODE, onNodeLoad);
yield takeLatest(NODE_ACTIONS.POST_COMMENT, onPostComment); yield takeLatest(NODE_ACTIONS.POST_COMMENT, onPostComment);
yield takeLatest(NODE_ACTIONS.CANCEL_COMMENT_EDIT, onCancelCommentEdit);
yield takeLatest(NODE_ACTIONS.UPDATE_TAGS, onUpdateTags); yield takeLatest(NODE_ACTIONS.UPDATE_TAGS, onUpdateTags);
yield takeLatest(NODE_ACTIONS.CREATE, onCreateSaga); yield takeLatest(NODE_ACTIONS.CREATE, onCreateSaga);
yield takeLatest(NODE_ACTIONS.EDIT, onEditSaga); yield takeLatest(NODE_ACTIONS.EDIT, onEditSaga);
@ -357,6 +357,5 @@ export default function* nodeSaga() {
yield takeLatest(NODE_ACTIONS.STAR, onStarSaga); yield takeLatest(NODE_ACTIONS.STAR, onStarSaga);
yield takeLatest(NODE_ACTIONS.LOCK, onLockSaga); yield takeLatest(NODE_ACTIONS.LOCK, onLockSaga);
yield takeLatest(NODE_ACTIONS.LOCK_COMMENT, onLockCommentSaga); yield takeLatest(NODE_ACTIONS.LOCK_COMMENT, onLockCommentSaga);
yield takeLatest(NODE_ACTIONS.EDIT_COMMENT, onEditCommentSaga);
yield takeLeading(NODE_ACTIONS.LOAD_MORE_COMMENTS, onNodeLoadMoreComments); yield takeLeading(NODE_ACTIONS.LOAD_MORE_COMMENTS, onNodeLoadMoreComments);
} }

View file

@ -1,4 +1,5 @@
import { INode } from '~/redux/types'; import { IComment, INode } from '~/redux/types';
import { INodeState } from '~/redux/node/reducer';
export interface IEditorComponentProps { export interface IEditorComponentProps {
data: INode; data: INode;
@ -31,3 +32,54 @@ export type PostCellViewRequest = {
flow: INode['flow']; flow: INode['flow'];
}; };
export type PostCellViewResult = unknown; // TODO: update it with actual type export type PostCellViewResult = unknown; // TODO: update it with actual type
export type ApiGetNodeRequest = {
id: string | number;
};
export type ApiGetNodeResult = { node: INode };
export type ApiGetNodeRelatedRequest = {
id: INode['id'];
};
export type ApiGetNodeRelatedResult = {
related: INodeState['related'];
};
export type ApiPostCommentRequest = {
id: INode['id'];
data: IComment;
};
export type ApiPostCommentResult = {
comment: IComment;
};
export type ApiPostNodeTagsRequest = {
id: INode['id'];
tags: string[];
};
export type ApiPostNodeTagsResult = {
node: INode;
};
export type ApiPostNodeLikeRequest = { id: INode['id'] };
export type ApiPostNodeLikeResult = { is_liked: boolean };
export type ApiPostNodeHeroicRequest = { id: INode['id'] };
export type ApiPostNodeHeroicResponse = { is_heroic: boolean };
export type ApiLockNodeRequest = {
id: INode['id'];
is_locked: boolean;
};
export type ApiLockNodeResult = {
deleted_at: string;
};
export type ApiLockCommentRequest = {
id: IComment['id'];
current: INode['id'];
is_locked: boolean;
};
export type ApiLockcommentResult = {
deleted_at: string;
};