Refine Tabularium into a readable light documentation workspace
Verify and deploy Tabularium / verify-and-deploy (push) Successful in 4m26s
Verify and deploy Tabularium / verify-and-deploy (push) Successful in 4m26s
This commit is contained in:
@@ -4,22 +4,24 @@ import path from "node:path";
|
||||
const directory = path.resolve(process.cwd(), "../../content/docs");
|
||||
const validSlug = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
||||
|
||||
export type DocLink = { slug: string; title: string };
|
||||
export type DocLink = { slug: string; title: string; searchText: string };
|
||||
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 };
|
||||
return { slug, title, content, searchText: content };
|
||||
}
|
||||
|
||||
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 } = await readDoc(file.slice(0, -3));
|
||||
return { slug, title };
|
||||
}));
|
||||
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 };
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
export type Section = {
|
||||
group: string;
|
||||
description: string;
|
||||
label: string;
|
||||
symbol: string;
|
||||
};
|
||||
export function sectionFor(slug: string): Section {
|
||||
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;
|
||||
const counts = new Map<string, number>();
|
||||
return content.split("\n").flatMap((line, index) => {
|
||||
if (/^\s*(```|~~~)/.test(line)) {
|
||||
fenced = !fenced;
|
||||
return [];
|
||||
}
|
||||
const match = !fenced && line.match(/^(#{2,3})\s+(.+?)\s*#*$/);
|
||||
if (!match) return [];
|
||||
const text = match[2].replace(/[`*_]/g, "");
|
||||
const base =
|
||||
text
|
||||
.toLowerCase()
|
||||
.replace(/[^\p{L}\p{N}\s-]/gu, "")
|
||||
.trim()
|
||||
.replace(/\s+/g, "-") || "section";
|
||||
const count = counts.get(base) ?? 0;
|
||||
counts.set(base, count + 1);
|
||||
return [
|
||||
{
|
||||
id: count ? `${base}-${count}` : base,
|
||||
text,
|
||||
level: match[1].length,
|
||||
line: index + 1,
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user