"use client"; import { useState, useEffect, useCallback } from "react"; import { History, X } from "lucide-react"; import { apiSnippetHistory } from "@/lib/api"; import type { SnippetVersionItem } from "@/lib/api"; import { toast } from "@/lib/toast"; import { formatRelative } from "@/lib/format"; import { smartDiff, toFoldedRows, type FoldedRow } from "@/lib/smartDiff"; import Modal from "@/components/Modal"; function DiffLineRow({ row }: { row: FoldedRow }) { if (row.kind === "fold") { return (
⋯ 折叠 {row.count} 行 ⋯
); } const bg = row.op === "del" ? "color-mix(in srgb, var(--danger) 8%, transparent)" : row.op === "ins" ? "color-mix(in srgb, var(--ok) 8%, transparent)" : "transparent"; const color = row.op === "del" ? "var(--danger)" : row.op === "ins" ? "var(--ok)" : "var(--ink)"; const prefix = row.op === "del" ? "- " : row.op === "ins" ? "+ " : " "; if (row.segments && row.segments.length > 0) { // 行内细分高亮 const parts: Array<{ text: string; changed: boolean }> = []; let last = 0; for (const seg of row.segments) { if (seg.start > last) parts.push({ text: row.text.slice(last, seg.start), changed: false }); parts.push({ text: row.text.slice(seg.start, seg.end), changed: true }); last = seg.end; } if (last < row.text.length) parts.push({ text: row.text.slice(last), changed: false }); return (
{prefix} {parts.map((p, i) => p.changed ? ( {p.text} ) : ( {p.text} ) )}
); } return (
{prefix} {row.text || " "}
); } function VersionDiff({ oldText, newText }: { oldText: string; newText: string }) { const result = smartDiff(oldText, newText); const rows = toFoldedRows(result.blocks, 3); return (
+{result.stats.adds} -{result.stats.dels}
{rows.map((row, i) => ( ))}
); } export default function SnippetVersionModal({ open, onClose, snippetId, currentCode, currentTitle, currentLanguage, currentDescription, }: { open: boolean; onClose: () => void; snippetId: number; currentCode: string; currentTitle: string; currentLanguage: string; currentDescription: string; }) { const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); const [expandedId, setExpandedId] = useState(null); const load = useCallback(async () => { setLoading(true); try { const res = await apiSnippetHistory(snippetId, 1, 50); setItems(res.items); } catch (e) { toast(e instanceof Error ? e.message : "加载历史失败"); } finally { setLoading(false); } }, [snippetId]); useEffect(() => { if (open) load(); }, [open, load]); const toggleExpand = (id: number) => { setExpandedId((prev) => (prev === id ? null : id)); }; return (
{loading ? (

加载中…

) : items.length === 0 ? (

暂无编辑历史

) : (
{items.map((v) => { const isExpanded = expandedId === v.id; const diffRows = isExpanded ? toFoldedRows(smartDiff(v.old_code, currentCode).blocks, 3) : null; return (
{isExpanded && diffRows && (
+{smartDiff(v.old_code, currentCode).stats.adds} -{smartDiff(v.old_code, currentCode).stats.dels}
{diffRows.map((row, i) => ( ))}
)}
); })}
)}
); }