mirror of
https://github.com/docmost/docmost.git
synced 2026-05-17 14:54:05 +08:00
feat(ee): viewer comments (#2060)
This commit is contained in:
@@ -6,6 +6,8 @@ import {
|
||||
activeCommentIdAtom,
|
||||
draftCommentIdAtom,
|
||||
showCommentPopupAtom,
|
||||
showReadOnlyCommentPopupAtom,
|
||||
readOnlyCommentDataAtom,
|
||||
} from "@/features/comment/atoms/comment-atom";
|
||||
import CommentEditor from "@/features/comment/components/comment-editor";
|
||||
import CommentActions from "@/features/comment/components/comment-actions";
|
||||
@@ -19,12 +21,15 @@ import { useTranslation } from "react-i18next";
|
||||
interface CommentDialogProps {
|
||||
editor: ReturnType<typeof useEditor>;
|
||||
pageId: string;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
function CommentDialog({ editor, pageId }: CommentDialogProps) {
|
||||
function CommentDialog({ editor, pageId, readOnly }: CommentDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const [comment, setComment] = useState("");
|
||||
const [, setShowCommentPopup] = useAtom(showCommentPopupAtom);
|
||||
const [, setShowReadOnlyCommentPopup] = useAtom(showReadOnlyCommentPopupAtom);
|
||||
const [readOnlyCommentData, setReadOnlyCommentData] = useAtom(readOnlyCommentDataAtom);
|
||||
const [, setActiveCommentId] = useAtom(activeCommentIdAtom);
|
||||
const [draftCommentId, setDraftCommentId] = useAtom(draftCommentIdAtom);
|
||||
const [currentUser] = useAtom(currentUserAtom);
|
||||
@@ -34,11 +39,17 @@ function CommentDialog({ editor, pageId }: CommentDialogProps) {
|
||||
handleDialogClose();
|
||||
});
|
||||
const createCommentMutation = useCreateCommentMutation();
|
||||
const { isPending } = createCommentMutation;
|
||||
const isPending = createCommentMutation.isPending;
|
||||
|
||||
const handleDialogClose = () => {
|
||||
setShowCommentPopup(false);
|
||||
editor.chain().focus().unsetCommentDecoration().run();
|
||||
if (readOnly) {
|
||||
setShowReadOnlyCommentPopup(false);
|
||||
// @ts-ignore
|
||||
setReadOnlyCommentData(null);
|
||||
} else {
|
||||
setShowCommentPopup(false);
|
||||
editor.chain().focus().unsetCommentDecoration().run();
|
||||
}
|
||||
};
|
||||
|
||||
const getSelectedText = () => {
|
||||
@@ -47,6 +58,11 @@ function CommentDialog({ editor, pageId }: CommentDialogProps) {
|
||||
};
|
||||
|
||||
const handleAddComment = async () => {
|
||||
if (readOnly) {
|
||||
await handleAddReadOnlyComment();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const selectedText = getSelectedText();
|
||||
const commentData = {
|
||||
@@ -65,7 +81,6 @@ function CommentDialog({ editor, pageId }: CommentDialogProps) {
|
||||
.run();
|
||||
setActiveCommentId(createdComment.id);
|
||||
|
||||
//unselect text to close bubble menu
|
||||
editor.commands.setTextSelection({ from: editor.view.state.selection.from, to: editor.view.state.selection.from });
|
||||
|
||||
setAsideState({ tab: "comments", isAsideOpen: true });
|
||||
@@ -85,6 +100,33 @@ function CommentDialog({ editor, pageId }: CommentDialogProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddReadOnlyComment = async () => {
|
||||
if (!readOnlyCommentData) return;
|
||||
|
||||
try {
|
||||
const createdComment = await createCommentMutation.mutateAsync({
|
||||
pageId,
|
||||
content: JSON.stringify(comment),
|
||||
selection: readOnlyCommentData.selectedText,
|
||||
type: "inline",
|
||||
yjsSelection: readOnlyCommentData.yjsSelection,
|
||||
});
|
||||
|
||||
setActiveCommentId(createdComment.id);
|
||||
setAsideState({ tab: "comments", isAsideOpen: true });
|
||||
|
||||
setTimeout(() => {
|
||||
const selector = `div[data-comment-id="${createdComment.id}"]`;
|
||||
const commentElement = document.querySelector(selector);
|
||||
commentElement?.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
}, 400);
|
||||
} finally {
|
||||
setShowReadOnlyCommentPopup(false);
|
||||
// @ts-ignore
|
||||
setReadOnlyCommentData(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCommentEditorChange = (newContent: any) => {
|
||||
setComment(newContent);
|
||||
};
|
||||
|
||||
@@ -44,7 +44,9 @@ function CommentListWithTabs() {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { data: space } = useGetSpaceBySlugQuery(page?.space?.slug);
|
||||
|
||||
const canComment = page?.permissions?.canEdit ?? false;
|
||||
const canComment =
|
||||
(page?.permissions?.canEdit ?? false) ||
|
||||
(space?.settings?.comments?.allowViewerComments === true);
|
||||
|
||||
// Separate active and resolved comments
|
||||
const { activeComments, resolvedComments } = useMemo(() => {
|
||||
@@ -153,7 +155,7 @@ function CommentListWithTabs() {
|
||||
)}
|
||||
</Paper>
|
||||
),
|
||||
[comments, handleAddReply, isLoading, space?.membership?.role],
|
||||
[comments, handleAddReply, isLoading, space?.membership?.role, canComment],
|
||||
);
|
||||
|
||||
if (isCommentsLoading) {
|
||||
|
||||
@@ -75,7 +75,7 @@ function CommentMenu({
|
||||
{isResolved ? t("Re-open comment") : t("Resolve comment")}
|
||||
</Menu.Item>
|
||||
) : (
|
||||
<Tooltip label={upgradeLabel} position="left">
|
||||
<Tooltip label={upgradeLabel} position="left" withPortal={false}>
|
||||
<Menu.Item disabled leftSection={<IconCircleCheck size={14} />}>
|
||||
{t("Resolve comment")}
|
||||
</Menu.Item>
|
||||
|
||||
Reference in New Issue
Block a user