'use client'; import React, { useState } from 'react'; import type { Value } from '@udecode/plate'; import { cn } from '@udecode/cn'; import { CommentsPlugin } from '@udecode/plate-comments/react'; import { Plate, useEditorPlugin, useStoreValue } from '@udecode/plate/react'; // import { // differenceInDays, // differenceInHours, // differenceInMinutes, // format, // } from 'date-fns'; import { CheckIcon, MoreHorizontalIcon, PencilIcon, TrashIcon, XIcon, } from 'lucide-react'; import { Avatar, AvatarFallback, AvatarImage } from './avatar'; import { discussionStore, useFakeCurrentUserId, useFakeUserInfo, } from './block-discussion'; import { Button } from './button'; import { useCommentEditor } from './comment-create-form'; import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuTrigger, } from './dropdown-menu'; import { Editor, EditorContainer } from './editor'; export const formatCommentDate = (date: Date) => { // const now = new Date(); // const diffMinutes = differenceInMinutes(now, date); // const diffHours = differenceInHours(now, date); // const diffDays = differenceInDays(now, date); // if (diffMinutes < 60) { // return `${diffMinutes}m`; // } // if (diffHours < 24) { // return `${diffHours}h`; // } // if (diffDays < 2) { // return `${diffDays}d`; // } // Removed to not use date-fns - improve if needed. // Format dates older than 2 days with MM/dd/yyyy pattern const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const year = date.getFullYear(); return `${month}/${day}/${year}`; // return format(date, 'MM/dd/yyyy'); }; export interface TComment { id: string; contentRich: Value; createdAt: Date; discussionId: string; isEdited: boolean; userId: string; } export function Comment(props: { comment: TComment; discussionLength: number; editingId: string | null; index: number; setEditingId: React.Dispatch>; documentContent?: string; showDocumentContent?: boolean; onEditorClick?: () => void; }) { const { comment, discussionLength, documentContent, editingId, index, setEditingId, showDocumentContent = false, onEditorClick, } = props; // const { user } = comment; const discussions = useStoreValue(discussionStore, 'discussions'); const userInfo = useFakeUserInfo(comment.userId); const currentUserId = useFakeCurrentUserId(); const resolveDiscussion = async (id: string) => { const updatedDiscussions = discussions.map((discussion) => { if (discussion.id === id) { return { ...discussion, isResolved: true }; } return discussion; }); discussionStore.set('discussions', updatedDiscussions); }; const removeDiscussion = async (id: string) => { const updatedDiscussions = discussions.filter( (discussion: any) => discussion.id !== id, ); discussionStore.set('discussions', updatedDiscussions); }; const updateComment = async (input: { id: string; contentRich: any; discussionId: string; isEdited: boolean; }) => { const updatedDiscussions = discussions.map((discussion) => { if (discussion.id === input.discussionId) { const updatedComments = discussion.comments.map((comment) => { if (comment.id === input.id) { return { ...comment, contentRich: input.contentRich, isEdited: true, updatedAt: new Date(), }; } return comment; }); return { ...discussion, comments: updatedComments }; } return discussion; }); discussionStore.set('discussions', updatedDiscussions); }; const { tf } = useEditorPlugin(CommentsPlugin); // Replace to your own backend or refer to potion const isMyComment = currentUserId === comment.userId; const initialValue = comment.contentRich; const commentEditor = useCommentEditor( { id: comment.id, value: initialValue, }, [initialValue], ); const onCancel = () => { setEditingId(null); commentEditor.tf.replaceNodes(initialValue, { at: [], children: true, }); }; const onSave = () => { void updateComment({ id: comment.id, contentRich: commentEditor.children, discussionId: comment.discussionId, isEdited: true, }); setEditingId(null); }; const onResolveComment = () => { void resolveDiscussion(comment.discussionId); tf.comment.unsetMark({ id: comment.discussionId }); }; const isFirst = index === 0; const isLast = index === discussionLength - 1; const isEditing = editingId && editingId === comment.id; const [hovering, setHovering] = useState(false); const [dropdownOpen, setDropdownOpen] = useState(false); return (
setHovering(true)} onMouseLeave={() => setHovering(false)} >
{userInfo?.name?.[0]}

{/* Replace to your own backend or refer to potion */} {userInfo?.name}

{formatCommentDate(new Date(comment.createdAt))} {comment.isEdited && (edited)}
{isMyComment && (hovering || dropdownOpen) && (
{index === 0 && ( )} { setTimeout(() => { commentEditor.tf.focus({ edge: 'endEditor' }); }, 0); }} onRemoveComment={() => { if (discussionLength === 1) { tf.comment.unsetMark({ id: comment.discussionId }); void removeDiscussion(comment.discussionId); } }} comment={comment} dropdownOpen={dropdownOpen} setDropdownOpen={setDropdownOpen} setEditingId={setEditingId} />
)}
{isFirst && showDocumentContent && (
{discussionLength > 1 && (
)}
{documentContent &&
{documentContent}
}
)}
{!isLast && (
)} onEditorClick?.()} /> {isEditing && (
)}
); } interface CommentMoreDropdownProps { comment: TComment; dropdownOpen: boolean; setDropdownOpen: React.Dispatch>; setEditingId: React.Dispatch>; onCloseAutoFocus?: () => void; onRemoveComment?: () => void; } export function CommentMoreDropdown(props: CommentMoreDropdownProps) { const { comment, dropdownOpen, setDropdownOpen, setEditingId, onCloseAutoFocus, onRemoveComment, } = props; const discussions = useStoreValue(discussionStore, 'discussions'); const selectedEditCommentRef = React.useRef(false); const onDeleteComment = React.useCallback(() => { if (!comment.id) // eslint-disable-next-line no-alert return alert('You are operating too quickly, please try again later.'); // Find and update the discussion const updatedDiscussions = discussions.map((discussion: any) => { if (discussion.id !== comment.discussionId) { return discussion; } const commentIndex = discussion.comments.findIndex( (c: any) => c.id === comment.id, ); if (commentIndex === -1) { return discussion; } return { ...discussion, comments: [ ...discussion.comments.slice(0, commentIndex), ...discussion.comments.slice(commentIndex + 1), ], }; }); // Save back to session storage discussionStore.set('discussions', updatedDiscussions); onRemoveComment?.(); }, [comment.discussionId, comment.id, discussions, onRemoveComment]); const onEditComment = React.useCallback(() => { selectedEditCommentRef.current = true; if (!comment.id) // eslint-disable-next-line no-alert return alert('You are operating too quickly, please try again later.'); setEditingId(comment.id); }, [comment.id, setEditingId]); return ( e.stopPropagation()}> { if (selectedEditCommentRef.current) { onCloseAutoFocus?.(); selectedEditCommentRef.current = false; } return e.preventDefault(); }} > Edit comment Delete comment ); }