Files
jiang13-bbs/frontend/components/CommentEditHistoryModal.tsx

174 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useCallback, useEffect, useState } from "react";
import Link from "next/link";
import { Loader2 } from "lucide-react";
import Modal from "@/components/Modal";
import Avatar from "@/components/Avatar";
import MarkdownBodyClient from "@/components/MarkdownBodyClient";
import {
apiAdminCommentHistory,
type CommentEditHistoryItem,
} from "@/lib/api";
const PAGE_SIZE = 10;
/**
* 版主可见:评论历次编辑前的旧正文快照(倒序分页)。
* 宽度对齐详情页评论主栏(约 800px+),长文可滚动;底部「加载更多」。
*/
export default function CommentEditHistoryModal({
open,
commentId,
onClose,
}: {
open: boolean;
commentId: number | null;
onClose: () => void;
}) {
const [loading, setLoading] = useState(false);
const [loadingMore, setLoadingMore] = useState(false);
const [error, setError] = useState("");
const [items, setItems] = useState<CommentEditHistoryItem[]>([]);
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const hasMore = items.length < total;
const loadPage = useCallback(
async (nextPage: number, append: boolean) => {
if (commentId == null) return;
if (append) setLoadingMore(true);
else {
setLoading(true);
setError("");
}
try {
const res = await apiAdminCommentHistory(commentId, nextPage, PAGE_SIZE);
const batch = res.items || [];
setError("");
setTotal(res.total ?? 0);
setPage(res.page ?? nextPage);
setItems((prev) => (append ? [...prev, ...batch] : batch));
} catch (e) {
const msg = e instanceof Error ? e.message : "加载失败";
if (append) {
// 追加失败不清空已有列表
setError(msg);
} else {
setError(msg);
setItems([]);
setTotal(0);
}
} finally {
setLoading(false);
setLoadingMore(false);
}
},
[commentId]
);
useEffect(() => {
if (!open || commentId == null) return;
setItems([]);
setPage(1);
setTotal(0);
setError("");
void loadPage(1, false);
}, [open, commentId, loadPage]);
return (
<Modal
open={open}
title="评论编辑历史"
description={total > 0 ? `共 ${total} 条修订记录` : undefined}
onClose={onClose}
// 对齐详情页中间评论栏可用宽度(1440 三栏布局下约 800px)
maxWidthClass="max-w-[min(840px,calc(100vw-2rem))]"
panelClassName="p-5 sm:p-6 max-h-[85vh] flex flex-col min-h-0 overflow-hidden"
bodyClassName="overflow-y-auto min-h-0 flex-1 -mx-1 px-1"
footer={
<button type="button" className="btn btn-line btn-sm" onClick={onClose}>
关闭
</button>
}
>
{loading ? (
<div className="flex items-center justify-center gap-2 py-10 meta text-[13px]">
<Loader2 size={16} className="animate-spin" />
加载中…
</div>
) : error && items.length === 0 ? (
<p className="text-[13.5px] py-6 text-center" style={{ color: "var(--danger)" }}>
{error}
</p>
) : items.length === 0 ? (
<p className="meta text-[13.5px] py-8 text-center">暂无编辑记录</p>
) : (
<div className="flex flex-col gap-4">
<ul className="flex flex-col gap-4">
{items.map((it) => {
const name = it.editor.nickname || it.editor.username || "用户";
return (
<li
key={it.id}
className="rounded-md p-3 sm:p-4"
style={{ background: "var(--panel-2)", border: "1px solid var(--line)" }}
>
<div className="flex items-center gap-2 mb-2.5">
<Link href={`/u/${it.editor.id}`} className="shrink-0">
<Avatar name={name} src={it.editor.avatar || undefined} size={28} />
</Link>
<div className="min-w-0 flex-1">
<Link
href={`/u/${it.editor.id}`}
className="text-[13px] hover:text-[var(--accent)]"
style={{ color: "var(--ink)" }}
>
{name}
</Link>
<p className="meta text-[11.5px] tabular-nums">
{new Date(it.created_at).toLocaleString("zh-CN")} · 编辑前正文
</p>
</div>
</div>
<MarkdownBodyClient
content={it.old_content}
className="prose-content prose-comment"
/>
</li>
);
})}
</ul>
{error && items.length > 0 && (
<p className="text-[12.5px] text-center" style={{ color: "var(--danger)" }}>
{error}
</p>
)}
{hasMore && (
<div className="flex justify-center pt-1 pb-1">
<button
type="button"
className="btn btn-line btn-sm"
disabled={loadingMore}
onClick={() => void loadPage(page + 1, true)}
>
{loadingMore ? (
<span className="inline-flex items-center gap-1.5">
<Loader2 size={14} className="animate-spin" />
加载中…
</span>
) : (
`加载更多(已显示 ${items.length}/${total})`
)}
</button>
</div>
)}
</div>
)}
</Modal>
);
}