feat: comments

* create comment
* reply to comment thread
* edit comment
* delete comment
* resolve comment
This commit is contained in:
Philipinho
2023-11-09 16:52:34 +00:00
parent dea2cad89c
commit 4cb7a56f65
49 changed files with 1486 additions and 87 deletions

View File

@@ -0,0 +1,99 @@
import React, { useState } from 'react';
import { Avatar, Dialog, Group, Stack, Text } from '@mantine/core';
import { useClickOutside } from '@mantine/hooks';
import { useAtom } from 'jotai';
import {
activeCommentIdAtom,
commentsAtom,
draftCommentIdAtom,
showCommentPopupAtom,
} from '@/features/comment/atoms/comment-atom';
import { Editor } from '@tiptap/core';
import CommentEditor from '@/features/comment/components/comment-editor';
import CommentActions from '@/features/comment/components/comment-actions';
import { currentUserAtom } from '@/features/user/atoms/current-user-atom';
import useComment from '@/features/comment/hooks/use-comment';
interface CommentDialogProps {
editor: Editor,
pageId: string,
}
function CommentDialog({ editor, pageId }: CommentDialogProps) {
const [comment, setComment] = useState('');
const [, setShowCommentPopup] = useAtom<boolean>(showCommentPopupAtom);
const [, setActiveCommentId] = useAtom<string | null>(activeCommentIdAtom);
const [draftCommentId, setDraftCommentId] = useAtom<string | null>(draftCommentIdAtom);
const [comments, setComments] = useAtom(commentsAtom(pageId));
const [currentUser] = useAtom(currentUserAtom);
const useClickOutsideRef = useClickOutside(() => {
handleDialogClose();
});
const { createCommentMutation } = useComment();
const { isLoading } = createCommentMutation;
const handleDialogClose = () => {
setShowCommentPopup(false);
editor.chain().focus().unsetCommentDecoration().run();
};
const getSelectedText = () => {
const { from, to } = editor.state.selection;
return editor.state.doc.textBetween(from, to);
};
const handleAddComment = async () => {
const selectedText = getSelectedText();
const commentData = {
id: draftCommentId,
pageId: pageId,
content: JSON.stringify(comment),
selection: selectedText,
};
try {
const createdComment = await createCommentMutation.mutateAsync(commentData);
setComments(prevComments => [...prevComments, createdComment]);
editor.chain().setComment(createdComment.id).unsetCommentDecoration().run();
setActiveCommentId(createdComment.id);
setTimeout(() => {
const selector = `div[data-comment-id="${createdComment.id}"]`;
const commentElement = document.querySelector(selector);
commentElement?.scrollIntoView();
});
} finally {
setShowCommentPopup(false);
setDraftCommentId(null);
}
};
const handleCommentEditorChange = (newContent) => {
setComment(newContent);
};
return (
<Dialog opened={true} onClose={handleDialogClose} ref={useClickOutsideRef} size="lg" radius="md"
w={300} position={{ bottom: 500, right: 50 }} withCloseButton withBorder>
<Stack gap={2}>
<Group>
<Avatar size="sm" color="blue">{currentUser.user.name.charAt(0)}</Avatar>
<div style={{ flex: 1 }}>
<Group justify="space-between" wrap="nowrap">
<Text size="sm" fw={500} lineClamp={1}>{currentUser.user.name}</Text>
</Group>
</div>
</Group>
<CommentEditor onUpdate={handleCommentEditorChange} placeholder="Write a comment"
editable={true} autofocus={true}
/>
<CommentActions onSave={handleAddComment} isLoading={isLoading}
/>
</Stack>
</Dialog>
);
}
export default CommentDialog;