Files
endorphin-tabularium-docs/apps/docs/src/pages/index.tsx
T
codex-bot d57e9b77d7
Verify database-backed documents / verify (pull_request) Failing after 6m6s
feat: make the site database authoritative for documents and catalog
2026-10-30 16:23:54 +08:00

62 lines
4.1 KiB
TypeScript

import type { GetServerSideProps, InferGetServerSidePropsType } from "next";
import Head from "next/head";
import Link from "next/link";
import { useState } from "react";
import { listDocs, type DocLink } from "@/lib/docs";
import { Shell, Navigation } from "@/components/Shell";
import { Icon } from "@/components/Icon";
import { collectionEntries, collectionIcon, collectionLabel, collectionTone } from "@/lib/collections";
export const getServerSideProps: GetServerSideProps<{ docs: DocLink[] }> = async ({ res }) => {
res.setHeader("Cache-Control", "private, no-store");
return { props: { docs: await listDocs() } };
};
const PAGE_SIZE = 8;
export default function Home({ docs }: InferGetServerSidePropsType<typeof getServerSideProps>) {
const [collection, setCollection] = useState("");
const [query, setQuery] = useState("");
const [page, setPage] = useState(0);
const collections = [...new Set(docs.map((doc) => doc.collection))];
const filtered = docs.filter((doc) => (!collection || doc.collection === collection) &&
`${doc.title} ${doc.description}`.toLocaleLowerCase().includes(query.trim().toLocaleLowerCase()));
const entries = collectionEntries(docs);
return <>
<Head><title>Tabularium · 资料库</title><meta name="description" content="Endor 文档、源码研究与学习资料。" /></Head>
<Shell docs={docs}>
<div className="home-layout">
<aside className="sidebar"><Navigation docs={docs} /></aside>
<main id="content" className="home-main">
<section className="library-intro">
<h1>文档中心</h1>
<p>系统设计、源码研究与学习笔记。</p>
</section>
<section className="featured-section" aria-labelledby="featured-title">
<h2 id="featured-title">选择一份文档</h2>
<div className="featured-grid">{entries.map(({ name, entry, count }) => <Link className="featured-link" data-tone={collectionTone(name)} href={`/docs/${entry.slug}`} key={name}>
<span className="collection-art"><Icon name={collectionIcon(name)} size={26} /></span>
<h3>{collectionLabel(name)}</h3>
<p>{entry.description}</p>
<span className="collection-entry">{count} 篇 <Icon name="arrow" size={17} /></span>
</Link>)}</div>
</section>
<section className="library-section" id="library" aria-labelledby="library-title">
<h2 id="library-title">全部资料</h2>
<div className="library-tools">
<div className="collection-filters" role="group" aria-label="资料分类">
{["", ...collections].map((name) => <button key={name} aria-pressed={collection === name} onClick={() => { setCollection(name); setPage(0); }}>{name || "全部"}</button>)}
</div>
<label className="library-search"><Icon name="search" /><input aria-label="筛选资料" placeholder="筛选资料…" value={query} onChange={(event) => {setQuery(event.target.value); setPage(0);}} /></label>
</div>
<ul className="library-list">{filtered.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE).map((doc) => <li key={doc.slug}>
<Link href={`/docs/${doc.slug}`}><span className="library-row-title">{doc.title.replace("(草案)", "")}{doc.draft && <span className="draft-badge">草案</span>}</span><span className="library-row-group">{doc.group}</span><Icon name="arrow" size={16} /></Link>
</li>)}</ul>
{filtered.length === 0 && <p className="library-empty">没有找到相关资料。</p>}
{filtered.length > PAGE_SIZE && <nav className="library-pagination" aria-label="资料分页"><button disabled={page === 0} onClick={() => setPage(page - 1)}>上一页</button><span>{page + 1} / {Math.ceil(filtered.length / PAGE_SIZE)}</span><button disabled={(page + 1) * PAGE_SIZE >= filtered.length} onClick={() => setPage(page + 1)}>下一页</button></nav>}
</section>
<footer className="site-footer"><span>Tabularium</span><a href="https://gitea.entroia.com/Zayden-Jung/endorphin-tabularium-docs">文档仓库 ↗</a></footer>
</main>
</div>
</Shell>
</>;
}