2023-08-18 15:30:59 +02:00
|
|
|
import {AddComment, Comment, CommentsOptions, EditableAppContext} from './AppContext';
|
|
|
|
import {AdminApi} from './utils/adminApi';
|
2023-06-27 14:51:37 +02:00
|
|
|
import {GhostApi} from './utils/api';
|
|
|
|
import {Page} from './pages';
|
|
|
|
|
2023-08-18 15:30:59 +02:00
|
|
|
async function loadMoreComments({state, api, options}: {state: EditableAppContext, api: GhostApi, options: CommentsOptions}): Promise<Partial<EditableAppContext>> {
|
2022-07-05 14:24:29 +02:00
|
|
|
let page = 1;
|
|
|
|
if (state.pagination && state.pagination.page) {
|
|
|
|
page = state.pagination.page + 1;
|
|
|
|
}
|
2023-08-18 15:30:59 +02:00
|
|
|
const data = await api.comments.browse({page, postId: options.postId});
|
2022-07-07 14:29:24 +02:00
|
|
|
|
2022-07-05 16:08:08 +02:00
|
|
|
// Note: we store the comments from new to old, and show them in reverse order
|
2022-07-05 14:24:29 +02:00
|
|
|
return {
|
2022-07-05 16:08:08 +02:00
|
|
|
comments: [...state.comments, ...data.comments],
|
2022-07-05 14:24:29 +02:00
|
|
|
pagination: data.meta.pagination
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-08-18 15:30:59 +02:00
|
|
|
async function loadMoreReplies({state, api, data: {comment, limit}}: {state: EditableAppContext, api: GhostApi, data: {comment: any, limit?: number | 'all'}}): Promise<Partial<EditableAppContext>> {
|
2022-08-10 16:14:42 +02:00
|
|
|
const data = await api.comments.replies({commentId: comment.id, afterReplyId: comment.replies[comment.replies.length - 1]?.id, limit});
|
|
|
|
|
|
|
|
// Note: we store the comments from new to old, and show them in reverse order
|
|
|
|
return {
|
|
|
|
comments: state.comments.map((c) => {
|
|
|
|
if (c.id === comment.id) {
|
|
|
|
return {
|
|
|
|
...comment,
|
|
|
|
replies: [...comment.replies, ...data.comments]
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return c;
|
|
|
|
})
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-08-18 15:30:59 +02:00
|
|
|
async function addComment({state, api, data: comment}: {state: EditableAppContext, api: GhostApi, data: AddComment}) {
|
2022-07-06 17:30:44 +02:00
|
|
|
const data = await api.comments.add({comment});
|
|
|
|
comment = data.comments[0];
|
2022-07-05 15:53:28 +02:00
|
|
|
|
2022-07-05 15:30:04 +02:00
|
|
|
return {
|
2022-08-10 16:14:42 +02:00
|
|
|
comments: [comment, ...state.comments],
|
2022-08-08 17:36:43 +01:00
|
|
|
commentCount: state.commentCount + 1
|
2022-07-05 15:30:04 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-08-18 15:30:59 +02:00
|
|
|
async function addReply({state, api, data: {reply, parent}}: {state: EditableAppContext, api: GhostApi, data: {reply: any, parent: any}}) {
|
2022-07-07 11:12:00 +02:00
|
|
|
let comment = reply;
|
|
|
|
comment.parent_id = parent.id;
|
|
|
|
|
|
|
|
const data = await api.comments.add({comment});
|
|
|
|
comment = data.comments[0];
|
|
|
|
|
2022-08-10 16:14:42 +02:00
|
|
|
// When we add a reply,
|
|
|
|
// it is possible that we didn't load all the replies for the given comment yet.
|
|
|
|
// To fix that, we'll save the reply to a different field that is created locally to differentiate between replies before and after pagination 😅
|
2022-07-07 14:29:24 +02:00
|
|
|
|
2022-07-07 11:12:00 +02:00
|
|
|
// Replace the comment in the state with the new one
|
|
|
|
return {
|
|
|
|
comments: state.comments.map((c) => {
|
|
|
|
if (c.id === parent.id) {
|
|
|
|
return {
|
|
|
|
...parent,
|
2022-08-10 16:14:42 +02:00
|
|
|
replies: [...parent.replies, comment],
|
|
|
|
count: {
|
|
|
|
...parent.count,
|
|
|
|
replies: parent.count.replies + 1
|
|
|
|
}
|
2022-07-07 11:12:00 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
return c;
|
2022-08-08 17:36:43 +01:00
|
|
|
}),
|
|
|
|
commentCount: state.commentCount + 1
|
2022-07-07 11:12:00 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-08-18 15:30:59 +02:00
|
|
|
async function hideComment({state, adminApi, data: comment}: {state: EditableAppContext, adminApi: any, data: {id: string}}) {
|
2022-07-06 11:11:54 +02:00
|
|
|
await adminApi.hideComment(comment.id);
|
|
|
|
|
|
|
|
return {
|
|
|
|
comments: state.comments.map((c) => {
|
2022-07-07 14:33:37 +02:00
|
|
|
const replies = c.replies.map((r) => {
|
|
|
|
if (r.id === comment.id) {
|
|
|
|
return {
|
|
|
|
...r,
|
|
|
|
status: 'hidden'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return r;
|
|
|
|
});
|
|
|
|
|
2022-07-06 11:11:54 +02:00
|
|
|
if (c.id === comment.id) {
|
|
|
|
return {
|
|
|
|
...c,
|
2022-07-07 14:33:37 +02:00
|
|
|
status: 'hidden',
|
|
|
|
replies
|
2022-07-06 11:11:54 +02:00
|
|
|
};
|
|
|
|
}
|
2022-07-07 14:33:37 +02:00
|
|
|
|
|
|
|
return {
|
|
|
|
...c,
|
|
|
|
replies
|
|
|
|
};
|
2022-08-08 17:36:43 +01:00
|
|
|
}),
|
|
|
|
commentCount: state.commentCount - 1
|
2022-07-06 11:11:54 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-08-18 15:30:59 +02:00
|
|
|
async function showComment({state, api, adminApi, data: comment}: {state: EditableAppContext, api: GhostApi, adminApi: any, data: {id: string}}) {
|
2022-07-06 11:45:45 +02:00
|
|
|
await adminApi.showComment(comment.id);
|
|
|
|
|
2022-08-12 14:22:48 +02:00
|
|
|
// We need to refetch the comment, to make sure we have an up to date HTML content
|
|
|
|
// + all relations are loaded as the current member (not the admin)
|
|
|
|
const data = await api.comments.read(comment.id);
|
|
|
|
const updatedComment = data.comments[0];
|
|
|
|
|
2022-07-06 11:45:45 +02:00
|
|
|
return {
|
|
|
|
comments: state.comments.map((c) => {
|
2022-07-07 14:29:24 +02:00
|
|
|
const replies = c.replies.map((r) => {
|
|
|
|
if (r.id === comment.id) {
|
2022-08-12 14:22:48 +02:00
|
|
|
return updatedComment;
|
2022-07-07 14:29:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return r;
|
|
|
|
});
|
|
|
|
|
2022-07-06 11:45:45 +02:00
|
|
|
if (c.id === comment.id) {
|
2022-08-12 14:22:48 +02:00
|
|
|
return updatedComment;
|
2022-07-06 11:45:45 +02:00
|
|
|
}
|
2022-07-07 14:29:24 +02:00
|
|
|
|
|
|
|
return {
|
|
|
|
...c,
|
|
|
|
replies
|
|
|
|
};
|
2022-08-08 17:36:43 +01:00
|
|
|
}),
|
|
|
|
commentCount: state.commentCount + 1
|
2022-07-06 11:45:45 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-08-18 15:30:59 +02:00
|
|
|
async function likeComment({state, api, data: comment}: {state: EditableAppContext, api: GhostApi, data: {id: string}}) {
|
2022-07-06 17:20:19 +02:00
|
|
|
await api.comments.like({comment});
|
|
|
|
|
|
|
|
return {
|
|
|
|
comments: state.comments.map((c) => {
|
2022-07-07 14:47:04 +02:00
|
|
|
const replies = c.replies.map((r) => {
|
|
|
|
if (r.id === comment.id) {
|
|
|
|
return {
|
|
|
|
...r,
|
|
|
|
liked: true,
|
2022-08-10 16:14:42 +02:00
|
|
|
count: {
|
|
|
|
...r.count,
|
|
|
|
likes: r.count.likes + 1
|
|
|
|
}
|
2022-07-07 14:47:04 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return r;
|
|
|
|
});
|
|
|
|
|
2022-07-06 17:20:19 +02:00
|
|
|
if (c.id === comment.id) {
|
|
|
|
return {
|
|
|
|
...c,
|
|
|
|
liked: true,
|
2022-08-10 16:14:42 +02:00
|
|
|
replies,
|
|
|
|
count: {
|
|
|
|
...c.count,
|
|
|
|
likes: c.count.likes + 1
|
|
|
|
}
|
2022-07-06 17:20:19 +02:00
|
|
|
};
|
|
|
|
}
|
2022-07-07 14:47:04 +02:00
|
|
|
|
|
|
|
return {
|
|
|
|
...c,
|
|
|
|
replies
|
|
|
|
};
|
2022-07-06 17:20:19 +02:00
|
|
|
})
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-07-27 09:49:51 +02:00
|
|
|
async function reportComment({api, data: comment}: {api: GhostApi, data: {id: string}}) {
|
2022-07-18 17:36:09 +02:00
|
|
|
await api.comments.report({comment});
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2023-08-18 15:30:59 +02:00
|
|
|
async function unlikeComment({state, api, data: comment}: {state: EditableAppContext, api: GhostApi, data: {id: string}}) {
|
2022-07-06 17:20:19 +02:00
|
|
|
await api.comments.unlike({comment});
|
|
|
|
|
|
|
|
return {
|
|
|
|
comments: state.comments.map((c) => {
|
2022-07-07 14:47:04 +02:00
|
|
|
const replies = c.replies.map((r) => {
|
|
|
|
if (r.id === comment.id) {
|
|
|
|
return {
|
|
|
|
...r,
|
|
|
|
liked: false,
|
2022-08-10 16:14:42 +02:00
|
|
|
count: {
|
|
|
|
...r.count,
|
|
|
|
likes: r.count.likes - 1
|
|
|
|
}
|
2022-07-07 14:47:04 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return r;
|
|
|
|
});
|
|
|
|
|
2022-07-06 17:20:19 +02:00
|
|
|
if (c.id === comment.id) {
|
|
|
|
return {
|
|
|
|
...c,
|
|
|
|
liked: false,
|
2022-08-10 16:14:42 +02:00
|
|
|
replies,
|
|
|
|
count: {
|
|
|
|
...c.count,
|
|
|
|
likes: c.count.likes - 1
|
|
|
|
}
|
2022-07-06 17:20:19 +02:00
|
|
|
};
|
|
|
|
}
|
2022-07-07 14:47:04 +02:00
|
|
|
return {
|
|
|
|
...c,
|
|
|
|
replies
|
|
|
|
};
|
2022-07-06 17:20:19 +02:00
|
|
|
})
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-08-18 15:30:59 +02:00
|
|
|
async function deleteComment({state, api, data: comment}: {state: EditableAppContext, api: GhostApi, data: {id: string}}) {
|
2022-07-06 11:27:03 +02:00
|
|
|
await api.comments.edit({
|
|
|
|
comment: {
|
|
|
|
id: comment.id,
|
|
|
|
status: 'deleted'
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
comments: state.comments.map((c) => {
|
2022-07-07 14:29:24 +02:00
|
|
|
const replies = c.replies.map((r) => {
|
|
|
|
if (r.id === comment.id) {
|
|
|
|
return {
|
|
|
|
...r,
|
|
|
|
status: 'deleted'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return r;
|
|
|
|
});
|
|
|
|
|
2022-07-06 11:27:03 +02:00
|
|
|
if (c.id === comment.id) {
|
|
|
|
return {
|
|
|
|
...c,
|
2022-07-07 14:29:24 +02:00
|
|
|
status: 'deleted',
|
|
|
|
replies
|
2022-07-06 11:27:03 +02:00
|
|
|
};
|
|
|
|
}
|
2022-07-07 14:29:24 +02:00
|
|
|
|
|
|
|
return {
|
|
|
|
...c,
|
|
|
|
replies
|
|
|
|
};
|
2022-08-08 17:36:43 +01:00
|
|
|
}),
|
|
|
|
commentCount: state.commentCount - 1
|
2022-07-06 11:27:03 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-08-18 15:30:59 +02:00
|
|
|
async function editComment({state, api, data: {comment, parent}}: {state: EditableAppContext, api: GhostApi, data: {comment: Partial<Comment> & {id: string}, parent?: Comment}}) {
|
2022-07-07 10:29:29 +02:00
|
|
|
const data = await api.comments.edit({
|
|
|
|
comment
|
|
|
|
});
|
|
|
|
comment = data.comments[0];
|
|
|
|
|
|
|
|
// Replace the comment in the state with the new one
|
|
|
|
return {
|
|
|
|
comments: state.comments.map((c) => {
|
2022-07-07 11:39:12 +02:00
|
|
|
if (parent && parent.id === c.id) {
|
|
|
|
return {
|
|
|
|
...c,
|
|
|
|
replies: c.replies.map((r) => {
|
|
|
|
if (r.id === comment.id) {
|
|
|
|
return comment;
|
|
|
|
}
|
|
|
|
return r;
|
|
|
|
})
|
|
|
|
};
|
|
|
|
} else if (c.id === comment.id) {
|
2022-07-07 10:29:29 +02:00
|
|
|
return comment;
|
|
|
|
}
|
2022-07-07 14:29:24 +02:00
|
|
|
|
2022-07-07 10:29:29 +02:00
|
|
|
return c;
|
|
|
|
})
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-08-18 15:30:59 +02:00
|
|
|
async function updateMember({data, state, api}: {data: {name: string, expertise: string}, state: EditableAppContext, api: GhostApi}) {
|
2022-09-09 09:05:39 +02:00
|
|
|
const {name, expertise} = data;
|
2023-06-27 14:51:37 +02:00
|
|
|
const patchData: {name?: string, expertise?: string} = {};
|
|
|
|
|
2022-07-21 15:47:51 +05:30
|
|
|
const originalName = state?.member?.name;
|
2022-08-04 10:15:43 +02:00
|
|
|
|
|
|
|
if (name && originalName !== name) {
|
|
|
|
patchData.name = name;
|
|
|
|
}
|
|
|
|
|
2022-09-09 09:05:39 +02:00
|
|
|
const originalExpertise = state?.member?.expertise;
|
|
|
|
if (expertise !== undefined && originalExpertise !== expertise) {
|
2022-08-04 10:15:43 +02:00
|
|
|
// Allow to set it to an empty string or to null
|
2022-09-09 09:05:39 +02:00
|
|
|
patchData.expertise = expertise;
|
2022-08-04 10:15:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (Object.keys(patchData).length > 0) {
|
2022-07-21 15:47:51 +05:30
|
|
|
try {
|
2022-08-04 10:15:43 +02:00
|
|
|
const member = await api.member.update(patchData);
|
2022-07-21 15:47:51 +05:30
|
|
|
if (!member) {
|
|
|
|
throw new Error('Failed to update member');
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
member,
|
|
|
|
success: true
|
|
|
|
};
|
|
|
|
} catch (err) {
|
|
|
|
return {
|
|
|
|
success: false,
|
|
|
|
error: err
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2023-06-27 14:51:37 +02:00
|
|
|
function openPopup({data}: {data: Page}) {
|
2022-07-21 16:59:12 +02:00
|
|
|
return {
|
|
|
|
popup: data
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function closePopup() {
|
|
|
|
return {
|
|
|
|
popup: null
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-08-18 15:30:59 +02:00
|
|
|
function increaseSecundaryFormCount({state}: {state: EditableAppContext}) {
|
2022-08-30 16:25:40 +02:00
|
|
|
return {
|
|
|
|
secundaryFormCount: state.secundaryFormCount + 1
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-08-18 15:30:59 +02:00
|
|
|
function decreaseSecundaryFormCount({state}: {state: EditableAppContext}) {
|
2022-08-30 16:25:40 +02:00
|
|
|
return {
|
|
|
|
secundaryFormCount: state.secundaryFormCount - 1
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sync actions make use of setState((currentState) => newState), to avoid 'race' conditions
|
2023-06-27 14:51:37 +02:00
|
|
|
export const SyncActions = {
|
2022-08-30 16:25:40 +02:00
|
|
|
openPopup,
|
|
|
|
closePopup,
|
|
|
|
increaseSecundaryFormCount,
|
|
|
|
decreaseSecundaryFormCount
|
|
|
|
};
|
|
|
|
|
2023-06-27 14:51:37 +02:00
|
|
|
export type SyncActionType = keyof typeof SyncActions;
|
|
|
|
|
|
|
|
export const Actions = {
|
2022-07-04 17:23:01 +02:00
|
|
|
// Put your actions here
|
2022-07-05 15:30:04 +02:00
|
|
|
addComment,
|
2022-07-07 10:29:29 +02:00
|
|
|
editComment,
|
2022-07-06 11:11:54 +02:00
|
|
|
hideComment,
|
2022-07-06 11:27:03 +02:00
|
|
|
deleteComment,
|
2022-07-06 11:45:45 +02:00
|
|
|
showComment,
|
2022-07-06 17:20:19 +02:00
|
|
|
likeComment,
|
|
|
|
unlikeComment,
|
2022-07-18 17:36:09 +02:00
|
|
|
reportComment,
|
2022-07-07 11:12:00 +02:00
|
|
|
addReply,
|
2022-07-21 15:47:51 +05:30
|
|
|
loadMoreComments,
|
2022-08-10 16:14:42 +02:00
|
|
|
loadMoreReplies,
|
2022-08-30 16:25:40 +02:00
|
|
|
updateMember
|
2022-07-04 17:23:01 +02:00
|
|
|
};
|
|
|
|
|
2023-06-27 14:51:37 +02:00
|
|
|
export type ActionType = keyof typeof Actions;
|
|
|
|
|
|
|
|
export function isSyncAction(action: string): action is SyncActionType {
|
|
|
|
return !!(SyncActions as any)[action];
|
2022-08-30 16:25:40 +02:00
|
|
|
}
|
|
|
|
|
2022-07-04 17:23:01 +02:00
|
|
|
/** Handle actions in the App, returns updated state */
|
2023-08-18 15:30:59 +02:00
|
|
|
export async function ActionHandler({action, data, state, api, adminApi, options}: {action: ActionType, data: any, state: EditableAppContext, options: CommentsOptions, api: GhostApi, adminApi: AdminApi}): Promise<Partial<EditableAppContext>> {
|
2022-07-04 17:23:01 +02:00
|
|
|
const handler = Actions[action];
|
|
|
|
if (handler) {
|
2023-08-18 15:30:59 +02:00
|
|
|
return await handler({data, state, api, adminApi, options} as any) || {};
|
2022-07-04 17:23:01 +02:00
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
2022-08-30 16:25:40 +02:00
|
|
|
|
|
|
|
/** Handle actions in the App, returns updated state */
|
2023-08-18 15:30:59 +02:00
|
|
|
export function SyncActionHandler({action, data, state, api, adminApi, options}: {action: SyncActionType, data: any, state: EditableAppContext, options: CommentsOptions, api: GhostApi, adminApi: AdminApi}): Partial<EditableAppContext> {
|
2022-08-30 16:25:40 +02:00
|
|
|
const handler = SyncActions[action];
|
|
|
|
if (handler) {
|
|
|
|
// Do not await here
|
2023-08-18 15:30:59 +02:00
|
|
|
return handler({data, state, api, adminApi, options} as any) || {};
|
2022-08-30 16:25:40 +02:00
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|