62 lines
4.1 KiB
TypeScript
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>
|
|
</>;
|
|
}
|