"use client"; import { useEffect, useRef, useState } from "react"; import { CodeXml, Eye, GitFork, Copy, Search } from "lucide-react"; import Modal from "@/components/Modal"; import { apiMySnippets, apiSearchSnippets, type SnippetListItem, } from "@/lib/api"; import { formatRelative } from "@/lib/format"; import { toast } from "@/lib/toast"; type Tab = "public" | "mine"; /** * 「插入片段」选择器:搜索公开广场 / 我的片段,点击插入 [snippet id=N] 指令。 */ export default function SnippetPickerModal({ open, onClose, onPick, }: { open: boolean; onClose: () => void; onPick: (id: number) => void; }) { const [tab, setTab] = useState("public"); const [q, setQ] = useState(""); const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); const [loaded, setLoaded] = useState(false); const seq = useRef(0); // 打开时重置 useEffect(() => { if (open) { setTab("public"); setQ(""); } }, [open]); useEffect(() => { if (!open) return; const mySeq = ++seq.current; setLoading(true); const timer = setTimeout(() => { const req = tab === "mine" ? apiMySnippets(q.trim(), 1, 20) : apiSearchSnippets({ q: q.trim(), size: 20 }); req .then((res) => { if (seq.current !== mySeq) return; setItems(res.items); setLoaded(true); }) .catch((e) => { if (seq.current !== mySeq) return; toast(e instanceof Error ? e.message : "加载失败"); }) .finally(() => { if (seq.current === mySeq) setLoading(false); }); }, 300); return () => clearTimeout(timer); }, [open, tab, q]); return (
{( [ ["public", "公开广场"], ["mine", "我的片段"], ] as [Tab, string][] ).map(([key, label]) => ( ))}
setQ(e.target.value)} placeholder="搜索标题或描述…" aria-label="搜索片段" className="w-full rounded-lg pl-7 pr-3 py-1.5 text-[13px]" style={{ background: "var(--bg)", border: "1px solid var(--line)", color: "var(--ink)" }} />
{loading && !loaded ? (

加载中…

) : items.length === 0 ? (

{tab === "mine" ? "还没有自己的片段,去片段广场新建一个吧" : "没有匹配的片段"}

) : (
    {items.map((it) => (
  • ))}
)}
); }