c758a36dd2
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>
65 lines
1.6 KiB
TypeScript
65 lines
1.6 KiB
TypeScript
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;
|