import Link from "next/link"; import { Icon } from "@/components/Icon"; import { useEffect, useRef, useState, type ReactNode } from "react"; import type { DocLink } from "@/lib/docs"; import { collectionEntries, collectionIcon, collectionLabel, collectionTone } from "@/lib/collections"; export function Brand() { return ( Tabularium Docs ); } export function Navigation({ docs, current, onNavigate, }: { docs: DocLink[]; current?: string; onNavigate?: () => void; }) { const collection = docs.find((d) => d.slug === current)?.collection; const scopedDocs = docs.filter((d) => d.collection === collection); const groups = [...new Set(scopedDocs.map((d) => d.group))]; const collections = collectionEntries(docs); return ( ); } export function Shell({ docs, current, children, }: { docs: DocLink[]; current?: string; children: ReactNode; }) { const dialog = useRef(null); const input = useRef(null); const trigger = useRef(null); const [query, setQuery] = useState(""); const [searchDocs, setSearchDocs] = useState(docs); const [searchLoaded, setSearchLoaded] = useState(false); const [searchStatus, setSearchStatus] = useState(""); const [selected, setSelected] = useState(0); const [menu, setMenu] = useState(false); const collection = docs.find((d) => d.slug === current)?.collection; const collections = collectionEntries(docs); const results = searchDocs.filter((d) => (!collection || d.collection === collection) && `${d.title} ${d.description} ${d.searchText ?? ""}` .toLocaleLowerCase() .includes(query.trim().toLocaleLowerCase()), ); async function openSearch() { setQuery(""); setSelected(0); dialog.current?.showModal(); input.current?.focus(); if (searchLoaded) return; setSearchStatus("正在加载全文索引…"); try { const response = await fetch("/api/search"); if (!response.ok) throw new Error("Search unavailable"); setSearchDocs(await response.json()); setSearchLoaded(true); setSearchStatus(""); } catch { setSearchStatus("全文搜索暂时不可用,当前仅搜索标题和摘要。关闭后重新打开可重试。"); } } function closeSearch() { dialog.current?.close(); trigger.current?.focus(); } useEffect(() => { function keyboard(e: KeyboardEvent) { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { e.preventDefault(); trigger.current?.click(); } } document.addEventListener("keydown", keyboard); return () => document.removeEventListener("keydown", keyboard); }, []); return (
跳到正文
{menu && (
setMenu(false)} />
)} {children} { if (e.target === e.currentTarget) closeSearch(); }} onClose={() => trigger.current?.focus()} >
{ setQuery(e.target.value); setSelected(0); }} onKeyDown={(e) => { if (e.key === "ArrowDown") { e.preventDefault(); setSelected((i) => Math.max(0, Math.min(i + 1, results.length - 1)), ); } if (e.key === "ArrowUp") { e.preventDefault(); setSelected((i) => Math.max(i - 1, 0)); } if (e.key === "Enter") { e.preventDefault(); dialog.current ?.querySelector( `[data-result="${selected}"]`, ) ?.click(); } }} />
{searchStatus &&

{searchStatus}

}

{query ? `${results.length} 篇匹配文档` : "浏览文档"}

{results.map((d, i) => ( {d.group} {d.title} {d.description} ))} {!results.length && (

没有找到相关内容,请尝试其他关键词。

)}
↑ ↓ 选择 Enter 打开 · Esc 关闭
); }