feat(comments): implement comment resolution for the community build
Add comment resolve/re-open as a community feature, written from scratch on top of the infrastructure already present in the community codebase: the resolved_at/resolved_by_id columns, the COMMENT_RESOLVED notification job, the resolveCommentMark collaboration handler, the commentResolved websocket event, the comment service/types and the Open/Resolved tabs. No Enterprise-Edition code is reused and there is no EE feature gating — resolving is available to anyone who can comment. Backend: - add POST /comments/resolve (ResolveCommentDto) guarded by validateCanComment; reject resolving replies - add CommentService.resolveComment: set/clear resolvedAt/resolvedById, sync the inline comment mark via collaboration handleYjsEvent, queue COMMENT_RESOLVED_NOTIFICATION (only when another user resolves), emit the commentResolved websocket event and write a resolve/reopen audit log Frontend: - add useResolveCommentMutation with optimistic update + rollback - add ResolveComment toggle button - wire the resolve button and menu item into comment-list-item / comment-menu, gated on canComment for parent comments Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { ActionIcon, Tooltip } from "@mantine/core";
|
||||
import { IconCircleCheck, IconCircleCheckFilled } from "@tabler/icons-react";
|
||||
import { useResolveCommentMutation } from "@/features/comment/queries/comment-query";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Editor } from "@tiptap/react";
|
||||
|
||||
interface ResolveCommentProps {
|
||||
editor: Editor | null;
|
||||
commentId: string;
|
||||
pageId: string;
|
||||
resolvedAt?: Date;
|
||||
}
|
||||
|
||||
function ResolveComment({
|
||||
editor,
|
||||
commentId,
|
||||
pageId,
|
||||
resolvedAt,
|
||||
}: ResolveCommentProps) {
|
||||
const { t } = useTranslation();
|
||||
const resolveCommentMutation = useResolveCommentMutation();
|
||||
|
||||
const isResolved = resolvedAt != null;
|
||||
|
||||
const handleResolveToggle = async () => {
|
||||
try {
|
||||
await resolveCommentMutation.mutateAsync({
|
||||
commentId,
|
||||
pageId,
|
||||
resolved: !isResolved,
|
||||
});
|
||||
|
||||
if (editor) {
|
||||
editor.commands.setCommentResolved(commentId, !isResolved);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to toggle resolved state:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
label={isResolved ? t("Re-open comment") : t("Resolve comment")}
|
||||
position="top"
|
||||
>
|
||||
<ActionIcon
|
||||
onClick={handleResolveToggle}
|
||||
variant="subtle"
|
||||
color={isResolved ? "green" : "gray"}
|
||||
size="sm"
|
||||
loading={resolveCommentMutation.isPending}
|
||||
disabled={resolveCommentMutation.isPending}
|
||||
>
|
||||
{isResolved ? (
|
||||
<IconCircleCheckFilled size={18} />
|
||||
) : (
|
||||
<IconCircleCheck size={18} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export default ResolveComment;
|
||||
Reference in New Issue
Block a user