feat(docs): doc-sections system, crdt package, MCP server, and responsive fixes
Adds a hand-authored .vue doc-sections system (intro + guide pages per package, #docs/sections map, sidebar Guide group, client-side TOC), registers @robonen/crdt, renders demos client-only, base64-encodes the server-metadata virtual, plus the MCP docs endpoint and responsive/overflow fixes across pages and tables.
This commit is contained in:
@@ -2,6 +2,7 @@ import metadata from '#docs/metadata';
|
||||
import type {
|
||||
CategoryMeta,
|
||||
ComponentMeta,
|
||||
DocSection,
|
||||
DocsMetadata,
|
||||
GuideSection,
|
||||
ItemMeta,
|
||||
@@ -13,7 +14,8 @@ import type {
|
||||
export type DocEntry
|
||||
= | { kind: 'api'; pkg: PackageMeta; category: CategoryMeta; item: ItemMeta }
|
||||
| { kind: 'components'; pkg: PackageMeta; component: ComponentMeta }
|
||||
| { kind: 'guide'; pkg: PackageMeta; section: GuideSection };
|
||||
| { kind: 'guide'; pkg: PackageMeta; section: GuideSection }
|
||||
| { kind: 'doc'; pkg: PackageMeta; section: DocSection };
|
||||
|
||||
export interface SearchResult {
|
||||
pkg: PackageMeta;
|
||||
@@ -62,11 +64,25 @@ export function useDocs() {
|
||||
return pkg.sections.length;
|
||||
}
|
||||
|
||||
/** The hand-authored intro section for a package, if any. */
|
||||
function getIntro(pkg: PackageMeta): DocSection | undefined {
|
||||
return pkg.docs.find(s => s.isIntro);
|
||||
}
|
||||
|
||||
/** Non-intro doc sections (the "Guide" list shown in the sidebar). */
|
||||
function getDocSections(pkg: PackageMeta): DocSection[] {
|
||||
return pkg.docs.filter(s => !s.isIntro);
|
||||
}
|
||||
|
||||
/** Resolve any `/:package/:slug` route to a normalised entry. */
|
||||
function resolveEntry(packageSlug: string, slug: string): DocEntry | undefined {
|
||||
const pkg = getPackage(packageSlug);
|
||||
if (!pkg) return undefined;
|
||||
|
||||
// Hand-authored doc sections take precedence over auto-generated leaves.
|
||||
const docSection = pkg.docs.find(s => !s.isIntro && s.slug === slug);
|
||||
if (docSection) return { kind: 'doc', pkg, section: docSection };
|
||||
|
||||
if (pkg.kind === 'api') {
|
||||
for (const category of pkg.categories) {
|
||||
const item = category.items.find(i => i.slug === slug);
|
||||
@@ -139,6 +155,8 @@ export function useDocs() {
|
||||
countEntries,
|
||||
resolveEntry,
|
||||
firstEntrySlug,
|
||||
getIntro,
|
||||
getDocSections,
|
||||
search,
|
||||
getTotalItems,
|
||||
};
|
||||
|
||||
@@ -9,10 +9,10 @@ export interface Heading {
|
||||
export function slugHeading(text: string): string {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.replace(/`/g, '')
|
||||
.replace(/[^\w\s-]/g, '')
|
||||
.replaceAll('`', '')
|
||||
.replaceAll(/[^\w\s-]/g, '')
|
||||
.trim()
|
||||
.replace(/\s+/g, '-');
|
||||
.replaceAll(/\s+/g, '-');
|
||||
}
|
||||
|
||||
/** Collect h2/h3 headings for the table of contents. */
|
||||
@@ -28,11 +28,13 @@ export function extractHeadings(markdown: string): Heading[] {
|
||||
}
|
||||
if (inFence) continue;
|
||||
|
||||
const m = line.match(/^(#{2,3})\s+(.+?)\s*#*$/);
|
||||
const m = line.match(/^(#{2,3})\s+(\S.*)$/);
|
||||
if (!m) continue;
|
||||
|
||||
const depth = m[1]!.length;
|
||||
const text = m[2]!.replace(/`/g, '').trim();
|
||||
// Strip an optional ATX closing run (a single space then trailing `#`s) without
|
||||
// a backtracking-prone pattern, then drop inline code ticks.
|
||||
const text = m[2]!.replace(/ #+ *$/, '').replaceAll('`', '').trim();
|
||||
let id = slugHeading(text);
|
||||
const count = seen.get(id) ?? 0;
|
||||
seen.set(id, count + 1);
|
||||
@@ -51,7 +53,7 @@ export function renderMarkdown(markdown: string): string {
|
||||
const renderer = new marked.Renderer();
|
||||
renderer.heading = function ({ tokens, depth }) {
|
||||
const inner = this.parser.parseInline(tokens);
|
||||
const plain = inner.replace(/<[^>]+>/g, '');
|
||||
const plain = inner.replaceAll(/<[^>]+>/g, '');
|
||||
let id = slugHeading(plain);
|
||||
const count = seen.get(id) ?? 0;
|
||||
seen.set(id, count + 1);
|
||||
|
||||
Reference in New Issue
Block a user