HASH 27daccaea403
DATE 2026-07-18
SUBJECT web: one kb, registers by dir.yml - notes/docs/reference decided by the
tree, not the url
FILES 27 CHANGED HASH 27daccaea403
DATE 2026-07-18
SUBJECT web: one kb, registers by dir.yml -
notes/docs/reference decided by the
tree, not the url
FILES 27 CHANGED
┌─ ooknet-design/src/components/ArticleFooter/ArticleFooter.ts ──────────────┐
│ diff --git a/ooknet-design/src/components/ArticleFooter/ArticleFooter.ts b/ooknet- │
│ design/src/components/ArticleFooter/ArticleFooter.ts │
│ index d2d98f2..69a9d39 100644 │
│ --- a/ooknet-design/src/components/ArticleFooter/ArticleFooter.ts │
│ +++ b/ooknet-design/src/components/ArticleFooter/ArticleFooter.ts │
│ @@ -1,9 +1,9 @@ │
│ -import { rule, row2 } from "../../lib/ascii"; │
│ +import { rule } from "../../lib/ascii"; │
│ │
│ export interface Props { │
│ width?: number; │
│ } │
│ │
│ export function buildFooter(width: number): string { │
│ - return [rule(width), row2("OOKNET", "Knowledge as Infrastructure", width)].join │
│ ("\n"); │
│ + return [rule(width), "OOKNET"].join("\n"); │
│ } │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...rticleFooter/ArticleFooter.ts ───┐
│ diff --git a/ooknet-design/src/components/Ar │
│ ticleFooter/ArticleFooter.ts b/ooknet-design │
│ /src/components/ArticleFooter/ArticleFooter. │
│ ts │
│ index d2d98f2..69a9d39 100644 │
│ --- a/ooknet-design/src/components/ArticleFo │
│ oter/ArticleFooter.ts │
│ +++ b/ooknet-design/src/components/ArticleFo │
│ oter/ArticleFooter.ts │
│ @@ -1,9 +1,9 @@ │
│ -import { rule, row2 } from "../../lib/ascii │
│ "; │
│ +import { rule } from "../../lib/ascii"; │
│ │
│ export interface Props { │
│ width?: number; │
│ } │
│ │
│ export function buildFooter(width: number): │
│ string { │
│ - return [rule(width), row2("OOKNET", "Know │
│ ledge as Infrastructure", width)].join("\n") │
│ ; │
│ + return [rule(width), "OOKNET"].join("\n") │
│ ; │
│ } │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/components/ArticleSeeAlso/ArticleSeeAlso.astro ─────────┐
│ diff --git a/ooknet-design/src/components/ArticleSeeAlso/ArticleSeeAlso.astro b/oo │
│ knet-design/src/components/ArticleSeeAlso/ArticleSeeAlso.astro │
│ index fa8a091..0965eaf 100644 │
│ --- a/ooknet-design/src/components/ArticleSeeAlso/ArticleSeeAlso.astro │
│ +++ b/ooknet-design/src/components/ArticleSeeAlso/ArticleSeeAlso.astro │
│ @@ -1,6 +1,6 @@ │
│ --- │
│ import Frame from "../Frame/Frame.astro"; │
│ import { buildSeeAlso, type Props } from "./ArticleSeeAlso"; │
│ -const { fm } = Astro.props as Props; │
│ +const { seeAlso = [] } = Astro.props as Props; │
│ --- │
│ -{(fm.seeAlso?.length ?? 0) > 0 && <Frame build={(w) => buildSeeAlso(w, fm)} />} │
│ +{seeAlso.length > 0 && <Frame build={(w) => buildSeeAlso(w, seeAlso)} />} │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...eSeeAlso/ArticleSeeAlso.astro ───┐
│ diff --git a/ooknet-design/src/components/Ar │
│ ticleSeeAlso/ArticleSeeAlso.astro b/ooknet-d │
│ esign/src/components/ArticleSeeAlso/ArticleS │
│ eeAlso.astro │
│ index fa8a091..0965eaf 100644 │
│ --- a/ooknet-design/src/components/ArticleSe │
│ eAlso/ArticleSeeAlso.astro │
│ +++ b/ooknet-design/src/components/ArticleSe │
│ eAlso/ArticleSeeAlso.astro │
│ @@ -1,6 +1,6 @@ │
│ --- │
│ import Frame from "../Frame/Frame.astro"; │
│ import { buildSeeAlso, type Props } from ". │
│ /ArticleSeeAlso"; │
│ -const { fm } = Astro.props as Props; │
│ +const { seeAlso = [] } = Astro.props as Pro │
│ ps; │
│ --- │
│ -{(fm.seeAlso?.length ?? 0) > 0 && <Frame bu │
│ ild={(w) => buildSeeAlso(w, fm)} />} │
│ +{seeAlso.length > 0 && <Frame build={(w) => │
│ buildSeeAlso(w, seeAlso)} />} │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/components/ArticleSeeAlso/ArticleSeeAlso.ts ────────────┐
│ diff --git a/ooknet-design/src/components/ArticleSeeAlso/ArticleSeeAlso.ts b/ookne │
│ t-design/src/components/ArticleSeeAlso/ArticleSeeAlso.ts │
│ index 6aec6ea..06af4cd 100644 │
│ --- a/ooknet-design/src/components/ArticleSeeAlso/ArticleSeeAlso.ts │
│ +++ b/ooknet-design/src/components/ArticleSeeAlso/ArticleSeeAlso.ts │
│ @@ -1,13 +1,11 @@ │
│ import { kv, rule } from "../../lib/ascii"; │
│ -import type { NoteFrontmatter } from "../../lib/types"; │
│ │
│ export interface Props { │
│ width?: number; │
│ - fm: NoteFrontmatter; │
│ + seeAlso?: string[]; │
│ } │
│ │
│ -export function buildSeeAlso(width: number, fm: NoteFrontmatter): string { │
│ - const refs = fm.seeAlso ?? []; │
│ +export function buildSeeAlso(width: number, refs: string[]): string { │
│ const out: string[] = [rule(width)]; │
│ out.push(kv("SEE ALSO", refs[0], 12, width)); │
│ for (const s of refs.slice(1)) out.push(kv("", s, 12, width)); │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...icleSeeAlso/ArticleSeeAlso.ts ───┐
│ diff --git a/ooknet-design/src/components/Ar │
│ ticleSeeAlso/ArticleSeeAlso.ts b/ooknet-desi │
│ gn/src/components/ArticleSeeAlso/ArticleSeeA │
│ lso.ts │
│ index 6aec6ea..06af4cd 100644 │
│ --- a/ooknet-design/src/components/ArticleSe │
│ eAlso/ArticleSeeAlso.ts │
│ +++ b/ooknet-design/src/components/ArticleSe │
│ eAlso/ArticleSeeAlso.ts │
│ @@ -1,13 +1,11 @@ │
│ import { kv, rule } from "../../lib/ascii"; │
│ -import type { NoteFrontmatter } from "../.. │
│ /lib/types"; │
│ │
│ export interface Props { │
│ width?: number; │
│ - fm: NoteFrontmatter; │
│ + seeAlso?: string[]; │
│ } │
│ │
│ -export function buildSeeAlso(width: number, │
│ fm: NoteFrontmatter): string { │
│ - const refs = fm.seeAlso ?? []; │
│ +export function buildSeeAlso(width: number, │
│ refs: string[]): string { │
│ const out: string[] = [rule(width)]; │
│ out.push(kv("SEE ALSO", refs[0], 12, widt │
│ h)); │
│ for (const s of refs.slice(1)) out.push(k │
│ v("", s, 12, width)); │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/components/Breadcrumbs/Breadcrumbs.ts ──────────────────┐
│ diff --git a/ooknet-design/src/components/Breadcrumbs/Breadcrumbs.ts b/ooknet-desi │
│ gn/src/components/Breadcrumbs/Breadcrumbs.ts │
│ index 1f40af1..067f94a 100644 │
│ --- a/ooknet-design/src/components/Breadcrumbs/Breadcrumbs.ts │
│ +++ b/ooknet-design/src/components/Breadcrumbs/Breadcrumbs.ts │
│ @@ -6,9 +6,7 @@ import { checkGrid, len } from "../../lib/ascii"; │
│ export const CRUMB_LABEL = "[FILED UNDER]: "; │
│ │
│ const ROOTS: Record<string, string> = { │
│ - NOTES: "/notes/", │
│ KB: "/kb/", │
│ - DOCS: "/docs/", │
│ GIT: "/git/", │
│ }; │
│ │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...ts/Breadcrumbs/Breadcrumbs.ts ───┐
│ diff --git a/ooknet-design/src/components/Br │
│ eadcrumbs/Breadcrumbs.ts b/ooknet-design/src │
│ /components/Breadcrumbs/Breadcrumbs.ts │
│ index 1f40af1..067f94a 100644 │
│ --- a/ooknet-design/src/components/Breadcrum │
│ bs/Breadcrumbs.ts │
│ +++ b/ooknet-design/src/components/Breadcrum │
│ bs/Breadcrumbs.ts │
│ @@ -6,9 +6,7 @@ import { checkGrid, len } fr │
│ om "../../lib/ascii"; │
│ export const CRUMB_LABEL = "[FILED UNDER]: │
│ "; │
│ │
│ const ROOTS: Record<string, string> = { │
│ - NOTES: "/notes/", │
│ KB: "/kb/", │
│ - DOCS: "/docs/", │
│ GIT: "/git/", │
│ }; │
│ │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/components/NavBar/NavBar.test.ts ───────────────────────┐
│ diff --git a/ooknet-design/src/components/NavBar/NavBar.test.ts b/ooknet-design/sr │
│ c/components/NavBar/NavBar.test.ts │
│ index d895ccf..c2ef0fe 100644 │
│ --- a/ooknet-design/src/components/NavBar/NavBar.test.ts │
│ +++ b/ooknet-design/src/components/NavBar/NavBar.test.ts │
│ @@ -3,13 +3,14 @@ import { NAV_GAP, NAV_ITEMS, NAV_LANG_W, itemsWidth, navRows } f │
│ rom "./NavBar"; │
│ import { MOBILE_FRAME_W, FRAME_W } from "../../lib/config"; │
│ │
│ describe("navRows", () => { │
│ - it("keeps all seven items on one row at desktop width", () => { │
│ + it("keeps every item on one row at desktop width", () => { │
│ const rows = navRows(NAV_ITEMS, FRAME_W, NAV_GAP, NAV_LANG_W + 1); │
│ expect(rows).toHaveLength(1); │
│ }); │
│ │
│ - it("wraps onto rows at the narrow width", () => { │
│ - const rows = navRows(NAV_ITEMS, MOBILE_FRAME_W, 1); │
│ + it("wraps when items exceed the width", () => { │
│ + const many = [...NAV_ITEMS, ...NAV_ITEMS, ...NAV_ITEMS]; │
│ + const rows = navRows(many, MOBILE_FRAME_W, 1); │
│ expect(rows.length).toBeGreaterThan(1); │
│ for (const row of rows) { │
│ expect(itemsWidth(row, 1)).toBeLessThanOrEqual(MOBILE_FRAME_W); │
│ @@ -17,7 +18,8 @@ describe("navRows", () => { │
│ }); │
│ │
│ it("preserves item order across rows", () => { │
│ - const rows = navRows(NAV_ITEMS, MOBILE_FRAME_W, 1); │
│ - expect(rows.flat().map((it) => it.label)).toEqual(NAV_ITEMS.map((it) => it.la │
│ bel)); │
│ + const many = [...NAV_ITEMS, ...NAV_ITEMS]; │
│ + const rows = navRows(many, MOBILE_FRAME_W, 1); │
│ + expect(rows.flat().map((it) => it.label)).toEqual(many.map((it) => it.label)) │
│ ; │
│ }); │
│ }); │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...ponents/NavBar/NavBar.test.ts ───┐
│ diff --git a/ooknet-design/src/components/Na │
│ vBar/NavBar.test.ts b/ooknet-design/src/comp │
│ onents/NavBar/NavBar.test.ts │
│ index d895ccf..c2ef0fe 100644 │
│ --- a/ooknet-design/src/components/NavBar/Na │
│ vBar.test.ts │
│ +++ b/ooknet-design/src/components/NavBar/Na │
│ vBar.test.ts │
│ @@ -3,13 +3,14 @@ import { NAV_GAP, NAV_ITEM │
│ S, NAV_LANG_W, itemsWidth, navRows } from ". │
│ /NavBar"; │
│ import { MOBILE_FRAME_W, FRAME_W } from ".. │
│ /../lib/config"; │
│ │
│ describe("navRows", () => { │
│ - it("keeps all seven items on one row at d │
│ esktop width", () => { │
│ + it("keeps every item on one row at deskto │
│ p width", () => { │
│ const rows = navRows(NAV_ITEMS, FRAME_W │
│ , NAV_GAP, NAV_LANG_W + 1); │
│ expect(rows).toHaveLength(1); │
│ }); │
│ │
│ - it("wraps onto rows at the narrow width", │
│ () => { │
│ - const rows = navRows(NAV_ITEMS, MOBILE_ │
│ FRAME_W, 1); │
│ + it("wraps when items exceed the width", ( │
│ ) => { │
│ + const many = [...NAV_ITEMS, ...NAV_ITEM │
│ S, ...NAV_ITEMS]; │
│ + const rows = navRows(many, MOBILE_FRAME │
│ _W, 1); │
│ expect(rows.length).toBeGreaterThan(1); │
│ for (const row of rows) { │
│ expect(itemsWidth(row, 1)).toBeLessTh │
│ anOrEqual(MOBILE_FRAME_W); │
│ @@ -17,7 +18,8 @@ describe("navRows", () => │
│ { │
│ }); │
│ │
│ it("preserves item order across rows", () │
│ => { │
│ - const rows = navRows(NAV_ITEMS, MOBILE_ │
│ FRAME_W, 1); │
│ - expect(rows.flat().map((it) => it.label │
│ )).toEqual(NAV_ITEMS.map((it) => it.label)); │
│ + const many = [...NAV_ITEMS, ...NAV_ITEM │
│ S]; │
│ + const rows = navRows(many, MOBILE_FRAME │
│ _W, 1); │
│ + expect(rows.flat().map((it) => it.label │
│ )).toEqual(many.map((it) => it.label)); │
│ }); │
│ }); │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/components/NavBar/NavBar.ts ────────────────────────────┐
│ diff --git a/ooknet-design/src/components/NavBar/NavBar.ts b/ooknet-design/src/com │
│ ponents/NavBar/NavBar.ts │
│ index e0c92fa..d37fe25 100644 │
│ --- a/ooknet-design/src/components/NavBar/NavBar.ts │
│ +++ b/ooknet-design/src/components/NavBar/NavBar.ts │
│ @@ -18,16 +18,13 @@ export interface Props { │
│ } │
│ │
│ export const NAV_ITEMS: NavItem[] = [ │
│ - { label: "NOTES", href: "/notes/" }, │
│ - { label: "KB", href: "/kb/" }, │
│ - { label: "DOCS", href: "/docs/" }, │
│ - { label: "GIT", href: "/git/" }, │
│ - { label: "PROJECTS", href: "/projects/" }, │
│ - { label: "RESOURCES", href: "/resources/" }, │
│ - { label: "ABOUT", href: "/about/" }, │
│ + { label: "KB", href: "/kb/" }, │
│ + { label: "GIT", href: "/git/" }, │
│ + { label: "PROJECTS", href: "/projects/" }, │
│ + { label: "ABOUT", href: "/about/" }, │
│ ]; │
│ │
│ -export const NAV_GAP = 3; // spaces between buttons - seven items fit one 8 │
│ 6-cell row │
│ +export const NAV_GAP = 4; // spaces between buttons │
│ export const NAV_LANG_W = 2; // visible width of "EN" │
│ │
│ /** Total width of all buttons + inter-button gaps. */ │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...c/components/NavBar/NavBar.ts ───┐
│ diff --git a/ooknet-design/src/components/Na │
│ vBar/NavBar.ts b/ooknet-design/src/component │
│ s/NavBar/NavBar.ts │
│ index e0c92fa..d37fe25 100644 │
│ --- a/ooknet-design/src/components/NavBar/Na │
│ vBar.ts │
│ +++ b/ooknet-design/src/components/NavBar/Na │
│ vBar.ts │
│ @@ -18,16 +18,13 @@ export interface Props { │
│ } │
│ │
│ export const NAV_ITEMS: NavItem[] = [ │
│ - { label: "NOTES", href: "/notes/" }, │
│ - { label: "KB", href: "/kb/" }, │
│ - { label: "DOCS", href: "/docs/" }, │
│ - { label: "GIT", href: "/git/" }, │
│ - { label: "PROJECTS", href: "/projects/" │
│ }, │
│ - { label: "RESOURCES", href: "/resources/" │
│ }, │
│ - { label: "ABOUT", href: "/about/" }, │
│ + { label: "KB", href: "/kb/" }, │
│ + { label: "GIT", href: "/git/" }, │
│ + { label: "PROJECTS", href: "/projects/" } │
│ , │
│ + { label: "ABOUT", href: "/about/" }, │
│ ]; │
│ │
│ -export const NAV_GAP = 3; // spaces b │
│ etween buttons - seven items fit one 86-cell │
│ row │
│ +export const NAV_GAP = 4; // spaces b │
│ etween buttons │
│ export const NAV_LANG_W = 2; // visible │
│ width of "EN" │
│ │
│ /** Total width of all buttons + inter-butt │
│ on gaps. */ │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/content.config.ts ──────────────────────────────────────┐
│ diff --git a/ooknet-design/src/content.config.ts b/ooknet-design/src/content.confi │
│ g.ts │
│ index d6e139b..1379305 100644 │
│ --- a/ooknet-design/src/content.config.ts │
│ +++ b/ooknet-design/src/content.config.ts │
│ @@ -1,46 +1,27 @@ │
│ import { defineCollection, z } from "astro:content"; │
│ import { glob } from "astro/loaders"; │
│ │
│ -// The directory is the taxonomy for every collection: notes split by │
│ -// subject, kb by topic, docs by project. Frontmatter never repeats │
│ -// what the path already says. │
│ - │
│ -// Personal writing and study notes. Only title and published are │
│ -// required so a bare draft can't crash the content sync. │
│ -const notes = defineCollection({ │
│ - loader: glob({ pattern: "**/*.md", base: "./src/content/notes" }), │
│ - schema: z.object({ │
│ - title: z.string(), │
│ - published: z.string(), // ISO date │
│ - status: z.string().default("DRAFT"), │
│ - tags: z.array(z.string()).default([]), │
│ - seeAlso: z.array(z.string()).optional(), │
│ - draft: z.boolean().default(false), │
│ - }), │
│ -}); │
│ - │
│ -// Copies of vendor documentation and guides, always credited. │
│ +// One collection: the knowledge base. What a page IS - personal note, │
│ +// project documentation, vendor reference - is declared by the nearest │
│ +// dir.yml above it (see lib/registers.ts), not by where it lives in │
│ +// the url. The schema stays permissive: only title is required, and │
│ +// register-specific fields (published for notes, source/url/retrieved │
│ +// for reference, order for docs) apply where the register wants them. │
│ const kb = defineCollection({ │
│ loader: glob({ pattern: "**/*.md", base: "./src/content/kb" }), │
│ schema: z.object({ │
│ title: z.string(), │
│ - source: z.string(), // vendor, e.g. "MICROSOFT LEARN" │
│ - url: z.string().optional(), │
│ - retrieved: z.string().optional(), // ISO date the copy was taken │
│ + published: z.string().optional(), // ISO date - notes │
│ + status: z.string().optional(), // notes │
│ tags: z.array(z.string()).default([]), │
│ + seeAlso: z.array(z.string()).optional(), │
│ + description: z.string().optional(), // docs │
│ + order: z.number().optional(), // docs │
│ + source: z.string().optional(), // reference: vendor name │
│ + url: z.string().optional(), // reference │
│ + retrieved: z.string().optional(), // reference: ISO date │
│ draft: z.boolean().default(false), │
│ }), │
│ }); │
│ │
│ -// Project documentation. `order` sorts pages within a directory. │
│ -const docs = defineCollection({ │
│ - loader: glob({ pattern: "**/*.md", base: "./src/content/docs" }), │
│ - schema: z.object({ │
│ - title: z.string(), │
│ - description: z.string().optional(), │
│ - order: z.number().optional(), │
│ - draft: z.boolean().default(false), │
│ - }), │
│ -}); │
│ - │
│ -export const collections = { notes, kb, docs }; │
│ +export const collections = { kb }; │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...-design/src/content.config.ts ───┐
│ diff --git a/ooknet-design/src/content.confi │
│ g.ts b/ooknet-design/src/content.config.ts │
│ index d6e139b..1379305 100644 │
│ --- a/ooknet-design/src/content.config.ts │
│ +++ b/ooknet-design/src/content.config.ts │
│ @@ -1,46 +1,27 @@ │
│ import { defineCollection, z } from "astro: │
│ content"; │
│ import { glob } from "astro/loaders"; │
│ │
│ -// The directory is the taxonomy for every │
│ collection: notes split by │
│ -// subject, kb by topic, docs by project. F │
│ rontmatter never repeats │
│ -// what the path already says. │
│ - │
│ -// Personal writing and study notes. Only t │
│ itle and published are │
│ -// required so a bare draft can't crash the │
│ content sync. │
│ -const notes = defineCollection({ │
│ - loader: glob({ pattern: "**/*.md", base: │
│ "./src/content/notes" }), │
│ - schema: z.object({ │
│ - title: z.string(), │
│ - published: z.string(), │
│ // ISO date │
│ - status: z.string().default("DRAFT"), │
│ - tags: z.array(z.string()).default([]), │
│ - seeAlso: z.array(z.string()).optional() │
│ , │
│ - draft: z.boolean().default(false), │
│ - }), │
│ -}); │
│ - │
│ -// Copies of vendor documentation and guide │
│ s, always credited. │
│ +// One collection: the knowledge base. What │
│ a page IS - personal note, │
│ +// project documentation, vendor reference │
│ - is declared by the nearest │
│ +// dir.yml above it (see lib/registers.ts), │
│ not by where it lives in │
│ +// the url. The schema stays permissive: on │
│ ly title is required, and │
│ +// register-specific fields (published for │
│ notes, source/url/retrieved │
│ +// for reference, order for docs) apply whe │
│ re the register wants them. │
│ const kb = defineCollection({ │
│ loader: glob({ pattern: "**/*.md", base: │
│ "./src/content/kb" }), │
│ schema: z.object({ │
│ title: z.string(), │
│ - source: z.string(), │
│ // vendor, e.g. "MICROSOFT LEARN" │
│ - url: z.string().optional(), │
│ - retrieved: z.string().optional(), │
│ // ISO date the copy was taken │
│ + published: z.string().optional(), // │
│ ISO date - notes │
│ + status: z.string().optional(), // │
│ notes │
│ tags: z.array(z.string()).default([]), │
│ + seeAlso: z.array(z.string()).optional() │
│ , │
│ + description: z.string().optional(), // │
│ docs │
│ + order: z.number().optional(), // │
│ docs │
│ + source: z.string().optional(), // │
│ reference: vendor name │
│ + url: z.string().optional(), // │
│ reference │
│ + retrieved: z.string().optional(), // │
│ reference: ISO date │
│ draft: z.boolean().default(false), │
│ }), │
│ }); │
│ │
│ -// Project documentation. `order` sorts pag │
│ es within a directory. │
│ -const docs = defineCollection({ │
│ - loader: glob({ pattern: "**/*.md", base: │
│ "./src/content/docs" }), │
│ - schema: z.object({ │
│ - title: z.string(), │
│ - description: z.string().optional(), │
│ - order: z.number().optional(), │
│ - draft: z.boolean().default(false), │
│ - }), │
│ -}); │
│ - │
│ -export const collections = { notes, kb, doc │
│ s }; │
│ +export const collections = { kb }; │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/content/kb/windows/dir.yml ─────────────────────────────┐
│ diff --git a/ooknet-design/src/content/kb/windows/dir.yml b/ooknet-design/src/cont │
│ ent/kb/windows/dir.yml │
│ new file mode 100644 │
│ index 0000000..9a803d7 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/content/kb/windows/dir.yml │
│ @@ -0,0 +1 @@ │
│ +register: reference │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...rc/content/kb/windows/dir.yml ───┐
│ diff --git a/ooknet-design/src/content/kb/wi │
│ ndows/dir.yml b/ooknet-design/src/content/kb │
│ /windows/dir.yml │
│ new file mode 100644 │
│ index 0000000..9a803d7 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/content/kb/windows/d │
│ ir.yml │
│ @@ -0,0 +1 @@ │
│ +register: reference │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/lib/registers.test.ts ──────────────────────────────────┐
│ diff --git a/ooknet-design/src/lib/registers.test.ts b/ooknet-design/src/lib/regis │
│ ters.test.ts │
│ new file mode 100644 │
│ index 0000000..2ee02fe │
│ --- /dev/null │
│ +++ b/ooknet-design/src/lib/registers.test.ts │
│ @@ -0,0 +1,40 @@ │
│ +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; │
│ +import { join } from "node:path"; │
│ +import { tmpdir } from "node:os"; │
│ +import { afterAll, beforeAll, describe, expect, it } from "vitest"; │
│ +import { readManifests, registerOf } from "./registers"; │
│ + │
│ +let dir: string; │
│ +beforeAll(() => { │
│ + dir = mkdtempSync(join(tmpdir(), "reg-")); │
│ + mkdirSync(join(dir, "wowsim-stats/api"), { recursive: true }); │
│ + mkdirSync(join(dir, "windows"), { recursive: true }); │
│ + mkdirSync(join(dir, "nix"), { recursive: true }); │
│ + writeFileSync(join(dir, "wowsim-stats/dir.yml"), 'register: docs\ntitle: "wowsi │
│ m-stats"\n'); │
│ + writeFileSync(join(dir, "windows/dir.yml"), "register: reference\n"); │
│ +}); │
│ +afterAll(() => rmSync(dir, { recursive: true, force: true })); │
│ + │
│ +describe("registers", () => { │
│ + it("resolves the nearest ancestor manifest", () => { │
│ + const m = readManifests(dir); │
│ + expect(registerOf("wowsim-stats/api/simulations", m)).toEqual({ │
│ + register: "docs", │
│ + root: "wowsim-stats", │
│ + title: "wowsim-stats", │
│ + }); │
│ + expect(registerOf("windows/tss", m).register).toBe("reference"); │
│ + }); │
│ + │
│ + it("defaults unmarked subtrees to notes", () => { │
│ + const m = readManifests(dir); │
│ + expect(registerOf("nix/flakes", m)).toEqual({ register: "notes", root: null, │
│ title: null }); │
│ + expect(registerOf("orphan", m).register).toBe("notes"); │
│ + }); │
│ + │
│ + it("rejects unknown registers", () => { │
│ + writeFileSync(join(dir, "nix/dir.yml"), "register: blog\n"); │
│ + expect(() => readManifests(dir)).toThrow(/register must be/); │
│ + rmSync(join(dir, "nix/dir.yml")); │
│ + }); │
│ +}); │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...ign/src/lib/registers.test.ts ───┐
│ diff --git a/ooknet-design/src/lib/registers │
│ .test.ts b/ooknet-design/src/lib/registers.t │
│ est.ts │
│ new file mode 100644 │
│ index 0000000..2ee02fe │
│ --- /dev/null │
│ +++ b/ooknet-design/src/lib/registers.test.t │
│ s │
│ @@ -0,0 +1,40 @@ │
│ +import { mkdtempSync, mkdirSync, rmSync, wr │
│ iteFileSync } from "node:fs"; │
│ +import { join } from "node:path"; │
│ +import { tmpdir } from "node:os"; │
│ +import { afterAll, beforeAll, describe, exp │
│ ect, it } from "vitest"; │
│ +import { readManifests, registerOf } from " │
│ ./registers"; │
│ + │
│ +let dir: string; │
│ +beforeAll(() => { │
│ + dir = mkdtempSync(join(tmpdir(), "reg-")) │
│ ; │
│ + mkdirSync(join(dir, "wowsim-stats/api"), │
│ { recursive: true }); │
│ + mkdirSync(join(dir, "windows"), { recursi │
│ ve: true }); │
│ + mkdirSync(join(dir, "nix"), { recursive: │
│ true }); │
│ + writeFileSync(join(dir, "wowsim-stats/dir │
│ .yml"), 'register: docs\ntitle: "wowsim-stat │
│ s"\n'); │
│ + writeFileSync(join(dir, "windows/dir.yml" │
│ ), "register: reference\n"); │
│ +}); │
│ +afterAll(() => rmSync(dir, { recursive: tru │
│ e, force: true })); │
│ + │
│ +describe("registers", () => { │
│ + it("resolves the nearest ancestor manifes │
│ t", () => { │
│ + const m = readManifests(dir); │
│ + expect(registerOf("wowsim-stats/api/sim │
│ ulations", m)).toEqual({ │
│ + register: "docs", │
│ + root: "wowsim-stats", │
│ + title: "wowsim-stats", │
│ + }); │
│ + expect(registerOf("windows/tss", m).reg │
│ ister).toBe("reference"); │
│ + }); │
│ + │
│ + it("defaults unmarked subtrees to notes", │
│ () => { │
│ + const m = readManifests(dir); │
│ + expect(registerOf("nix/flakes", m)).toE │
│ qual({ register: "notes", root: null, title: │
│ null }); │
│ + expect(registerOf("orphan", m).register │
│ ).toBe("notes"); │
│ + }); │
│ + │
│ + it("rejects unknown registers", () => { │
│ + writeFileSync(join(dir, "nix/dir.yml"), │
│ "register: blog\n"); │
│ + expect(() => readManifests(dir)).toThro │
│ w(/register must be/); │
│ + rmSync(join(dir, "nix/dir.yml")); │
│ + }); │
│ +}); │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/lib/registers.ts ───────────────────────────────────────┐
│ diff --git a/ooknet-design/src/lib/registers.ts b/ooknet-design/src/lib/registers. │
│ ts │
│ new file mode 100644 │
│ index 0000000..0e277a5 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/lib/registers.ts │
│ @@ -0,0 +1,72 @@ │
│ +// Directory registers: a dir.yml at any directory inside the kb tree │
│ +// declares what its subtree is - personal notes, project docs, or │
│ +// vendor reference copies. Resolution is nearest-ancestor; unmarked │
│ +// content is notes, the natural resting state of a wiki. │
│ +import { existsSync, readFileSync, readdirSync } from "node:fs"; │
│ +import { join } from "node:path"; │
│ + │
│ +export type Register = "notes" | "docs" | "reference"; │
│ + │
│ +export const DEFAULT_REGISTER: Register = "notes"; │
│ + │
│ +export interface DirManifest { │
│ + register: Register; │
│ + /** Display name for the subtree, e.g. a project name. */ │
│ + title?: string; │
│ +} │
│ + │
│ +export interface RegisterInfo { │
│ + register: Register; │
│ + /** Manifest directory that decided the register; null when default. */ │
│ + root: string | null; │
│ + title: string | null; │
│ +} │
│ + │
│ +const REGISTERS: Register[] = ["notes", "docs", "reference"]; │
│ + │
│ +// dir.yml is two known keys - a tiny parser beats a yaml dependency │
│ +function parseManifest(text: string, at: string): DirManifest { │
│ + const field = (key: string): string | null => { │
│ + const m = text.match(new RegExp(`^${key}:\\s*["']?(.+?)["']?\\s*$`, "m")); │
│ + return m ? m[1] : null; │
│ + }; │
│ + const register = field("register") as Register | null; │
│ + if (!register || !REGISTERS.includes(register)) { │
│ + throw new Error(`[kb] ${at}: register must be one of ${REGISTERS.join(", ")}` │
│ ); │
│ + } │
│ + return { register, title: field("title") ?? undefined }; │
│ +} │
│ + │
│ +/** Every dir.yml under the kb tree, keyed by directory id ("" root). */ │
│ +export function readManifests(contentDir = "src/content/kb"): Map<string, DirMani │
│ fest> { │
│ + const out = new Map<string, DirManifest>(); │
│ + if (!existsSync(contentDir)) return out; │
│ + const walk = (dir: string, prefix: string) => { │
│ + const manifest = join(dir, "dir.yml"); │
│ + if (existsSync(manifest)) { │
│ + out.set(prefix, parseManifest(readFileSync(manifest, "utf8"), manifest)); │
│ + } │
│ + for (const entry of readdirSync(dir, { withFileTypes: true })) { │
│ + if (entry.isDirectory()) { │
│ + walk(join(dir, entry.name), prefix ? `${prefix}/${entry.name}` : entry.na │
│ me); │
│ + } │
│ + } │
│ + }; │
│ + walk(contentDir, ""); │
│ + return out; │
│ +} │
│ + │
│ +/** Register of a page id, from the nearest ancestor manifest. */ │
│ +export function registerOf(id: string, manifests: Map<string, DirManifest>): Regi │
│ sterInfo { │
│ + const segments = id.split("/").slice(0, -1); │
│ + for (let i = segments.length; i >= 0; i--) { │
│ + const dir = segments.slice(0, i).join("/"); │
│ + const m = manifests.get(dir); │
│ + if (m) return { register: m.register, root: dir, title: m.title ?? null }; │
│ + } │
│ + return { register: DEFAULT_REGISTER, root: null, title: null }; │
│ +} │
│ + │
│ +/** Display label for a register, as shown in chrome and search. */ │
│ +export const registerLabel = (r: Register): string => │
│ + r === "notes" ? "NOTE" : r === "docs" ? "DOC" : "REF"; │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...t-design/src/lib/registers.ts ───┐
│ diff --git a/ooknet-design/src/lib/registers │
│ .ts b/ooknet-design/src/lib/registers.ts │
│ new file mode 100644 │
│ index 0000000..0e277a5 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/lib/registers.ts │
│ @@ -0,0 +1,72 @@ │
│ +// Directory registers: a dir.yml at any di │
│ rectory inside the kb tree │
│ +// declares what its subtree is - personal │
│ notes, project docs, or │
│ +// vendor reference copies. Resolution is n │
│ earest-ancestor; unmarked │
│ +// content is notes, the natural resting st │
│ ate of a wiki. │
│ +import { existsSync, readFileSync, readdirS │
│ ync } from "node:fs"; │
│ +import { join } from "node:path"; │
│ + │
│ +export type Register = "notes" | "docs" | " │
│ reference"; │
│ + │
│ +export const DEFAULT_REGISTER: Register = " │
│ notes"; │
│ + │
│ +export interface DirManifest { │
│ + register: Register; │
│ + /** Display name for the subtree, e.g. a │
│ project name. */ │
│ + title?: string; │
│ +} │
│ + │
│ +export interface RegisterInfo { │
│ + register: Register; │
│ + /** Manifest directory that decided the r │
│ egister; null when default. */ │
│ + root: string | null; │
│ + title: string | null; │
│ +} │
│ + │
│ +const REGISTERS: Register[] = ["notes", "do │
│ cs", "reference"]; │
│ + │
│ +// dir.yml is two known keys - a tiny parse │
│ r beats a yaml dependency │
│ +function parseManifest(text: string, at: st │
│ ring): DirManifest { │
│ + const field = (key: string): string | nul │
│ l => { │
│ + const m = text.match(new RegExp(`^${key │
│ }:\\s*["']?(.+?)["']?\\s*$`, "m")); │
│ + return m ? m[1] : null; │
│ + }; │
│ + const register = field("register") as Reg │
│ ister | null; │
│ + if (!register || !REGISTERS.includes(regi │
│ ster)) { │
│ + throw new Error(`[kb] ${at}: register m │
│ ust be one of ${REGISTERS.join(", ")}`); │
│ + } │
│ + return { register, title: field("title") │
│ ?? undefined }; │
│ +} │
│ + │
│ +/** Every dir.yml under the kb tree, keyed │
│ by directory id ("" root). */ │
│ +export function readManifests(contentDir = │
│ "src/content/kb"): Map<string, DirManifest> │
│ { │
│ + const out = new Map<string, DirManifest>( │
│ ); │
│ + if (!existsSync(contentDir)) return out; │
│ + const walk = (dir: string, prefix: string │
│ ) => { │
│ + const manifest = join(dir, "dir.yml"); │
│ + if (existsSync(manifest)) { │
│ + out.set(prefix, parseManifest(readFil │
│ eSync(manifest, "utf8"), manifest)); │
│ + } │
│ + for (const entry of readdirSync(dir, { │
│ withFileTypes: true })) { │
│ + if (entry.isDirectory()) { │
│ + walk(join(dir, entry.name), prefix │
│ ? `${prefix}/${entry.name}` : entry.name); │
│ + } │
│ + } │
│ + }; │
│ + walk(contentDir, ""); │
│ + return out; │
│ +} │
│ + │
│ +/** Register of a page id, from the nearest │
│ ancestor manifest. */ │
│ +export function registerOf(id: string, mani │
│ fests: Map<string, DirManifest>): RegisterIn │
│ fo { │
│ + const segments = id.split("/").slice(0, - │
│ 1); │
│ + for (let i = segments.length; i >= 0; i-- │
│ ) { │
│ + const dir = segments.slice(0, i).join(" │
│ /"); │
│ + const m = manifests.get(dir); │
│ + if (m) return { register: m.register, r │
│ oot: dir, title: m.title ?? null }; │
│ + } │
│ + return { register: DEFAULT_REGISTER, root │
│ : null, title: null }; │
│ +} │
│ + │
│ +/** Display label for a register, as shown │
│ in chrome and search. */ │
│ +export const registerLabel = (r: Register): │
│ string => │
│ + r === "notes" ? "NOTE" : r === "docs" ? " │
│ DOC" : "REF"; │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/lib/types.ts ───────────────────────────────────────────┐
│ diff --git a/ooknet-design/src/lib/types.ts b/ooknet-design/src/lib/types.ts │
│ deleted file mode 100644 │
│ index 6d1a06d..0000000 │
│ --- a/ooknet-design/src/lib/types.ts │
│ +++ /dev/null │
│ @@ -1,19 +0,0 @@ │
│ -// Mirrors the collection schemas in content.config.ts. │
│ - │
│ -export interface NoteFrontmatter { │
│ - title: string; │
│ - published: string; // ISO date │
│ - status: string; │
│ - tags: string[]; │
│ - seeAlso?: string[]; │
│ - draft?: boolean; │
│ -} │
│ - │
│ -export interface KbFrontmatter { │
│ - title: string; │
│ - source: string; // vendor, e.g. "MICROSOFT LEARN" │
│ - url?: string; │
│ - retrieved?: string; // ISO date │
│ - tags: string[]; │
│ - draft?: boolean; │
│ -} │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ooknet-design/src/lib/types.ts ─────┐
│ diff --git a/ooknet-design/src/lib/types.ts │
│ b/ooknet-design/src/lib/types.ts │
│ deleted file mode 100644 │
│ index 6d1a06d..0000000 │
│ --- a/ooknet-design/src/lib/types.ts │
│ +++ /dev/null │
│ @@ -1,19 +0,0 @@ │
│ -// Mirrors the collection schemas in conten │
│ t.config.ts. │
│ - │
│ -export interface NoteFrontmatter { │
│ - title: string; │
│ - published: string; // ISO date │
│ - status: string; │
│ - tags: string[]; │
│ - seeAlso?: string[]; │
│ - draft?: boolean; │
│ -} │
│ - │
│ -export interface KbFrontmatter { │
│ - title: string; │
│ - source: string; // vendor, e.g. │
│ "MICROSOFT LEARN" │
│ - url?: string; │
│ - retrieved?: string; // ISO date │
│ - tags: string[]; │
│ - draft?: boolean; │
│ -} │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/lib/wikilinks.test.ts ──────────────────────────────────┐
│ diff --git a/ooknet-design/src/lib/wikilinks.test.ts b/ooknet-design/src/lib/wikil │
│ inks.test.ts │
│ index 34a53c1..b024470 100644 │
│ --- a/ooknet-design/src/lib/wikilinks.test.ts │
│ +++ b/ooknet-design/src/lib/wikilinks.test.ts │
│ @@ -16,20 +16,22 @@ const write = (rel: string, fm: string, body = "") => { │
│ │
│ beforeAll(() => { │
│ dir = mkdtempSync(join(tmpdir(), "wiki-")); │
│ - write("notes/nix/flakes.md", 'title: "FLAKES FIELD NOTES"', "cites [[windows/ts │
│ s]] and [[Docs Page]]."); │
│ - write("notes/nix/draft.md", 'title: "DRAFT"\ndraft: true', "cites [[flakes fiel │
│ d notes]]."); │
│ + write("kb/nix/flakes.md", 'title: "FLAKES FIELD NOTES"', "cites [[windows/tss]] │
│ and [[Docs Page]]."); │
│ + write("kb/nix/draft.md", 'title: "DRAFT"\ndraft: true', "cites [[flakes field n │
│ otes]]."); │
│ write("kb/windows/tss.md", 'title: "TSS TOOLSET"\nsource: "MS"', "literal `[[fl │
│ akes field notes]]` only."); │
│ - write("docs/site/page.md", 'title: "Docs Page"', "self [[Docs Page]], live [[FL │
│ AKES FIELD NOTES]], dead [[nope]]."); │
│ + writeFileSync(join(dir, "kb/windows/dir.yml"), "register: reference\n"); │
│ + write("kb/site/page.md", 'title: "Docs Page"', "self [[Docs Page]], live [[FLAK │
│ ES FIELD NOTES]], dead [[nope]]."); │
│ + writeFileSync(join(dir, "kb/site/dir.yml"), "register: docs\n"); │
│ }); │
│ afterAll(() => rmSync(dir, { recursive: true, force: true })); │
│ │
│ describe("buildWikiMap", () => { │
│ it("indexes every collection by id and title", () => { │
│ const map = buildWikiMap(dir); │
│ - expect(resolveWikilink("nix/flakes", map)).toBe("/notes/nix/flakes/"); │
│ - expect(resolveWikilink("Flakes Field Notes", map)).toBe("/notes/nix/flakes/") │
│ ; │
│ + expect(resolveWikilink("nix/flakes", map)).toBe("/kb/nix/flakes/"); │
│ + expect(resolveWikilink("Flakes Field Notes", map)).toBe("/kb/nix/flakes/"); │
│ expect(resolveWikilink("windows/tss", map)).toBe("/kb/windows/tss/"); │
│ - expect(resolveWikilink("Docs Page", map)).toBe("/docs/site/page/"); │
│ + expect(resolveWikilink("Docs Page", map)).toBe("/kb/site/page/"); │
│ }); │
│ │
│ it("is case-insensitive and trims", () => { │
│ @@ -47,7 +49,7 @@ describe("buildWikiMap", () => { │
│ }); │
│ │
│ describe("wikiNodes", () => { │
│ - const map = new Map([["docs page", "/docs/site/page/"]]); │
│ + const map = new Map([["docs page", "/kb/site/page/"]]); │
│ │
│ it("leaves plain text untouched", () => { │
│ expect(wikiNodes("no links here", map)).toBeNull(); │
│ @@ -56,7 +58,7 @@ describe("wikiNodes", () => { │
│ it("splits text around a resolved link", () => { │
│ const nodes = wikiNodes("see [[Docs Page]] for glyphs", map)!; │
│ expect(nodes.map((n) => n.type)).toEqual(["text", "link", "text"]); │
│ - expect(nodes[1].url).toBe("/docs/site/page/"); │
│ + expect(nodes[1].url).toBe("/kb/site/page/"); │
│ }); │
│ │
│ it("uses the alias as the label", () => { │
│ @@ -83,34 +85,26 @@ describe("extractWikiTargets", () => { │
│ }); │
│ │
│ describe("buildBacklinks", () => { │
│ - it("inverts the graph across collections", () => { │
│ + it("inverts the graph across the kb", () => { │
│ const bl = buildBacklinks(dir); │
│ const urls = (t: string) => (bl.get(t) ?? []).map((r) => r.url); │
│ - expect(urls("/kb/windows/tss/")).toEqual(["/notes/nix/flakes/"]); │
│ - expect(urls("/notes/nix/flakes/")).toEqual(["/docs/site/page/"]); │
│ + expect(urls("/kb/windows/tss/")).toEqual(["/kb/nix/flakes/"]); │
│ + expect(urls("/kb/nix/flakes/")).toEqual(["/kb/site/page/"]); │
│ }); │
│ │
│ - it("labels sources with their collection", () => { │
│ + it("labels sources with their register", () => { │
│ const src = buildBacklinks(dir).get("/kb/windows/tss/")![0]; │
│ - expect(src.label).toBe("NOTES - FLAKES FIELD NOTES"); │
│ + expect(src.label).toBe("NOTE - FLAKES FIELD NOTES"); │
│ }); │
│ │
│ it("drops self-references, drafts, and code-span citations", () => { │
│ const bl = buildBacklinks(dir); │
│ for (const [target, list] of bl) { │
│ expect(list.map((r) => r.url)).not.toContain(target); │
│ - expect(list.map((r) => r.url)).not.toContain("/notes/nix/draft/"); │
│ + expect(list.map((r) => r.url)).not.toContain("/kb/nix/draft/"); │
│ } │
│ - // the kb body only cites in a code span - it must not be a source │
│ - const flakes = bl.get("/notes/nix/flakes/") ?? []; │
│ + // the reference body only cites in a code span - it must not be a source │
│ + const flakes = bl.get("/kb/nix/flakes/") ?? []; │
│ expect(flakes.map((r) => r.url)).not.toContain("/kb/windows/tss/"); │
│ }); │
│ - │
│ - it("sorts sources notes, kb, docs", () => { │
│ - const order = ["/notes/", "/kb/", "/docs/"]; │
│ - for (const list of buildBacklinks(dir).values()) { │
│ - const kinds = list.map((r) => order.findIndex((p) => r.url.startsWith(p))); │
│ - expect(kinds).toEqual([...kinds].sort()); │
│ - } │
│ - }); │
│ }); │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...ign/src/lib/wikilinks.test.ts ───┐
│ diff --git a/ooknet-design/src/lib/wikilinks │
│ .test.ts b/ooknet-design/src/lib/wikilinks.t │
│ est.ts │
│ index 34a53c1..b024470 100644 │
│ --- a/ooknet-design/src/lib/wikilinks.test.t │
│ s │
│ +++ b/ooknet-design/src/lib/wikilinks.test.t │
│ s │
│ @@ -16,20 +16,22 @@ const write = (rel: stri │
│ ng, fm: string, body = "") => { │
│ │
│ beforeAll(() => { │
│ dir = mkdtempSync(join(tmpdir(), "wiki-") │
│ ); │
│ - write("notes/nix/flakes.md", 'title: "FLA │
│ KES FIELD NOTES"', "cites [[windows/tss]] an │
│ d [[Docs Page]]."); │
│ - write("notes/nix/draft.md", 'title: "DRAF │
│ T"\ndraft: true', "cites [[flakes field note │
│ s]]."); │
│ + write("kb/nix/flakes.md", 'title: "FLAKES │
│ FIELD NOTES"', "cites [[windows/tss]] and [ │
│ [Docs Page]]."); │
│ + write("kb/nix/draft.md", 'title: "DRAFT"\ │
│ ndraft: true', "cites [[flakes field notes]] │
│ ."); │
│ write("kb/windows/tss.md", 'title: "TSS T │
│ OOLSET"\nsource: "MS"', "literal `[[flakes f │
│ ield notes]]` only."); │
│ - write("docs/site/page.md", 'title: "Docs │
│ Page"', "self [[Docs Page]], live [[FLAKES F │
│ IELD NOTES]], dead [[nope]]."); │
│ + writeFileSync(join(dir, "kb/windows/dir.y │
│ ml"), "register: reference\n"); │
│ + write("kb/site/page.md", 'title: "Docs Pa │
│ ge"', "self [[Docs Page]], live [[FLAKES FIE │
│ LD NOTES]], dead [[nope]]."); │
│ + writeFileSync(join(dir, "kb/site/dir.yml" │
│ ), "register: docs\n"); │
│ }); │
│ afterAll(() => rmSync(dir, { recursive: tru │
│ e, force: true })); │
│ │
│ describe("buildWikiMap", () => { │
│ it("indexes every collection by id and ti │
│ tle", () => { │
│ const map = buildWikiMap(dir); │
│ - expect(resolveWikilink("nix/flakes", ma │
│ p)).toBe("/notes/nix/flakes/"); │
│ - expect(resolveWikilink("Flakes Field No │
│ tes", map)).toBe("/notes/nix/flakes/"); │
│ + expect(resolveWikilink("nix/flakes", ma │
│ p)).toBe("/kb/nix/flakes/"); │
│ + expect(resolveWikilink("Flakes Field No │
│ tes", map)).toBe("/kb/nix/flakes/"); │
│ expect(resolveWikilink("windows/tss", m │
│ ap)).toBe("/kb/windows/tss/"); │
│ - expect(resolveWikilink("Docs Page", map │
│ )).toBe("/docs/site/page/"); │
│ + expect(resolveWikilink("Docs Page", map │
│ )).toBe("/kb/site/page/"); │
│ }); │
│ │
│ it("is case-insensitive and trims", () => │
│ { │
│ @@ -47,7 +49,7 @@ describe("buildWikiMap", ( │
│ ) => { │
│ }); │
│ │
│ describe("wikiNodes", () => { │
│ - const map = new Map([["docs page", "/docs │
│ /site/page/"]]); │
│ + const map = new Map([["docs page", "/kb/s │
│ ite/page/"]]); │
│ │
│ it("leaves plain text untouched", () => { │
│ expect(wikiNodes("no links here", map)) │
│ .toBeNull(); │
│ @@ -56,7 +58,7 @@ describe("wikiNodes", () = │
│ > { │
│ it("splits text around a resolved link", │
│ () => { │
│ const nodes = wikiNodes("see [[Docs Pag │
│ e]] for glyphs", map)!; │
│ expect(nodes.map((n) => n.type)).toEqua │
│ l(["text", "link", "text"]); │
│ - expect(nodes[1].url).toBe("/docs/site/p │
│ age/"); │
│ + expect(nodes[1].url).toBe("/kb/site/pag │
│ e/"); │
│ }); │
│ │
│ it("uses the alias as the label", () => { │
│ @@ -83,34 +85,26 @@ describe("extractWikiTar │
│ gets", () => { │
│ }); │
│ │
│ describe("buildBacklinks", () => { │
│ - it("inverts the graph across collections" │
│ , () => { │
│ + it("inverts the graph across the kb", () │
│ => { │
│ const bl = buildBacklinks(dir); │
│ const urls = (t: string) => (bl.get(t) │
│ ?? []).map((r) => r.url); │
│ - expect(urls("/kb/windows/tss/")).toEqua │
│ l(["/notes/nix/flakes/"]); │
│ - expect(urls("/notes/nix/flakes/")).toEq │
│ ual(["/docs/site/page/"]); │
│ + expect(urls("/kb/windows/tss/")).toEqua │
│ l(["/kb/nix/flakes/"]); │
│ + expect(urls("/kb/nix/flakes/")).toEqual │
│ (["/kb/site/page/"]); │
│ }); │
│ │
│ - it("labels sources with their collection" │
│ , () => { │
│ + it("labels sources with their register", │
│ () => { │
│ const src = buildBacklinks(dir).get("/k │
│ b/windows/tss/")![0]; │
│ - expect(src.label).toBe("NOTES - FLAKES │
│ FIELD NOTES"); │
│ + expect(src.label).toBe("NOTE - FLAKES F │
│ IELD NOTES"); │
│ }); │
│ │
│ it("drops self-references, drafts, and co │
│ de-span citations", () => { │
│ const bl = buildBacklinks(dir); │
│ for (const [target, list] of bl) { │
│ expect(list.map((r) => r.url)).not.to │
│ Contain(target); │
│ - expect(list.map((r) => r.url)).not.to │
│ Contain("/notes/nix/draft/"); │
│ + expect(list.map((r) => r.url)).not.to │
│ Contain("/kb/nix/draft/"); │
│ } │
│ - // the kb body only cites in a code spa │
│ n - it must not be a source │
│ - const flakes = bl.get("/notes/nix/flake │
│ s/") ?? []; │
│ + // the reference body only cites in a c │
│ ode span - it must not be a source │
│ + const flakes = bl.get("/kb/nix/flakes/" │
│ ) ?? []; │
│ expect(flakes.map((r) => r.url)).not.to │
│ Contain("/kb/windows/tss/"); │
│ }); │
│ - │
│ - it("sorts sources notes, kb, docs", () => │
│ { │
│ - const order = ["/notes/", "/kb/", "/doc │
│ s/"]; │
│ - for (const list of buildBacklinks(dir). │
│ values()) { │
│ - const kinds = list.map((r) => order.f │
│ indIndex((p) => r.url.startsWith(p))); │
│ - expect(kinds).toEqual([...kinds].sort │
│ ()); │
│ - } │
│ - }); │
│ }); │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/lib/wikilinks.ts ───────────────────────────────────────┐
│ diff --git a/ooknet-design/src/lib/wikilinks.ts b/ooknet-design/src/lib/wikilinks. │
│ ts │
│ index 9aa1a76..22884f1 100644 │
│ --- a/ooknet-design/src/lib/wikilinks.ts │
│ +++ b/ooknet-design/src/lib/wikilinks.ts │
│ @@ -1,9 +1,9 @@ │
│ // Wikilink resolution. The map is built by scanning content frontmatter │
│ // directly (remark plugins run outside astro:content), keyed by every │
│ -// name an author might reach for: file id and title, across all three │
│ -// collections. │
│ +// name an author might reach for: file id and title. │
│ import { readdirSync, readFileSync } from "node:fs"; │
│ import { join } from "node:path"; │
│ +import { readManifests, registerOf, registerLabel } from "./registers"; │
│ │
│ export type WikiMap = Map<string, string>; │
│ │
│ @@ -24,9 +24,6 @@ function mdFiles(dir: string, prefix = ""): string[] { │
│ return out; │
│ } │
│ │
│ -const COLLECTIONS = ["notes", "kb", "docs"] as const; │
│ -export type Collection = (typeof COLLECTIONS)[number]; │
│ - │
│ function scan(map: WikiMap, dir: string, urlFor: (id: string) => string) { │
│ let files: string[]; │
│ try { │
│ @@ -47,9 +44,7 @@ function scan(map: WikiMap, dir: string, urlFor: (id: string) => │
│ string) { │
│ │
│ export function buildWikiMap(contentDir = "src/content"): WikiMap { │
│ const map: WikiMap = new Map(); │
│ - for (const c of COLLECTIONS) { │
│ - scan(map, join(contentDir, c), (id) => `/${c}/${id}/`); │
│ - } │
│ + scan(map, join(contentDir, "kb"), (id) => `/kb/${id}/`); │
│ return map; │
│ } │
│ │
│ @@ -110,23 +105,17 @@ function scanSources( │
│ } │
│ │
│ /** Target url ? pages whose body links to it. Dead links and │
│ - * self-references are dropped; sources sort notes, kb, docs. */ │
│ + * self-references are dropped; sources carry their register label. */ │
│ export function buildBacklinks(contentDir = "src/content"): Map<string, Backlink[ │
│ ]> { │
│ const map = buildWikiMap(contentDir); │
│ + const manifests = readManifests(join(contentDir, "kb")); │
│ const out = new Map<string, Backlink[]>(); │
│ - for (const c of COLLECTIONS) { │
│ - scanSources(out, map, join(contentDir, c), (id) => `/${c}/${id}/`, (id, fm) = │
│ > { │
│ - const title = fmField(fm, "title") ?? id; │
│ - return `${c.toUpperCase()} - ${title.toUpperCase()}`; │
│ - }); │
│ - } │
│ - const group = (u: string) => COLLECTIONS.findIndex((c) => u.startsWith(`/${c}/` │
│ )); │
│ + scanSources(out, map, join(contentDir, "kb"), (id) => `/kb/${id}/`, (id, fm) => │
│ { │
│ + const title = fmField(fm, "title") ?? id; │
│ + return `${registerLabel(registerOf(id, manifests).register)} - ${title.toUppe │
│ rCase()}`; │
│ + }); │
│ for (const list of out.values()) { │
│ - list.sort( │
│ - (a, b) => │
│ - group(a.url) - group(b.url) || │
│ - a.label.localeCompare(b.label, "en", { numeric: true }), │
│ - ); │
│ + list.sort((a, b) => a.label.localeCompare(b.label, "en", { numeric: true })); │
│ } │
│ return out; │
│ } │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...t-design/src/lib/wikilinks.ts ───┐
│ diff --git a/ooknet-design/src/lib/wikilinks │
│ .ts b/ooknet-design/src/lib/wikilinks.ts │
│ index 9aa1a76..22884f1 100644 │
│ --- a/ooknet-design/src/lib/wikilinks.ts │
│ +++ b/ooknet-design/src/lib/wikilinks.ts │
│ @@ -1,9 +1,9 @@ │
│ // Wikilink resolution. The map is built by │
│ scanning content frontmatter │
│ // directly (remark plugins run outside ast │
│ ro:content), keyed by every │
│ -// name an author might reach for: file id │
│ and title, across all three │
│ -// collections. │
│ +// name an author might reach for: file id │
│ and title. │
│ import { readdirSync, readFileSync } from " │
│ node:fs"; │
│ import { join } from "node:path"; │
│ +import { readManifests, registerOf, registe │
│ rLabel } from "./registers"; │
│ │
│ export type WikiMap = Map<string, string>; │
│ │
│ @@ -24,9 +24,6 @@ function mdFiles(dir: stri │
│ ng, prefix = ""): string[] { │
│ return out; │
│ } │
│ │
│ -const COLLECTIONS = ["notes", "kb", "docs"] │
│ as const; │
│ -export type Collection = (typeof COLLECTION │
│ S)[number]; │
│ - │
│ function scan(map: WikiMap, dir: string, ur │
│ lFor: (id: string) => string) { │
│ let files: string[]; │
│ try { │
│ @@ -47,9 +44,7 @@ function scan(map: WikiMap │
│ , dir: string, urlFor: (id: string) => strin │
│ g) { │
│ │
│ export function buildWikiMap(contentDir = " │
│ src/content"): WikiMap { │
│ const map: WikiMap = new Map(); │
│ - for (const c of COLLECTIONS) { │
│ - scan(map, join(contentDir, c), (id) => │
│ `/${c}/${id}/`); │
│ - } │
│ + scan(map, join(contentDir, "kb"), (id) => │
│ `/kb/${id}/`); │
│ return map; │
│ } │
│ │
│ @@ -110,23 +105,17 @@ function scanSources( │
│ } │
│ │
│ /** Target url ? pages whose body links to │
│ it. Dead links and │
│ - * self-references are dropped; sources so │
│ rt notes, kb, docs. */ │
│ + * self-references are dropped; sources ca │
│ rry their register label. */ │
│ export function buildBacklinks(contentDir = │
│ "src/content"): Map<string, Backlink[]> { │
│ const map = buildWikiMap(contentDir); │
│ + const manifests = readManifests(join(cont │
│ entDir, "kb")); │
│ const out = new Map<string, Backlink[]>() │
│ ; │
│ - for (const c of COLLECTIONS) { │
│ - scanSources(out, map, join(contentDir, │
│ c), (id) => `/${c}/${id}/`, (id, fm) => { │
│ - const title = fmField(fm, "title") ?? │
│ id; │
│ - return `${c.toUpperCase()} - ${title. │
│ toUpperCase()}`; │
│ - }); │
│ - } │
│ - const group = (u: string) => COLLECTIONS. │
│ findIndex((c) => u.startsWith(`/${c}/`)); │
│ + scanSources(out, map, join(contentDir, "k │
│ b"), (id) => `/kb/${id}/`, (id, fm) => { │
│ + const title = fmField(fm, "title") ?? i │
│ d; │
│ + return `${registerLabel(registerOf(id, │
│ manifests).register)} - ${title.toUpperCase( │
│ )}`; │
│ + }); │
│ for (const list of out.values()) { │
│ - list.sort( │
│ - (a, b) => │
│ - group(a.url) - group(b.url) || │
│ - a.label.localeCompare(b.label, "en" │
│ , { numeric: true }), │
│ - ); │
│ + list.sort((a, b) => a.label.localeCompa │
│ re(b.label, "en", { numeric: true })); │
│ } │
│ return out; │
│ } │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/about.astro ──────────────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/about.astro b/ooknet-design/src/pages/about.a │
│ stro │
│ index 23a1374..1eabc0e 100644 │
│ --- a/ooknet-design/src/pages/about.astro │
│ +++ b/ooknet-design/src/pages/about.astro │
│ @@ -4,30 +4,16 @@ import TopHeader from "../components/TopHeader/TopHeader.astro"; │
│ import Frame from "../components/Frame/Frame.astro"; │
│ import Pre from "../components/Pre/Pre.astro"; │
│ import ArticleFooter from "../components/ArticleFooter/ArticleFooter.astro"; │
│ -import { kv, row2, rule, ruleD } from "../lib/ascii"; │
│ +import { row2, ruleD } from "../lib/ascii"; │
│ │
│ const buildHeader = (w: number) => │
│ [row2("ABOUT THIS INSTALLATION", "OOKNET", w), ruleD(w)].join("\n"); │
│ │
│ -const buildColophon = (w: number) => │
│ - [ │
│ - row2("COLOPHON", "", w), │
│ - rule(w), │
│ - kv("OPERATOR", "LIAM W", 12, w), │
│ - kv("ENGINE", "astro, rendered static", 12, w), │
│ - kv("FRAMES", "strings, built at compile time", 12, w), │
│ - kv("BODY TYPE", "IBM Plex Mono", 12, w), │
│ - kv("FRAME TYPE", "Cascadia Mono", 12, w), │
│ - kv("GRID", "86 cells / 48 on paper tape", 12, w), │
│ - ruleD(w), │
│ - ].join("\n"); │
│ --- │
│ <ArticleLayout title="OOKNET KB - About" │
│ description="OOKNET - a personal knowledge base rendered as pure text on a mono │
│ space grid."> │
│ <TopHeader /> │
│ <Frame build={buildHeader} /> │
│ <Pre>{" "}</Pre> │
│ - <Frame build={buildColophon} /> │
│ - <Pre>{" "}</Pre> │
│ <ArticleFooter /> │
│ </ArticleLayout> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...-design/src/pages/about.astro ───┐
│ diff --git a/ooknet-design/src/pages/about.a │
│ stro b/ooknet-design/src/pages/about.astro │
│ index 23a1374..1eabc0e 100644 │
│ --- a/ooknet-design/src/pages/about.astro │
│ +++ b/ooknet-design/src/pages/about.astro │
│ @@ -4,30 +4,16 @@ import TopHeader from "../ │
│ components/TopHeader/TopHeader.astro"; │
│ import Frame from "../components/Frame/Fram │
│ e.astro"; │
│ import Pre from "../components/Pre/Pre.astr │
│ o"; │
│ import ArticleFooter from "../components/Ar │
│ ticleFooter/ArticleFooter.astro"; │
│ -import { kv, row2, rule, ruleD } from "../l │
│ ib/ascii"; │
│ +import { row2, ruleD } from "../lib/ascii"; │
│ │
│ const buildHeader = (w: number) => │
│ [row2("ABOUT THIS INSTALLATION", "OOKNET" │
│ , w), ruleD(w)].join("\n"); │
│ │
│ -const buildColophon = (w: number) => │
│ - [ │
│ - row2("COLOPHON", "", w), │
│ - rule(w), │
│ - kv("OPERATOR", "LIAM W", │
│ 12, w), │
│ - kv("ENGINE", "astro, rendered stati │
│ c", 12, w), │
│ - kv("FRAMES", "strings, built at com │
│ pile time", 12, w), │
│ - kv("BODY TYPE", "IBM Plex Mono", │
│ 12, w), │
│ - kv("FRAME TYPE", "Cascadia Mono", │
│ 12, w), │
│ - kv("GRID", "86 cells / 48 on pape │
│ r tape", 12, w), │
│ - ruleD(w), │
│ - ].join("\n"); │
│ --- │
│ <ArticleLayout title="OOKNET KB - About" │
│ description="OOKNET - a personal knowledg │
│ e base rendered as pure text on a monospace │
│ grid."> │
│ <TopHeader /> │
│ <Frame build={buildHeader} /> │
│ <Pre>{" "}</Pre> │
│ - <Frame build={buildColophon} /> │
│ - <Pre>{" "}</Pre> │
│ <ArticleFooter /> │
│ </ArticleLayout> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/docs/[...slug].astro ─────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/docs/[...slug].astro b/ooknet-design/src/page │
│ s/docs/[...slug].astro │
│ deleted file mode 100644 │
│ index 3565b79..0000000 │
│ --- a/ooknet-design/src/pages/docs/[...slug].astro │
│ +++ /dev/null │
│ @@ -1,102 +0,0 @@ │
│ ---- │
│ -import { getCollection, render, type CollectionEntry } from "astro:content"; │
│ -import ArticleLayout from "../../layouts/ArticleLayout/ArticleLayout.astro"; │
│ -import TopHeader from "../../components/TopHeader/TopHeader.astro"; │
│ -import Frame from "../../components/Frame/Frame.astro"; │
│ -import Pre from "../../components/Pre/Pre.astro"; │
│ -import Breadcrumbs from "../../components/Breadcrumbs/Breadcrumbs.astro"; │
│ -import TableOfContents from "../../components/TableOfContents/TableOfContents.ast │
│ ro"; │
│ -import Prose from "../../components/Prose/Prose.astro"; │
│ -import ArticlePagination from "../../components/ArticlePagination/ArticlePaginati │
│ on.astro"; │
│ -import type { PageLink } from "../../components/ArticlePagination/ArticlePaginati │
│ on"; │
│ -import DocsNav from "../../components/DocsNav/DocsNav.astro"; │
│ -import ArticleBacklinks from "../../components/ArticleBacklinks/ArticleBacklinks. │
│ astro"; │
│ -import { box, center, len, rule, ruleD, row2, wrap } from "../../lib/ascii"; │
│ -import { published, topSegment } from "../../lib/notes"; │
│ -import { docsOrder, projectLabel, type DocEntry } from "../../lib/docs"; │
│ -import { buildBacklinks, type Backlink } from "../../lib/wikilinks"; │
│ -import { describeBody } from "../../lib/meta"; │
│ - │
│ -export async function getStaticPaths() { │
│ - const docs = await getCollection("docs", published); │
│ - const backlinks = buildBacklinks(); │
│ - const ordered = docsOrder( │
│ - docs.map((d) => ({ id: d.id, title: d.data.title, order: d.data.order })), │
│ - ); │
│ - const byId = new Map(docs.map((d) => [d.id, d])); │
│ - │
│ - // Pagination labels are titles, truncated to keep the buttons on the │
│ - // narrow frame. │
│ - const link = (id?: string): PageLink | null => { │
│ - if (!id) return null; │
│ - const title = byId.get(id)!.data.title.toUpperCase(); │
│ - const note = len(title) > 18 ? [...title].slice(0, 15).join("") + "..." : tit │
│ le; │
│ - return { href: `/docs/${id}/`, note }; │
│ - }; │
│ - │
│ - // Docs are siloed by project: the index card and prev/next never │
│ - // cross into another project's pages. │
│ - return ordered.map((d) => { │
│ - const project = topSegment(d.id); │
│ - const projectDocs = ordered.filter((p) => topSegment(p.id) === project); │
│ - const i = projectDocs.findIndex((p) => p.id === d.id); │
│ - const strip = (id: string) => id.slice(project.length + 1) || id; │
│ - return { │
│ - params: { slug: d.id }, │
│ - props: { │
│ - doc: byId.get(d.id)!, │
│ - prev: link(projectDocs[i - 1]?.id), │
│ - next: link(projectDocs[i + 1]?.id), │
│ - all: projectDocs.map((p) => ({ ...p, id: strip(p.id) })), │
│ - currentId: strip(d.id), │
│ - refs: backlinks.get(`/docs/${d.id}/`) ?? [], │
│ - }, │
│ - }; │
│ - }); │
│ -} │
│ - │
│ -interface Props { │
│ - doc: CollectionEntry<"docs">; │
│ - prev: PageLink | null; │
│ - next: PageLink | null; │
│ - all: DocEntry[]; │
│ - currentId: string; │
│ - refs: Backlink[]; │
│ -} │
│ -const { doc, prev, next, all, currentId, refs } = Astro.props as Props; │
│ -const project = topSegment(doc.id); │
│ -const { Content, headings } = await render(doc); │
│ -const description = │
│ - doc.data.description ?? (describeBody(doc.body ?? "") || `OOKNET documentation │
│ - ${doc.data.title}.`); │
│ - │
│ -const buildTitleCard = (w: number) => { │
│ - const cardW = Math.min(46, w - 8); │
│ - const card = box({ │
│ - width: cardW, │
│ - lines: ["OOKNET DOCS", ...wrap(doc.data.title.toUpperCase(), cardW - 4).split │
│ ("\n")], │
│ - align: "center", │
│ - }); │
│ - return card.split("\n").map((line) => center(line, w)).join("\n"); │
│ -}; │
│ ---- │
│ -<ArticleLayout title={`DOCS - ${doc.data.title}`} description={description} type= │
│ "article"> │
│ - <TopHeader /> │
│ - <Frame build={(w) => [row2("OOKNET DOCUMENTATION", `PROJECT: ${projectLabel(pro │
│ ject)}`, w), rule(w)].join("\n")} /> │
│ - <Pre>{" "}</Pre> │
│ - <Frame build={buildTitleCard} /> │
│ - <Pre>{" "}</Pre> │
│ - <Breadcrumbs filed={`/DOCS/${doc.id.toUpperCase()}`} /> │
│ - <Frame build={(w) => ruleD(w)} /> │
│ - <DocsNav docs={all} currentId={currentId} title={projectLabel(project)} base={` │
│ /docs/${project}/`} /> │
│ - {headings.length >= 3 && <TableOfContents headings={headings} />} │
│ - <Prose><Content /></Prose> │
│ - {refs.length > 0 && ( │
│ - <Fragment> │
│ - <Frame build={(w) => rule(w)} /> │
│ - <ArticleBacklinks refs={refs} /> │
│ - </Fragment> │
│ - )} │
│ - <Pre>{" "}</Pre> │
│ - <ArticlePagination prev={prev} next={next} /> │
│ - <Frame build={(w) => rule(w)} /> │
│ -</ArticleLayout> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...rc/pages/docs/[...slug].astro ───┐
│ diff --git a/ooknet-design/src/pages/docs/[. │
│ ..slug].astro b/ooknet-design/src/pages/docs │
│ /[...slug].astro │
│ deleted file mode 100644 │
│ index 3565b79..0000000 │
│ --- a/ooknet-design/src/pages/docs/[...slug] │
│ .astro │
│ +++ /dev/null │
│ @@ -1,102 +0,0 @@ │
│ ---- │
│ -import { getCollection, render, type Collec │
│ tionEntry } from "astro:content"; │
│ -import ArticleLayout from "../../layouts/Ar │
│ ticleLayout/ArticleLayout.astro"; │
│ -import TopHeader from "../../components/Top │
│ Header/TopHeader.astro"; │
│ -import Frame from "../../components/Frame/F │
│ rame.astro"; │
│ -import Pre from "../../components/Pre/Pre.a │
│ stro"; │
│ -import Breadcrumbs from "../../components/B │
│ readcrumbs/Breadcrumbs.astro"; │
│ -import TableOfContents from "../../componen │
│ ts/TableOfContents/TableOfContents.astro"; │
│ -import Prose from "../../components/Prose/P │
│ rose.astro"; │
│ -import ArticlePagination from "../../compon │
│ ents/ArticlePagination/ArticlePagination.ast │
│ ro"; │
│ -import type { PageLink } from "../../compon │
│ ents/ArticlePagination/ArticlePagination"; │
│ -import DocsNav from "../../components/DocsN │
│ av/DocsNav.astro"; │
│ -import ArticleBacklinks from "../../compone │
│ nts/ArticleBacklinks/ArticleBacklinks.astro" │
│ ; │
│ -import { box, center, len, rule, ruleD, row │
│ 2, wrap } from "../../lib/ascii"; │
│ -import { published, topSegment } from "../. │
│ ./lib/notes"; │
│ -import { docsOrder, projectLabel, type DocE │
│ ntry } from "../../lib/docs"; │
│ -import { buildBacklinks, type Backlink } fr │
│ om "../../lib/wikilinks"; │
│ -import { describeBody } from "../../lib/met │
│ a"; │
│ - │
│ -export async function getStaticPaths() { │
│ - const docs = await getCollection("docs", │
│ published); │
│ - const backlinks = buildBacklinks(); │
│ - const ordered = docsOrder( │
│ - docs.map((d) => ({ id: d.id, title: d.d │
│ ata.title, order: d.data.order })), │
│ - ); │
│ - const byId = new Map(docs.map((d) => [d.i │
│ d, d])); │
│ - │
│ - // Pagination labels are titles, truncate │
│ d to keep the buttons on the │
│ - // narrow frame. │
│ - const link = (id?: string): PageLink | nu │
│ ll => { │
│ - if (!id) return null; │
│ - const title = byId.get(id)!.data.title. │
│ toUpperCase(); │
│ - const note = len(title) > 18 ? [...titl │
│ e].slice(0, 15).join("") + "..." : title; │
│ - return { href: `/docs/${id}/`, note }; │
│ - }; │
│ - │
│ - // Docs are siloed by project: the index │
│ card and prev/next never │
│ - // cross into another project's pages. │
│ - return ordered.map((d) => { │
│ - const project = topSegment(d.id); │
│ - const projectDocs = ordered.filter((p) │
│ => topSegment(p.id) === project); │
│ - const i = projectDocs.findIndex((p) => │
│ p.id === d.id); │
│ - const strip = (id: string) => id.slice( │
│ project.length + 1) || id; │
│ - return { │
│ - params: { slug: d.id }, │
│ - props: { │
│ - doc: byId.get(d.id)!, │
│ - prev: link(projectDocs[i - 1]?.id), │
│ - next: link(projectDocs[i + 1]?.id), │
│ - all: projectDocs.map((p) => ({ ...p │
│ , id: strip(p.id) })), │
│ - currentId: strip(d.id), │
│ - refs: backlinks.get(`/docs/${d.id}/ │
│ `) ?? [], │
│ - }, │
│ - }; │
│ - }); │
│ -} │
│ - │
│ -interface Props { │
│ - doc: CollectionEntry<"docs">; │
│ - prev: PageLink | null; │
│ - next: PageLink | null; │
│ - all: DocEntry[]; │
│ - currentId: string; │
│ - refs: Backlink[]; │
│ -} │
│ -const { doc, prev, next, all, currentId, re │
│ fs } = Astro.props as Props; │
│ -const project = topSegment(doc.id); │
│ -const { Content, headings } = await render( │
│ doc); │
│ -const description = │
│ - doc.data.description ?? (describeBody(doc │
│ .body ?? "") || `OOKNET documentation - ${do │
│ c.data.title}.`); │
│ - │
│ -const buildTitleCard = (w: number) => { │
│ - const cardW = Math.min(46, w - 8); │
│ - const card = box({ │
│ - width: cardW, │
│ - lines: ["OOKNET DOCS", ...wrap(doc.data │
│ .title.toUpperCase(), cardW - 4).split("\n") │
│ ], │
│ - align: "center", │
│ - }); │
│ - return card.split("\n").map((line) => cen │
│ ter(line, w)).join("\n"); │
│ -}; │
│ ---- │
│ -<ArticleLayout title={`DOCS - ${doc.data.ti │
│ tle}`} description={description} type="artic │
│ le"> │
│ - <TopHeader /> │
│ - <Frame build={(w) => [row2("OOKNET DOCUME │
│ NTATION", `PROJECT: ${projectLabel(project)} │
│ `, w), rule(w)].join("\n")} /> │
│ - <Pre>{" "}</Pre> │
│ - <Frame build={buildTitleCard} /> │
│ - <Pre>{" "}</Pre> │
│ - <Breadcrumbs filed={`/DOCS/${doc.id.toUpp │
│ erCase()}`} /> │
│ - <Frame build={(w) => ruleD(w)} /> │
│ - <DocsNav docs={all} currentId={currentId} │
│ title={projectLabel(project)} base={`/docs/ │
│ ${project}/`} /> │
│ - {headings.length >= 3 && <TableOfContents │
│ headings={headings} />} │
│ - <Prose><Content /></Prose> │
│ - {refs.length > 0 && ( │
│ - <Fragment> │
│ - <Frame build={(w) => rule(w)} /> │
│ - <ArticleBacklinks refs={refs} /> │
│ - </Fragment> │
│ - )} │
│ - <Pre>{" "}</Pre> │
│ - <ArticlePagination prev={prev} next={next │
│ } /> │
│ - <Frame build={(w) => rule(w)} /> │
│ -</ArticleLayout> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/docs/index.astro ─────────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/docs/index.astro b/ooknet-design/src/pages/do │
│ cs/index.astro │
│ deleted file mode 100644 │
│ index a0d429f..0000000 │
│ --- a/ooknet-design/src/pages/docs/index.astro │
│ +++ /dev/null │
│ @@ -1,27 +0,0 @@ │
│ ---- │
│ -import { getCollection } from "astro:content"; │
│ -import ArticleLayout from "../../layouts/ArticleLayout/ArticleLayout.astro"; │
│ -import TopHeader from "../../components/TopHeader/TopHeader.astro"; │
│ -import Frame from "../../components/Frame/Frame.astro"; │
│ -import Pre from "../../components/Pre/Pre.astro"; │
│ -import Tree from "../../components/Tree/Tree.astro"; │
│ -import { rule, ruleD, row2 } from "../../lib/ascii"; │
│ -import { published } from "../../lib/notes"; │
│ -import { docsTree } from "../../lib/docs"; │
│ - │
│ -const docs = (await getCollection("docs", published)).map((d) => ({ │
│ - id: d.id, │
│ - title: d.data.title, │
│ - order: d.data.order, │
│ -})); │
│ -const tree = docsTree(docs); │
│ ---- │
│ -<ArticleLayout title="OOKNET KB - Docs" │
│ - description="OOKNET documentation - how the ASCII framework, the markdown pipel │
│ ine, and the filing system work."> │
│ - <TopHeader /> │
│ - <Frame build={(w) => [row2("DOCUMENTATION", `${docs.length} pages`, w), ruleD(w │
│ )].join("\n")} /> │
│ - <Pre>{" "}</Pre> │
│ - <Tree nodes={tree} /> │
│ - <Pre>{" "}</Pre> │
│ - <Frame build={(w) => rule(w)} /> │
│ -</ArticleLayout> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...gn/src/pages/docs/index.astro ───┐
│ diff --git a/ooknet-design/src/pages/docs/in │
│ dex.astro b/ooknet-design/src/pages/docs/ind │
│ ex.astro │
│ deleted file mode 100644 │
│ index a0d429f..0000000 │
│ --- a/ooknet-design/src/pages/docs/index.ast │
│ ro │
│ +++ /dev/null │
│ @@ -1,27 +0,0 @@ │
│ ---- │
│ -import { getCollection } from "astro:conten │
│ t"; │
│ -import ArticleLayout from "../../layouts/Ar │
│ ticleLayout/ArticleLayout.astro"; │
│ -import TopHeader from "../../components/Top │
│ Header/TopHeader.astro"; │
│ -import Frame from "../../components/Frame/F │
│ rame.astro"; │
│ -import Pre from "../../components/Pre/Pre.a │
│ stro"; │
│ -import Tree from "../../components/Tree/Tre │
│ e.astro"; │
│ -import { rule, ruleD, row2 } from "../../li │
│ b/ascii"; │
│ -import { published } from "../../lib/notes" │
│ ; │
│ -import { docsTree } from "../../lib/docs"; │
│ - │
│ -const docs = (await getCollection("docs", p │
│ ublished)).map((d) => ({ │
│ - id: d.id, │
│ - title: d.data.title, │
│ - order: d.data.order, │
│ -})); │
│ -const tree = docsTree(docs); │
│ ---- │
│ -<ArticleLayout title="OOKNET KB - Docs" │
│ - description="OOKNET documentation - how t │
│ he ASCII framework, the markdown pipeline, a │
│ nd the filing system work."> │
│ - <TopHeader /> │
│ - <Frame build={(w) => [row2("DOCUMENTATION │
│ ", `${docs.length} pages`, w), ruleD(w)].joi │
│ n("\n")} /> │
│ - <Pre>{" "}</Pre> │
│ - <Tree nodes={tree} /> │
│ - <Pre>{" "}</Pre> │
│ - <Frame build={(w) => rule(w)} /> │
│ -</ArticleLayout> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/index.astro ──────────────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/index.astro b/ooknet-design/src/pages/index.a │
│ stro │
│ index 9ee7dcf..0a9a77b 100644 │
│ --- a/ooknet-design/src/pages/index.astro │
│ +++ b/ooknet-design/src/pages/index.astro │
│ @@ -5,40 +5,24 @@ import TopHeader from "../components/TopHeader/TopHeader.astro"; │
│ import Frame from "../components/Frame/Frame.astro"; │
│ import Pre from "../components/Pre/Pre.astro"; │
│ import Tree from "../components/Tree/Tree.astro"; │
│ -import { rule, ruleD, row2 } from "../lib/ascii"; │
│ +import ArticleFooter from "../components/ArticleFooter/ArticleFooter.astro"; │
│ +import { ruleD, row2 } from "../lib/ascii"; │
│ import { published } from "../lib/notes"; │
│ import { docsTree } from "../lib/docs"; │
│ │
│ -const section = (title: string, right: string) => (w: number) => │
│ - [row2(title, right, w), ruleD(w)].join("\n"); │
│ - │
│ -const notes = await getCollection("notes", published); │
│ -const kb = await getCollection("kb", published); │
│ -const docs = await getCollection("docs", published); │
│ - │
│ -const asTree = (entries: { id: string; data: { title: string; order?: number } }[ │
│ ], base: string) => │
│ - docsTree(entries.map((e) => ({ id: e.id, title: e.data.title, order: e.data.ord │
│ er })), undefined, base); │
│ +const entries = await getCollection("kb", published); │
│ +const tree = docsTree( │
│ + entries.map((e) => ({ id: e.id, title: e.data.title, order: e.data.order })), │
│ + undefined, │
│ + "/kb/", │
│ +); │
│ --- │
│ <ArticleLayout title="OOKNET KB - Index" │
│ - description="OOKNET archive - personal notes by subject, a credited knowledge b │
│ ase, and project documentation, cross-linked."> │
│ + description="OOKNET - a text-mode knowledge base on a strict monospace grid."> │
│ <TopHeader /> │
│ - <Frame build={(w) => [row2("INDEX OF THE ARCHIVE", `${notes.length + kb.length │
│ + docs.length} PAGES`, w), ruleD(w)].join("\n")} /> │
│ - <Pre>{" "}</Pre> │
│ - │
│ - <Frame build={section("NOTES", "personal writing, by subject")} /> │
│ - <Pre>{" "}</Pre> │
│ - <Tree nodes={asTree(notes, "/notes/")} /> │
│ + <Frame build={(w) => [row2("INDEX OF THE ARCHIVE", `${entries.length} PAGES`, w │
│ ), ruleD(w)].join("\n")} /> │
│ <Pre>{" "}</Pre> │
│ - │
│ - <Frame build={section("KNOWLEDGE BASE", "vendor material, credited")} /> │
│ + <Tree nodes={tree} /> │
│ <Pre>{" "}</Pre> │
│ - <Tree nodes={asTree(kb, "/kb/")} /> │
│ - <Pre>{" "}</Pre> │
│ - │
│ - <Frame build={section("DOCS", "documentation, by project")} /> │
│ - <Pre>{" "}</Pre> │
│ - <Tree nodes={asTree(docs, "/docs/")} /> │
│ - <Pre>{" "}</Pre> │
│ - │
│ - <Frame build={(w) => rule(w)} /> │
│ + <ArticleFooter /> │
│ </ArticleLayout> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...-design/src/pages/index.astro ───┐
│ diff --git a/ooknet-design/src/pages/index.a │
│ stro b/ooknet-design/src/pages/index.astro │
│ index 9ee7dcf..0a9a77b 100644 │
│ --- a/ooknet-design/src/pages/index.astro │
│ +++ b/ooknet-design/src/pages/index.astro │
│ @@ -5,40 +5,24 @@ import TopHeader from "../ │
│ components/TopHeader/TopHeader.astro"; │
│ import Frame from "../components/Frame/Fram │
│ e.astro"; │
│ import Pre from "../components/Pre/Pre.astr │
│ o"; │
│ import Tree from "../components/Tree/Tree.a │
│ stro"; │
│ -import { rule, ruleD, row2 } from "../lib/a │
│ scii"; │
│ +import ArticleFooter from "../components/Ar │
│ ticleFooter/ArticleFooter.astro"; │
│ +import { ruleD, row2 } from "../lib/ascii"; │
│ import { published } from "../lib/notes"; │
│ import { docsTree } from "../lib/docs"; │
│ │
│ -const section = (title: string, right: stri │
│ ng) => (w: number) => │
│ - [row2(title, right, w), ruleD(w)].join("\ │
│ n"); │
│ - │
│ -const notes = await getCollection("notes", │
│ published); │
│ -const kb = await getCollection("kb", publis │
│ hed); │
│ -const docs = await getCollection("docs", pu │
│ blished); │
│ - │
│ -const asTree = (entries: { id: string; data │
│ : { title: string; order?: number } }[], bas │
│ e: string) => │
│ - docsTree(entries.map((e) => ({ id: e.id, │
│ title: e.data.title, order: e.data.order })) │
│ , undefined, base); │
│ +const entries = await getCollection("kb", p │
│ ublished); │
│ +const tree = docsTree( │
│ + entries.map((e) => ({ id: e.id, title: e. │
│ data.title, order: e.data.order })), │
│ + undefined, │
│ + "/kb/", │
│ +); │
│ --- │
│ <ArticleLayout title="OOKNET KB - Index" │
│ - description="OOKNET archive - personal no │
│ tes by subject, a credited knowledge base, a │
│ nd project documentation, cross-linked."> │
│ + description="OOKNET - a text-mode knowled │
│ ge base on a strict monospace grid."> │
│ <TopHeader /> │
│ - <Frame build={(w) => [row2("INDEX OF THE │
│ ARCHIVE", `${notes.length + kb.length + docs │
│ .length} PAGES`, w), ruleD(w)].join("\n")} / │
│ > │
│ - <Pre>{" "}</Pre> │
│ - │
│ - <Frame build={section("NOTES", "personal │
│ writing, by subject")} /> │
│ - <Pre>{" "}</Pre> │
│ - <Tree nodes={asTree(notes, "/notes/")} /> │
│ + <Frame build={(w) => [row2("INDEX OF THE │
│ ARCHIVE", `${entries.length} PAGES`, w), rul │
│ eD(w)].join("\n")} /> │
│ <Pre>{" "}</Pre> │
│ - │
│ - <Frame build={section("KNOWLEDGE BASE", " │
│ vendor material, credited")} /> │
│ + <Tree nodes={tree} /> │
│ <Pre>{" "}</Pre> │
│ - <Tree nodes={asTree(kb, "/kb/")} /> │
│ - <Pre>{" "}</Pre> │
│ - │
│ - <Frame build={section("DOCS", "documentat │
│ ion, by project")} /> │
│ - <Pre>{" "}</Pre> │
│ - <Tree nodes={asTree(docs, "/docs/")} /> │
│ - <Pre>{" "}</Pre> │
│ - │
│ - <Frame build={(w) => rule(w)} /> │
│ + <ArticleFooter /> │
│ </ArticleLayout> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/kb/[...slug].astro ───────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/kb/[...slug].astro b/ooknet-design/src/pages/ │
│ kb/[...slug].astro │
│ index 4e2c5f5..fb50461 100644 │
│ --- a/ooknet-design/src/pages/kb/[...slug].astro │
│ +++ b/ooknet-design/src/pages/kb/[...slug].astro │
│ @@ -1,4 +1,7 @@ │
│ --- │
│ +// The one content route. What chrome a page gets - note card, project │
│ +// docs with a scoped index, or vendor reference copy - is decided by │
│ +// its register (nearest dir.yml; see lib/registers.ts). │
│ import { getCollection, render, type CollectionEntry } from "astro:content"; │
│ import ArticleLayout from "../../layouts/ArticleLayout/ArticleLayout.astro"; │
│ import TopHeader from "../../components/TopHeader/TopHeader.astro"; │
│ @@ -8,70 +11,144 @@ import ArticleMeta from "../../components/ArticleMeta/ArticleM │
│ eta.astro"; │
│ import type { MetaEntry } from "../../components/ArticleMeta/ArticleMeta"; │
│ import TableOfContents from "../../components/TableOfContents/TableOfContents.ast │
│ ro"; │
│ import Breadcrumbs from "../../components/Breadcrumbs/Breadcrumbs.astro"; │
│ +import ArticleSeeAlso from "../../components/ArticleSeeAlso/ArticleSeeAlso.astro" │
│ ; │
│ import ArticleBacklinks from "../../components/ArticleBacklinks/ArticleBacklinks. │
│ astro"; │
│ import ArticlePagination from "../../components/ArticlePagination/ArticlePaginati │
│ on.astro"; │
│ import ArticleFooter from "../../components/ArticleFooter/ArticleFooter.astro"; │
│ +import DocsNav from "../../components/DocsNav/DocsNav.astro"; │
│ import Prose from "../../components/Prose/Prose.astro"; │
│ import Pre from "../../components/Pre/Pre.astro"; │
│ import type { PageLink } from "../../components/ArticlePagination/ArticlePaginati │
│ on"; │
│ -import { published } from "../../lib/notes"; │
│ -import { docsOrder } from "../../lib/docs"; │
│ +import { published, topSegment } from "../../lib/notes"; │
│ +import { docsOrder, type DocEntry } from "../../lib/docs"; │
│ +import { readManifests, registerOf, type RegisterInfo } from "../../lib/registers │
│ "; │
│ import { buildBacklinks, type Backlink } from "../../lib/wikilinks"; │
│ import { describeBody } from "../../lib/meta"; │
│ import { len } from "../../lib/ascii"; │
│ │
│ export async function getStaticPaths() { │
│ const entries = await getCollection("kb", published); │
│ + const manifests = readManifests(); │
│ const backlinks = buildBacklinks(); │
│ - const ordered = docsOrder(entries.map((e) => ({ id: e.id, title: e.data.title } │
│ ))); │
│ - const byId = new Map(entries.map((e) => [e.id, e])); │
│ - const link = (id?: string): PageLink | null => { │
│ - if (!id) return null; │
│ - const t = byId.get(id)!.data.title; │
│ + const info = new Map(entries.map((e) => [e.id, registerOf(e.id, manifests)])); │
│ + │
│ + const link = (e?: { id: string; title: string }): PageLink | null => { │
│ + if (!e) return null; │
│ + const t = e.title.toUpperCase(); │
│ const note = len(t) > 18 ? [...t].slice(0, 15).join("") + "..." : t; │
│ - return { href: `/kb/${id}/`, note }; │
│ + return { href: `/kb/${e.id}/`, note }; │
│ }; │
│ - return ordered.map((e, i) => ({ │
│ - params: { slug: e.id }, │
│ - props: { │
│ - entry: byId.get(e.id)!, │
│ - prev: link(ordered[i - 1]?.id), │
│ - next: link(ordered[i + 1]?.id), │
│ - refs: backlinks.get(`/kb/${e.id}/`) ?? [], │
│ - }, │
│ - })); │
│ + │
│ + // notes page in chronological order across the whole register; │
│ + // docs and reference page within their manifest root, reading order │
│ + const notes = entries │
│ + .filter((e) => info.get(e.id)!.register === "notes") │
│ + .map((e) => ({ id: e.id, title: e.data.title, date: e.data.published ?? "" }) │
│ ) │
│ + .sort((a, b) => a.date.localeCompare(b.date) || a.id.localeCompare(b.id)); │
│ + │
│ + const rooted = new Map<string, DocEntry[]>(); │
│ + for (const e of entries) { │
│ + const i = info.get(e.id)!; │
│ + if (i.register === "notes") continue; │
│ + const key = `${i.register}:${i.root ?? ""}`; │
│ + if (!rooted.has(key)) rooted.set(key, []); │
│ + rooted.get(key)!.push({ id: e.id, title: e.data.title, order: e.data.order }) │
│ ; │
│ + } │
│ + for (const [k, list] of rooted) rooted.set(k, docsOrder(list)); │
│ + │
│ + return entries.map((entry) => { │
│ + const i = info.get(entry.id)!; │
│ + let prev: PageLink | null = null; │
│ + let next: PageLink | null = null; │
│ + let nav: DocEntry[] = []; │
│ + if (i.register === "notes") { │
│ + const at = notes.findIndex((n) => n.id === entry.id); │
│ + prev = link(notes[at - 1]); │
│ + next = link(notes[at + 1]); │
│ + } else { │
│ + const group = rooted.get(`${i.register}:${i.root ?? ""}`)!; │
│ + const at = group.findIndex((d) => d.id === entry.id); │
│ + prev = link(group[at - 1] && { id: group[at - 1].id, title: group[at - 1].t │
│ itle }); │
│ + next = link(group[at + 1] && { id: group[at + 1].id, title: group[at + 1].t │
│ itle }); │
│ + if (i.register === "docs") { │
│ + const strip = (id: string) => │
│ + i.root ? id.slice(i.root.length + 1) || id : id; │
│ + nav = group.map((d) => ({ ...d, id: strip(d.id) })); │
│ + } │
│ + } │
│ + return { │
│ + params: { slug: entry.id }, │
│ + props: { │
│ + entry, │
│ + info: i, │
│ + prev, │
│ + next, │
│ + nav, │
│ + currentId: i.root ? entry.id.slice(i.root.length + 1) || entry.id : entry │
│ .id, │
│ + refs: backlinks.get(`/kb/${entry.id}/`) ?? [], │
│ + }, │
│ + }; │
│ + }); │
│ } │
│ │
│ interface Props { │
│ entry: CollectionEntry<"kb">; │
│ + info: RegisterInfo; │
│ prev: PageLink | null; │
│ next: PageLink | null; │
│ + nav: DocEntry[]; │
│ + currentId: string; │
│ refs: Backlink[]; │
│ } │
│ -const { entry, prev, next, refs } = Astro.props as Props; │
│ +const { entry, info, prev, next, nav, currentId, refs } = Astro.props as Props; │
│ const { Content, headings } = await render(entry); │
│ const dirs = entry.id.split("/").slice(0, -1).join("/"); │
│ const description = │
│ - describeBody(entry.body ?? "") || `${entry.data.title} - from ${entry.data.sour │
│ ce}.`; │
│ + entry.data.description ?? │
│ + (describeBody(entry.body ?? "") || `${entry.data.title} - ooknet kb.`); │
│ │
│ -const meta: MetaEntry[] = [{ key: "SOURCE", value: entry.data.source }]; │
│ -if (entry.data.url) { │
│ - meta.push({ │
│ - key: "URL", │
│ - value: entry.data.url.replace(/^https?:\/\//, ""), │
│ - href: entry.data.url, │
│ - }); │
│ +const subject = topSegment(entry.id); │
│ +const rootTitle = (info.title ?? info.root ?? subject).toUpperCase(); │
│ + │
│ +const meta: MetaEntry[] = []; │
│ +const tags = entry.data.tags; │
│ +if (info.register === "reference") { │
│ + if (entry.data.source) meta.push({ key: "SOURCE", value: entry.data.source }); │
│ + if (entry.data.url) { │
│ + meta.push({ │
│ + key: "URL", │
│ + value: entry.data.url.replace(/^https?:\/\//, ""), │
│ + href: entry.data.url, │
│ + }); │
│ + } │
│ + if (entry.data.retrieved) meta.push({ key: "RETRIEVED", value: entry.data.retri │
│ eved }); │
│ +} else { │
│ + if (entry.data.published) meta.push({ key: "PUBLISHED", value: entry.data.publi │
│ shed }); │
│ + if (entry.data.status) meta.push({ key: "STATUS", value: entry.data.status }); │
│ } │
│ -if (entry.data.retrieved) meta.push({ key: "RETRIEVED", value: entry.data.retriev │
│ ed }); │
│ + │
│ +const header = │
│ + info.register === "docs" │
│ + ? { left: "OOKNET KB", right: `DOCS: ${rootTitle}` } │
│ + : info.register === "reference" │
│ + ? { left: "OOKNET KB", right: `SOURCE: ${entry.data.source ?? rootTitle}` } │
│ + : { left: "OOKNET KB", right: `SUBJECT: ${subject.toUpperCase()}` }; │
│ + │
│ +const cardLabel = │
│ + info.register === "docs" ? "DOCUMENTATION" : info.register === "reference" ? "R │
│ EFERENCE COPY" : "NOTE"; │
│ --- │
│ <ArticleLayout title={entry.data.title} description={description} type="article"> │
│ <TopHeader /> │
│ - <ArticleHeader left="OOKNET KNOWLEDGE BASE" right={`SOURCE: ${entry.data.source │
│ }`} date={entry.data.retrieved} /> │
│ - <DocumentTitle label="REFERENCE COPY" title={entry.data.title} /> │
│ + <ArticleHeader left={header.left} right={header.right} date={entry.data.publish │
│ ed ?? entry.data.retrieved} /> │
│ + <DocumentTitle label={cardLabel} title={entry.data.title} /> │
│ <Breadcrumbs filed={`/KB/${dirs.toUpperCase()}`} /> │
│ - <ArticleMeta entries={meta} tags={entry.data.tags} /> │
│ + {(meta.length > 0 || tags.length > 0) && <ArticleMeta entries={meta} tags={tags │
│ } />} │
│ + {info.register === "docs" && nav.length > 1 && ( │
│ + <DocsNav docs={nav} currentId={currentId} title={rootTitle} base={`/kb/${info │
│ .root ? info.root + "/" : ""}`} /> │
│ + )} │
│ {headings.length >= 3 && <TableOfContents headings={headings} />} │
│ <Prose><Content /></Prose> │
│ + <ArticleSeeAlso seeAlso={entry.data.seeAlso} /> │
│ <ArticleBacklinks refs={refs} /> │
│ <Pre>{" "}</Pre> │
│ <ArticlePagination prev={prev} next={next} /> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ .../src/pages/kb/[...slug].astro ───┐
│ diff --git a/ooknet-design/src/pages/kb/[... │
│ slug].astro b/ooknet-design/src/pages/kb/[.. │
│ .slug].astro │
│ index 4e2c5f5..fb50461 100644 │
│ --- a/ooknet-design/src/pages/kb/[...slug].a │
│ stro │
│ +++ b/ooknet-design/src/pages/kb/[...slug].a │
│ stro │
│ @@ -1,4 +1,7 @@ │
│ --- │
│ +// The one content route. What chrome a pag │
│ e gets - note card, project │
│ +// docs with a scoped index, or vendor refe │
│ rence copy - is decided by │
│ +// its register (nearest dir.yml; see lib/r │
│ egisters.ts). │
│ import { getCollection, render, type Collec │
│ tionEntry } from "astro:content"; │
│ import ArticleLayout from "../../layouts/Ar │
│ ticleLayout/ArticleLayout.astro"; │
│ import TopHeader from "../../components/Top │
│ Header/TopHeader.astro"; │
│ @@ -8,70 +11,144 @@ import ArticleMeta from │
│ "../../components/ArticleMeta/ArticleMeta.as │
│ tro"; │
│ import type { MetaEntry } from "../../compo │
│ nents/ArticleMeta/ArticleMeta"; │
│ import TableOfContents from "../../componen │
│ ts/TableOfContents/TableOfContents.astro"; │
│ import Breadcrumbs from "../../components/B │
│ readcrumbs/Breadcrumbs.astro"; │
│ +import ArticleSeeAlso from "../../component │
│ s/ArticleSeeAlso/ArticleSeeAlso.astro"; │
│ import ArticleBacklinks from "../../compone │
│ nts/ArticleBacklinks/ArticleBacklinks.astro" │
│ ; │
│ import ArticlePagination from "../../compon │
│ ents/ArticlePagination/ArticlePagination.ast │
│ ro"; │
│ import ArticleFooter from "../../components │
│ /ArticleFooter/ArticleFooter.astro"; │
│ +import DocsNav from "../../components/DocsN │
│ av/DocsNav.astro"; │
│ import Prose from "../../components/Prose/P │
│ rose.astro"; │
│ import Pre from "../../components/Pre/Pre.a │
│ stro"; │
│ import type { PageLink } from "../../compon │
│ ents/ArticlePagination/ArticlePagination"; │
│ -import { published } from "../../lib/notes" │
│ ; │
│ -import { docsOrder } from "../../lib/docs"; │
│ +import { published, topSegment } from "../. │
│ ./lib/notes"; │
│ +import { docsOrder, type DocEntry } from ". │
│ ./../lib/docs"; │
│ +import { readManifests, registerOf, type Re │
│ gisterInfo } from "../../lib/registers"; │
│ import { buildBacklinks, type Backlink } fr │
│ om "../../lib/wikilinks"; │
│ import { describeBody } from "../../lib/met │
│ a"; │
│ import { len } from "../../lib/ascii"; │
│ │
│ export async function getStaticPaths() { │
│ const entries = await getCollection("kb", │
│ published); │
│ + const manifests = readManifests(); │
│ const backlinks = buildBacklinks(); │
│ - const ordered = docsOrder(entries.map((e) │
│ => ({ id: e.id, title: e.data.title }))); │
│ - const byId = new Map(entries.map((e) => [ │
│ e.id, e])); │
│ - const link = (id?: string): PageLink | nu │
│ ll => { │
│ - if (!id) return null; │
│ - const t = byId.get(id)!.data.title; │
│ + const info = new Map(entries.map((e) => [ │
│ e.id, registerOf(e.id, manifests)])); │
│ + │
│ + const link = (e?: { id: string; title: st │
│ ring }): PageLink | null => { │
│ + if (!e) return null; │
│ + const t = e.title.toUpperCase(); │
│ const note = len(t) > 18 ? [...t].slice │
│ (0, 15).join("") + "..." : t; │
│ - return { href: `/kb/${id}/`, note }; │
│ + return { href: `/kb/${e.id}/`, note }; │
│ }; │
│ - return ordered.map((e, i) => ({ │
│ - params: { slug: e.id }, │
│ - props: { │
│ - entry: byId.get(e.id)!, │
│ - prev: link(ordered[i - 1]?.id), │
│ - next: link(ordered[i + 1]?.id), │
│ - refs: backlinks.get(`/kb/${e.id}/`) ? │
│ ? [], │
│ - }, │
│ - })); │
│ + │
│ + // notes page in chronological order acro │
│ ss the whole register; │
│ + // docs and reference page within their m │
│ anifest root, reading order │
│ + const notes = entries │
│ + .filter((e) => info.get(e.id)!.register │
│ === "notes") │
│ + .map((e) => ({ id: e.id, title: e.data. │
│ title, date: e.data.published ?? "" })) │
│ + .sort((a, b) => a.date.localeCompare(b. │
│ date) || a.id.localeCompare(b.id)); │
│ + │
│ + const rooted = new Map<string, DocEntry[] │
│ >(); │
│ + for (const e of entries) { │
│ + const i = info.get(e.id)!; │
│ + if (i.register === "notes") continue; │
│ + const key = `${i.register}:${i.root ?? │
│ ""}`; │
│ + if (!rooted.has(key)) rooted.set(key, [ │
│ ]); │
│ + rooted.get(key)!.push({ id: e.id, title │
│ : e.data.title, order: e.data.order }); │
│ + } │
│ + for (const [k, list] of rooted) rooted.se │
│ t(k, docsOrder(list)); │
│ + │
│ + return entries.map((entry) => { │
│ + const i = info.get(entry.id)!; │
│ + let prev: PageLink | null = null; │
│ + let next: PageLink | null = null; │
│ + let nav: DocEntry[] = []; │
│ + if (i.register === "notes") { │
│ + const at = notes.findIndex((n) => n.i │
│ d === entry.id); │
│ + prev = link(notes[at - 1]); │
│ + next = link(notes[at + 1]); │
│ + } else { │
│ + const group = rooted.get(`${i.registe │
│ r}:${i.root ?? ""}`)!; │
│ + const at = group.findIndex((d) => d.i │
│ d === entry.id); │
│ + prev = link(group[at - 1] && { id: gr │
│ oup[at - 1].id, title: group[at - 1].title } │
│ ); │
│ + next = link(group[at + 1] && { id: gr │
│ oup[at + 1].id, title: group[at + 1].title } │
│ ); │
│ + if (i.register === "docs") { │
│ + const strip = (id: string) => │
│ + i.root ? id.slice(i.root.length + │
│ 1) || id : id; │
│ + nav = group.map((d) => ({ ...d, id: │
│ strip(d.id) })); │
│ + } │
│ + } │
│ + return { │
│ + params: { slug: entry.id }, │
│ + props: { │
│ + entry, │
│ + info: i, │
│ + prev, │
│ + next, │
│ + nav, │
│ + currentId: i.root ? entry.id.slice( │
│ i.root.length + 1) || entry.id : entry.id, │
│ + refs: backlinks.get(`/kb/${entry.id │
│ }/`) ?? [], │
│ + }, │
│ + }; │
│ + }); │
│ } │
│ │
│ interface Props { │
│ entry: CollectionEntry<"kb">; │
│ + info: RegisterInfo; │
│ prev: PageLink | null; │
│ next: PageLink | null; │
│ + nav: DocEntry[]; │
│ + currentId: string; │
│ refs: Backlink[]; │
│ } │
│ -const { entry, prev, next, refs } = Astro.p │
│ rops as Props; │
│ +const { entry, info, prev, next, nav, curre │
│ ntId, refs } = Astro.props as Props; │
│ const { Content, headings } = await render( │
│ entry); │
│ const dirs = entry.id.split("/").slice(0, - │
│ 1).join("/"); │
│ const description = │
│ - describeBody(entry.body ?? "") || `${entr │
│ y.data.title} - from ${entry.data.source}.`; │
│ + entry.data.description ?? │
│ + (describeBody(entry.body ?? "") || `${ent │
│ ry.data.title} - ooknet kb.`); │
│ │
│ -const meta: MetaEntry[] = [{ key: "SOURCE", │
│ value: entry.data.source }]; │
│ -if (entry.data.url) { │
│ - meta.push({ │
│ - key: "URL", │
│ - value: entry.data.url.replace(/^https?: │
│ \/\//, ""), │
│ - href: entry.data.url, │
│ - }); │
│ +const subject = topSegment(entry.id); │
│ +const rootTitle = (info.title ?? info.root │
│ ?? subject).toUpperCase(); │
│ + │
│ +const meta: MetaEntry[] = []; │
│ +const tags = entry.data.tags; │
│ +if (info.register === "reference") { │
│ + if (entry.data.source) meta.push({ key: " │
│ SOURCE", value: entry.data.source }); │
│ + if (entry.data.url) { │
│ + meta.push({ │
│ + key: "URL", │
│ + value: entry.data.url.replace(/^https │
│ ?:\/\//, ""), │
│ + href: entry.data.url, │
│ + }); │
│ + } │
│ + if (entry.data.retrieved) meta.push({ key │
│ : "RETRIEVED", value: entry.data.retrieved } │
│ ); │
│ +} else { │
│ + if (entry.data.published) meta.push({ key │
│ : "PUBLISHED", value: entry.data.published } │
│ ); │
│ + if (entry.data.status) meta.push({ key: " │
│ STATUS", value: entry.data.status }); │
│ } │
│ -if (entry.data.retrieved) meta.push({ key: │
│ "RETRIEVED", value: entry.data.retrieved }); │
│ + │
│ +const header = │
│ + info.register === "docs" │
│ + ? { left: "OOKNET KB", right: `DOCS: ${ │
│ rootTitle}` } │
│ + : info.register === "reference" │
│ + ? { left: "OOKNET KB", right: `SOURCE │
│ : ${entry.data.source ?? rootTitle}` } │
│ + : { left: "OOKNET KB", right: `SUBJEC │
│ T: ${subject.toUpperCase()}` }; │
│ + │
│ +const cardLabel = │
│ + info.register === "docs" ? "DOCUMENTATION │
│ " : info.register === "reference" ? "REFEREN │
│ CE COPY" : "NOTE"; │
│ --- │
│ <ArticleLayout title={entry.data.title} des │
│ cription={description} type="article"> │
│ <TopHeader /> │
│ - <ArticleHeader left="OOKNET KNOWLEDGE BAS │
│ E" right={`SOURCE: ${entry.data.source}`} da │
│ te={entry.data.retrieved} /> │
│ - <DocumentTitle label="REFERENCE COPY" tit │
│ le={entry.data.title} /> │
│ + <ArticleHeader left={header.left} right={ │
│ header.right} date={entry.data.published ?? │
│ entry.data.retrieved} /> │
│ + <DocumentTitle label={cardLabel} title={e │
│ ntry.data.title} /> │
│ <Breadcrumbs filed={`/KB/${dirs.toUpperCa │
│ se()}`} /> │
│ - <ArticleMeta entries={meta} tags={entry.d │
│ ata.tags} /> │
│ + {(meta.length > 0 || tags.length > 0) && │
│ <ArticleMeta entries={meta} tags={tags} />} │
│ + {info.register === "docs" && nav.length > │
│ 1 && ( │
│ + <DocsNav docs={nav} currentId={currentI │
│ d} title={rootTitle} base={`/kb/${info.root │
│ ? info.root + "/" : ""}`} /> │
│ + )} │
│ {headings.length >= 3 && <TableOfContents │
│ headings={headings} />} │
│ <Prose><Content /></Prose> │
│ + <ArticleSeeAlso seeAlso={entry.data.seeAl │
│ so} /> │
│ <ArticleBacklinks refs={refs} /> │
│ <Pre>{" "}</Pre> │
│ <ArticlePagination prev={prev} next={next │
│ } /> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/kb/index.astro ───────────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/kb/index.astro b/ooknet-design/src/pages/kb/i │
│ ndex.astro │
│ index 7f2d9ee..194f409 100644 │
│ --- a/ooknet-design/src/pages/kb/index.astro │
│ +++ b/ooknet-design/src/pages/kb/index.astro │
│ @@ -5,23 +5,36 @@ import TopHeader from "../../components/TopHeader/TopHeader.astr │
│ o"; │
│ import Frame from "../../components/Frame/Frame.astro"; │
│ import Pre from "../../components/Pre/Pre.astro"; │
│ import Tree from "../../components/Tree/Tree.astro"; │
│ -import { rule, ruleD, row2 } from "../../lib/ascii"; │
│ +import KeyValue from "../../components/KeyValue/KeyValue.astro"; │
│ +import ArticleFooter from "../../components/ArticleFooter/ArticleFooter.astro"; │
│ +import { ruleD, row2 } from "../../lib/ascii"; │
│ import { published } from "../../lib/notes"; │
│ import { docsTree } from "../../lib/docs"; │
│ +import { readManifests, registerOf, type Register } from "../../lib/registers"; │
│ │
│ const entries = await getCollection("kb", published); │
│ +const manifests = readManifests(); │
│ +const counts: Record<Register, number> = { notes: 0, docs: 0, reference: 0 }; │
│ +for (const e of entries) counts[registerOf(e.id, manifests).register]++; │
│ + │
│ const tree = docsTree( │
│ - entries.map((e) => ({ id: e.id, title: e.data.title })), │
│ + entries.map((e) => ({ id: e.id, title: e.data.title, order: e.data.order })), │
│ undefined, │
│ "/kb/", │
│ ); │
│ --- │
│ <ArticleLayout title="OOKNET KB - Knowledge base" │
│ - description="Vendor documentation and guides on file, credited to their sources │
│ ."> │
│ + description="The ooknet knowledge base - notes, documentation, and references, │
│ one tree."> │
│ <TopHeader /> │
│ <Frame build={(w) => [row2("OOKNET KNOWLEDGE BASE", `${entries.length} ON FILE` │
│ , w), ruleD(w)].join("\n")} /> │
│ <Pre>{" "}</Pre> │
│ + <KeyValue entries={[ │
│ + ["NOTES", `${counts.notes}`], │
│ + ["DOCS", `${counts.docs}`], │
│ + ["REFERENCE", `${counts.reference}`], │
│ + ]} /> │
│ + <Pre>{" "}</Pre> │
│ <Tree nodes={tree} /> │
│ <Pre>{" "}</Pre> │
│ - <Frame build={(w) => rule(w)} /> │
│ + <ArticleFooter /> │
│ </ArticleLayout> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...sign/src/pages/kb/index.astro ───┐
│ diff --git a/ooknet-design/src/pages/kb/inde │
│ x.astro b/ooknet-design/src/pages/kb/index.a │
│ stro │
│ index 7f2d9ee..194f409 100644 │
│ --- a/ooknet-design/src/pages/kb/index.astro │
│ +++ b/ooknet-design/src/pages/kb/index.astro │
│ @@ -5,23 +5,36 @@ import TopHeader from "../ │
│ ../components/TopHeader/TopHeader.astro"; │
│ import Frame from "../../components/Frame/F │
│ rame.astro"; │
│ import Pre from "../../components/Pre/Pre.a │
│ stro"; │
│ import Tree from "../../components/Tree/Tre │
│ e.astro"; │
│ -import { rule, ruleD, row2 } from "../../li │
│ b/ascii"; │
│ +import KeyValue from "../../components/KeyV │
│ alue/KeyValue.astro"; │
│ +import ArticleFooter from "../../components │
│ /ArticleFooter/ArticleFooter.astro"; │
│ +import { ruleD, row2 } from "../../lib/asci │
│ i"; │
│ import { published } from "../../lib/notes" │
│ ; │
│ import { docsTree } from "../../lib/docs"; │
│ +import { readManifests, registerOf, type Re │
│ gister } from "../../lib/registers"; │
│ │
│ const entries = await getCollection("kb", p │
│ ublished); │
│ +const manifests = readManifests(); │
│ +const counts: Record<Register, number> = { │
│ notes: 0, docs: 0, reference: 0 }; │
│ +for (const e of entries) counts[registerOf( │
│ e.id, manifests).register]++; │
│ + │
│ const tree = docsTree( │
│ - entries.map((e) => ({ id: e.id, title: e. │
│ data.title })), │
│ + entries.map((e) => ({ id: e.id, title: e. │
│ data.title, order: e.data.order })), │
│ undefined, │
│ "/kb/", │
│ ); │
│ --- │
│ <ArticleLayout title="OOKNET KB - Knowledge │
│ base" │
│ - description="Vendor documentation and gui │
│ des on file, credited to their sources."> │
│ + description="The ooknet knowledge base - │
│ notes, documentation, and references, one tr │
│ ee."> │
│ <TopHeader /> │
│ <Frame build={(w) => [row2("OOKNET KNOWLE │
│ DGE BASE", `${entries.length} ON FILE`, w), │
│ ruleD(w)].join("\n")} /> │
│ <Pre>{" "}</Pre> │
│ + <KeyValue entries={[ │
│ + ["NOTES", `${counts.notes}`], │
│ + ["DOCS", `${counts.docs}`], │
│ + ["REFERENCE", `${counts.reference}`], │
│ + ]} /> │
│ + <Pre>{" "}</Pre> │
│ <Tree nodes={tree} /> │
│ <Pre>{" "}</Pre> │
│ - <Frame build={(w) => rule(w)} /> │
│ + <ArticleFooter /> │
│ </ArticleLayout> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/notes/[...slug].astro ────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/notes/[...slug].astro b/ooknet-design/src/pag │
│ es/notes/[...slug].astro │
│ deleted file mode 100644 │
│ index d1ed2ba..0000000 │
│ --- a/ooknet-design/src/pages/notes/[...slug].astro │
│ +++ /dev/null │
│ @@ -1,74 +0,0 @@ │
│ ---- │
│ -import { getCollection, render, type CollectionEntry } from "astro:content"; │
│ -import ArticleLayout from "../../layouts/ArticleLayout/ArticleLayout.astro"; │
│ -import TopHeader from "../../components/TopHeader/TopHeader.astro"; │
│ -import ArticleHeader from "../../components/ArticleHeader/ArticleHeader.astro"; │
│ -import DocumentTitle from "../../components/DocumentTitle/DocumentTitle.astro"; │
│ -import ArticleMeta from "../../components/ArticleMeta/ArticleMeta.astro"; │
│ -import TableOfContents from "../../components/TableOfContents/TableOfContents.ast │
│ ro"; │
│ -import Breadcrumbs from "../../components/Breadcrumbs/Breadcrumbs.astro"; │
│ -import ArticleSeeAlso from "../../components/ArticleSeeAlso/ArticleSeeAlso.astro" │
│ ; │
│ -import ArticleBacklinks from "../../components/ArticleBacklinks/ArticleBacklinks. │
│ astro"; │
│ -import ArticlePagination from "../../components/ArticlePagination/ArticlePaginati │
│ on.astro"; │
│ -import ArticleFooter from "../../components/ArticleFooter/ArticleFooter.astro"; │
│ -import Prose from "../../components/Prose/Prose.astro"; │
│ -import Pre from "../../components/Pre/Pre.astro"; │
│ -import type { PageLink } from "../../components/ArticlePagination/ArticlePaginati │
│ on"; │
│ -import { published, byPublished, topSegment } from "../../lib/notes"; │
│ -import { buildBacklinks, type Backlink } from "../../lib/wikilinks"; │
│ -import { describeBody } from "../../lib/meta"; │
│ -import { len } from "../../lib/ascii"; │
│ - │
│ -export async function getStaticPaths() { │
│ - const notes = (await getCollection("notes", published)).sort(byPublished); │
│ - const backlinks = buildBacklinks(); │
│ - const link = (n?: (typeof notes)[number]): PageLink | null => { │
│ - if (!n) return null; │
│ - const t = n.data.title; │
│ - const note = len(t) > 18 ? [...t].slice(0, 15).join("") + "..." : t; │
│ - return { href: `/notes/${n.id}/`, note }; │
│ - }; │
│ - return notes.map((n, i) => ({ │
│ - params: { slug: n.id }, │
│ - props: { │
│ - note: n, │
│ - prev: link(notes[i - 1]), │
│ - next: link(notes[i + 1]), │
│ - refs: backlinks.get(`/notes/${n.id}/`) ?? [], │
│ - }, │
│ - })); │
│ -} │
│ - │
│ -interface Props { │
│ - note: CollectionEntry<"notes">; │
│ - prev: PageLink | null; │
│ - next: PageLink | null; │
│ - refs: Backlink[]; │
│ -} │
│ -const { note, prev, next, refs } = Astro.props as Props; │
│ -const { Content, headings } = await render(note); │
│ -const subject = topSegment(note.id); │
│ -const dirs = note.id.split("/").slice(0, -1).join("/"); │
│ -const description = │
│ - describeBody(note.body ?? "") || `Note filed under ${subject}.`; │
│ ---- │
│ -<ArticleLayout title={note.data.title} description={description} type="article"> │
│ - <TopHeader /> │
│ - <ArticleHeader left="OOKNET NOTES" right={`SUBJECT: ${subject.toUpperCase()}`} │
│ date={note.data.published} /> │
│ - <DocumentTitle label="NOTE" title={note.data.title} /> │
│ - <Breadcrumbs filed={`/NOTES/${dirs.toUpperCase()}`} /> │
│ - <ArticleMeta │
│ - entries={[ │
│ - { key: "PUBLISHED", value: note.data.published }, │
│ - { key: "STATUS", value: note.data.status }, │
│ - ]} │
│ - tags={note.data.tags} │
│ - /> │
│ - {headings.length >= 3 && <TableOfContents headings={headings} />} │
│ - <Prose><Content /></Prose> │
│ - <ArticleSeeAlso fm={note.data} /> │
│ - <ArticleBacklinks refs={refs} /> │
│ - <Pre>{" "}</Pre> │
│ - <ArticlePagination prev={prev} next={next} /> │
│ - <ArticleFooter /> │
│ -</ArticleLayout> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...c/pages/notes/[...slug].astro ───┐
│ diff --git a/ooknet-design/src/pages/notes/[ │
│ ...slug].astro b/ooknet-design/src/pages/not │
│ es/[...slug].astro │
│ deleted file mode 100644 │
│ index d1ed2ba..0000000 │
│ --- a/ooknet-design/src/pages/notes/[...slug │
│ ].astro │
│ +++ /dev/null │
│ @@ -1,74 +0,0 @@ │
│ ---- │
│ -import { getCollection, render, type Collec │
│ tionEntry } from "astro:content"; │
│ -import ArticleLayout from "../../layouts/Ar │
│ ticleLayout/ArticleLayout.astro"; │
│ -import TopHeader from "../../components/Top │
│ Header/TopHeader.astro"; │
│ -import ArticleHeader from "../../components │
│ /ArticleHeader/ArticleHeader.astro"; │
│ -import DocumentTitle from "../../components │
│ /DocumentTitle/DocumentTitle.astro"; │
│ -import ArticleMeta from "../../components/A │
│ rticleMeta/ArticleMeta.astro"; │
│ -import TableOfContents from "../../componen │
│ ts/TableOfContents/TableOfContents.astro"; │
│ -import Breadcrumbs from "../../components/B │
│ readcrumbs/Breadcrumbs.astro"; │
│ -import ArticleSeeAlso from "../../component │
│ s/ArticleSeeAlso/ArticleSeeAlso.astro"; │
│ -import ArticleBacklinks from "../../compone │
│ nts/ArticleBacklinks/ArticleBacklinks.astro" │
│ ; │
│ -import ArticlePagination from "../../compon │
│ ents/ArticlePagination/ArticlePagination.ast │
│ ro"; │
│ -import ArticleFooter from "../../components │
│ /ArticleFooter/ArticleFooter.astro"; │
│ -import Prose from "../../components/Prose/P │
│ rose.astro"; │
│ -import Pre from "../../components/Pre/Pre.a │
│ stro"; │
│ -import type { PageLink } from "../../compon │
│ ents/ArticlePagination/ArticlePagination"; │
│ -import { published, byPublished, topSegment │
│ } from "../../lib/notes"; │
│ -import { buildBacklinks, type Backlink } fr │
│ om "../../lib/wikilinks"; │
│ -import { describeBody } from "../../lib/met │
│ a"; │
│ -import { len } from "../../lib/ascii"; │
│ - │
│ -export async function getStaticPaths() { │
│ - const notes = (await getCollection("notes │
│ ", published)).sort(byPublished); │
│ - const backlinks = buildBacklinks(); │
│ - const link = (n?: (typeof notes)[number]) │
│ : PageLink | null => { │
│ - if (!n) return null; │
│ - const t = n.data.title; │
│ - const note = len(t) > 18 ? [...t].slice │
│ (0, 15).join("") + "..." : t; │
│ - return { href: `/notes/${n.id}/`, note │
│ }; │
│ - }; │
│ - return notes.map((n, i) => ({ │
│ - params: { slug: n.id }, │
│ - props: { │
│ - note: n, │
│ - prev: link(notes[i - 1]), │
│ - next: link(notes[i + 1]), │
│ - refs: backlinks.get(`/notes/${n.id}/` │
│ ) ?? [], │
│ - }, │
│ - })); │
│ -} │
│ - │
│ -interface Props { │
│ - note: CollectionEntry<"notes">; │
│ - prev: PageLink | null; │
│ - next: PageLink | null; │
│ - refs: Backlink[]; │
│ -} │
│ -const { note, prev, next, refs } = Astro.pr │
│ ops as Props; │
│ -const { Content, headings } = await render( │
│ note); │
│ -const subject = topSegment(note.id); │
│ -const dirs = note.id.split("/").slice(0, -1 │
│ ).join("/"); │
│ -const description = │
│ - describeBody(note.body ?? "") || `Note fi │
│ led under ${subject}.`; │
│ ---- │
│ -<ArticleLayout title={note.data.title} desc │
│ ription={description} type="article"> │
│ - <TopHeader /> │
│ - <ArticleHeader left="OOKNET NOTES" right= │
│ {`SUBJECT: ${subject.toUpperCase()}`} date={ │
│ note.data.published} /> │
│ - <DocumentTitle label="NOTE" title={note.d │
│ ata.title} /> │
│ - <Breadcrumbs filed={`/NOTES/${dirs.toUppe │
│ rCase()}`} /> │
│ - <ArticleMeta │
│ - entries={[ │
│ - { key: "PUBLISHED", value: note.data. │
│ published }, │
│ - { key: "STATUS", value: note.data.sta │
│ tus }, │
│ - ]} │
│ - tags={note.data.tags} │
│ - /> │
│ - {headings.length >= 3 && <TableOfContents │
│ headings={headings} />} │
│ - <Prose><Content /></Prose> │
│ - <ArticleSeeAlso fm={note.data} /> │
│ - <ArticleBacklinks refs={refs} /> │
│ - <Pre>{" "}</Pre> │
│ - <ArticlePagination prev={prev} next={next │
│ } /> │
│ - <ArticleFooter /> │
│ -</ArticleLayout> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/notes/index.astro ────────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/notes/index.astro b/ooknet-design/src/pages/n │
│ otes/index.astro │
│ deleted file mode 100644 │
│ index 06e2867..0000000 │
│ --- a/ooknet-design/src/pages/notes/index.astro │
│ +++ /dev/null │
│ @@ -1,27 +0,0 @@ │
│ ---- │
│ -import { getCollection } from "astro:content"; │
│ -import ArticleLayout from "../../layouts/ArticleLayout/ArticleLayout.astro"; │
│ -import TopHeader from "../../components/TopHeader/TopHeader.astro"; │
│ -import Frame from "../../components/Frame/Frame.astro"; │
│ -import Pre from "../../components/Pre/Pre.astro"; │
│ -import Tree from "../../components/Tree/Tree.astro"; │
│ -import { rule, ruleD, row2 } from "../../lib/ascii"; │
│ -import { published } from "../../lib/notes"; │
│ -import { docsTree } from "../../lib/docs"; │
│ - │
│ -const notes = await getCollection("notes", published); │
│ -const tree = docsTree( │
│ - notes.map((n) => ({ id: n.id, title: n.data.title })), │
│ - undefined, │
│ - "/notes/", │
│ -); │
│ ---- │
│ -<ArticleLayout title="OOKNET KB - Notes" │
│ - description="Personal notes and study writing, filed by subject."> │
│ - <TopHeader /> │
│ - <Frame build={(w) => [row2("OOKNET NOTES", `${notes.length} FILED`, w), ruleD(w │
│ )].join("\n")} /> │
│ - <Pre>{" "}</Pre> │
│ - <Tree nodes={tree} /> │
│ - <Pre>{" "}</Pre> │
│ - <Frame build={(w) => rule(w)} /> │
│ -</ArticleLayout> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...n/src/pages/notes/index.astro ───┐
│ diff --git a/ooknet-design/src/pages/notes/i │
│ ndex.astro b/ooknet-design/src/pages/notes/i │
│ ndex.astro │
│ deleted file mode 100644 │
│ index 06e2867..0000000 │
│ --- a/ooknet-design/src/pages/notes/index.as │
│ tro │
│ +++ /dev/null │
│ @@ -1,27 +0,0 @@ │
│ ---- │
│ -import { getCollection } from "astro:conten │
│ t"; │
│ -import ArticleLayout from "../../layouts/Ar │
│ ticleLayout/ArticleLayout.astro"; │
│ -import TopHeader from "../../components/Top │
│ Header/TopHeader.astro"; │
│ -import Frame from "../../components/Frame/F │
│ rame.astro"; │
│ -import Pre from "../../components/Pre/Pre.a │
│ stro"; │
│ -import Tree from "../../components/Tree/Tre │
│ e.astro"; │
│ -import { rule, ruleD, row2 } from "../../li │
│ b/ascii"; │
│ -import { published } from "../../lib/notes" │
│ ; │
│ -import { docsTree } from "../../lib/docs"; │
│ - │
│ -const notes = await getCollection("notes", │
│ published); │
│ -const tree = docsTree( │
│ - notes.map((n) => ({ id: n.id, title: n.da │
│ ta.title })), │
│ - undefined, │
│ - "/notes/", │
│ -); │
│ ---- │
│ -<ArticleLayout title="OOKNET KB - Notes" │
│ - description="Personal notes and study wri │
│ ting, filed by subject."> │
│ - <TopHeader /> │
│ - <Frame build={(w) => [row2("OOKNET NOTES" │
│ , `${notes.length} FILED`, w), ruleD(w)].joi │
│ n("\n")} /> │
│ - <Pre>{" "}</Pre> │
│ - <Tree nodes={tree} /> │
│ - <Pre>{" "}</Pre> │
│ - <Frame build={(w) => rule(w)} /> │
│ -</ArticleLayout> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/projects.astro ───────────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/projects.astro b/ooknet-design/src/pages/proj │
│ ects.astro │
│ index cf6d570..533705c 100644 │
│ --- a/ooknet-design/src/pages/projects.astro │
│ +++ b/ooknet-design/src/pages/projects.astro │
│ @@ -11,24 +11,21 @@ import { row2, ruleD } from "../lib/ascii"; │
│ const buildHeader = (w: number) => │
│ [row2("PROJECT LEDGER", "DEPT. OF OOKS", w), ruleD(w)].join("\n"); │
│ │
│ -const PROJECTS: { name: string; desc: string; git: string; docs: string; web?: st │
│ ring }[] = [ │
│ +const PROJECTS: { name: string; desc: string; git: string; docs?: string; web?: s │
│ tring }[] = [ │
│ { │
│ name: "OOKNET", │
│ desc: "my personal nixos infrastructure configuration", │
│ git: "https://github.com/ooks-io/ooknet", │
│ - docs: "/docs/ooknet/overview/", │
│ }, │
│ { │
│ name: "OOKNET.ORG", │
│ desc: "personal website - a text-mode knowledge base and the ascii design sys │
│ tem that renders it, everything on a strict monospace grid", │
│ git: "https://github.com/ooks-io/ooknet.org", │
│ - docs: "/docs/ooknet-org/overview/", │
│ }, │
│ { │
│ name: "WOWSIM-STATS", │
│ desc: "a nix pipeline that orchestrates mist of pandaria simulations at scale │
│ , and a statically generated challenge mode leaderboard", │
│ git: "https://github.com/ooks-io/wowsim-stats", │
│ - docs: "/docs/wowsim-stats/overview/", │
│ web: "https://wowsimstats.com", │
│ }, │
│ ]; │
│ @@ -42,7 +39,7 @@ const PROJECTS: { name: string; desc: string; git: string; docs: │
│ string; web?: s │
│ <Fragment> │
│ <Panel title={p.name}> │
│ <p class="project-desc">{p.desc}</p> │
│ - <Pre><Button label="GIT" href={p.git} />{" "}<Button label="MORE" href={ │
│ p.docs} />{p.web && <Fragment>{" "}<Button label="WEB" href={p.web} /></Fragment> │
│ }</Pre> │
│ + <Pre><Button label="GIT" href={p.git} />{p.docs && <Fragment>{" "}<Butto │
│ n label="MORE" href={p.docs} /></Fragment>}{p.web && <Fragment>{" "}<Button label │
│ ="WEB" href={p.web} /></Fragment>}</Pre> │
│ </Panel> │
│ <Pre>{" "}</Pre> │
│ </Fragment> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...sign/src/pages/projects.astro ───┐
│ diff --git a/ooknet-design/src/pages/project │
│ s.astro b/ooknet-design/src/pages/projects.a │
│ stro │
│ index cf6d570..533705c 100644 │
│ --- a/ooknet-design/src/pages/projects.astro │
│ +++ b/ooknet-design/src/pages/projects.astro │
│ @@ -11,24 +11,21 @@ import { row2, ruleD } f │
│ rom "../lib/ascii"; │
│ const buildHeader = (w: number) => │
│ [row2("PROJECT LEDGER", "DEPT. OF OOKS", │
│ w), ruleD(w)].join("\n"); │
│ │
│ -const PROJECTS: { name: string; desc: strin │
│ g; git: string; docs: string; web?: string } │
│ [] = [ │
│ +const PROJECTS: { name: string; desc: strin │
│ g; git: string; docs?: string; web?: string │
│ }[] = [ │
│ { │
│ name: "OOKNET", │
│ desc: "my personal nixos infrastructure │
│ configuration", │
│ git: "https://github.com/ooks-io/ooknet │
│ ", │
│ - docs: "/docs/ooknet/overview/", │
│ }, │
│ { │
│ name: "OOKNET.ORG", │
│ desc: "personal website - a text-mode k │
│ nowledge base and the ascii design system th │
│ at renders it, everything on a strict monosp │
│ ace grid", │
│ git: "https://github.com/ooks-io/ooknet │
│ .org", │
│ - docs: "/docs/ooknet-org/overview/", │
│ }, │
│ { │
│ name: "WOWSIM-STATS", │
│ desc: "a nix pipeline that orchestrates │
│ mist of pandaria simulations at scale, and │
│ a statically generated challenge mode leader │
│ board", │
│ git: "https://github.com/ooks-io/wowsim │
│ -stats", │
│ - docs: "/docs/wowsim-stats/overview/", │
│ web: "https://wowsimstats.com", │
│ }, │
│ ]; │
│ @@ -42,7 +39,7 @@ const PROJECTS: { name: st │
│ ring; desc: string; git: string; docs: strin │
│ g; web?: s │
│ <Fragment> │
│ <Panel title={p.name}> │
│ <p class="project-desc">{p.desc}</p │
│ > │
│ - <Pre><Button label="GIT" href={p.gi │
│ t} />{" "}<Button label="MORE" href={p.docs │
│ } />{p.web && <Fragment>{" "}<Button label= │
│ "WEB" href={p.web} /></Fragment>}</Pre> │
│ + <Pre><Button label="GIT" href={p.gi │
│ t} />{p.docs && <Fragment>{" "}<Button labe │
│ l="MORE" href={p.docs} /></Fragment>}{p.web │
│ && <Fragment>{" "}<Button label="WEB" href= │
│ {p.web} /></Fragment>}</Pre> │
│ </Panel> │
│ <Pre>{" "}</Pre> │
│ </Fragment> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/resources.astro ──────────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/resources.astro b/ooknet-design/src/pages/res │
│ ources.astro │
│ deleted file mode 100644 │
│ index cac1a60..0000000 │
│ --- a/ooknet-design/src/pages/resources.astro │
│ +++ /dev/null │
│ @@ -1,30 +0,0 @@ │
│ ---- │
│ -import ArticleLayout from "../layouts/ArticleLayout/ArticleLayout.astro"; │
│ -import TopHeader from "../components/TopHeader/TopHeader.astro"; │
│ -import Frame from "../components/Frame/Frame.astro"; │
│ -import Pre from "../components/Pre/Pre.astro"; │
│ -import Badge from "../components/Badge/Badge.astro"; │
│ -import ArticleFooter from "../components/ArticleFooter/ArticleFooter.astro"; │
│ -import { pad, row2, ruleD } from "../lib/ascii"; │
│ - │
│ -const buildSection = (title: string, right: string) => (w: number) => │
│ - [row2(title, right, w), ruleD(w)].join("\n"); │
│ - │
│ -const TOOLING: [string, string, string][] = [ │
│ - ["FRAMEWORK", "ASTRO.BUILD", "https://astro.build"], │
│ - ["FRAME FONT", "CASCADIA-MONO", "https://github.com/microsoft/cascadia-code"], │
│ - ["BODY FONT", "IBM-PLEX-MONO", "https://github.com/IBM/plex"], │
│ - ["DIAGRAMS", "MERMAID-ASCII", "https://github.com/AlexanderGrooff/mermaid-ascii │
│ "], │
│ -]; │
│ ---- │
│ -<ArticleLayout title="OOKNET KB - Resources" │
│ - description="External references filed by the OOKNET knowledge base - tools, pa │
│ pers, and reading."> │
│ - <TopHeader /> │
│ - <Frame build={buildSection("TOOLING", "EXTERNAL")} /> │
│ - <Pre>{" "}</Pre> │
│ - {TOOLING.map(([label, name, href]) => ( │
│ - <Pre>{pad(label, 12)}<Badge label={name} href={href} /></Pre> │
│ - ))} │
│ - <Pre>{" "}</Pre> │
│ - <ArticleFooter /> │
│ -</ArticleLayout> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...ign/src/pages/resources.astro ───┐
│ diff --git a/ooknet-design/src/pages/resourc │
│ es.astro b/ooknet-design/src/pages/resources │
│ .astro │
│ deleted file mode 100644 │
│ index cac1a60..0000000 │
│ --- a/ooknet-design/src/pages/resources.astr │
│ o │
│ +++ /dev/null │
│ @@ -1,30 +0,0 @@ │
│ ---- │
│ -import ArticleLayout from "../layouts/Artic │
│ leLayout/ArticleLayout.astro"; │
│ -import TopHeader from "../components/TopHea │
│ der/TopHeader.astro"; │
│ -import Frame from "../components/Frame/Fram │
│ e.astro"; │
│ -import Pre from "../components/Pre/Pre.astr │
│ o"; │
│ -import Badge from "../components/Badge/Badg │
│ e.astro"; │
│ -import ArticleFooter from "../components/Ar │
│ ticleFooter/ArticleFooter.astro"; │
│ -import { pad, row2, ruleD } from "../lib/as │
│ cii"; │
│ - │
│ -const buildSection = (title: string, right: │
│ string) => (w: number) => │
│ - [row2(title, right, w), ruleD(w)].join("\ │
│ n"); │
│ - │
│ -const TOOLING: [string, string, string][] = │
│ [ │
│ - ["FRAMEWORK", "ASTRO.BUILD", "https://ast │
│ ro.build"], │
│ - ["FRAME FONT", "CASCADIA-MONO", "https:// │
│ github.com/microsoft/cascadia-code"], │
│ - ["BODY FONT", "IBM-PLEX-MONO", "https://g │
│ ithub.com/IBM/plex"], │
│ - ["DIAGRAMS", "MERMAID-ASCII", "https://gi │
│ thub.com/AlexanderGrooff/mermaid-ascii"], │
│ -]; │
│ ---- │
│ -<ArticleLayout title="OOKNET KB - Resources │
│ " │
│ - description="External references filed by │
│ the OOKNET knowledge base - tools, papers, │
│ and reading."> │
│ - <TopHeader /> │
│ - <Frame build={buildSection("TOOLING", "EX │
│ TERNAL")} /> │
│ - <Pre>{" "}</Pre> │
│ - {TOOLING.map(([label, name, href]) => ( │
│ - <Pre>{pad(label, 12)}<Badge label={name │
│ } href={href} /></Pre> │
│ - ))} │
│ - <Pre>{" "}</Pre> │
│ - <ArticleFooter /> │
│ -</ArticleLayout> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/rss.xml.ts ───────────────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/rss.xml.ts b/ooknet-design/src/pages/rss.xml. │
│ ts │
│ index 9416347..1d69d49 100644 │
│ --- a/ooknet-design/src/pages/rss.xml.ts │
│ +++ b/ooknet-design/src/pages/rss.xml.ts │
│ @@ -1,22 +1,25 @@ │
│ -// Notes feed, newest first - personal writing only. │
│ +// Personal writing only: kb pages whose register is notes, newest │
│ +// first. Docs and reference copies stay out of the feed. │
│ import type { APIRoute } from "astro"; │
│ import { getCollection } from "astro:content"; │
│ import { published, topSegment } from "../lib/notes"; │
│ +import { readManifests, registerOf } from "../lib/registers"; │
│ import { describeBody } from "../lib/meta"; │
│ import { buildRss } from "../lib/feeds"; │
│ │
│ export const GET: APIRoute = async ({ site }) => { │
│ - const notes = (await getCollection("notes", published)).sort((a, b) => │
│ - b.data.published.localeCompare(a.data.published), │
│ - ); │
│ + const manifests = readManifests(); │
│ + const notes = (await getCollection("kb", published)) │
│ + .filter((e) => registerOf(e.id, manifests).register === "notes" && e.data.pub │
│ lished) │
│ + .sort((a, b) => b.data.published!.localeCompare(a.data.published!)); │
│ const xml = buildRss({ │
│ title: "OOKNET KB", │
│ site: site!.href, │
│ description: "OOKNET notes - personal writing, filed by subject.", │
│ items: notes.map((n) => ({ │
│ title: n.data.title, │
│ - url: new URL(`/notes/${n.id}/`, site).href, │
│ - published: n.data.published, │
│ + url: new URL(`/kb/${n.id}/`, site).href, │
│ + published: n.data.published!, │
│ description: describeBody(n.body ?? "") || `Note filed under ${topSegment(n │
│ .id)}.`, │
│ })), │
│ }); │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...t-design/src/pages/rss.xml.ts ───┐
│ diff --git a/ooknet-design/src/pages/rss.xml │
│ .ts b/ooknet-design/src/pages/rss.xml.ts │
│ index 9416347..1d69d49 100644 │
│ --- a/ooknet-design/src/pages/rss.xml.ts │
│ +++ b/ooknet-design/src/pages/rss.xml.ts │
│ @@ -1,22 +1,25 @@ │
│ -// Notes feed, newest first - personal writ │
│ ing only. │
│ +// Personal writing only: kb pages whose re │
│ gister is notes, newest │
│ +// first. Docs and reference copies stay ou │
│ t of the feed. │
│ import type { APIRoute } from "astro"; │
│ import { getCollection } from "astro:conten │
│ t"; │
│ import { published, topSegment } from "../l │
│ ib/notes"; │
│ +import { readManifests, registerOf } from " │
│ ../lib/registers"; │
│ import { describeBody } from "../lib/meta"; │
│ import { buildRss } from "../lib/feeds"; │
│ │
│ export const GET: APIRoute = async ({ site │
│ }) => { │
│ - const notes = (await getCollection("notes │
│ ", published)).sort((a, b) => │
│ - b.data.published.localeCompare(a.data.p │
│ ublished), │
│ - ); │
│ + const manifests = readManifests(); │
│ + const notes = (await getCollection("kb", │
│ published)) │
│ + .filter((e) => registerOf(e.id, manifes │
│ ts).register === "notes" && e.data.published │
│ ) │
│ + .sort((a, b) => b.data.published!.local │
│ eCompare(a.data.published!)); │
│ const xml = buildRss({ │
│ title: "OOKNET KB", │
│ site: site!.href, │
│ description: "OOKNET notes - personal w │
│ riting, filed by subject.", │
│ items: notes.map((n) => ({ │
│ title: n.data.title, │
│ - url: new URL(`/notes/${n.id}/`, site) │
│ .href, │
│ - published: n.data.published, │
│ + url: new URL(`/kb/${n.id}/`, site).hr │
│ ef, │
│ + published: n.data.published!, │
│ description: describeBody(n.body ?? " │
│ ") || `Note filed under ${topSegment(n.id)}. │
│ `, │
│ })), │
│ }); │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/search.json.ts ───────────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/search.json.ts b/ooknet-design/src/pages/sear │
│ ch.json.ts │
│ index 2b58afd..b3e52ad 100644 │
│ --- a/ooknet-design/src/pages/search.json.ts │
│ +++ b/ooknet-design/src/pages/search.json.ts │
│ @@ -1,41 +1,26 @@ │
│ // Static index consumed by the masthead fuzzy search. `note` doubles │
│ -// as the type marker shown next to results. │
│ +// as the register marker shown next to results. │
│ import type { APIRoute } from "astro"; │
│ import { getCollection } from "astro:content"; │
│ import { published, topSegment } from "../lib/notes"; │
│ +import { readManifests, registerOf, registerLabel } from "../lib/registers"; │
│ │
│ const dirsOf = (id: string) => id.split("/").slice(0, -1).join("/").toUpperCase() │
│ ; │
│ │
│ export const GET: APIRoute = async () => { │
│ - const notes = await getCollection("notes", published); │
│ - const kb = await getCollection("kb", published); │
│ - const docs = await getCollection("docs", published); │
│ - const data = [ │
│ - ...notes.map((n) => ({ │
│ - note: "NOTE", │
│ - title: n.data.title, │
│ - category: topSegment(n.id), │
│ - tags: n.data.tags, │
│ - filed: `/NOTES/${dirsOf(n.id)}`, │
│ - url: `/notes/${n.id}/`, │
│ - })), │
│ - ...kb.map((e) => ({ │
│ - note: "KB", │
│ + const entries = await getCollection("kb", published); │
│ + const manifests = readManifests(); │
│ + const data = entries.map((e) => { │
│ + const info = registerOf(e.id, manifests); │
│ + return { │
│ + note: registerLabel(info.register), │
│ title: e.data.title, │
│ - category: e.data.source.toLowerCase(), │
│ + category: (info.title ?? topSegment(e.id)).toLowerCase(), │
│ tags: e.data.tags, │
│ filed: `/KB/${dirsOf(e.id)}`, │
│ url: `/kb/${e.id}/`, │
│ - })), │
│ - ...docs.map((d) => ({ │
│ - note: "DOC", │
│ - title: d.data.title, │
│ - category: topSegment(d.id), │
│ - tags: [], │
│ - filed: `/DOCS/${d.id.toUpperCase()}`, │
│ - url: `/docs/${d.id}/`, │
│ - })), │
│ - ]; │
│ + }; │
│ + }); │
│ return new Response(JSON.stringify(data), { │
│ headers: { "Content-Type": "application/json" }, │
│ }); │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...sign/src/pages/search.json.ts ───┐
│ diff --git a/ooknet-design/src/pages/search. │
│ json.ts b/ooknet-design/src/pages/search.jso │
│ n.ts │
│ index 2b58afd..b3e52ad 100644 │
│ --- a/ooknet-design/src/pages/search.json.ts │
│ +++ b/ooknet-design/src/pages/search.json.ts │
│ @@ -1,41 +1,26 @@ │
│ // Static index consumed by the masthead fu │
│ zzy search. `note` doubles │
│ -// as the type marker shown next to results │
│ . │
│ +// as the register marker shown next to res │
│ ults. │
│ import type { APIRoute } from "astro"; │
│ import { getCollection } from "astro:conten │
│ t"; │
│ import { published, topSegment } from "../l │
│ ib/notes"; │
│ +import { readManifests, registerOf, registe │
│ rLabel } from "../lib/registers"; │
│ │
│ const dirsOf = (id: string) => id.split("/" │
│ ).slice(0, -1).join("/").toUpperCase(); │
│ │
│ export const GET: APIRoute = async () => { │
│ - const notes = await getCollection("notes" │
│ , published); │
│ - const kb = await getCollection("kb", publ │
│ ished); │
│ - const docs = await getCollection("docs", │
│ published); │
│ - const data = [ │
│ - ...notes.map((n) => ({ │
│ - note: "NOTE", │
│ - title: n.data.title, │
│ - category: topSegment(n.id), │
│ - tags: n.data.tags, │
│ - filed: `/NOTES/${dirsOf(n.id)}`, │
│ - url: `/notes/${n.id}/`, │
│ - })), │
│ - ...kb.map((e) => ({ │
│ - note: "KB", │
│ + const entries = await getCollection("kb", │
│ published); │
│ + const manifests = readManifests(); │
│ + const data = entries.map((e) => { │
│ + const info = registerOf(e.id, manifests │
│ ); │
│ + return { │
│ + note: registerLabel(info.register), │
│ title: e.data.title, │
│ - category: e.data.source.toLowerCase() │
│ , │
│ + category: (info.title ?? topSegment(e │
│ .id)).toLowerCase(), │
│ tags: e.data.tags, │
│ filed: `/KB/${dirsOf(e.id)}`, │
│ url: `/kb/${e.id}/`, │
│ - })), │
│ - ...docs.map((d) => ({ │
│ - note: "DOC", │
│ - title: d.data.title, │
│ - category: topSegment(d.id), │
│ - tags: [], │
│ - filed: `/DOCS/${d.id.toUpperCase()}`, │
│ - url: `/docs/${d.id}/`, │
│ - })), │
│ - ]; │
│ + }; │
│ + }); │
│ return new Response(JSON.stringify(data), │
│ { │
│ headers: { "Content-Type": "application │
│ /json" }, │
│ }); │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/sitemap.xml.ts ───────────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/sitemap.xml.ts b/ooknet-design/src/pages/site │
│ map.xml.ts │
│ index f47b99d..eed111d 100644 │
│ --- a/ooknet-design/src/pages/sitemap.xml.ts │
│ +++ b/ooknet-design/src/pages/sitemap.xml.ts │
│ @@ -3,18 +3,17 @@ import { getCollection } from "astro:content"; │
│ import { published } from "../lib/notes"; │
│ import { buildSitemap } from "../lib/feeds"; │
│ │
│ -const STATIC_PAGES = ["/", "/notes/", "/kb/", "/docs/", "/about/", "/projects/", │
│ "/resources/", "/components/", "/status/"]; │
│ +const STATIC_PAGES = ["/", "/kb/", "/git/", "/projects/", "/about/", "/components │
│ /", "/status/"]; │
│ │
│ export const GET: APIRoute = async ({ site }) => { │
│ - const notes = await getCollection("notes", published); │
│ - const kb = await getCollection("kb", published); │
│ - const docs = await getCollection("docs", published); │
│ + const entries = await getCollection("kb", published); │
│ const abs = (path: string) => new URL(path, site).href; │
│ const xml = buildSitemap([ │
│ ...STATIC_PAGES.map((p) => ({ loc: abs(p) })), │
│ - ...notes.map((n) => ({ loc: abs(`/notes/${n.id}/`), lastmod: n.data.published │
│ })), │
│ - ...kb.map((e) => ({ loc: abs(`/kb/${e.id}/`), lastmod: e.data.retrieved })), │
│ - ...docs.map((d) => ({ loc: abs(`/docs/${d.id}/`) })), │
│ + ...entries.map((e) => ({ │
│ + loc: abs(`/kb/${e.id}/`), │
│ + lastmod: e.data.published ?? e.data.retrieved, │
│ + })), │
│ ]); │
│ return new Response(xml, { │
│ headers: { "Content-Type": "application/xml; charset=utf-8" }, │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...sign/src/pages/sitemap.xml.ts ───┐
│ diff --git a/ooknet-design/src/pages/sitemap │
│ .xml.ts b/ooknet-design/src/pages/sitemap.xm │
│ l.ts │
│ index f47b99d..eed111d 100644 │
│ --- a/ooknet-design/src/pages/sitemap.xml.ts │
│ +++ b/ooknet-design/src/pages/sitemap.xml.ts │
│ @@ -3,18 +3,17 @@ import { getCollection } f │
│ rom "astro:content"; │
│ import { published } from "../lib/notes"; │
│ import { buildSitemap } from "../lib/feeds" │
│ ; │
│ │
│ -const STATIC_PAGES = ["/", "/notes/", "/kb/ │
│ ", "/docs/", "/about/", "/projects/", "/reso │
│ urces/", "/components/", "/status/"]; │
│ +const STATIC_PAGES = ["/", "/kb/", "/git/", │
│ "/projects/", "/about/", "/components/", "/ │
│ status/"]; │
│ │
│ export const GET: APIRoute = async ({ site │
│ }) => { │
│ - const notes = await getCollection("notes" │
│ , published); │
│ - const kb = await getCollection("kb", publ │
│ ished); │
│ - const docs = await getCollection("docs", │
│ published); │
│ + const entries = await getCollection("kb", │
│ published); │
│ const abs = (path: string) => new URL(pat │
│ h, site).href; │
│ const xml = buildSitemap([ │
│ ...STATIC_PAGES.map((p) => ({ loc: abs( │
│ p) })), │
│ - ...notes.map((n) => ({ loc: abs(`/notes │
│ /${n.id}/`), lastmod: n.data.published })), │
│ - ...kb.map((e) => ({ loc: abs(`/kb/${e.i │
│ d}/`), lastmod: e.data.retrieved })), │
│ - ...docs.map((d) => ({ loc: abs(`/docs/$ │
│ {d.id}/`) })), │
│ + ...entries.map((e) => ({ │
│ + loc: abs(`/kb/${e.id}/`), │
│ + lastmod: e.data.published ?? e.data.r │
│ etrieved, │
│ + })), │
│ ]); │
│ return new Response(xml, { │
│ headers: { "Content-Type": "application │
│ /xml; charset=utf-8" }, │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/status.astro ─────────────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/status.astro b/ooknet-design/src/pages/status │
│ .astro │
│ index 78e5f1c..547bc04 100644 │
│ --- a/ooknet-design/src/pages/status.astro │
│ +++ b/ooknet-design/src/pages/status.astro │
│ @@ -16,30 +16,36 @@ import BigNumber from "../components/BigNumber/BigNumber.astro │
│ "; │
│ import BarChart from "../components/BarChart/BarChart.astro"; │
│ import Panel from "../components/Panel/Panel.astro"; │
│ import { rule, ruleD, row2, pad } from "../lib/ascii"; │
│ -import { published, byPublished, topSegment } from "../lib/notes"; │
│ +import { published, topSegment } from "../lib/notes"; │
│ +import { readManifests, registerOf } from "../lib/registers"; │
│ │
│ const section = (title: string, right: string) => (w: number) => │
│ [row2(title, right, w), ruleD(w)].join("\n"); │
│ const LABEL_W = 12; │
│ │
│ -const notes = (await getCollection("notes", published)).sort(byPublished); │
│ -const kb = await getCollection("kb", published); │
│ -const docs = await getCollection("docs", published); │
│ -const total = notes.length + kb.length + docs.length; │
│ +const entries = await getCollection("kb", published); │
│ +const manifests = readManifests(); │
│ +const reg = (e: { id: string }) => registerOf(e.id, manifests).register; │
│ +const notes = entries │
│ + .filter((e) => reg(e) === "notes" && e.data.published) │
│ + .sort((a, b) => a.data.published!.localeCompare(b.data.published!)); │
│ +const docs = entries.filter((e) => reg(e) === "docs"); │
│ +const reference = entries.filter((e) => reg(e) === "reference"); │
│ +const total = entries.length; │
│ │
│ const words = notes.map((n) => (n.body ?? "").split(/\s+/).filter(Boolean).length │
│ ); │
│ const totalWords = words.reduce((a, b) => a + b, 0); │
│ │
│ -const byDate = [...notes].sort((a, b) => (a.data.published < b.data.published ? 1 │
│ : -1)); │
│ +const byDate = [...notes].reverse(); │
│ const latestDate = byDate[0]?.data.published ?? null; │
│ const [year, month] = (latestDate ?? "").split("-").map(Number); │
│ const monthPrefix = latestDate ? `${year}-${String(month).padStart(2, "0")}` : "" │
│ ; │
│ const marks = byDate │
│ - .filter((n) => n.data.published.startsWith(monthPrefix)) │
│ + .filter((n) => n.data.published!.startsWith(monthPrefix)) │
│ .map((n) => Number(n.data.published.slice(8))); │
│ │
│ const tagFreq = new Map<string, number>(); │
│ -for (const entry of [...notes, ...kb]) { │
│ +for (const entry of entries) { │
│ for (const t of entry.data.tags) tagFreq.set(t, (tagFreq.get(t) ?? 0) + 1); │
│ } │
│ const topTags = [...tagFreq.entries()] │
│ @@ -48,10 +54,10 @@ const topTags = [...tagFreq.entries()] │
│ .map(([label, value]) => ({ label: label.toUpperCase(), value })); │
│ │
│ const log: TimelineEntry[] = byDate.map((n) => ({ │
│ - date: n.data.published, │
│ + date: n.data.published!, │
│ title: `FILED /${topSegment(n.id).toUpperCase()}`, │
│ desc: n.data.title.toLowerCase(), │
│ - href: `/notes/${n.id}/`, │
│ + href: `/kb/${n.id}/`, │
│ })); │
│ --- │
│ <ArticleLayout title="OOKNET KB - Status" │
│ @@ -65,8 +71,8 @@ const log: TimelineEntry[] = byDate.map((n) => ({ │
│ │
│ <KeyValue entries={[ │
│ ["NOTES", `${notes.length} FILED`], │
│ - ["KB", `${kb.length} ON FILE`], │
│ ["DOCS", `${docs.length} PAGES`], │
│ + ["REFERENCE", `${reference.length} ON FILE`], │
│ ...(notes.length > 0 │
│ ? [["WORDS", `${totalWords} TOTAL / ${Math.round(totalWords / notes.length) │
│ } AVG PER NOTE`] as [string, string]] │
│ : []), │
│ @@ -85,7 +91,7 @@ const log: TimelineEntry[] = byDate.map((n) => ({ │
│ │
│ {topTags.length > 0 && ( │
│ <Fragment> │
│ - <Frame build={section("TAXONOMY", `tags across notes + kb, top ${topTags.le │
│ ngth}`)} /> │
│ + <Frame build={section("TAXONOMY", `tags across the kb, top ${topTags.length │
│ }`)} /> │
│ <Pre>{" "}</Pre> │
│ <BarChart items={topTags} /> │
│ <Pre>{" "}</Pre> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...design/src/pages/status.astro ───┐
│ diff --git a/ooknet-design/src/pages/status. │
│ astro b/ooknet-design/src/pages/status.astro │
│ index 78e5f1c..547bc04 100644 │
│ --- a/ooknet-design/src/pages/status.astro │
│ +++ b/ooknet-design/src/pages/status.astro │
│ @@ -16,30 +16,36 @@ import BigNumber from ". │
│ ./components/BigNumber/BigNumber.astro"; │
│ import BarChart from "../components/BarChar │
│ t/BarChart.astro"; │
│ import Panel from "../components/Panel/Pane │
│ l.astro"; │
│ import { rule, ruleD, row2, pad } from "../ │
│ lib/ascii"; │
│ -import { published, byPublished, topSegment │
│ } from "../lib/notes"; │
│ +import { published, topSegment } from "../l │
│ ib/notes"; │
│ +import { readManifests, registerOf } from " │
│ ../lib/registers"; │
│ │
│ const section = (title: string, right: stri │
│ ng) => (w: number) => │
│ [row2(title, right, w), ruleD(w)].join("\ │
│ n"); │
│ const LABEL_W = 12; │
│ │
│ -const notes = (await getCollection("notes", │
│ published)).sort(byPublished); │
│ -const kb = await getCollection("kb", publis │
│ hed); │
│ -const docs = await getCollection("docs", pu │
│ blished); │
│ -const total = notes.length + kb.length + do │
│ cs.length; │
│ +const entries = await getCollection("kb", p │
│ ublished); │
│ +const manifests = readManifests(); │
│ +const reg = (e: { id: string }) => register │
│ Of(e.id, manifests).register; │
│ +const notes = entries │
│ + .filter((e) => reg(e) === "notes" && e.da │
│ ta.published) │
│ + .sort((a, b) => a.data.published!.localeC │
│ ompare(b.data.published!)); │
│ +const docs = entries.filter((e) => reg(e) = │
│ == "docs"); │
│ +const reference = entries.filter((e) => reg │
│ (e) === "reference"); │
│ +const total = entries.length; │
│ │
│ const words = notes.map((n) => (n.body ?? " │
│ ").split(/\s+/).filter(Boolean).length); │
│ const totalWords = words.reduce((a, b) => a │
│ + b, 0); │
│ │
│ -const byDate = [...notes].sort((a, b) => (a │
│ .data.published < b.data.published ? 1 : -1) │
│ ); │
│ +const byDate = [...notes].reverse(); │
│ const latestDate = byDate[0]?.data.publishe │
│ d ?? null; │
│ const [year, month] = (latestDate ?? "").sp │
│ lit("-").map(Number); │
│ const monthPrefix = latestDate ? `${year}-$ │
│ {String(month).padStart(2, "0")}` : ""; │
│ const marks = byDate │
│ - .filter((n) => n.data.published.startsWit │
│ h(monthPrefix)) │
│ + .filter((n) => n.data.published!.startsWi │
│ th(monthPrefix)) │
│ .map((n) => Number(n.data.published.slice │
│ (8))); │
│ │
│ const tagFreq = new Map<string, number>(); │
│ -for (const entry of [...notes, ...kb]) { │
│ +for (const entry of entries) { │
│ for (const t of entry.data.tags) tagFreq. │
│ set(t, (tagFreq.get(t) ?? 0) + 1); │
│ } │
│ const topTags = [...tagFreq.entries()] │
│ @@ -48,10 +54,10 @@ const topTags = [...tagF │
│ req.entries()] │
│ .map(([label, value]) => ({ label: label. │
│ toUpperCase(), value })); │
│ │
│ const log: TimelineEntry[] = byDate.map((n) │
│ => ({ │
│ - date: n.data.published, │
│ + date: n.data.published!, │
│ title: `FILED /${topSegment(n.id).toUpper │
│ Case()}`, │
│ desc: n.data.title.toLowerCase(), │
│ - href: `/notes/${n.id}/`, │
│ + href: `/kb/${n.id}/`, │
│ })); │
│ --- │
│ <ArticleLayout title="OOKNET KB - Status" │
│ @@ -65,8 +71,8 @@ const log: TimelineEntry[] │
│ = byDate.map((n) => ({ │
│ │
│ <KeyValue entries={[ │
│ ["NOTES", `${notes.length} FILED`], │
│ - ["KB", `${kb.length} ON FILE`], │
│ ["DOCS", `${docs.length} PAGES`], │
│ + ["REFERENCE", `${reference.length} ON F │
│ ILE`], │
│ ...(notes.length > 0 │
│ ? [["WORDS", `${totalWords} TOTAL / $ │
│ {Math.round(totalWords / notes.length)} AVG │
│ PER NOTE`] as [string, string]] │
│ : []), │
│ @@ -85,7 +91,7 @@ const log: TimelineEntry[] │
│ = byDate.map((n) => ({ │
│ │
│ {topTags.length > 0 && ( │
│ <Fragment> │
│ - <Frame build={section("TAXONOMY", `ta │
│ gs across notes + kb, top ${topTags.length}` │
│ )} /> │
│ + <Frame build={section("TAXONOMY", `ta │
│ gs across the kb, top ${topTags.length}`)} / │
│ > │
│ <Pre>{" "}</Pre> │
│ <BarChart items={topTags} /> │
│ <Pre>{" "}</Pre> │
└──────────────────────────────────────────────┘
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET