45bfae6e94
Initial import of all source code, config, and README assets: the packages workspace (cli, core, server, web, docs, landing, skills), build scripts, tooling config, and CI workflows. Includes the data-layout revision made on this branch: the local data root defaults to ~/.penguin/data (PENGUIN_HOME still overrides; the installer keeps its binaries in ~/.penguin), and every Agent lives under <project>/agents/<agent>/ — path helpers, the three agent-enumeration scans, the system prompt, built-in Skills, tests and docs all follow the new layout. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ihk8iQuo3kv2aPjAYEPuR
61 lines
1.9 KiB
TypeScript
61 lines
1.9 KiB
TypeScript
/**
|
|
* Docs index: local Markdown pages imported at build time via import.meta.glob.
|
|
* File naming: content/<slug>.<lang>.md — one file per page per language; a page
|
|
* missing the active language falls back to the other one, so navigation is always
|
|
* complete in both locales. Same architecture as the landing page blog.
|
|
*/
|
|
import { parseFrontmatter } from "./frontmatter";
|
|
import type { Locale } from "../state/locale";
|
|
|
|
export interface DocPage {
|
|
slug: string;
|
|
lang: Locale;
|
|
title: string;
|
|
/** One-line summary rendered under the title (optional). */
|
|
description: string;
|
|
body: string;
|
|
}
|
|
|
|
const files = import.meta.glob("../../content/*.md", {
|
|
query: "?raw",
|
|
import: "default",
|
|
eager: true,
|
|
}) as Record<string, string>;
|
|
|
|
function toDoc(path: string, raw: string): DocPage | null {
|
|
const file = path.split("/").pop() ?? "";
|
|
const match = /^(.+)\.(zh|en)\.md$/.exec(file);
|
|
if (!match) return null;
|
|
const { meta, body } = parseFrontmatter(raw);
|
|
return {
|
|
slug: match[1]!,
|
|
lang: match[2] as Locale,
|
|
title: meta.title ?? match[1]!,
|
|
description: meta.description ?? "",
|
|
body,
|
|
};
|
|
}
|
|
|
|
const ALL: DocPage[] = Object.entries(files)
|
|
.map(([path, raw]) => toDoc(path, raw))
|
|
.filter((doc): doc is DocPage => doc !== null);
|
|
|
|
/** The locale's version of a page (fallback to the other language). */
|
|
export function getDoc(slug: string, locale: Locale): DocPage | undefined {
|
|
const candidates = ALL.filter((doc) => doc.slug === slug);
|
|
return candidates.find((doc) => doc.lang === locale) ?? candidates[0];
|
|
}
|
|
|
|
/** Localized page title for sidebar / pagination labels. */
|
|
export function docTitle(slug: string, locale: Locale): string {
|
|
return getDoc(slug, locale)?.title ?? slug;
|
|
}
|
|
|
|
/**
|
|
* The page as plain Markdown (title heading + body) — what the per-page
|
|
* "Copy Markdown" button puts on the clipboard.
|
|
*/
|
|
export function docMarkdown(doc: DocPage): string {
|
|
return `# ${doc.title}\n\n${doc.body}\n`;
|
|
}
|