HASH 012d217add3d
DATE 2026-07-17
SUBJECT web: meta and og tags, rss feed, sitemap, robots.txt
FILES 18 CHANGED
HASH 012d217add3d
DATE 2026-07-17
SUBJECT web: meta and og tags, rss feed,
sitemap, robots.txt
FILES 18 CHANGED
┌─ ooknet-design/astro.config.ts ────────────────────────────────────────────┐
│ diff --git a/ooknet-design/astro.config.ts b/ooknet-design/astro.config.ts │
│ index 1d569a4..b651c66 100644 │
│ --- a/ooknet-design/astro.config.ts │
│ +++ b/ooknet-design/astro.config.ts │
│ @@ -3,6 +3,7 @@ import { rehypeAscii } from "./src/lib/rehype-ascii"; │
│ import { remarkWikilinks } from "./src/lib/remark-wikilinks"; │
│ │
│ export default defineConfig({ │
│ + site: "https://ooknet.org", │
│ markdown: { │
│ syntaxHighlight: false, │
│ remarkPlugins: [remarkWikilinks], │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ooknet-design/astro.config.ts ──────┐
│ diff --git a/ooknet-design/astro.config.ts b │
│ /ooknet-design/astro.config.ts │
│ index 1d569a4..b651c66 100644 │
│ --- a/ooknet-design/astro.config.ts │
│ +++ b/ooknet-design/astro.config.ts │
│ @@ -3,6 +3,7 @@ import { rehypeAscii } from │
│ "./src/lib/rehype-ascii"; │
│ import { remarkWikilinks } from "./src/lib/ │
│ remark-wikilinks"; │
│ │
│ export default defineConfig({ │
│ + site: "https://ooknet.org", │
│ markdown: { │
│ syntaxHighlight: false, │
│ remarkPlugins: [remarkWikilinks], │
└──────────────────────────────────────────────┘
┌─ ooknet-design/public/robots.txt ──────────────────────────────────────────┐
│ diff --git a/ooknet-design/public/robots.txt b/ooknet-design/public/robots.txt │
│ new file mode 100644 │
│ index 0000000..e2a357d │
│ --- /dev/null │
│ +++ b/ooknet-design/public/robots.txt │
│ @@ -0,0 +1,4 @@ │
│ +User-agent: * │
│ +Allow: / │
│ + │
│ +Sitemap: https://ooknet.org/sitemap.xml │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ooknet-design/public/robots.txt ────┐
│ diff --git a/ooknet-design/public/robots.txt │
│ b/ooknet-design/public/robots.txt │
│ new file mode 100644 │
│ index 0000000..e2a357d │
│ --- /dev/null │
│ +++ b/ooknet-design/public/robots.txt │
│ @@ -0,0 +1,4 @@ │
│ +User-agent: * │
│ +Allow: / │
│ + │
│ +Sitemap: https://ooknet.org/sitemap.xml │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/layouts/ArticleLayout/ArticleLayout.astro ──────────────┐
│ diff --git a/ooknet-design/src/layouts/ArticleLayout/ArticleLayout.astro b/ooknet- │
│ design/src/layouts/ArticleLayout/ArticleLayout.astro │
│ index 2eefd2b..bc0c0c7 100644 │
│ --- a/ooknet-design/src/layouts/ArticleLayout/ArticleLayout.astro │
│ +++ b/ooknet-design/src/layouts/ArticleLayout/ArticleLayout.astro │
│ @@ -25,7 +25,12 @@ import plex600 from "@fontsource/ibm-plex-mono/files/ibm-plex-m │
│ ono-latin-600-nor │
│ // symbols2 carries the box-drawing glyphs - every frame needs it at │
│ // first paint just as much as latin. │
│ const preloadFonts = [cascadia400, cascadia700, cascadiaSym, plex400, plex600]; │
│ -const { title = "OOKNET KB" } = Astro.props; │
│ +const { │
│ + title = "OOKNET KB", │
│ + description = "OOKNET knowledge base - a text-mode wiki of filed notes, docs, a │
│ nd references, rendered on a strict ASCII grid.", │
│ + type = "website", │
│ +} = Astro.props; │
│ +const canonical = new URL(Astro.url.pathname, Astro.site); │
│ --- │
│ <!doctype html> │
│ <html lang="en"> │
│ @@ -37,6 +42,15 @@ const { title = "OOKNET KB" } = Astro.props; │
│ <link rel="preload" as="font" type="font/woff2" crossorigin href={href} /> │
│ ))} │
│ <title>{title}</title> │
│ + <meta name="description" content={description} /> │
│ + <link rel="canonical" href={canonical} /> │
│ + <meta property="og:site_name" content="OOKNET KB" /> │
│ + <meta property="og:type" content={type} /> │
│ + <meta property="og:title" content={title} /> │
│ + <meta property="og:description" content={description} /> │
│ + <meta property="og:url" content={canonical} /> │
│ + <meta name="twitter:card" content="summary" /> │
│ + <link rel="alternate" type="application/rss+xml" title="OOKNET KB" href="/rss │
│ .xml" /> │
│ <script is:inline> │
│ (() => { │
│ try { │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...cleLayout/ArticleLayout.astro ───┐
│ diff --git a/ooknet-design/src/layouts/Artic │
│ leLayout/ArticleLayout.astro b/ooknet-design │
│ /src/layouts/ArticleLayout/ArticleLayout.ast │
│ ro │
│ index 2eefd2b..bc0c0c7 100644 │
│ --- a/ooknet-design/src/layouts/ArticleLayou │
│ t/ArticleLayout.astro │
│ +++ b/ooknet-design/src/layouts/ArticleLayou │
│ t/ArticleLayout.astro │
│ @@ -25,7 +25,12 @@ import plex600 from "@fon │
│ tsource/ibm-plex-mono/files/ibm-plex-mono-la │
│ tin-600-nor │
│ // symbols2 carries the box-drawing glyphs │
│ - every frame needs it at │
│ // first paint just as much as latin. │
│ const preloadFonts = [cascadia400, cascadia │
│ 700, cascadiaSym, plex400, plex600]; │
│ -const { title = "OOKNET KB" } = Astro.props │
│ ; │
│ +const { │
│ + title = "OOKNET KB", │
│ + description = "OOKNET knowledge base - a │
│ text-mode wiki of filed notes, docs, and ref │
│ erences, rendered on a strict ASCII grid.", │
│ + type = "website", │
│ +} = Astro.props; │
│ +const canonical = new URL(Astro.url.pathnam │
│ e, Astro.site); │
│ --- │
│ <!doctype html> │
│ <html lang="en"> │
│ @@ -37,6 +42,15 @@ const { title = "OOKNET K │
│ B" } = Astro.props; │
│ <link rel="preload" as="font" type="f │
│ ont/woff2" crossorigin href={href} /> │
│ ))} │
│ <title>{title}</title> │
│ + <meta name="description" content={descr │
│ iption} /> │
│ + <link rel="canonical" href={canonical} │
│ /> │
│ + <meta property="og:site_name" content=" │
│ OOKNET KB" /> │
│ + <meta property="og:type" content={type} │
│ /> │
│ + <meta property="og:title" content={titl │
│ e} /> │
│ + <meta property="og:description" content │
│ ={description} /> │
│ + <meta property="og:url" content={canoni │
│ cal} /> │
│ + <meta name="twitter:card" content="summ │
│ ary" /> │
│ + <link rel="alternate" type="application │
│ /rss+xml" title="OOKNET KB" href="/rss.xml" │
│ /> │
│ <script is:inline> │
│ (() => { │
│ try { │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/lib/feeds.test.ts ──────────────────────────────────────┐
│ diff --git a/ooknet-design/src/lib/feeds.test.ts b/ooknet-design/src/lib/feeds.tes │
│ t.ts │
│ new file mode 100644 │
│ index 0000000..da68f58 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/lib/feeds.test.ts │
│ @@ -0,0 +1,45 @@ │
│ +import { describe, expect, it } from "vitest"; │
│ +import { buildRss, buildSitemap } from "./feeds"; │
│ + │
│ +const item = { │
│ + title: "ENTROPY <BOUNDS> & GRAPHS", │
│ + url: "https://ooknet.org/notes/n217-entropy-bounds/", │
│ + published: "2026-04-01", │
│ + description: 'Bounds on "sparse" graphs.', │
│ +}; │
│ + │
│ +describe("buildRss", () => { │
│ + const xml = buildRss({ │
│ + title: "OOKNET KB", │
│ + site: "https://ooknet.org/", │
│ + description: "notes feed", │
│ + items: [item], │
│ + }); │
│ + │
│ + it("escapes xml in titles and descriptions", () => { │
│ + expect(xml).toContain("ENTROPY <BOUNDS> & GRAPHS"); │
│ + expect(xml).toContain(""sparse""); │
│ + expect(xml).not.toContain("<BOUNDS>"); │
│ + }); │
│ + │
│ + it("formats pubDate as RFC 822", () => { │
│ + expect(xml).toContain("<pubDate>Wed, 01 Apr 2026"); │
│ + }); │
│ + │
│ + it("links item and self", () => { │
│ + expect(xml).toContain(`<link>${item.url}</link>`); │
│ + expect(xml).toContain('href="https://ooknet.org/rss.xml" rel="self"'); │
│ + }); │
│ +}); │
│ + │
│ +describe("buildSitemap", () => { │
│ + it("emits one url entry per page, lastmod only when known", () => { │
│ + const xml = buildSitemap([ │
│ + { loc: "https://ooknet.org/" }, │
│ + { loc: "https://ooknet.org/notes/n217-entropy-bounds/", lastmod: "2026-04-0 │
│ 1" }, │
│ + ]); │
│ + expect(xml.match(/<url>/g)).toHaveLength(2); │
│ + expect(xml.match(/<lastmod>/g)).toHaveLength(1); │
│ + expect(xml).toContain("<lastmod>2026-04-01</lastmod>"); │
│ + }); │
│ +}); │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...-design/src/lib/feeds.test.ts ───┐
│ diff --git a/ooknet-design/src/lib/feeds.tes │
│ t.ts b/ooknet-design/src/lib/feeds.test.ts │
│ new file mode 100644 │
│ index 0000000..da68f58 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/lib/feeds.test.ts │
│ @@ -0,0 +1,45 @@ │
│ +import { describe, expect, it } from "vites │
│ t"; │
│ +import { buildRss, buildSitemap } from "./f │
│ eeds"; │
│ + │
│ +const item = { │
│ + title: "ENTROPY <BOUNDS> & GRAPHS", │
│ + url: "https://ooknet.org/notes/n217-entro │
│ py-bounds/", │
│ + published: "2026-04-01", │
│ + description: 'Bounds on "sparse" graphs.' │
│ , │
│ +}; │
│ + │
│ +describe("buildRss", () => { │
│ + const xml = buildRss({ │
│ + title: "OOKNET KB", │
│ + site: "https://ooknet.org/", │
│ + description: "notes feed", │
│ + items: [item], │
│ + }); │
│ + │
│ + it("escapes xml in titles and description │
│ s", () => { │
│ + expect(xml).toContain("ENTROPY <BOUN │
│ DS> & GRAPHS"); │
│ + expect(xml).toContain(""sparse&quo │
│ t;"); │
│ + expect(xml).not.toContain("<BOUNDS>"); │
│ + }); │
│ + │
│ + it("formats pubDate as RFC 822", () => { │
│ + expect(xml).toContain("<pubDate>Wed, 01 │
│ Apr 2026"); │
│ + }); │
│ + │
│ + it("links item and self", () => { │
│ + expect(xml).toContain(`<link>${item.url │
│ }</link>`); │
│ + expect(xml).toContain('href="https://oo │
│ knet.org/rss.xml" rel="self"'); │
│ + }); │
│ +}); │
│ + │
│ +describe("buildSitemap", () => { │
│ + it("emits one url entry per page, lastmod │
│ only when known", () => { │
│ + const xml = buildSitemap([ │
│ + { loc: "https://ooknet.org/" }, │
│ + { loc: "https://ooknet.org/notes/n217 │
│ -entropy-bounds/", lastmod: "2026-04-01" }, │
│ + ]); │
│ + expect(xml.match(/<url>/g)).toHaveLengt │
│ h(2); │
│ + expect(xml.match(/<lastmod>/g)).toHaveL │
│ ength(1); │
│ + expect(xml).toContain("<lastmod>2026-04 │
│ -01</lastmod>"); │
│ + }); │
│ +}); │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/lib/feeds.ts ───────────────────────────────────────────┐
│ diff --git a/ooknet-design/src/lib/feeds.ts b/ooknet-design/src/lib/feeds.ts │
│ new file mode 100644 │
│ index 0000000..aae243f │
│ --- /dev/null │
│ +++ b/ooknet-design/src/lib/feeds.ts │
│ @@ -0,0 +1,78 @@ │
│ +// Hand-rolled RSS and sitemap XML - no integration dependency, and the │
│ +// builders stay pure and testable. │
│ + │
│ +export interface FeedItem { │
│ + title: string; │
│ + url: string; // absolute │
│ + published: string; // ISO date │
│ + description: string; │
│ +} │
│ + │
│ +export interface SitemapUrl { │
│ + loc: string; // absolute │
│ + lastmod?: string; // ISO date │
│ +} │
│ + │
│ +const esc = (s: string) => │
│ + s │
│ + .replace(/&/g, "&") │
│ + .replace(/</g, "<") │
│ + .replace(/>/g, ">") │
│ + .replace(/"/g, """); │
│ + │
│ +export interface RssOpts { │
│ + title: string; │
│ + site: string; // absolute site url │
│ + description: string; │
│ + items: FeedItem[]; │
│ +} │
│ + │
│ +export function buildRss({ title, site, description, items }: RssOpts): string { │
│ + const channel = items │
│ + .map((it) => │
│ + [ │
│ + " <item>", │
│ + ` <title>${esc(it.title)}</title>`, │
│ + ` <link>${esc(it.url)}</link>`, │
│ + ` <guid>${esc(it.url)}</guid>`, │
│ + ` <pubDate>${new Date(it.published).toUTCString()}</pubDate>`, │
│ + ` <description>${esc(it.description)}</description>`, │
│ + " </item>", │
│ + ].join("\n"), │
│ + ) │
│ + .join("\n"); │
│ + return [ │
│ + `<?xml version="1.0" encoding="UTF-8"?>`, │
│ + `<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">`, │
│ + " <channel>", │
│ + ` <title>${esc(title)}</title>`, │
│ + ` <link>${esc(site)}</link>`, │
│ + ` <atom:link href="${esc(site)}rss.xml" rel="self" type="application/rss+x │
│ ml"/>`, │
│ + ` <description>${esc(description)}</description>`, │
│ + " <language>en</language>", │
│ + channel, │
│ + " </channel>", │
│ + "</rss>", │
│ + "", │
│ + ].join("\n"); │
│ +} │
│ + │
│ +export function buildSitemap(urls: SitemapUrl[]): string { │
│ + const body = urls │
│ + .map((u) => │
│ + [ │
│ + " <url>", │
│ + ` <loc>${esc(u.loc)}</loc>`, │
│ + ...(u.lastmod ? [` <lastmod>${esc(u.lastmod)}</lastmod>`] : []), │
│ + " </url>", │
│ + ].join("\n"), │
│ + ) │
│ + .join("\n"); │
│ + return [ │
│ + `<?xml version="1.0" encoding="UTF-8"?>`, │
│ + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`, │
│ + body, │
│ + "</urlset>", │
│ + "", │
│ + ].join("\n"); │
│ +} │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ooknet-design/src/lib/feeds.ts ─────┐
│ diff --git a/ooknet-design/src/lib/feeds.ts │
│ b/ooknet-design/src/lib/feeds.ts │
│ new file mode 100644 │
│ index 0000000..aae243f │
│ --- /dev/null │
│ +++ b/ooknet-design/src/lib/feeds.ts │
│ @@ -0,0 +1,78 @@ │
│ +// Hand-rolled RSS and sitemap XML - no int │
│ egration dependency, and the │
│ +// builders stay pure and testable. │
│ + │
│ +export interface FeedItem { │
│ + title: string; │
│ + url: string; // absolute │
│ + published: string; // ISO date │
│ + description: string; │
│ +} │
│ + │
│ +export interface SitemapUrl { │
│ + loc: string; // absolute │
│ + lastmod?: string; // ISO date │
│ +} │
│ + │
│ +const esc = (s: string) => │
│ + s │
│ + .replace(/&/g, "&") │
│ + .replace(/</g, "<") │
│ + .replace(/>/g, ">") │
│ + .replace(/"/g, """); │
│ + │
│ +export interface RssOpts { │
│ + title: string; │
│ + site: string; // absolute site url │
│ + description: string; │
│ + items: FeedItem[]; │
│ +} │
│ + │
│ +export function buildRss({ title, site, des │
│ cription, items }: RssOpts): string { │
│ + const channel = items │
│ + .map((it) => │
│ + [ │
│ + " <item>", │
│ + ` <title>${esc(it.title)}</tit │
│ le>`, │
│ + ` <link>${esc(it.url)}</link>` │
│ , │
│ + ` <guid>${esc(it.url)}</guid>` │
│ , │
│ + ` <pubDate>${new Date(it.publi │
│ shed).toUTCString()}</pubDate>`, │
│ + ` <description>${esc(it.descri │
│ ption)}</description>`, │
│ + " </item>", │
│ + ].join("\n"), │
│ + ) │
│ + .join("\n"); │
│ + return [ │
│ + `<?xml version="1.0" encoding="UTF-8"?> │
│ `, │
│ + `<rss version="2.0" xmlns:atom="http:// │
│ www.w3.org/2005/Atom">`, │
│ + " <channel>", │
│ + ` <title>${esc(title)}</title>`, │
│ + ` <link>${esc(site)}</link>`, │
│ + ` <atom:link href="${esc(site)}rss.x │
│ ml" rel="self" type="application/rss+xml"/>` │
│ , │
│ + ` <description>${esc(description)}</ │
│ description>`, │
│ + " <language>en</language>", │
│ + channel, │
│ + " </channel>", │
│ + "</rss>", │
│ + "", │
│ + ].join("\n"); │
│ +} │
│ + │
│ +export function buildSitemap(urls: SitemapU │
│ rl[]): string { │
│ + const body = urls │
│ + .map((u) => │
│ + [ │
│ + " <url>", │
│ + ` <loc>${esc(u.loc)}</loc>`, │
│ + ...(u.lastmod ? [` <lastmod>${es │
│ c(u.lastmod)}</lastmod>`] : []), │
│ + " </url>", │
│ + ].join("\n"), │
│ + ) │
│ + .join("\n"); │
│ + return [ │
│ + `<?xml version="1.0" encoding="UTF-8"?> │
│ `, │
│ + `<urlset xmlns="http://www.sitemaps.org │
│ /schemas/sitemap/0.9">`, │
│ + body, │
│ + "</urlset>", │
│ + "", │
│ + ].join("\n"); │
│ +} │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/lib/meta.test.ts ───────────────────────────────────────┐
│ diff --git a/ooknet-design/src/lib/meta.test.ts b/ooknet-design/src/lib/meta.test. │
│ ts │
│ new file mode 100644 │
│ index 0000000..65e1609 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/lib/meta.test.ts │
│ @@ -0,0 +1,35 @@ │
│ +import { describe, expect, it } from "vitest"; │
│ +import { describeBody } from "./meta"; │
│ + │
│ +describe("describeBody", () => { │
│ + it("takes the first prose paragraph, skipping headings and fences", () => { │
│ + const md = "# Title\n\n```\ncode\n```\n\nThe actual lede sentence.\n\nSecond │
│ para."; │
│ + expect(describeBody(md)).toBe("The actual lede sentence."); │
│ + }); │
│ + │
│ + it("skips tables, quotes, and lists", () => { │
│ + const md = "| A | B |\n| - | - |\n\n> quote\n\n- item\n\nProse at last."; │
│ + expect(describeBody(md)).toBe("Prose at last."); │
│ + }); │
│ + │
│ + it("strips wikilinks, links, and emphasis", () => { │
│ + const md = "See [[N217|the bound]] and [docs](/docs/) for *context* on `check │
│ Grid`."; │
│ + expect(describeBody(md)).toBe("See the bound and docs for context on checkGri │
│ d."); │
│ + }); │
│ + │
│ + it("collapses hard-wrapped lines into one sentence", () => { │
│ + expect(describeBody("wrapped\nacross\nlines")).toBe("wrapped across lines"); │
│ + }); │
│ + │
│ + it("truncates at a word boundary with an ellipsis", () => { │
│ + const long = "word ".repeat(60).trim(); │
│ + const out = describeBody(long, 40); │
│ + expect([...out].length).toBeLessThanOrEqual(40); │
│ + expect(out.endsWith("...")).toBe(true); │
│ + expect(out).not.toContain("wor..."); │
│ + }); │
│ + │
│ + it("returns empty for bodies with no prose", () => { │
│ + expect(describeBody("# Only\n\n## Headings")).toBe(""); │
│ + }); │
│ +}); │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...t-design/src/lib/meta.test.ts ───┐
│ diff --git a/ooknet-design/src/lib/meta.test │
│ .ts b/ooknet-design/src/lib/meta.test.ts │
│ new file mode 100644 │
│ index 0000000..65e1609 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/lib/meta.test.ts │
│ @@ -0,0 +1,35 @@ │
│ +import { describe, expect, it } from "vites │
│ t"; │
│ +import { describeBody } from "./meta"; │
│ + │
│ +describe("describeBody", () => { │
│ + it("takes the first prose paragraph, skip │
│ ping headings and fences", () => { │
│ + const md = "# Title\n\n```\ncode\n```\n │
│ \nThe actual lede sentence.\n\nSecond para." │
│ ; │
│ + expect(describeBody(md)).toBe("The actu │
│ al lede sentence."); │
│ + }); │
│ + │
│ + it("skips tables, quotes, and lists", () │
│ => { │
│ + const md = "| A | B |\n| - | - |\n\n> q │
│ uote\n\n- item\n\nProse at last."; │
│ + expect(describeBody(md)).toBe("Prose at │
│ last."); │
│ + }); │
│ + │
│ + it("strips wikilinks, links, and emphasis │
│ ", () => { │
│ + const md = "See [[N217|the bound]] and │
│ [docs](/docs/) for *context* on `checkGrid`. │
│ "; │
│ + expect(describeBody(md)).toBe("See the │
│ bound and docs for context on checkGrid."); │
│ + }); │
│ + │
│ + it("collapses hard-wrapped lines into one │
│ sentence", () => { │
│ + expect(describeBody("wrapped\nacross\nl │
│ ines")).toBe("wrapped across lines"); │
│ + }); │
│ + │
│ + it("truncates at a word boundary with an │
│ ellipsis", () => { │
│ + const long = "word ".repeat(60).trim(); │
│ + const out = describeBody(long, 40); │
│ + expect([...out].length).toBeLessThanOrE │
│ qual(40); │
│ + expect(out.endsWith("...")).toBe(true); │
│ + expect(out).not.toContain("wor..."); │
│ + }); │
│ + │
│ + it("returns empty for bodies with no pros │
│ e", () => { │
│ + expect(describeBody("# Only\n\n## Headi │
│ ngs")).toBe(""); │
│ + }); │
│ +}); │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/lib/meta.ts ────────────────────────────────────────────┐
│ diff --git a/ooknet-design/src/lib/meta.ts b/ooknet-design/src/lib/meta.ts │
│ new file mode 100644 │
│ index 0000000..1efaed9 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/lib/meta.ts │
│ @@ -0,0 +1,28 @@ │
│ +// Page descriptions for <meta>, OG, and feeds - derived from the first │
│ +// prose paragraph of a markdown body so notes don't need a description │
│ +// field in frontmatter. │
│ + │
│ +/** First prose paragraph, markdown stripped, truncated at a word │
│ + * boundary. Empty string when the body has no prose (tables, code, │
│ + * and headings don't count). */ │
│ +export function describeBody(md: string, max = 160): string { │
│ + let text = ""; │
│ + for (const raw of md.split(/\n\s*\n/)) { │
│ + const b = raw.trim(); │
│ + if (!b) continue; │
│ + if (/^(#|```|~~~|\||>|[-*+] |\d+\. |!\[|<)/.test(b)) continue; │
│ + text = b; │
│ + break; │
│ + } │
│ + if (!text) return ""; │
│ + const clean = text │
│ + .replace(/\[\[([^\]|]+)\|([^\]]+)\]\]/g, "$2") │
│ + .replace(/\[\[([^\]|]+)\]\]/g, "$1") │
│ + .replace(/\[([^\]]+)\]\([^)]*\)/g, "$1") │
│ + .replace(/[`*_]/g, "") │
│ + .replace(/\s+/g, " ") │
│ + .trim(); │
│ + if ([...clean].length <= max) return clean; │
│ + const cut = [...clean].slice(0, max - 1).join(""); │
│ + return cut.replace(/\s+\S*$/, "") + "..."; │
│ +} │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ooknet-design/src/lib/meta.ts ──────┐
│ diff --git a/ooknet-design/src/lib/meta.ts b │
│ /ooknet-design/src/lib/meta.ts │
│ new file mode 100644 │
│ index 0000000..1efaed9 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/lib/meta.ts │
│ @@ -0,0 +1,28 @@ │
│ +// Page descriptions for <meta>, OG, and fe │
│ eds - derived from the first │
│ +// prose paragraph of a markdown body so no │
│ tes don't need a description │
│ +// field in frontmatter. │
│ + │
│ +/** First prose paragraph, markdown strippe │
│ d, truncated at a word │
│ + * boundary. Empty string when the body ha │
│ s no prose (tables, code, │
│ + * and headings don't count). */ │
│ +export function describeBody(md: string, ma │
│ x = 160): string { │
│ + let text = ""; │
│ + for (const raw of md.split(/\n\s*\n/)) { │
│ + const b = raw.trim(); │
│ + if (!b) continue; │
│ + if (/^(#|```|~~~|\||>|[-*+] |\d+\. |!\[ │
│ |<)/.test(b)) continue; │
│ + text = b; │
│ + break; │
│ + } │
│ + if (!text) return ""; │
│ + const clean = text │
│ + .replace(/\[\[([^\]|]+)\|([^\]]+)\]\]/g │
│ , "$2") │
│ + .replace(/\[\[([^\]|]+)\]\]/g, "$1") │
│ + .replace(/\[([^\]]+)\]\([^)]*\)/g, "$1" │
│ ) │
│ + .replace(/[`*_]/g, "") │
│ + .replace(/\s+/g, " ") │
│ + .trim(); │
│ + if ([...clean].length <= max) return clea │
│ n; │
│ + const cut = [...clean].slice(0, max - 1). │
│ join(""); │
│ + return cut.replace(/\s+\S*$/, "") + "..." │
│ ; │
│ +} │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/about.astro ──────────────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/about.astro b/ooknet-design/src/pages/about.a │
│ stro │
│ index 95c4cda..e726ec8 100644 │
│ --- a/ooknet-design/src/pages/about.astro │
│ +++ b/ooknet-design/src/pages/about.astro │
│ @@ -23,7 +23,8 @@ const buildColophon = (w: number) => │
│ ruleD(w), │
│ ].join("\n"); │
│ --- │
│ -<ArticleLayout title="OOKNET KB - About"> │
│ +<ArticleLayout title="OOKNET KB - About" │
│ + description="What OOKNET is: a personal knowledge base rendered as pure text on │
│ a monospace grid - every border, chart, and frame is a real character."> │
│ <TopHeader /> │
│ <Frame build={buildHeader} /> │
│ <Prose> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...-design/src/pages/about.astro ───┐
│ diff --git a/ooknet-design/src/pages/about.a │
│ stro b/ooknet-design/src/pages/about.astro │
│ index 95c4cda..e726ec8 100644 │
│ --- a/ooknet-design/src/pages/about.astro │
│ +++ b/ooknet-design/src/pages/about.astro │
│ @@ -23,7 +23,8 @@ const buildColophon = (w: │
│ number) => │
│ ruleD(w), │
│ ].join("\n"); │
│ --- │
│ -<ArticleLayout title="OOKNET KB - About"> │
│ +<ArticleLayout title="OOKNET KB - About" │
│ + description="What OOKNET is: a personal k │
│ nowledge base rendered as pure text on a mon │
│ ospace grid - every border, chart, and frame │
│ is a real character."> │
│ <TopHeader /> │
│ <Frame build={buildHeader} /> │
│ <Prose> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/components.astro ─────────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/components.astro b/ooknet-design/src/pages/co │
│ mponents.astro │
│ index 9f7d9f0..0f28dcb 100644 │
│ --- a/ooknet-design/src/pages/components.astro │
│ +++ b/ooknet-design/src/pages/components.astro │
│ @@ -95,7 +95,8 @@ const FILED_TREE: TreeNode[] = [ │
│ }, │
│ ]; │
│ --- │
│ -<ArticleLayout title="OOKNET KB - Components"> │
│ +<ArticleLayout title="OOKNET KB - Components" │
│ + description="The OOKNET ASCII component library - every frame, chart, and contr │
│ ol the design system ships, demonstrated live."> │
│ <TopHeader /> │
│ <Frame build={(w) => [row2("COMPONENT INDEX", "DESIGN SYSTEM v0.1", w), ruleD(w │
│ )].join("\n")} /> │
│ <Pre>{" "}</Pre> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...gn/src/pages/components.astro ───┐
│ diff --git a/ooknet-design/src/pages/compone │
│ nts.astro b/ooknet-design/src/pages/componen │
│ ts.astro │
│ index 9f7d9f0..0f28dcb 100644 │
│ --- a/ooknet-design/src/pages/components.ast │
│ ro │
│ +++ b/ooknet-design/src/pages/components.ast │
│ ro │
│ @@ -95,7 +95,8 @@ const FILED_TREE: TreeNode │
│ [] = [ │
│ }, │
│ ]; │
│ --- │
│ -<ArticleLayout title="OOKNET KB - Component │
│ s"> │
│ +<ArticleLayout title="OOKNET KB - Component │
│ s" │
│ + description="The OOKNET ASCII component l │
│ ibrary - every frame, chart, and control the │
│ design system ships, demonstrated live."> │
│ <TopHeader /> │
│ <Frame build={(w) => [row2("COMPONENT IND │
│ EX", "DESIGN SYSTEM v0.1", w), ruleD(w)].joi │
│ n("\n")} /> │
│ <Pre>{" "}</Pre> │
└──────────────────────────────────────────────┘
┌─ 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 │
│ index 97c33ee..2210104 100644 │
│ --- a/ooknet-design/src/pages/docs/[...slug].astro │
│ +++ b/ooknet-design/src/pages/docs/[...slug].astro │
│ @@ -15,6 +15,7 @@ import { box, center, len, rule, ruleD, row2, wrap } from "../.. │
│ /lib/ascii"; │
│ import { published } from "../../lib/notes"; │
│ import { docsOrder, 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); │
│ @@ -54,6 +55,8 @@ interface Props { │
│ } │
│ const { doc, prev, next, all, refs } = Astro.props as Props; │
│ 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); │
│ @@ -65,7 +68,7 @@ const buildTitleCard = (w: number) => { │
│ return card.split("\n").map((line) => center(line, w)).join("\n"); │
│ }; │
│ --- │
│ -<ArticleLayout title={`DOCS - ${doc.data.title}`}> │
│ +<ArticleLayout title={`DOCS - ${doc.data.title}`} description={description} type= │
│ "article"> │
│ <TopHeader /> │
│ <Frame build={(w) => [row2("OOKNET DOCUMENTATION", doc.id.toUpperCase(), w), ru │
│ le(w)].join("\n")} /> │
│ <Pre>{" "}</Pre> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...rc/pages/docs/[...slug].astro ───┐
│ diff --git a/ooknet-design/src/pages/docs/[. │
│ ..slug].astro b/ooknet-design/src/pages/docs │
│ /[...slug].astro │
│ index 97c33ee..2210104 100644 │
│ --- a/ooknet-design/src/pages/docs/[...slug] │
│ .astro │
│ +++ b/ooknet-design/src/pages/docs/[...slug] │
│ .astro │
│ @@ -15,6 +15,7 @@ import { box, center, len, │
│ rule, ruleD, row2, wrap } from "../../lib/a │
│ scii"; │
│ import { published } from "../../lib/notes" │
│ ; │
│ import { docsOrder, type DocEntry } 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); │
│ @@ -54,6 +55,8 @@ interface Props { │
│ } │
│ const { doc, prev, next, all, refs } = Astr │
│ o.props as Props; │
│ 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); │
│ @@ -65,7 +68,7 @@ const buildTitleCard = (w: │
│ number) => { │
│ return card.split("\n").map((line) => cen │
│ ter(line, w)).join("\n"); │
│ }; │
│ --- │
│ -<ArticleLayout title={`DOCS - ${doc.data.ti │
│ tle}`}> │
│ +<ArticleLayout title={`DOCS - ${doc.data.ti │
│ tle}`} description={description} type="artic │
│ le"> │
│ <TopHeader /> │
│ <Frame build={(w) => [row2("OOKNET DOCUME │
│ NTATION", doc.id.toUpperCase(), w), rule(w)] │
│ .join("\n")} /> │
│ <Pre>{" "}</Pre> │
└──────────────────────────────────────────────┘
┌─ 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 │
│ index fae3fd1..e22ce1d 100644 │
│ --- a/ooknet-design/src/pages/docs/index.astro │
│ +++ b/ooknet-design/src/pages/docs/index.astro │
│ @@ -16,7 +16,8 @@ const docs = (await getCollection("docs", published)).map((d) => │
│ ({ │
│ })); │
│ const tree = docsTree(docs); │
│ --- │
│ -<ArticleLayout title="OOKNET KB - 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> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...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 │
│ index fae3fd1..e22ce1d 100644 │
│ --- a/ooknet-design/src/pages/docs/index.ast │
│ ro │
│ +++ b/ooknet-design/src/pages/docs/index.ast │
│ ro │
│ @@ -16,7 +16,8 @@ const docs = (await getCol │
│ lection("docs", published)).map((d) => ({ │
│ })); │
│ const tree = docsTree(docs); │
│ --- │
│ -<ArticleLayout title="OOKNET KB - 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> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/index.astro ──────────────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/index.astro b/ooknet-design/src/pages/index.a │
│ stro │
│ index be49b27..016fafc 100644 │
│ --- a/ooknet-design/src/pages/index.astro │
│ +++ b/ooknet-design/src/pages/index.astro │
│ @@ -20,7 +20,8 @@ const buildIndexBody = (w: number) => │
│ │
│ const buildIndexRule = (w: number) => rule(w); │
│ --- │
│ -<ArticleLayout title="OOKNET KB - Index"> │
│ +<ArticleLayout title="OOKNET KB - Index" │
│ + description="OOKNET knowledge base - the index of filed notes, browsable by tax │
│ onomy and searchable by tag, title, or note number."> │
│ <TopHeader /> │
│ <Frame build={buildIndexHeader} /> │
│ <Frame build={buildIndexBody} /> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...-design/src/pages/index.astro ───┐
│ diff --git a/ooknet-design/src/pages/index.a │
│ stro b/ooknet-design/src/pages/index.astro │
│ index be49b27..016fafc 100644 │
│ --- a/ooknet-design/src/pages/index.astro │
│ +++ b/ooknet-design/src/pages/index.astro │
│ @@ -20,7 +20,8 @@ const buildIndexBody = (w: │
│ number) => │
│ │
│ const buildIndexRule = (w: number) => rule( │
│ w); │
│ --- │
│ -<ArticleLayout title="OOKNET KB - Index"> │
│ +<ArticleLayout title="OOKNET KB - Index" │
│ + description="OOKNET knowledge base - the │
│ index of filed notes, browsable by taxonomy │
│ and searchable by tag, title, or note number │
│ ."> │
│ <TopHeader /> │
│ <Frame build={buildIndexHeader} /> │
│ <Frame build={buildIndexBody} /> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/notes/[slug].astro ───────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/notes/[slug].astro b/ooknet-design/src/pages/ │
│ notes/[slug].astro │
│ index fe783ff..97120ed 100644 │
│ --- a/ooknet-design/src/pages/notes/[slug].astro │
│ +++ b/ooknet-design/src/pages/notes/[slug].astro │
│ @@ -17,6 +17,7 @@ import ArticleBacklinks from "../../components/ArticleBacklinks/ │
│ ArticleBacklinks │
│ import type { PageLink } from "../../components/ArticlePagination/ArticlePaginati │
│ on"; │
│ import { published, byNoteNum } from "../../lib/notes"; │
│ import { buildBacklinks, type Backlink } from "../../lib/wikilinks"; │
│ +import { describeBody } from "../../lib/meta"; │
│ │
│ export async function getStaticPaths() { │
│ const notes = (await getCollection("notes", published)).sort(byNoteNum); │
│ @@ -42,8 +43,10 @@ interface Props { │
│ } │
│ const { note, prev, next, refs } = Astro.props as Props; │
│ const { Content, headings } = await render(note); │
│ +const description = │
│ + describeBody(note.body ?? "") || `${note.data.note} - filed ${note.data.filed}. │
│ `; │
│ --- │
│ -<ArticleLayout title={note.data.title}> │
│ +<ArticleLayout title={note.data.title} description={description} type="article"> │
│ <TopHeader /> │
│ <ArticleHeader fm={note.data} /> │
│ <DocumentTitle fm={note.data} /> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ .../src/pages/notes/[slug].astro ───┐
│ diff --git a/ooknet-design/src/pages/notes/[ │
│ slug].astro b/ooknet-design/src/pages/notes/ │
│ [slug].astro │
│ index fe783ff..97120ed 100644 │
│ --- a/ooknet-design/src/pages/notes/[slug].a │
│ stro │
│ +++ b/ooknet-design/src/pages/notes/[slug].a │
│ stro │
│ @@ -17,6 +17,7 @@ import ArticleBacklinks fr │
│ om "../../components/ArticleBacklinks/Articl │
│ eBacklinks │
│ import type { PageLink } from "../../compon │
│ ents/ArticlePagination/ArticlePagination"; │
│ import { published, byNoteNum } from "../.. │
│ /lib/notes"; │
│ import { buildBacklinks, type Backlink } fr │
│ om "../../lib/wikilinks"; │
│ +import { describeBody } from "../../lib/met │
│ a"; │
│ │
│ export async function getStaticPaths() { │
│ const notes = (await getCollection("notes │
│ ", published)).sort(byNoteNum); │
│ @@ -42,8 +43,10 @@ interface Props { │
│ } │
│ const { note, prev, next, refs } = Astro.pr │
│ ops as Props; │
│ const { Content, headings } = await render( │
│ note); │
│ +const description = │
│ + describeBody(note.body ?? "") || `${note. │
│ data.note} - filed ${note.data.filed}.`; │
│ --- │
│ -<ArticleLayout title={note.data.title}> │
│ +<ArticleLayout title={note.data.title} desc │
│ ription={description} type="article"> │
│ <TopHeader /> │
│ <ArticleHeader fm={note.data} /> │
│ <DocumentTitle fm={note.data} /> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/projects.astro ───────────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/projects.astro b/ooknet-design/src/pages/proj │
│ ects.astro │
│ index 78b36b1..c6255da 100644 │
│ --- a/ooknet-design/src/pages/projects.astro │
│ +++ b/ooknet-design/src/pages/projects.astro │
│ @@ -17,7 +17,8 @@ const PROJECTS = [ │
│ ["NIX-FIELD-GUIDE", "climbing the NixOS mountain without a sherpa", "DRAFT"], │
│ ]; │
│ --- │
│ -<ArticleLayout title="OOKNET KB - Projects"> │
│ +<ArticleLayout title="OOKNET KB - Projects" │
│ + description="Active and archived OOKNET projects, with status and stack for eac │
│ h."> │
│ <TopHeader /> │
│ <Frame build={buildHeader} /> │
│ <Pre>{" "}</Pre> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...sign/src/pages/projects.astro ───┐
│ diff --git a/ooknet-design/src/pages/project │
│ s.astro b/ooknet-design/src/pages/projects.a │
│ stro │
│ index 78b36b1..c6255da 100644 │
│ --- a/ooknet-design/src/pages/projects.astro │
│ +++ b/ooknet-design/src/pages/projects.astro │
│ @@ -17,7 +17,8 @@ const PROJECTS = [ │
│ ["NIX-FIELD-GUIDE", "climbing the NixOS m │
│ ountain without a sherpa", "DRAFT"], │
│ ]; │
│ --- │
│ -<ArticleLayout title="OOKNET KB - Projects" │
│ > │
│ +<ArticleLayout title="OOKNET KB - Projects" │
│ + description="Active and archived OOKNET p │
│ rojects, with status and stack for each."> │
│ <TopHeader /> │
│ <Frame build={buildHeader} /> │
│ <Pre>{" "}</Pre> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/resources.astro ──────────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/resources.astro b/ooknet-design/src/pages/res │
│ ources.astro │
│ index da0d82e..a9d3658 100644 │
│ --- a/ooknet-design/src/pages/resources.astro │
│ +++ b/ooknet-design/src/pages/resources.astro │
│ @@ -26,7 +26,8 @@ const TOOLING: [string, string, string][] = [ │
│ ["SEARCH", "FUSE.JS", "https://www.fusejs.io"], │
│ ]; │
│ --- │
│ -<ArticleLayout title="OOKNET KB - Resources"> │
│ +<ArticleLayout title="OOKNET KB - Resources" │
│ + description="External references filed by the OOKNET knowledge base - tools, pa │
│ pers, and reading."> │
│ <TopHeader /> │
│ <Frame build={buildSection("FILING TAXONOMY", `${notes.length} SHEETS`)} /> │
│ <Pre>{" "}</Pre> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...ign/src/pages/resources.astro ───┐
│ diff --git a/ooknet-design/src/pages/resourc │
│ es.astro b/ooknet-design/src/pages/resources │
│ .astro │
│ index da0d82e..a9d3658 100644 │
│ --- a/ooknet-design/src/pages/resources.astr │
│ o │
│ +++ b/ooknet-design/src/pages/resources.astr │
│ o │
│ @@ -26,7 +26,8 @@ const TOOLING: [string, st │
│ ring, string][] = [ │
│ ["SEARCH", "FUSE.JS", "https://www.fusejs │
│ .io"], │
│ ]; │
│ --- │
│ -<ArticleLayout title="OOKNET KB - Resources │
│ "> │
│ +<ArticleLayout title="OOKNET KB - Resources │
│ " │
│ + description="External references filed by │
│ the OOKNET knowledge base - tools, papers, │
│ and reading."> │
│ <TopHeader /> │
│ <Frame build={buildSection("FILING TAXONO │
│ MY", `${notes.length} SHEETS`)} /> │
│ <Pre>{" "}</Pre> │
└──────────────────────────────────────────────┘
┌─ 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 │
│ new file mode 100644 │
│ index 0000000..8c8b6c8 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/rss.xml.ts │
│ @@ -0,0 +1,26 @@ │
│ +// Notes feed, newest first. │
│ +import type { APIRoute } from "astro"; │
│ +import { getCollection } from "astro:content"; │
│ +import { published } from "../lib/notes"; │
│ +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 xml = buildRss({ │
│ + title: "OOKNET KB", │
│ + site: site!.href, │
│ + description: "OOKNET knowledge base - filed notes, newest first.", │
│ + items: notes.map((n) => ({ │
│ + title: `${n.data.note} - ${n.data.title}`, │
│ + url: new URL(`/notes/${n.id}/`, site).href, │
│ + published: n.data.published, │
│ + description: describeBody(n.body ?? "") || `Filed ${n.data.filed}.`, │
│ + })), │
│ + }); │
│ + return new Response(xml, { │
│ + headers: { "Content-Type": "application/rss+xml; charset=utf-8" }, │
│ + }); │
│ +}; │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...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 │
│ new file mode 100644 │
│ index 0000000..8c8b6c8 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/rss.xml.ts │
│ @@ -0,0 +1,26 @@ │
│ +// Notes feed, newest first. │
│ +import type { APIRoute } from "astro"; │
│ +import { getCollection } from "astro:conten │
│ t"; │
│ +import { published } from "../lib/notes"; │
│ +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 xml = buildRss({ │
│ + title: "OOKNET KB", │
│ + site: site!.href, │
│ + description: "OOKNET knowledge base - f │
│ iled notes, newest first.", │
│ + items: notes.map((n) => ({ │
│ + title: `${n.data.note} - ${n.data.tit │
│ le}`, │
│ + url: new URL(`/notes/${n.id}/`, site) │
│ .href, │
│ + published: n.data.published, │
│ + description: describeBody(n.body ?? " │
│ ") || `Filed ${n.data.filed}.`, │
│ + })), │
│ + }); │
│ + return new Response(xml, { │
│ + headers: { "Content-Type": "application │
│ /rss+xml; charset=utf-8" }, │
│ + }); │
│ +}; │
└──────────────────────────────────────────────┘
┌─ 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 │
│ new file mode 100644 │
│ index 0000000..9085558 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/sitemap.xml.ts │
│ @@ -0,0 +1,20 @@ │
│ +import type { APIRoute } from "astro"; │
│ +import { getCollection } from "astro:content"; │
│ +import { published } from "../lib/notes"; │
│ +import { buildSitemap } from "../lib/feeds"; │
│ + │
│ +const STATIC_PAGES = ["/", "/about/", "/projects/", "/resources/", "/components/" │
│ , "/docs/", "/status/"]; │
│ + │
│ +export const GET: APIRoute = async ({ site }) => { │
│ + const notes = await getCollection("notes", published); │
│ + const docs = await getCollection("docs", 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 │
│ })), │
│ + ...docs.map((d) => ({ loc: abs(`/docs/${d.id}/`) })), │
│ + ]); │
│ + 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 │
│ new file mode 100644 │
│ index 0000000..9085558 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/sitemap.xml.ts │
│ @@ -0,0 +1,20 @@ │
│ +import type { APIRoute } from "astro"; │
│ +import { getCollection } from "astro:conten │
│ t"; │
│ +import { published } from "../lib/notes"; │
│ +import { buildSitemap } from "../lib/feeds" │
│ ; │
│ + │
│ +const STATIC_PAGES = ["/", "/about/", "/pro │
│ jects/", "/resources/", "/components/", "/do │
│ cs/", "/status/"]; │
│ + │
│ +export const GET: APIRoute = async ({ site │
│ }) => { │
│ + const notes = await getCollection("notes" │
│ , published); │
│ + const docs = await getCollection("docs", │
│ 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 })), │
│ + ...docs.map((d) => ({ loc: abs(`/docs/$ │
│ {d.id}/`) })), │
│ + ]); │
│ + 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 ccf2afd..231c1fe 100644 │
│ --- a/ooknet-design/src/pages/status.astro │
│ +++ b/ooknet-design/src/pages/status.astro │
│ @@ -44,7 +44,8 @@ const log: TimelineEntry[] = byDate.map((n) => ({ │
│ href: `/notes/${n.id}/`, │
│ })); │
│ --- │
│ -<ArticleLayout title="OOKNET KB - Status"> │
│ +<ArticleLayout title="OOKNET KB - Status" │
│ + description="OOKNET system telemetry - filing capacity, note volume, and activi │
│ ty, charted in text."> │
│ <TopHeader /> │
│ <Frame build={(w) => [row2("SYSTEM TELEMETRY", `AS OF ${latestDate}`, w), ruleD │
│ (w)].join("\n")} /> │
│ <Pre>{" "}</Pre> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...design/src/pages/status.astro ───┐
│ diff --git a/ooknet-design/src/pages/status. │
│ astro b/ooknet-design/src/pages/status.astro │
│ index ccf2afd..231c1fe 100644 │
│ --- a/ooknet-design/src/pages/status.astro │
│ +++ b/ooknet-design/src/pages/status.astro │
│ @@ -44,7 +44,8 @@ const log: TimelineEntry[] │
│ = byDate.map((n) => ({ │
│ href: `/notes/${n.id}/`, │
│ })); │
│ --- │
│ -<ArticleLayout title="OOKNET KB - Status"> │
│ +<ArticleLayout title="OOKNET KB - Status" │
│ + description="OOKNET system telemetry - fi │
│ ling capacity, note volume, and activity, ch │
│ arted in text."> │
│ <TopHeader /> │
│ <Frame build={(w) => [row2("SYSTEM TELEME │
│ TRY", `AS OF ${latestDate}`, w), ruleD(w)].j │
│ oin("\n")} /> │
│ <Pre>{" "}</Pre> │
└──────────────────────────────────────────────┘
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET