ttrpg-tools/src/components/doc-data.ts

79 lines
2.0 KiB
TypeScript
Raw Normal View History

import yaml from "js-yaml";
export interface DocEntry {
tag: string;
icon: string;
title: string;
description: string;
syntax: string;
props: { name: string; type: string; default?: string; desc: string }[];
/** Markdown body (everything after frontmatter ---) */
body: string;
}
/** Splits frontmatter and markdown body from a raw .md string. */
function parseFrontmatter(raw: string): Record<string, unknown> | null {
const match = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
if (!match) return null;
try {
return yaml.load(match[1]) as Record<string, unknown>;
} catch {
return null;
}
}
function getBody(raw: string): string {
const match = raw.match(/^---\n[\s\S]*?\n---\n?([\s\S]*)$/);
return match ? match[1] : raw;
}
/** Glob import all .md files in src/doc-entries/ */
const modules = import.meta.webpackContext("./", {
recursive: false,
regExp: /\.md$/,
});
/** Lazy-loaded doc entries, parsed on first access. */
let _entries: DocEntry[] | null = null;
export function loadDocEntries(): DocEntry[] {
if (_entries) return _entries;
const entries: DocEntry[] = [];
for (const key of modules.keys()) {
const mod = modules(key);
const raw =
typeof mod === "string"
? mod
: ((mod as { default?: string }).default ?? "");
if (!raw) continue;
const fm = parseFrontmatter(raw);
if (!fm) continue;
const body = getBody(raw);
entries.push({
tag: fm.tag as string,
icon: fm.icon as string,
title: fm.title as string,
description: fm.description as string,
syntax: fm.syntax as string,
props: (fm.props as DocEntry["props"]) ?? [],
body,
});
}
// Sort alphabetically by tag
entries.sort((a, b) => a.tag.localeCompare(b.tag));
_entries = entries;
return entries;
}
/**
* Direct static array for the DocDialog sidebar uses webpackContext
* so all .md files in doc-entries/ are bundled automatically.
*/
export const docEntries = loadDocEntries();