Expand document collections with Pretext tutorial and simplify library navigation
Verify and deploy Tabularium / verify-and-deploy (push) Successful in 5m47s

This commit is contained in:
2026-10-28 13:42:51 +08:00
parent 1f3ab5a7ee
commit 2b3b3a1ae4
28 changed files with 1174 additions and 761 deletions
+30 -13
View File
@@ -1,27 +1,44 @@
import fs from "node:fs/promises";
import path from "node:path";
import catalog from "../../../../content/catalog.json";
const directory = path.resolve(process.cwd(), "../../content/docs");
const validSlug = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
export type DocLink = { slug: string; title: string; searchText: string };
const validSlug = /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*)*$/;
type Metadata = { group?: string; description?: string; draft?: boolean; featured?: boolean };
export type DocLink = {
slug: string; title: string; searchText?: string;
group: string; collection: string; description: string; draft: boolean; featured: boolean;
};
export type Doc = DocLink & { content: string };
export async function readDoc(slug: string): Promise<Doc> {
if (!validSlug.test(slug)) throw new Error("Invalid document slug");
const content = await fs.readFile(path.join(directory, `${slug}.md`), "utf8");
const title = content.match(/^# (.+)$/m)?.[1]?.trim() ?? slug;
return { slug, title, content, searchText: content };
const collection = catalog.collections.find((c) => !c.path || slug.startsWith(`${c.path}/`))!;
const metadata: Metadata = (catalog.documents as Record<string, Metadata>)[slug] ?? {};
return { slug, title, content, searchText: content,
group: metadata.group ?? collection.title, collection: collection.title,
description: metadata.description ?? collection.description,
draft: metadata.draft ?? false, featured: metadata.featured ?? false,
};
}
export async function listDocs(): Promise<DocLink[]> {
const files = (await fs.readdir(directory))
.filter((file) => file.endsWith(".md") && validSlug.test(file.slice(0, -3)))
.sort();
return Promise.all(
files.map(async (file) => {
const { slug, title, searchText } = await readDoc(file.slice(0, -3));
return { slug, title, searchText };
}),
);
async function walk(folder: string): Promise<string[]> {
const entries = await fs.readdir(path.join(directory, folder), { withFileTypes: true });
const files = await Promise.all(entries.map(async (entry) => {
const relative = folder ? `${folder}/${entry.name}` : entry.name;
if (entry.isDirectory()) return walk(relative);
if (entry.isFile() && relative.endsWith(".md") && validSlug.test(relative.slice(0, -3)))
return [relative.slice(0, -3)];
return [];
}));
return files.flat();
}
return Promise.all((await walk("")).sort().map(async (slug) => {
const { content: _, searchText: __, ...doc } = await readDoc(slug);
void _; void __;
return doc;
}));
}
-42
View File
@@ -1,45 +1,3 @@
export type Section = {
group: string;
description: string;
label: string;
symbol: string;
};
export function sectionFor(slug: string): Section {
if (slug.includes("endor-purpose"))
return {
group: "项目讨论",
label: "讨论草案",
symbol: "○",
description: "逐步讨论 Endor 的意义:从程序入口走向用户与 AI 的对象操作。",
};
if (slug.includes("endor-concepts"))
return {
group: "架构设计",
label: "系统架构",
symbol: "◇",
description: "从全球入口、区域运行时到对象数据,了解 Endor 的系统架构。",
};
if (slug.includes("mounting"))
return {
group: "架构设计",
label: "讨论草案",
symbol: "◇",
description: "探索节点、对象与统一接入协议,记录仍在演进的设计。",
};
if (slug.includes("figures"))
return {
group: "图形档案",
label: "工具与工作流",
symbol: "⌘",
description: "从 Python 源码到可编辑 SVG,让图形与文档一起演进。",
};
return {
group: "开始使用",
label: "指南",
symbol: "↗",
description: "搭建本地工作台,添加第一篇文档,了解检查与发布流程。",
};
}
export type Heading = { id: string; text: string; level: number; line: number };
export function headingsFor(content: string): Heading[] {
let fenced = false;