docs.ts

2.2 KB · raw

1import type { Database } from "bun:sqlite";2import { readFileSync } from "node:fs";3import { join } from "node:path";4import type { Hono } from "hono";5import { describeFile } from "../describe/describe.ts";6import { setting } from "../server/db.ts";7import { type Html, html } from "./html.ts";8import { cacheFor, notFound, type PageContext, page } from "./layout.ts";9import { renderParagraph, renderSiteMarkdown } from "./markdown.ts";1011const SITE_DIR = join(import.meta.dir, "../../docs/site");1213/**14 * §21.10: `/docs` is a Markdown file in Kepr's repo, rendered once at startup. `/support` is the15 * operator's, edited in the console (June, 2026-09-29): the shipped file is only its first words,16 * and the page can be switched off, which takes it out of the nav too.17 */18export function registerDocsPages(app: Hono, ctx: PageContext): void {19	const docs = readFileSync(join(SITE_DIR, "docs.md"), "utf8");20	const docsBody = sitePage(docs, true);21	app.get("/docs", (c) => {22		cacheFor(c, false);23		return c.html(page(ctx, { title: "docs", path: "/docs", ...docsBody }));24	});25	app.get("/support", (c) => {26		if (setting(ctx.kepr, "support_off")) return notFound(c, ctx);27		cacheFor(c, false);28		const body = sitePage(supportText(ctx.kepr));29		return c.html(page(ctx, { title: "support", path: "/support", ...body }));30	});31}3233/** The support page's Markdown: the operator's, or the shipped words until they write their own. */34export function supportText(db: Database): string {35	return setting(db, "support_page") ?? readFileSync(join(SITE_DIR, "support.md"), "utf8");36}3738/** A site page; with `contents`, a sidebar links each of its sections. */39function sitePage(source: string, contents = false): { description: string; body: Html } {40	const intro = describeFile("page.md", source);41	const doc = renderSiteMarkdown(source);42	const article = html`<article class="site-doc markdown">${doc.html}</article>`;43	const links = doc.sections.map(({ id, title }) => html`<a href="#${id}">${title}</a>`);44	return {45		description: intro ? renderParagraph(intro, { repo: "", dir: "", at: null }).text : "",46		body: contents47			? html`<div class="site-doc-layout">48<nav class="site-doc-nav mono" aria-label="sections">${links}</nav>49${article}50</div>`51			: article,52	};53}