"use client"; import { useState, useEffect, useRef, useLayoutEffect } from "react"; import { Check, Copy, ChevronDown, ChevronUp } from "lucide-react"; import { toast } from "@/lib/toast"; import { apiRecordSnippetCopy } from "@/lib/api"; import { siteCodeToHtml } from "@/components/markdown/shikiHighlighter"; const MONO = "var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace)"; const LINE_H = 22; // px,与下方 leading 严格对齐 const PAD_Y = 14; /** 从 shiki 双主题输出提取 pre 上的 CSS 变量与 code 内逐行 HTML */ function extractShiki(html: string): { vars: Record; lines: string[] } | null { const codeM = /]*>([\s\S]*?)<\/code>/i.exec(html); if (!codeM) return null; const vars: Record = {}; const preM = /]*\sstyle="([^"]*)"[^>]*>/i.exec(html); if (preM) { for (const m of preM[1].matchAll(/(--[\w-]+)\s*:\s*([^;]+)/g)) { vars[m[1]] = m[2].trim(); } } return { vars, lines: codeM[1].split("\n") }; } function WindowDots() { return ( ); } /** 片段代码视图:编辑器窗格 + shiki 高亮 + 行号 + 复制 + 超长折叠 */ export default function SnippetCodeView({ code, language, snippetId, defaultFold = true, foldLines = 25, }: { code: string; language: string; snippetId: number; defaultFold?: boolean; foldLines?: number; }) { const [copied, setCopied] = useState(false); const [expanded, setExpanded] = useState(!defaultFold); const [hl, setHl] = useState<{ vars: Record; lines: string[] } | null>(null); const [maxH, setMaxH] = useState(undefined); const bodyRef = useRef(null); const plainLines = code.split("\n"); const lineCount = plainLines.length; const canCollapse = defaultFold && lineCount > foldLines; const collapsedH = PAD_Y + foldLines * LINE_H; const rows = hl?.lines ?? plainLines; const highlighted = hl !== null; useEffect(() => { let cancelled = false; siteCodeToHtml(code, language) .then((html) => { if (cancelled) return; const parsed = extractShiki(html); if (parsed && parsed.lines.length > 0) { setHl({ vars: parsed.vars, lines: parsed.lines.slice(0, lineCount) }); } }) .catch(() => {}); return () => { cancelled = true; }; }, [code, language, lineCount]); useLayoutEffect(() => { if (!canCollapse || !bodyRef.current) return; setMaxH(expanded ? bodyRef.current.scrollHeight : collapsedH); }, [expanded, canCollapse, collapsedH, hl]); const onCopy = async () => { try { await navigator.clipboard.writeText(code); setCopied(true); toast("已复制到剪贴板", "ok"); apiRecordSnippetCopy(snippetId).catch(() => {}); window.setTimeout(() => setCopied(false), 1600); } catch { toast("复制失败,请手动选择文本"); } }; const fileName = `snippet.${language === "plaintext" ? "txt" : language}`; return (
{/* 窗格标题栏 */}
{fileName} {lineCount} 行 {language || "text"}
{/* 代码主体 */}
          {rows.map((ln, i) => (
            
{i + 1} {highlighted ? ( ) : ( {ln || " "} )}
))}
{canCollapse && !expanded && (
)}
{canCollapse && ( )}
); }