import { useToLogin, useToUserProfile } from "@/hooks/use-route"; import { User } from "@/models/user"; import { useTranslations } from "next-intl"; import { useState } from "react"; import { toast } from "sonner"; import { getGravatarByUser } from "@/components/common/gravatar"; import { Reply, Trash, Heart, Pencil, Lock } from "lucide-react"; import { Comment } from "@/models/comment"; import { TargetType } from "@/models/types"; import { toggleLike } from "@/api/like"; import { useDoubleConfirm } from "@/hooks/use-double-confirm"; import { CommentInput } from "./comment-input"; import { createComment, deleteComment, listComments, updateComment } from "@/api/comment"; import { OrderBy } from "@/models/common"; import config from "@/config"; export function CommentItem( { user, comment, parentComment, onCommentDelete, }: { user: User | null, comment: Comment, parentComment: Comment | null, onCommentDelete: ({ commentId }: { commentId: number }) => void, } ) { const t = useTranslations("Comment") const commonT = useTranslations('Common') const clickToUserProfile = useToUserProfile(); const clickToLogin = useToLogin(); const { confirming, onClick, onBlur } = useDoubleConfirm(); const [likeCount, setLikeCount] = useState(comment.likeCount); const [liked, setLiked] = useState(comment.isLiked); const [isPrivate, setIsPrivate] = useState(comment.isPrivate); const [replyCount, setReplyCount] = useState(comment.replyCount); const [showReplies, setShowReplies] = useState(false); const [showReplyInput, setShowReplyInput] = useState(false); const [replies, setReplies] = useState([]); const [repliesLoaded, setRepliesLoaded] = useState(false); const [showEditInput, setShowEditInput] = useState(false); const handleToggleLike = () => { if (!user) { toast.error(t("login_required"), { action:
, }); return; } toggleLike( { targetType: TargetType.Comment, targetId: comment.id } ).then(res => { toast.success(res.data.status ? t("like_success") : t("unlike_success")); setLiked(res.data.status); setLikeCount(res.data.status ? likeCount + 1 : likeCount - 1); }).catch(error => { toast.error(t("like_failed") + ": " + error.message); }); } const reloadReplies = () => { listComments( { targetType: comment.targetType, targetId: comment.targetId, depth: comment.depth + 1, orderBy: OrderBy.CreatedAt, desc: false, page: 1, size: config.commentsPerPage, commentId: comment.id } ).then(response => { setReplies(response.data); setRepliesLoaded(true); }); } const toggleReplies = () => { if (!showReplies && !repliesLoaded) { reloadReplies(); } setShowReplies(!showReplies); } const onCommentEdit = ({ commentContent: newContent, isPrivate }: { commentContent: string, isPrivate: boolean }) => { updateComment({ id: comment.id, content: newContent, isPrivate }).then(() => { toast.success(t("edit_success")); comment.content = newContent; setIsPrivate(isPrivate); setShowEditInput(false); }).catch(error => { toast.error(t("edit_failed") + ": " + error.message); }); } const onReply = ({ commentContent, isPrivate }: { commentContent: string, isPrivate: boolean }) => { createComment({ targetType: comment.targetType, targetId: comment.targetId, content: commentContent, replyId: comment.id, isPrivate, }).then(() => { toast.success(t("comment_success")); reloadReplies(); setShowReplies(true); setShowReplyInput(false); setReplyCount(replyCount + 1); }).catch(error => { toast.error(t("comment_failed") + ": " + error?.response?.data?.message || error?.message ); }); } const onReplyDelete = ({ commentId: replyId }: { commentId: number }) => { deleteComment({ id: replyId }).then(() => { toast.success(t("delete_success")); setReplyCount(replyCount - 1); setReplies(replies.filter(r => r.id !== replyId)); }).catch(error => { toast.error(t("delete_failed") + ": " + error.message); }); } return (
{getGravatarByUser(comment.user)}
{comment.user.nickname}

{ isPrivate && } { parentComment && <>{t("reply")} : } {comment.content}

{new Date(comment.updatedAt).toLocaleString()} {/* 点赞按钮 */} {/* 回复按钮 */} {/* 编辑和删除按钮 仅自己的评论可见 */} {user?.id === comment.user.id && ( <> )} {replyCount > 0 && }
{/* 这俩输入框一次只能显示一个 */} {showReplyInput && !showEditInput && } {showEditInput && !showReplyInput && } {showReplies && replies.length > 0 && (
{replies.map((reply) => ( ))}
)}
) }