// c51 (docs-layout) — UI: GitBook-style chrome around the existing // renderPage. Wraps content with a right "on this page" anchor rail // (h2/h3 from the rendered body), an edit-on-GitHub link at the top // of content, and a prev/next navigator at the bottom. Per SAMA: // imports c31 (data), c32 (logic), and c51_render_layout (chrome). // No I/O of its own. import { marked } from "marked"; import { resolveDocsLocation, type ResolvedDocsLocation, } from "./a31_docs_nav.ts"; import { extractAnchors, type Anchor } from "./b32_anchor_extract.ts"; import { renderPage, escape, type PageOptions, } from "./b51_render_layout.ts"; export interface DocsPageOptions extends Omit { // The route path the user is on, e.g. "/sama/discipline/sorted". Used to // compute prev/next. pathForDocs: string; // Optional override of which file the "edit on GitHub" link // targets, when the body isn't a content/
/.md. // Defaults to the editPath from the resolved nav location. editPathOverride?: string | null; } const renderAnchorRail = (anchors: Anchor[]): string => { if (anchors.length === 0) return ""; const items = anchors .map((a) => { const cls = a.level === 3 ? "docs-rail-link docs-rail-link-h3" : "docs-rail-link"; return `
  • ${escape(a.text)}
  • `; }) .join(""); return ``; }; // Derive (section, slug) from a content/
    /.md editPath. // Returns null when the path doesn't follow the convention (in which // case there's no editor route to link to). const sectionSlugFromEditPath = (editPath: string): { section: string; slug: string } | null => { const m = /^content\/([a-z]+)\/([a-z0-9][a-z0-9-]*)\.md$/.exec(editPath); return m ? { section: m[1]!, slug: m[2]! } : null; }; const renderEditLink = (editPath: string | null): string => { if (!editPath) return ""; // Source view is served from tdd.md itself (c21_handlers_source); // we no longer depend on the git.tdd.md (Forgejo) subdomain for // the docs site's "view source" link. const ss = sectionSlugFromEditPath(editPath); const sourceHref = ss ? `/content/${ss.section}/${ss.slug}.md` : `/${editPath}`; const editHref = ss ? `/edit/${ss.section}/${ss.slug}` : null; const editAnchor = editHref ? `propose an edit → · ` : ""; return `

    ${editAnchor}view source →

    `; }; const renderPrevNext = (loc: ResolvedDocsLocation | null): string => { if (!loc) return ""; const prev = loc.prev ? `${escape(loc.prev.label)}` : ``; const next = loc.next ? `${escape(loc.next.label)}` : ``; return ``; }; // Wrap a heading element with an anchor link for hover-click access. // Also injects an `id` if marked didn't (rare with our config but // possible). Operates on the rendered HTML before composing the page. const enrichHeadings = (html: string): string => html.replace( /]*)?>([\s\S]*?)<\/h\1>/g, (_full, level, attrs, inner) => { const idMatch = /\bid="([^"]+)"/.exec(attrs ?? ""); const id = idMatch?.[1] ?? inner.replace(/<[^>]*>/g, "").toLowerCase().replace(/[^a-z0-9\s-]/g, "").trim().replace(/\s+/g, "-"); const finalAttrs = idMatch ? attrs : `${attrs ?? ""} id="${id}"`; return `#${inner}`; }, ); export const renderDocsPage = async (opts: DocsPageOptions): Promise => { const rawHtml = opts.bodyMarkdown ? await marked.parse(opts.bodyMarkdown, { gfm: true, breaks: false }) : ""; const enriched = enrichHeadings(rawHtml); const anchors = extractAnchors(enriched); const loc = resolveDocsLocation(opts.pathForDocs); const editPath = opts.editPathOverride !== undefined ? opts.editPathOverride : loc?.current.editPath ?? null; const rail = renderAnchorRail(anchors); const editLink = renderEditLink(editPath); const prevNext = renderPrevNext(loc); const composed = `
    ${editLink} ${enriched} ${prevNext}
    ${rail}
    `; return renderPage({ title: opts.title, bodyHtml: composed, description: opts.description, ogPath: opts.ogPath, active: opts.active, noindex: opts.noindex, jsonLd: opts.jsonLd, bodyClass: "docs-body", }); };