HASH 6b9265c18b2e
DATE 2026-07-18
SUBJECT web: git pages grow branches, per-file diffs, file history, and the site's
own markdown renderer
FILES 18 CHANGED HASH 6b9265c18b2e
DATE 2026-07-18
SUBJECT web: git pages grow branches, per-file
diffs, file history, and the site's
own markdown renderer
FILES 18 CHANGED
┌─ .gitignore ───────────────────────────────────────────────────────────────┐
│ diff --git a/.gitignore b/.gitignore │
│ index e43b0f9..f7beecb 100644 │
│ --- a/.gitignore │
│ +++ b/.gitignore │
│ @@ -1 +1,4 @@ │
│ .DS_Store │
│ + │
│ +# machine-local planning docs │
│ +.internal/ │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ .gitignore ─────────────────────────┐
│ diff --git a/.gitignore b/.gitignore │
│ index e43b0f9..f7beecb 100644 │
│ --- a/.gitignore │
│ +++ b/.gitignore │
│ @@ -1 +1,4 @@ │
│ .DS_Store │
│ + │
│ +# machine-local planning docs │
│ +.internal/ │
└──────────────────────────────────────────────┘
┌─ ooknet-design/package-lock.json ──────────────────────────────────────────┐
│ diff --git a/ooknet-design/package-lock.json b/ooknet-design/package-lock.json │
│ index d58ec8a..4f60302 100644 │
│ --- a/ooknet-design/package-lock.json │
│ +++ b/ooknet-design/package-lock.json │
│ @@ -11,7 +11,11 @@ │
│ "@fontsource/cascadia-mono": "^5.2.3", │
│ "@fontsource/ibm-plex-mono": "^5.2.7", │
│ "astro": "^6.1.9", │
│ - "marked": "^18.0.6" │
│ + "rehype-stringify": "^10.0.1", │
│ + "remark-gfm": "^4.0.1", │
│ + "remark-parse": "^11.0.0", │
│ + "remark-rehype": "^11.1.2", │
│ + "unified": "^11.0.5" │
│ }, │
│ "devDependencies": { │
│ "sass": "^1.99.0", │
│ @@ -3292,18 +3296,6 @@ │
│ "url": "https://github.com/sponsors/wooorm" │
│ } │
│ }, │
│ - "node_modules/marked": { │
│ - "version": "18.0.6", │
│ - "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.6.tgz", │
│ - "integrity": "sha512-MrV5puXBfuiy6wl6DLaq3BtIJQAJToAd5zt/ZKhRfGRAuFPALE7/4Y │
│ 7jnxRQoEgK/pBgurGqLyAuRgZ2xOjr6w==", │
│ - "license": "MIT", │
│ - "bin": { │
│ - "marked": "bin/marked.js" │
│ - }, │
│ - "engines": { │
│ - "node": ">= 20" │
│ - } │
│ - }, │
│ "node_modules/mdast-util-definitions": { │
│ "version": "6.0.0", │
│ "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util │
│ -definitions-6.0.0.tgz", │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ooknet-design/package-lock.json ────┐
│ diff --git a/ooknet-design/package-lock.json │
│ b/ooknet-design/package-lock.json │
│ index d58ec8a..4f60302 100644 │
│ --- a/ooknet-design/package-lock.json │
│ +++ b/ooknet-design/package-lock.json │
│ @@ -11,7 +11,11 @@ │
│ "@fontsource/cascadia-mono": "^5.2. │
│ 3", │
│ "@fontsource/ibm-plex-mono": "^5.2. │
│ 7", │
│ "astro": "^6.1.9", │
│ - "marked": "^18.0.6" │
│ + "rehype-stringify": "^10.0.1", │
│ + "remark-gfm": "^4.0.1", │
│ + "remark-parse": "^11.0.0", │
│ + "remark-rehype": "^11.1.2", │
│ + "unified": "^11.0.5" │
│ }, │
│ "devDependencies": { │
│ "sass": "^1.99.0", │
│ @@ -3292,18 +3296,6 @@ │
│ "url": "https://github.com/sponsors │
│ /wooorm" │
│ } │
│ }, │
│ - "node_modules/marked": { │
│ - "version": "18.0.6", │
│ - "resolved": "https://registry.npmjs.o │
│ rg/marked/-/marked-18.0.6.tgz", │
│ - "integrity": "sha512-MrV5puXBfuiy6wl6 │
│ DLaq3BtIJQAJToAd5zt/ZKhRfGRAuFPALE7/4Y7jnxRQ │
│ oEgK/pBgurGqLyAuRgZ2xOjr6w==", │
│ - "license": "MIT", │
│ - "bin": { │
│ - "marked": "bin/marked.js" │
│ - }, │
│ - "engines": { │
│ - "node": ">= 20" │
│ - } │
│ - }, │
│ "node_modules/mdast-util-definitions": │
│ { │
│ "version": "6.0.0", │
│ "resolved": "https://registry.npmjs.o │
│ rg/mdast-util-definitions/-/mdast-util-defin │
│ itions-6.0.0.tgz", │
└──────────────────────────────────────────────┘
┌─ ooknet-design/package.json ───────────────────────────────────────────────┐
│ diff --git a/ooknet-design/package.json b/ooknet-design/package.json │
│ index 112a66e..3dcd473 100644 │
│ --- a/ooknet-design/package.json │
│ +++ b/ooknet-design/package.json │
│ @@ -16,7 +16,11 @@ │
│ "@fontsource/cascadia-mono": "^5.2.3", │
│ "@fontsource/ibm-plex-mono": "^5.2.7", │
│ "astro": "^6.1.9", │
│ - "marked": "^18.0.6" │
│ + "rehype-stringify": "^10.0.1", │
│ + "remark-gfm": "^4.0.1", │
│ + "remark-parse": "^11.0.0", │
│ + "remark-rehype": "^11.1.2", │
│ + "unified": "^11.0.5" │
│ }, │
│ "devDependencies": { │
│ "sass": "^1.99.0", │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ooknet-design/package.json ─────────┐
│ diff --git a/ooknet-design/package.json b/oo │
│ knet-design/package.json │
│ index 112a66e..3dcd473 100644 │
│ --- a/ooknet-design/package.json │
│ +++ b/ooknet-design/package.json │
│ @@ -16,7 +16,11 @@ │
│ "@fontsource/cascadia-mono": "^5.2.3", │
│ "@fontsource/ibm-plex-mono": "^5.2.7", │
│ "astro": "^6.1.9", │
│ - "marked": "^18.0.6" │
│ + "rehype-stringify": "^10.0.1", │
│ + "remark-gfm": "^4.0.1", │
│ + "remark-parse": "^11.0.0", │
│ + "remark-rehype": "^11.1.2", │
│ + "unified": "^11.0.5" │
│ }, │
│ "devDependencies": { │
│ "sass": "^1.99.0", │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/components/CodeFrame/CodeFrame.astro ───────────────────┐
│ diff --git a/ooknet-design/src/components/CodeFrame/CodeFrame.astro b/ooknet-desig │
│ n/src/components/CodeFrame/CodeFrame.astro │
│ new file mode 100644 │
│ index 0000000..4c67580 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/components/CodeFrame/CodeFrame.astro │
│ @@ -0,0 +1,46 @@ │
│ +--- │
│ +// Framed code block for content outside markdown - blobs and per-file │
│ +// commit diffs. Same markup as the pipeline's code fences, so Prose │
│ +// styles and the copy handler apply unchanged. │
│ +import { G } from "../../lib/ascii"; │
│ +import { FRAME_W, MOBILE_FRAME_W } from "../../lib/config"; │
│ +import { layoutDiff } from "../Diff/Diff"; │
│ +import { frameLabel, type Props } from "./CodeFrame"; │
│ +import "../Prose/Prose.scss"; │
│ + │
│ +const { code, label, diff = false } = Astro.props as Props; │
│ +const raw = code.replace(/\n+$/, ""); │
│ + │
│ +const esc = (s: string) => │
│ + s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); │
│ +const frame = (s: string) => │
│ + `<span class="code-frame" aria-hidden="true">${esc(s)}</span>`; │
│ + │
│ +function renderCode(w: number): string { │
│ + const lbl = frameLabel(label, w); │
│ + const parts: string[] = [ │
│ + frame(G.TL + G.H + ` ${lbl} ` + G.H.repeat(Math.max(0, w - 12 - lbl.length - │
│ 2))), │
│ + '<button type="button" class="code-copy" aria-label="Copy code">[ COPY ]</but │
│ ton>', │
│ + frame(G.H + G.TR), │
│ + "\n", │
│ + ]; │
│ + for (const r of layoutDiff(code, w, diff)) { │
│ + parts.push(frame(G.V + " ")); │
│ + parts.push(r.cls ? `<span class="${r.cls}">${esc(r.text)}</span>` : esc(r.tex │
│ t)); │
│ + parts.push(frame(" ".repeat(r.pad) + " " + G.V)); │
│ + parts.push("\n"); │
│ + } │
│ + parts.push(frame(G.BL + G.H.repeat(w - 2) + G.BR)); │
│ + return `<code>${parts.join("")}</code>`; │
│ +} │
│ + │
│ +const variants = [ │
│ + { cls: "frame-wide", w: FRAME_W }, │
│ + { cls: "frame-narrow", w: MOBILE_FRAME_W }, │
│ +]; │
│ +--- │
│ +<div class="code-frames prose"> │
│ + {variants.map(({ cls, w }) => ( │
│ + <pre class={"ascii code-block " + cls} data-raw-code={raw} set:html={renderCo │
│ de(w)} /> │
│ + ))} │
│ +</div> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...nts/CodeFrame/CodeFrame.astro ───┐
│ diff --git a/ooknet-design/src/components/Co │
│ deFrame/CodeFrame.astro b/ooknet-design/src/ │
│ components/CodeFrame/CodeFrame.astro │
│ new file mode 100644 │
│ index 0000000..4c67580 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/components/CodeFrame │
│ /CodeFrame.astro │
│ @@ -0,0 +1,46 @@ │
│ +--- │
│ +// Framed code block for content outside ma │
│ rkdown - blobs and per-file │
│ +// commit diffs. Same markup as the pipelin │
│ e's code fences, so Prose │
│ +// styles and the copy handler apply unchan │
│ ged. │
│ +import { G } from "../../lib/ascii"; │
│ +import { FRAME_W, MOBILE_FRAME_W } from ".. │
│ /../lib/config"; │
│ +import { layoutDiff } from "../Diff/Diff"; │
│ +import { frameLabel, type Props } from "./C │
│ odeFrame"; │
│ +import "../Prose/Prose.scss"; │
│ + │
│ +const { code, label, diff = false } = Astro │
│ .props as Props; │
│ +const raw = code.replace(/\n+$/, ""); │
│ + │
│ +const esc = (s: string) => │
│ + s.replace(/&/g, "&").replace(/</g, "& │
│ lt;").replace(/>/g, ">"); │
│ +const frame = (s: string) => │
│ + `<span class="code-frame" aria-hidden="tr │
│ ue">${esc(s)}</span>`; │
│ + │
│ +function renderCode(w: number): string { │
│ + const lbl = frameLabel(label, w); │
│ + const parts: string[] = [ │
│ + frame(G.TL + G.H + ` ${lbl} ` + G.H.rep │
│ eat(Math.max(0, w - 12 - lbl.length - 2))), │
│ + '<button type="button" class="code-copy │
│ " aria-label="Copy code">[ COPY ]</button>', │
│ + frame(G.H + G.TR), │
│ + "\n", │
│ + ]; │
│ + for (const r of layoutDiff(code, w, diff) │
│ ) { │
│ + parts.push(frame(G.V + " ")); │
│ + parts.push(r.cls ? `<span class="${r.cl │
│ s}">${esc(r.text)}</span>` : esc(r.text)); │
│ + parts.push(frame(" ".repeat(r.pad) + " │
│ " + G.V)); │
│ + parts.push("\n"); │
│ + } │
│ + parts.push(frame(G.BL + G.H.repeat(w - 2) │
│ + G.BR)); │
│ + return `<code>${parts.join("")}</code>`; │
│ +} │
│ + │
│ +const variants = [ │
│ + { cls: "frame-wide", w: FRAME_W }, │
│ + { cls: "frame-narrow", w: MOBILE_FRAME_W │
│ }, │
│ +]; │
│ +--- │
│ +<div class="code-frames prose"> │
│ + {variants.map(({ cls, w }) => ( │
│ + <pre class={"ascii code-block " + cls} │
│ data-raw-code={raw} set:html={renderCode(w)} │
│ /> │
│ + ))} │
│ +</div> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/components/CodeFrame/CodeFrame.test.ts ─────────────────┐
│ diff --git a/ooknet-design/src/components/CodeFrame/CodeFrame.test.ts b/ooknet-des │
│ ign/src/components/CodeFrame/CodeFrame.test.ts │
│ new file mode 100644 │
│ index 0000000..d954e53 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/components/CodeFrame/CodeFrame.test.ts │
│ @@ -0,0 +1,26 @@ │
│ +import { describe, expect, it } from "vitest"; │
│ +import { frameLabel } from "./CodeFrame"; │
│ +import { layoutDiff } from "../Diff/Diff"; │
│ + │
│ +describe("frameLabel", () => { │
│ + it("passes short labels through", () => { │
│ + expect(frameLabel("TS", 48)).toBe("TS"); │
│ + }); │
│ + it("keeps the tail of long paths", () => { │
│ + const long = "ooknet-design/src/components/ArticleBacklinks/ArticleBacklinks. │
│ astro"; │
│ + const label = frameLabel(long, 48); │
│ + expect(label.startsWith("...")).toBe(true); │
│ + expect(label.endsWith("ArticleBacklinks.astro")).toBe(true); │
│ + }); │
│ +}); │
│ + │
│ +describe("layoutDiff classify flag", () => { │
│ + it("skips diff classes when off", () => { │
│ + const rows = layoutDiff("+added\n-removed", 48, false); │
│ + expect(rows.every((r) => r.cls === null)).toBe(true); │
│ + }); │
│ + it("keeps them when on", () => { │
│ + const rows = layoutDiff("+added\n-removed", 48, true); │
│ + expect(rows.map((r) => r.cls)).toEqual(["diff-add", "diff-del"]); │
│ + }); │
│ +}); │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...s/CodeFrame/CodeFrame.test.ts ───┐
│ diff --git a/ooknet-design/src/components/Co │
│ deFrame/CodeFrame.test.ts b/ooknet-design/sr │
│ c/components/CodeFrame/CodeFrame.test.ts │
│ new file mode 100644 │
│ index 0000000..d954e53 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/components/CodeFrame │
│ /CodeFrame.test.ts │
│ @@ -0,0 +1,26 @@ │
│ +import { describe, expect, it } from "vites │
│ t"; │
│ +import { frameLabel } from "./CodeFrame"; │
│ +import { layoutDiff } from "../Diff/Diff"; │
│ + │
│ +describe("frameLabel", () => { │
│ + it("passes short labels through", () => { │
│ + expect(frameLabel("TS", 48)).toBe("TS") │
│ ; │
│ + }); │
│ + it("keeps the tail of long paths", () => │
│ { │
│ + const long = "ooknet-design/src/compone │
│ nts/ArticleBacklinks/ArticleBacklinks.astro" │
│ ; │
│ + const label = frameLabel(long, 48); │
│ + expect(label.startsWith("...")).toBe(tr │
│ ue); │
│ + expect(label.endsWith("ArticleBacklinks │
│ .astro")).toBe(true); │
│ + }); │
│ +}); │
│ + │
│ +describe("layoutDiff classify flag", () => │
│ { │
│ + it("skips diff classes when off", () => { │
│ + const rows = layoutDiff("+added\n-remov │
│ ed", 48, false); │
│ + expect(rows.every((r) => r.cls === null │
│ )).toBe(true); │
│ + }); │
│ + it("keeps them when on", () => { │
│ + const rows = layoutDiff("+added\n-remov │
│ ed", 48, true); │
│ + expect(rows.map((r) => r.cls)).toEqual( │
│ ["diff-add", "diff-del"]); │
│ + }); │
│ +}); │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/components/CodeFrame/CodeFrame.ts ──────────────────────┐
│ diff --git a/ooknet-design/src/components/CodeFrame/CodeFrame.ts b/ooknet-design/s │
│ rc/components/CodeFrame/CodeFrame.ts │
│ new file mode 100644 │
│ index 0000000..e2e850f │
│ --- /dev/null │
│ +++ b/ooknet-design/src/components/CodeFrame/CodeFrame.ts │
│ @@ -0,0 +1,18 @@ │
│ +// Shared math for the framed code block: label seat and width checks. │
│ +import { checkGrid, len } from "../../lib/ascii"; │
│ + │
│ +export interface Props { │
│ + code: string; │
│ + /** Frame label: a language tag or a file path. */ │
│ + label: string; │
│ + /** Apply diff line colors. */ │
│ + diff?: boolean; │
│ +} │
│ + │
│ +/** Label truncated so the top border always seats " LABEL " plus the │
│ + * copy button at the narrow frame. */ │
│ +export function frameLabel(label: string, width: number): string { │
│ + const max = width - 16; │
│ + const cut = len(label) > max ? "..." + [...label].slice(-(max - 3)).join("") : │
│ label; │
│ + return checkGrid(cut, max, "code frame label"); │
│ +} │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...onents/CodeFrame/CodeFrame.ts ───┐
│ diff --git a/ooknet-design/src/components/Co │
│ deFrame/CodeFrame.ts b/ooknet-design/src/com │
│ ponents/CodeFrame/CodeFrame.ts │
│ new file mode 100644 │
│ index 0000000..e2e850f │
│ --- /dev/null │
│ +++ b/ooknet-design/src/components/CodeFrame │
│ /CodeFrame.ts │
│ @@ -0,0 +1,18 @@ │
│ +// Shared math for the framed code block: l │
│ abel seat and width checks. │
│ +import { checkGrid, len } from "../../lib/a │
│ scii"; │
│ + │
│ +export interface Props { │
│ + code: string; │
│ + /** Frame label: a language tag or a file │
│ path. */ │
│ + label: string; │
│ + /** Apply diff line colors. */ │
│ + diff?: boolean; │
│ +} │
│ + │
│ +/** Label truncated so the top border alway │
│ s seats " LABEL " plus the │
│ + * copy button at the narrow frame. */ │
│ +export function frameLabel(label: string, w │
│ idth: number): string { │
│ + const max = width - 16; │
│ + const cut = len(label) > max ? "..." + [. │
│ ..label].slice(-(max - 3)).join("") : label; │
│ + return checkGrid(cut, max, "code frame la │
│ bel"); │
│ +} │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/components/Diff/Diff.ts ────────────────────────────────┐
│ diff --git a/ooknet-design/src/components/Diff/Diff.ts b/ooknet-design/src/compone │
│ nts/Diff/Diff.ts │
│ index 8161722..7c7684e 100644 │
│ --- a/ooknet-design/src/components/Diff/Diff.ts │
│ +++ b/ooknet-design/src/components/Diff/Diff.ts │
│ @@ -14,13 +14,13 @@ export interface DiffRow { │
│ pad: number; │
│ } │
│ │
│ -export function layoutDiff(code: string, width: number): DiffRow[] { │
│ +export function layoutDiff(code: string, width: number, classify = true): DiffRow │
│ [] { │
│ const inner = width - 4; │
│ return code │
│ .replace(/\n+$/, "") │
│ .split("\n") │
│ .flatMap((line) => { │
│ - const cls = diffLineClass(line); │
│ + const cls = classify ? diffLineClass(line) : null; │
│ const cps = [...line]; │
│ const chunks = cps.length <= inner │
│ ? [line] │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...n/src/components/Diff/Diff.ts ───┐
│ diff --git a/ooknet-design/src/components/Di │
│ ff/Diff.ts b/ooknet-design/src/components/Di │
│ ff/Diff.ts │
│ index 8161722..7c7684e 100644 │
│ --- a/ooknet-design/src/components/Diff/Diff │
│ .ts │
│ +++ b/ooknet-design/src/components/Diff/Diff │
│ .ts │
│ @@ -14,13 +14,13 @@ export interface DiffRow │
│ { │
│ pad: number; │
│ } │
│ │
│ -export function layoutDiff(code: string, wi │
│ dth: number): DiffRow[] { │
│ +export function layoutDiff(code: string, wi │
│ dth: number, classify = true): DiffRow[] { │
│ const inner = width - 4; │
│ return code │
│ .replace(/\n+$/, "") │
│ .split("\n") │
│ .flatMap((line) => { │
│ - const cls = diffLineClass(line); │
│ + const cls = classify ? diffLineClass( │
│ line) : null; │
│ const cps = [...line]; │
│ const chunks = cps.length <= inner │
│ ? [line] │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/lib/git.test.ts ────────────────────────────────────────┐
│ diff --git a/ooknet-design/src/lib/git.test.ts b/ooknet-design/src/lib/git.test.ts │
│ index fed2655..8672894 100644 │
│ --- a/ooknet-design/src/lib/git.test.ts │
│ +++ b/ooknet-design/src/lib/git.test.ts │
│ @@ -1,5 +1,5 @@ │
│ import { describe, expect, it } from "vitest"; │
│ -import { filesTree, repoAvailable, repoInfo } from "./git"; │
│ +import { branches, langOf, repoAvailable, repoInfo } from "./git"; │
│ import { toGrid } from "./ascii"; │
│ │
│ describe("toGrid", () => { │
│ @@ -17,17 +17,12 @@ describe("toGrid", () => { │
│ }); │
│ }); │
│ │
│ -describe("filesTree", () => { │
│ - const tree = filesTree(["flake.nix", "src/lib/ascii.ts", "src/pages/index.astro │
│ "], "/git/x/blob/"); │
│ - │
│ - it("branches directories and links files", () => { │
│ - expect(tree.map((n) => n.label)).toEqual(["src/", "flake.nix"]); │
│ - const src = tree[0].children!; │
│ - expect(src.map((n) => n.label)).toEqual(["lib/", "pages/"]); │
│ - expect(src[0].children![0]).toEqual({ │
│ - label: "ascii.ts", │
│ - href: "/git/x/blob/src/lib/ascii.ts/", │
│ - }); │
│ +describe("langOf", () => { │
│ + it("maps extensions to frame labels", () => { │
│ + expect(langOf("src/lib/ascii.ts")).toBe("TS"); │
│ + expect(langOf("flake.nix")).toBe("NIX"); │
│ + expect(langOf("mod.rs")).toBe("RS"); │
│ + expect(langOf("Makefile")).toBe("TXT"); │
│ }); │
│ }); │
│ │
│ @@ -37,6 +32,7 @@ describe("repoInfo", () => { │
│ const info = repoInfo(".."); │
│ expect(info.commits).toBeGreaterThan(30); │
│ expect(info.branch).toBe("master"); │
│ + expect(branches("..")[0]).toBe("master"); │
│ expect(info.lastDate).toMatch(/^\d{4}-\d{2}-\d{2}$/); │
│ }); │
│ }); │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...et-design/src/lib/git.test.ts ───┐
│ diff --git a/ooknet-design/src/lib/git.test. │
│ ts b/ooknet-design/src/lib/git.test.ts │
│ index fed2655..8672894 100644 │
│ --- a/ooknet-design/src/lib/git.test.ts │
│ +++ b/ooknet-design/src/lib/git.test.ts │
│ @@ -1,5 +1,5 @@ │
│ import { describe, expect, it } from "vites │
│ t"; │
│ -import { filesTree, repoAvailable, repoInfo │
│ } from "./git"; │
│ +import { branches, langOf, repoAvailable, r │
│ epoInfo } from "./git"; │
│ import { toGrid } from "./ascii"; │
│ │
│ describe("toGrid", () => { │
│ @@ -17,17 +17,12 @@ describe("toGrid", () => │
│ { │
│ }); │
│ }); │
│ │
│ -describe("filesTree", () => { │
│ - const tree = filesTree(["flake.nix", "src │
│ /lib/ascii.ts", "src/pages/index.astro"], "/ │
│ git/x/blob/"); │
│ - │
│ - it("branches directories and links files" │
│ , () => { │
│ - expect(tree.map((n) => n.label)).toEqua │
│ l(["src/", "flake.nix"]); │
│ - const src = tree[0].children!; │
│ - expect(src.map((n) => n.label)).toEqual │
│ (["lib/", "pages/"]); │
│ - expect(src[0].children![0]).toEqual({ │
│ - label: "ascii.ts", │
│ - href: "/git/x/blob/src/lib/ascii.ts/" │
│ , │
│ - }); │
│ +describe("langOf", () => { │
│ + it("maps extensions to frame labels", () │
│ => { │
│ + expect(langOf("src/lib/ascii.ts")).toBe │
│ ("TS"); │
│ + expect(langOf("flake.nix")).toBe("NIX") │
│ ; │
│ + expect(langOf("mod.rs")).toBe("RS"); │
│ + expect(langOf("Makefile")).toBe("TXT"); │
│ }); │
│ }); │
│ │
│ @@ -37,6 +32,7 @@ describe("repoInfo", () => │
│ { │
│ const info = repoInfo(".."); │
│ expect(info.commits).toBeGreaterThan(30 │
│ ); │
│ expect(info.branch).toBe("master"); │
│ + expect(branches("..")[0]).toBe("master" │
│ ); │
│ expect(info.lastDate).toMatch(/^\d{4}-\ │
│ d{2}-\d{2}$/); │
│ }); │
│ }); │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/lib/git.ts ─────────────────────────────────────────────┐
│ diff --git a/ooknet-design/src/lib/git.ts b/ooknet-design/src/lib/git.ts │
│ index 489be48..7e388d7 100644 │
│ --- a/ooknet-design/src/lib/git.ts │
│ +++ b/ooknet-design/src/lib/git.ts │
│ @@ -2,11 +2,11 @@ │
│ // from real repositories at build time - stagit-style, but rendered │
│ // through the site's own components. Anything that can land in a │
│ // frame passes toGrid() so history written before the og-ascii rule │
│ -// can't fail the build. │
│ +// can't fail the build. Every reader takes a ref so branch pages are │
│ +// first-class; urls follow the github shape (tree/<branch>/<path>). │
│ import { execFileSync } from "node:child_process"; │
│ import { existsSync } from "node:fs"; │
│ import { toGrid } from "./ascii"; │
│ -import type { TreeNode } from "../components/Tree/Tree"; │
│ import type { TimelineEntry } from "../components/Timeline/Timeline"; │
│ │
│ export interface GitRepo { │
│ @@ -30,60 +30,70 @@ const git = (path: string, args: string[]): string => │
│ │
│ export const repoAvailable = (r: GitRepo): boolean => existsSync(r.path); │
│ │
│ +/** Local branches, default branch first. */ │
│ +export function branches(path: string): string[] { │
│ + const head = defaultBranch(path); │
│ + const all = git(path, ["for-each-ref", "refs/heads", "--format=%(refname:short) │
│ "]) │
│ + .split("\n") │
│ + .filter(Boolean); │
│ + return [head, ...all.filter((b) => b !== head)]; │
│ +} │
│ + │
│ +export const defaultBranch = (path: string): string => │
│ + git(path, ["symbolic-ref", "--short", "HEAD"]).trim(); │
│ + │
│ export interface RepoInfo { │
│ branch: string; │
│ commits: number; │
│ + lastHash: string; │
│ lastDate: string; │
│ lastSubject: string; │
│ } │
│ │
│ -export function repoInfo(path: string): RepoInfo { │
│ - const [lastDate, ...subject] = git(path, ["log", "-1", "--format=%as|%s"]) │
│ +export function repoInfo(path: string, ref?: string): RepoInfo { │
│ + const branch = ref ?? defaultBranch(path); │
│ + const [lastHash, lastDate, ...subject] = git(path, ["log", "-1", "--format=%h|% │
│ as|%s", branch]) │
│ .trim() │
│ .split("|"); │
│ return { │
│ - branch: git(path, ["symbolic-ref", "--short", "HEAD"]).trim(), │
│ - commits: parseInt(git(path, ["rev-list", "--count", "HEAD"]).trim(), 10), │
│ + branch, │
│ + commits: parseInt(git(path, ["rev-list", "--count", branch]).trim(), 10), │
│ + lastHash, │
│ lastDate, │
│ lastSubject: toGrid(subject.join("|")), │
│ }; │
│ } │
│ │
│ -export function repoFiles(path: string): string[] { │
│ - return git(path, ["ls-tree", "-r", "--name-only", "HEAD"]) │
│ +export function repoFiles(path: string, ref = "HEAD"): string[] { │
│ + return git(path, ["ls-tree", "-r", "--name-only", ref]) │
│ .split("\n") │
│ .filter(Boolean); │
│ } │
│ │
│ -/** File list as a Tree: directories branch, files link to blob pages. */ │
│ -export function filesTree(files: string[], base: string): TreeNode[] { │
│ - type Branch = { dirs: Map<string, Branch>; files: string[] }; │
│ - const root: Branch = { dirs: new Map(), files: [] }; │
│ - for (const file of files) { │
│ - const segments = file.split("/"); │
│ - let branch = root; │
│ - for (const seg of segments.slice(0, -1)) { │
│ - if (!branch.dirs.has(seg)) branch.dirs.set(seg, { dirs: new Map(), files: [ │
│ ] }); │
│ - branch = branch.dirs.get(seg)!; │
│ - } │
│ - branch.files.push(file); │
│ - } │
│ - const toNodes = (b: Branch): TreeNode[] => [ │
│ - ...[...b.dirs.entries()] │
│ - .sort(([a], [c]) => a.localeCompare(c)) │
│ - .map(([seg, child]) => ({ label: `${toGrid(seg)}/`, children: toNodes(child │
│ ) })), │
│ - ...b.files │
│ - .sort() │
│ - .map((f) => ({ │
│ - label: toGrid(f.split("/").pop()!), │
│ - href: `${base}${f}/`, │
│ - })), │
│ - ]; │
│ - return toNodes(root); │
│ -} │
│ - │
│ -export function repoLog(path: string, limit: number, base: string): TimelineEntry │
│ [] { │
│ - return git(path, ["log", `-${limit}`, "--format=%h|%as|%s"]) │
│ +export function repoLog(path: string, limit: number, base: string, ref = "HEAD"): │
│ TimelineEntry[] { │
│ + return git(path, ["log", `-${limit}`, "--format=%h|%as|%s", ref]) │
│ + .split("\n") │
│ + .filter(Boolean) │
│ + .map((line) => { │
│ + const [hash, date, ...subject] = line.split("|"); │
│ + return { │
│ + date, │
│ + title: toGrid(hash), │
│ + desc: toGrid(subject.join("|")), │
│ + href: `${base}commit/${hash}/`, │
│ + }; │
│ + }); │
│ +} │
│ + │
│ +/** Commits touching one path - the file history page. */ │
│ +export function pathLog( │
│ + path: string, │
│ + file: string, │
│ + limit: number, │
│ + base: string, │
│ + ref = "HEAD", │
│ +): TimelineEntry[] { │
│ + return git(path, ["log", `-${limit}`, "--format=%h|%as|%s", ref, "--", file]) │
│ .split("\n") │
│ .filter(Boolean) │
│ .map((line) => { │
│ @@ -109,8 +119,8 @@ export interface DirEntry { │
│ │
│ /** Immediate children of a directory, github-style: dirs first, each │
│ * with the last commit that touched it. */ │
│ -export function dirEntries(path: string, dir = ""): DirEntry[] { │
│ - const spec = dir ? `HEAD:${dir}` : "HEAD"; │
│ +export function dirEntries(path: string, dir = "", ref = "HEAD"): DirEntry[] { │
│ + const spec = dir ? `${ref}:${dir}` : ref; │
│ const rows = git(path, ["ls-tree", spec]) │
│ .split("\n") │
│ .filter(Boolean) │
│ @@ -124,7 +134,7 @@ export function dirEntries(path: string, dir = ""): DirEntry[] │
│ { │
│ .sort((a, b) => (a.kind === b.kind ? a.name.localeCompare(b.name) : a.kind == │
│ = "tree" ? -1 : 1)) │
│ .map(({ name, kind }) => { │
│ const full = dir ? `${dir}/${name}` : name; │
│ - const [hash, date, ...subject] = git(path, ["log", "-1", "--format=%h|%as|% │
│ s", "HEAD", "--", full]) │
│ + const [hash, date, ...subject] = git(path, ["log", "-1", "--format=%h|%as|% │
│ s", ref, "--", full]) │
│ .trim() │
│ .split("|"); │
│ return { name: toGrid(name), path: full, kind, hash, date, subject: toGrid( │
│ subject.join("|")) }; │
│ @@ -143,11 +153,11 @@ export function repoDirs(files: string[]): string[] { │
│ return [...dirs].sort(); │
│ } │
│ │
│ -/** Repo README content at HEAD, if present. */ │
│ -export function readReadme(path: string): string | null { │
│ +/** Repo README content, if present. */ │
│ +export function readReadme(path: string, ref = "HEAD"): string | null { │
│ for (const name of ["README.md", "readme.md", "README"]) { │
│ try { │
│ - return git(path, ["show", `HEAD:${name}`]); │
│ + return git(path, ["show", `${ref}:${name}`]); │
│ } catch { │
│ continue; │
│ } │
│ @@ -162,8 +172,8 @@ export interface Blob { │
│ lines: number; │
│ } │
│ │
│ -export function readBlob(path: string, file: string): Blob { │
│ - const raw = execFileSync("git", ["-C", path, "show", `HEAD:${file}`], { │
│ +export function readBlob(path: string, file: string, ref = "HEAD"): Blob { │
│ + const raw = execFileSync("git", ["-C", path, "show", `${ref}:${file}`], { │
│ maxBuffer: 32 * 1024 * 1024, │
│ }); │
│ if (raw.subarray(0, 8000).includes(0)) { │
│ @@ -179,23 +189,47 @@ export function readBlob(path: string, file: string): Blob { │
│ }; │
│ } │
│ │
│ +export interface CommitFile { │
│ + path: string; │
│ + diff: string; │
│ +} │
│ + │
│ export interface Commit { │
│ hash: string; │
│ date: string; │
│ subject: string; │
│ body: string; │
│ - diff: string; │
│ + files: CommitFile[]; │
│ } │
│ │
│ +/** One commit with its patch split per file, github-style. */ │
│ export function readCommit(path: string, hash: string): Commit { │
│ const meta = git(path, ["show", "-s", "--format=%H|%as|%s|%b", hash]).trim(); │
│ const [full, date, subject, ...body] = meta.split("|"); │
│ - const diff = git(path, ["show", "--stat", "--patch", "--format=", hash]); │
│ + const patch = git(path, ["show", "--patch", "--format=", hash]); │
│ + const files: CommitFile[] = []; │
│ + for (const chunk of patch.split(/^(?=diff --git )/m)) { │
│ + if (!chunk.trim()) continue; │
│ + const m = chunk.match(/^diff --git a\/.+? b\/(.+)$/m); │
│ + const file = m ? m[1] : "?"; │
│ + const trimmed = │
│ + chunk.length > MAX_BLOB ? chunk.slice(0, MAX_BLOB) + "\n[diff truncated]" : │
│ chunk; │
│ + files.push({ path: file, diff: trimmed }); │
│ + } │
│ return { │
│ hash: full, │
│ date, │
│ subject: toGrid(subject), │
│ body: toGrid(body.join("|").trim()), │
│ - diff: diff.length > MAX_BLOB ? diff.slice(0, MAX_BLOB) + "\n[diff truncated]" │
│ : diff, │
│ + files, │
│ }; │
│ } │
│ + │
│ +/** Frame label for a file: the uppercased extension, same convention │
│ + * as the pipeline's code fences (label = uppercase of the token you │
│ + * have). No language table to maintain. */ │
│ +export function langOf(file: string): string { │
│ + const name = file.split("/").pop() ?? file; │
│ + const ext = name.includes(".") ? name.split(".").pop()! : ""; │
│ + return ext ? ext.toUpperCase().slice(0, 8) : "TXT"; │
│ +} │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ooknet-design/src/lib/git.ts ───────┐
│ diff --git a/ooknet-design/src/lib/git.ts b/ │
│ ooknet-design/src/lib/git.ts │
│ index 489be48..7e388d7 100644 │
│ --- a/ooknet-design/src/lib/git.ts │
│ +++ b/ooknet-design/src/lib/git.ts │
│ @@ -2,11 +2,11 @@ │
│ // from real repositories at build time - s │
│ tagit-style, but rendered │
│ // through the site's own components. Anyth │
│ ing that can land in a │
│ // frame passes toGrid() so history written │
│ before the og-ascii rule │
│ -// can't fail the build. │
│ +// can't fail the build. Every reader takes │
│ a ref so branch pages are │
│ +// first-class; urls follow the github shap │
│ e (tree/<branch>/<path>). │
│ import { execFileSync } from "node:child_pr │
│ ocess"; │
│ import { existsSync } from "node:fs"; │
│ import { toGrid } from "./ascii"; │
│ -import type { TreeNode } from "../component │
│ s/Tree/Tree"; │
│ import type { TimelineEntry } from "../comp │
│ onents/Timeline/Timeline"; │
│ │
│ export interface GitRepo { │
│ @@ -30,60 +30,70 @@ const git = (path: strin │
│ g, args: string[]): string => │
│ │
│ export const repoAvailable = (r: GitRepo): │
│ boolean => existsSync(r.path); │
│ │
│ +/** Local branches, default branch first. * │
│ / │
│ +export function branches(path: string): str │
│ ing[] { │
│ + const head = defaultBranch(path); │
│ + const all = git(path, ["for-each-ref", "r │
│ efs/heads", "--format=%(refname:short)"]) │
│ + .split("\n") │
│ + .filter(Boolean); │
│ + return [head, ...all.filter((b) => b !== │
│ head)]; │
│ +} │
│ + │
│ +export const defaultBranch = (path: string) │
│ : string => │
│ + git(path, ["symbolic-ref", "--short", "HE │
│ AD"]).trim(); │
│ + │
│ export interface RepoInfo { │
│ branch: string; │
│ commits: number; │
│ + lastHash: string; │
│ lastDate: string; │
│ lastSubject: string; │
│ } │
│ │
│ -export function repoInfo(path: string): Rep │
│ oInfo { │
│ - const [lastDate, ...subject] = git(path, │
│ ["log", "-1", "--format=%as|%s"]) │
│ +export function repoInfo(path: string, ref? │
│ : string): RepoInfo { │
│ + const branch = ref ?? defaultBranch(path) │
│ ; │
│ + const [lastHash, lastDate, ...subject] = │
│ git(path, ["log", "-1", "--format=%h|%as|%s" │
│ , branch]) │
│ .trim() │
│ .split("|"); │
│ return { │
│ - branch: git(path, ["symbolic-ref", "--s │
│ hort", "HEAD"]).trim(), │
│ - commits: parseInt(git(path, ["rev-list" │
│ , "--count", "HEAD"]).trim(), 10), │
│ + branch, │
│ + commits: parseInt(git(path, ["rev-list" │
│ , "--count", branch]).trim(), 10), │
│ + lastHash, │
│ lastDate, │
│ lastSubject: toGrid(subject.join("|")), │
│ }; │
│ } │
│ │
│ -export function repoFiles(path: string): st │
│ ring[] { │
│ - return git(path, ["ls-tree", "-r", "--nam │
│ e-only", "HEAD"]) │
│ +export function repoFiles(path: string, ref │
│ = "HEAD"): string[] { │
│ + return git(path, ["ls-tree", "-r", "--nam │
│ e-only", ref]) │
│ .split("\n") │
│ .filter(Boolean); │
│ } │
│ │
│ -/** File list as a Tree: directories branch │
│ , files link to blob pages. */ │
│ -export function filesTree(files: string[], │
│ base: string): TreeNode[] { │
│ - type Branch = { dirs: Map<string, Branch> │
│ ; files: string[] }; │
│ - const root: Branch = { dirs: new Map(), f │
│ iles: [] }; │
│ - for (const file of files) { │
│ - const segments = file.split("/"); │
│ - let branch = root; │
│ - for (const seg of segments.slice(0, -1) │
│ ) { │
│ - if (!branch.dirs.has(seg)) branch.dir │
│ s.set(seg, { dirs: new Map(), files: [] }); │
│ - branch = branch.dirs.get(seg)!; │
│ - } │
│ - branch.files.push(file); │
│ - } │
│ - const toNodes = (b: Branch): TreeNode[] = │
│ > [ │
│ - ...[...b.dirs.entries()] │
│ - .sort(([a], [c]) => a.localeCompare(c │
│ )) │
│ - .map(([seg, child]) => ({ label: `${t │
│ oGrid(seg)}/`, children: toNodes(child) })), │
│ - ...b.files │
│ - .sort() │
│ - .map((f) => ({ │
│ - label: toGrid(f.split("/").pop()!), │
│ - href: `${base}${f}/`, │
│ - })), │
│ - ]; │
│ - return toNodes(root); │
│ -} │
│ - │
│ -export function repoLog(path: string, limit │
│ : number, base: string): TimelineEntry[] { │
│ - return git(path, ["log", `-${limit}`, "-- │
│ format=%h|%as|%s"]) │
│ +export function repoLog(path: string, limit │
│ : number, base: string, ref = "HEAD"): Timel │
│ ineEntry[] { │
│ + return git(path, ["log", `-${limit}`, "-- │
│ format=%h|%as|%s", ref]) │
│ + .split("\n") │
│ + .filter(Boolean) │
│ + .map((line) => { │
│ + const [hash, date, ...subject] = line │
│ .split("|"); │
│ + return { │
│ + date, │
│ + title: toGrid(hash), │
│ + desc: toGrid(subject.join("|")), │
│ + href: `${base}commit/${hash}/`, │
│ + }; │
│ + }); │
│ +} │
│ + │
│ +/** Commits touching one path - the file hi │
│ story page. */ │
│ +export function pathLog( │
│ + path: string, │
│ + file: string, │
│ + limit: number, │
│ + base: string, │
│ + ref = "HEAD", │
│ +): TimelineEntry[] { │
│ + return git(path, ["log", `-${limit}`, "-- │
│ format=%h|%as|%s", ref, "--", file]) │
│ .split("\n") │
│ .filter(Boolean) │
│ .map((line) => { │
│ @@ -109,8 +119,8 @@ export interface DirEntr │
│ y { │
│ │
│ /** Immediate children of a directory, gith │
│ ub-style: dirs first, each │
│ * with the last commit that touched it. * │
│ / │
│ -export function dirEntries(path: string, di │
│ r = ""): DirEntry[] { │
│ - const spec = dir ? `HEAD:${dir}` : "HEAD" │
│ ; │
│ +export function dirEntries(path: string, di │
│ r = "", ref = "HEAD"): DirEntry[] { │
│ + const spec = dir ? `${ref}:${dir}` : ref; │
│ const rows = git(path, ["ls-tree", spec]) │
│ .split("\n") │
│ .filter(Boolean) │
│ @@ -124,7 +134,7 @@ export function dirEntri │
│ es(path: string, dir = ""): DirEntry[] { │
│ .sort((a, b) => (a.kind === b.kind ? a. │
│ name.localeCompare(b.name) : a.kind === "tre │
│ e" ? -1 : 1)) │
│ .map(({ name, kind }) => { │
│ const full = dir ? `${dir}/${name}` : │
│ name; │
│ - const [hash, date, ...subject] = git( │
│ path, ["log", "-1", "--format=%h|%as|%s", "H │
│ EAD", "--", full]) │
│ + const [hash, date, ...subject] = git( │
│ path, ["log", "-1", "--format=%h|%as|%s", re │
│ f, "--", full]) │
│ .trim() │
│ .split("|"); │
│ return { name: toGrid(name), path: fu │
│ ll, kind, hash, date, subject: toGrid(subjec │
│ t.join("|")) }; │
│ @@ -143,11 +153,11 @@ export function repoDi │
│ rs(files: string[]): string[] { │
│ return [...dirs].sort(); │
│ } │
│ │
│ -/** Repo README content at HEAD, if present │
│ . */ │
│ -export function readReadme(path: string): s │
│ tring | null { │
│ +/** Repo README content, if present. */ │
│ +export function readReadme(path: string, re │
│ f = "HEAD"): string | null { │
│ for (const name of ["README.md", "readme. │
│ md", "README"]) { │
│ try { │
│ - return git(path, ["show", `HEAD:${nam │
│ e}`]); │
│ + return git(path, ["show", `${ref}:${n │
│ ame}`]); │
│ } catch { │
│ continue; │
│ } │
│ @@ -162,8 +172,8 @@ export interface Blob { │
│ lines: number; │
│ } │
│ │
│ -export function readBlob(path: string, file │
│ : string): Blob { │
│ - const raw = execFileSync("git", ["-C", pa │
│ th, "show", `HEAD:${file}`], { │
│ +export function readBlob(path: string, file │
│ : string, ref = "HEAD"): Blob { │
│ + const raw = execFileSync("git", ["-C", pa │
│ th, "show", `${ref}:${file}`], { │
│ maxBuffer: 32 * 1024 * 1024, │
│ }); │
│ if (raw.subarray(0, 8000).includes(0)) { │
│ @@ -179,23 +189,47 @@ export function readBl │
│ ob(path: string, file: string): Blob { │
│ }; │
│ } │
│ │
│ +export interface CommitFile { │
│ + path: string; │
│ + diff: string; │
│ +} │
│ + │
│ export interface Commit { │
│ hash: string; │
│ date: string; │
│ subject: string; │
│ body: string; │
│ - diff: string; │
│ + files: CommitFile[]; │
│ } │
│ │
│ +/** One commit with its patch split per fil │
│ e, github-style. */ │
│ export function readCommit(path: string, ha │
│ sh: string): Commit { │
│ const meta = git(path, ["show", "-s", "-- │
│ format=%H|%as|%s|%b", hash]).trim(); │
│ const [full, date, subject, ...body] = me │
│ ta.split("|"); │
│ - const diff = git(path, ["show", "--stat", │
│ "--patch", "--format=", hash]); │
│ + const patch = git(path, ["show", "--patch │
│ ", "--format=", hash]); │
│ + const files: CommitFile[] = []; │
│ + for (const chunk of patch.split(/^(?=diff │
│ --git )/m)) { │
│ + if (!chunk.trim()) continue; │
│ + const m = chunk.match(/^diff --git a\/. │
│ +? b\/(.+)$/m); │
│ + const file = m ? m[1] : "?"; │
│ + const trimmed = │
│ + chunk.length > MAX_BLOB ? chunk.slice │
│ (0, MAX_BLOB) + "\n[diff truncated]" : chunk │
│ ; │
│ + files.push({ path: file, diff: trimmed │
│ }); │
│ + } │
│ return { │
│ hash: full, │
│ date, │
│ subject: toGrid(subject), │
│ body: toGrid(body.join("|").trim()), │
│ - diff: diff.length > MAX_BLOB ? diff.sli │
│ ce(0, MAX_BLOB) + "\n[diff truncated]" : dif │
│ f, │
│ + files, │
│ }; │
│ } │
│ + │
│ +/** Frame label for a file: the uppercased │
│ extension, same convention │
│ + * as the pipeline's code fences (label = │
│ uppercase of the token you │
│ + * have). No language table to maintain. * │
│ / │
│ +export function langOf(file: string): strin │
│ g { │
│ + const name = file.split("/").pop() ?? fil │
│ e; │
│ + const ext = name.includes(".") ? name.spl │
│ it(".").pop()! : ""; │
│ + return ext ? ext.toUpperCase().slice(0, 8 │
│ ) : "TXT"; │
│ +} │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/lib/markdown.ts ────────────────────────────────────────┐
│ diff --git a/ooknet-design/src/lib/markdown.ts b/ooknet-design/src/lib/markdown.ts │
│ new file mode 100644 │
│ index 0000000..8853e06 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/lib/markdown.ts │
│ @@ -0,0 +1,23 @@ │
│ +// Standalone markdown rendering for strings that live outside the │
│ +// content collections (repo READMEs). Same shape as the astro │
│ +// pipeline: gfm, then rehype-ascii for head rules, code frames, and │
│ +// ascii tables. Input goes through toGrid first so foreign text can't │
│ +// fail the grid checks the pipeline runs. │
│ +import { unified } from "unified"; │
│ +import remarkParse from "remark-parse"; │
│ +import remarkGfm from "remark-gfm"; │
│ +import remarkRehype from "remark-rehype"; │
│ +import rehypeStringify from "rehype-stringify"; │
│ +import { rehypeAscii } from "./rehype-ascii"; │
│ +import { toGrid } from "./ascii"; │
│ + │
│ +const pipeline = unified() │
│ + .use(remarkParse) │
│ + .use(remarkGfm) │
│ + .use(remarkRehype) │
│ + .use(rehypeAscii) │
│ + .use(rehypeStringify); │
│ + │
│ +export function renderMarkdown(md: string): string { │
│ + return String(pipeline.processSync(toGrid(md))); │
│ +} │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...et-design/src/lib/markdown.ts ───┐
│ diff --git a/ooknet-design/src/lib/markdown. │
│ ts b/ooknet-design/src/lib/markdown.ts │
│ new file mode 100644 │
│ index 0000000..8853e06 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/lib/markdown.ts │
│ @@ -0,0 +1,23 @@ │
│ +// Standalone markdown rendering for string │
│ s that live outside the │
│ +// content collections (repo READMEs). Same │
│ shape as the astro │
│ +// pipeline: gfm, then rehype-ascii for hea │
│ d rules, code frames, and │
│ +// ascii tables. Input goes through toGrid │
│ first so foreign text can't │
│ +// fail the grid checks the pipeline runs. │
│ +import { unified } from "unified"; │
│ +import remarkParse from "remark-parse"; │
│ +import remarkGfm from "remark-gfm"; │
│ +import remarkRehype from "remark-rehype"; │
│ +import rehypeStringify from "rehype-stringi │
│ fy"; │
│ +import { rehypeAscii } from "./rehype-ascii │
│ "; │
│ +import { toGrid } from "./ascii"; │
│ + │
│ +const pipeline = unified() │
│ + .use(remarkParse) │
│ + .use(remarkGfm) │
│ + .use(remarkRehype) │
│ + .use(rehypeAscii) │
│ + .use(rehypeStringify); │
│ + │
│ +export function renderMarkdown(md: string): │
│ string { │
│ + return String(pipeline.processSync(toGrid │
│ (md))); │
│ +} │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/git/[repo]/blob/[...rest].astro ──────────────────┐
│ diff --git a/ooknet-design/src/pages/git/[repo]/blob/[...path].astro b/ooknet-desi │
│ gn/src/pages/git/[repo]/blob/[...rest].astro │
│ similarity index 63% │
│ rename from ooknet-design/src/pages/git/[repo]/blob/[...path].astro │
│ rename to ooknet-design/src/pages/git/[repo]/blob/[...rest].astro │
│ index 6f33a4e..2c3a758 100644 │
│ --- a/ooknet-design/src/pages/git/[repo]/blob/[...path].astro │
│ +++ b/ooknet-design/src/pages/git/[repo]/blob/[...rest].astro │
│ @@ -4,27 +4,28 @@ import TopHeader from "../../../../components/TopHeader/TopHeade │
│ r.astro"; │
│ import ArticleHeader from "../../../../components/ArticleHeader/ArticleHeader.ast │
│ ro"; │
│ import Pre from "../../../../components/Pre/Pre.astro"; │
│ import KeyValue from "../../../../components/KeyValue/KeyValue.astro"; │
│ -import Diff from "../../../../components/Diff/Diff.astro"; │
│ +import CodeFrame from "../../../../components/CodeFrame/CodeFrame.astro"; │
│ import Button from "../../../../components/Button/Button.astro"; │
│ import ArticleFooter from "../../../../components/ArticleFooter/ArticleFooter.ast │
│ ro"; │
│ import { toGrid } from "../../../../lib/ascii"; │
│ import { REPOS } from "../../../../git.config"; │
│ -import { repoAvailable, repoFiles, readBlob, type GitRepo } from "../../../../lib │
│ /git"; │
│ +import { repoAvailable, branches, repoFiles, readBlob, langOf, type GitRepo } fro │
│ m "../../../../lib/git"; │
│ │
│ export function getStaticPaths() { │
│ return REPOS.filter(repoAvailable).flatMap((r) => │
│ - repoFiles(r.path).map((file) => ({ │
│ - params: { repo: r.slug, path: file }, │
│ - props: { repo: r, file }, │
│ - })), │
│ + branches(r.path).flatMap((branch) => │
│ + repoFiles(r.path, branch).map((file) => ({ │
│ + params: { repo: r.slug, rest: `${branch}/${file}` }, │
│ + props: { repo: r, branch, file }, │
│ + })), │
│ + ), │
│ ); │
│ } │
│ │
│ -const { repo, file } = Astro.props as { repo: GitRepo; file: string }; │
│ -const blob = readBlob(repo.path, file); │
│ +const { repo, branch, file } = Astro.props as { repo: GitRepo; branch: string; fi │
│ le: string }; │
│ +const blob = readBlob(repo.path, file, branch); │
│ const name = file.split("/").pop()!; │
│ -// paths are one long "word" - chunk them so KeyValue stays on-grid at │
│ -// the narrow frame (48 - keyW 8 = 40 cells) │
│ +const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : ""; │
│ const chunk = (s: string, n: number) => { │
│ const cps = [...s]; │
│ return Array.from({ length: Math.ceil(cps.length / n) }, (_, i) => │
│ @@ -39,14 +40,16 @@ const pathRows = chunk(toGrid(file), 38); │
│ <KeyValue entries={[ │
│ ["FILE", pathRows[0]], │
│ ...pathRows.slice(1).map((c) => ["", c] as [string, string]), │
│ + ["BRANCH", branch], │
│ ["LINES", blob.binary ? "BINARY" : `${blob.lines}`], │
│ ]} /> │
│ <Pre>{" "}</Pre> │
│ + <Pre><Button label="HISTORY" href={`/git/${repo.slug}/history/${branch}/${file} │
│ /`} />{" "}<Button label="UP" href={dir ? `/git/${repo.slug}/tree/${branch}/${dir │
│ }/` : `/git/${repo.slug}/`} /></Pre> │
│ + <Pre>{" "}</Pre> │
│ {blob.binary │
│ ? <Pre>*** BINARY FILE - NOT RENDERED ***</Pre> │
│ - : <Diff code={toGrid(blob.text)} />} │
│ + : <CodeFrame code={toGrid(blob.text)} label={langOf(file)} />} │
│ {blob.truncated && <Pre>*** TRUNCATED AT 200 KIB ***</Pre>} │
│ <Pre>{" "}</Pre> │
│ - <Pre><Button label="BACK TO REPO" href={`/git/${repo.slug}/`} /></Pre> │
│ <ArticleFooter /> │
│ </ArticleLayout> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...t/[repo]/blob/[...rest].astro ───┐
│ diff --git a/ooknet-design/src/pages/git/[re │
│ po]/blob/[...path].astro b/ooknet-design/src │
│ /pages/git/[repo]/blob/[...rest].astro │
│ similarity index 63% │
│ rename from ooknet-design/src/pages/git/[rep │
│ o]/blob/[...path].astro │
│ rename to ooknet-design/src/pages/git/[repo] │
│ /blob/[...rest].astro │
│ index 6f33a4e..2c3a758 100644 │
│ --- a/ooknet-design/src/pages/git/[repo]/blo │
│ b/[...path].astro │
│ +++ b/ooknet-design/src/pages/git/[repo]/blo │
│ b/[...rest].astro │
│ @@ -4,27 +4,28 @@ import TopHeader from "../ │
│ ../../../components/TopHeader/TopHeader.astr │
│ o"; │
│ import ArticleHeader from "../../../../comp │
│ onents/ArticleHeader/ArticleHeader.astro"; │
│ import Pre from "../../../../components/Pre │
│ /Pre.astro"; │
│ import KeyValue from "../../../../component │
│ s/KeyValue/KeyValue.astro"; │
│ -import Diff from "../../../../components/Di │
│ ff/Diff.astro"; │
│ +import CodeFrame from "../../../../componen │
│ ts/CodeFrame/CodeFrame.astro"; │
│ import Button from "../../../../components/ │
│ Button/Button.astro"; │
│ import ArticleFooter from "../../../../comp │
│ onents/ArticleFooter/ArticleFooter.astro"; │
│ import { toGrid } from "../../../../lib/asc │
│ ii"; │
│ import { REPOS } from "../../../../git.conf │
│ ig"; │
│ -import { repoAvailable, repoFiles, readBlob │
│ , type GitRepo } from "../../../../lib/git"; │
│ +import { repoAvailable, branches, repoFiles │
│ , readBlob, langOf, type GitRepo } from "../ │
│ ../../../lib/git"; │
│ │
│ export function getStaticPaths() { │
│ return REPOS.filter(repoAvailable).flatMa │
│ p((r) => │
│ - repoFiles(r.path).map((file) => ({ │
│ - params: { repo: r.slug, path: file }, │
│ - props: { repo: r, file }, │
│ - })), │
│ + branches(r.path).flatMap((branch) => │
│ + repoFiles(r.path, branch).map((file) │
│ => ({ │
│ + params: { repo: r.slug, rest: `${br │
│ anch}/${file}` }, │
│ + props: { repo: r, branch, file }, │
│ + })), │
│ + ), │
│ ); │
│ } │
│ │
│ -const { repo, file } = Astro.props as { rep │
│ o: GitRepo; file: string }; │
│ -const blob = readBlob(repo.path, file); │
│ +const { repo, branch, file } = Astro.props │
│ as { repo: GitRepo; branch: string; file: st │
│ ring }; │
│ +const blob = readBlob(repo.path, file, bran │
│ ch); │
│ const name = file.split("/").pop()!; │
│ -// paths are one long "word" - chunk them s │
│ o KeyValue stays on-grid at │
│ -// the narrow frame (48 - keyW 8 = 40 cells │
│ ) │
│ +const dir = file.includes("/") ? file.slice │
│ (0, file.lastIndexOf("/")) : ""; │
│ const chunk = (s: string, n: number) => { │
│ const cps = [...s]; │
│ return Array.from({ length: Math.ceil(cps │
│ .length / n) }, (_, i) => │
│ @@ -39,14 +40,16 @@ const pathRows = chunk(t │
│ oGrid(file), 38); │
│ <KeyValue entries={[ │
│ ["FILE", pathRows[0]], │
│ ...pathRows.slice(1).map((c) => ["", c] │
│ as [string, string]), │
│ + ["BRANCH", branch], │
│ ["LINES", blob.binary ? "BINARY" : `${b │
│ lob.lines}`], │
│ ]} /> │
│ <Pre>{" "}</Pre> │
│ + <Pre><Button label="HISTORY" href={`/git/ │
│ ${repo.slug}/history/${branch}/${file}/`} /> │
│ {" "}<Button label="UP" href={dir ? `/git/$ │
│ {repo.slug}/tree/${branch}/${dir}/` : `/git/ │
│ ${repo.slug}/`} /></Pre> │
│ + <Pre>{" "}</Pre> │
│ {blob.binary │
│ ? <Pre>*** BINARY FILE - NOT RENDERED * │
│ **</Pre> │
│ - : <Diff code={toGrid(blob.text)} />} │
│ + : <CodeFrame code={toGrid(blob.text)} l │
│ abel={langOf(file)} />} │
│ {blob.truncated && <Pre>*** TRUNCATED AT │
│ 200 KIB ***</Pre>} │
│ <Pre>{" "}</Pre> │
│ - <Pre><Button label="BACK TO REPO" href={` │
│ /git/${repo.slug}/`} /></Pre> │
│ <ArticleFooter /> │
│ </ArticleLayout> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/git/[repo]/commit/[hash].astro ───────────────────┐
│ diff --git a/ooknet-design/src/pages/git/[repo]/commit/[hash].astro b/ooknet-desig │
│ n/src/pages/git/[repo]/commit/[hash].astro │
│ index 391cea9..a262295 100644 │
│ --- a/ooknet-design/src/pages/git/[repo]/commit/[hash].astro │
│ +++ b/ooknet-design/src/pages/git/[repo]/commit/[hash].astro │
│ @@ -4,12 +4,12 @@ import TopHeader from "../../../../components/TopHeader/TopHeade │
│ r.astro"; │
│ import ArticleHeader from "../../../../components/ArticleHeader/ArticleHeader.ast │
│ ro"; │
│ import Pre from "../../../../components/Pre/Pre.astro"; │
│ import KeyValue from "../../../../components/KeyValue/KeyValue.astro"; │
│ -import Diff from "../../../../components/Diff/Diff.astro"; │
│ +import CodeFrame from "../../../../components/CodeFrame/CodeFrame.astro"; │
│ import Button from "../../../../components/Button/Button.astro"; │
│ import ArticleFooter from "../../../../components/ArticleFooter/ArticleFooter.ast │
│ ro"; │
│ import { toGrid } from "../../../../lib/ascii"; │
│ import { REPOS } from "../../../../git.config"; │
│ -import { repoAvailable, repoLog, readCommit, type GitRepo } from "../../../../lib │
│ /git"; │
│ +import { repoAvailable, defaultBranch, repoLog, readCommit, type GitRepo } from " │
│ ../../../../lib/git"; │
│ │
│ export function getStaticPaths() { │
│ return REPOS.filter(repoAvailable).flatMap((r) => │
│ @@ -23,6 +23,7 @@ export function getStaticPaths() { │
│ const { repo } = Astro.props as { repo: GitRepo }; │
│ const { hash } = Astro.params; │
│ const commit = readCommit(repo.path, hash!); │
│ +const branch = defaultBranch(repo.path); │
│ --- │
│ <ArticleLayout title={`GIT - ${repo.name} @ ${hash}`} description={commit.subject │
│ }> │
│ <TopHeader /> │
│ @@ -32,10 +33,15 @@ const commit = readCommit(repo.path, hash!); │
│ ["HASH", commit.hash.slice(0, 12)], │
│ ["DATE", commit.date], │
│ ["SUBJECT", commit.subject], │
│ + ["FILES", `${commit.files.length} CHANGED`], │
│ ]} /> │
│ <Pre>{" "}</Pre> │
│ - <Diff code={toGrid(commit.diff)} /> │
│ - <Pre>{" "}</Pre> │
│ - <Pre><Button label="BACK TO LOG" href={`/git/${repo.slug}/log/`} /></Pre> │
│ + {commit.files.map((f) => ( │
│ + <Fragment> │
│ + <CodeFrame code={toGrid(f.diff)} label={toGrid(f.path)} diff /> │
│ + <Pre>{" "}</Pre> │
│ + </Fragment> │
│ + ))} │
│ + <Pre><Button label="BACK TO LOG" href={`/git/${repo.slug}/log/${branch}/`} /></ │
│ Pre> │
│ <ArticleFooter /> │
│ </ArticleLayout> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...it/[repo]/commit/[hash].astro ───┐
│ diff --git a/ooknet-design/src/pages/git/[re │
│ po]/commit/[hash].astro b/ooknet-design/src/ │
│ pages/git/[repo]/commit/[hash].astro │
│ index 391cea9..a262295 100644 │
│ --- a/ooknet-design/src/pages/git/[repo]/com │
│ mit/[hash].astro │
│ +++ b/ooknet-design/src/pages/git/[repo]/com │
│ mit/[hash].astro │
│ @@ -4,12 +4,12 @@ import TopHeader from "../ │
│ ../../../components/TopHeader/TopHeader.astr │
│ o"; │
│ import ArticleHeader from "../../../../comp │
│ onents/ArticleHeader/ArticleHeader.astro"; │
│ import Pre from "../../../../components/Pre │
│ /Pre.astro"; │
│ import KeyValue from "../../../../component │
│ s/KeyValue/KeyValue.astro"; │
│ -import Diff from "../../../../components/Di │
│ ff/Diff.astro"; │
│ +import CodeFrame from "../../../../componen │
│ ts/CodeFrame/CodeFrame.astro"; │
│ import Button from "../../../../components/ │
│ Button/Button.astro"; │
│ import ArticleFooter from "../../../../comp │
│ onents/ArticleFooter/ArticleFooter.astro"; │
│ import { toGrid } from "../../../../lib/asc │
│ ii"; │
│ import { REPOS } from "../../../../git.conf │
│ ig"; │
│ -import { repoAvailable, repoLog, readCommit │
│ , type GitRepo } from "../../../../lib/git"; │
│ +import { repoAvailable, defaultBranch, repo │
│ Log, readCommit, type GitRepo } from "../../ │
│ ../../lib/git"; │
│ │
│ export function getStaticPaths() { │
│ return REPOS.filter(repoAvailable).flatMa │
│ p((r) => │
│ @@ -23,6 +23,7 @@ export function getStaticP │
│ aths() { │
│ const { repo } = Astro.props as { repo: Git │
│ Repo }; │
│ const { hash } = Astro.params; │
│ const commit = readCommit(repo.path, hash!) │
│ ; │
│ +const branch = defaultBranch(repo.path); │
│ --- │
│ <ArticleLayout title={`GIT - ${repo.name} @ │
│ ${hash}`} description={commit.subject}> │
│ <TopHeader /> │
│ @@ -32,10 +33,15 @@ const commit = readCommi │
│ t(repo.path, hash!); │
│ ["HASH", commit.hash.slice(0, 12)], │
│ ["DATE", commit.date], │
│ ["SUBJECT", commit.subject], │
│ + ["FILES", `${commit.files.length} CHANG │
│ ED`], │
│ ]} /> │
│ <Pre>{" "}</Pre> │
│ - <Diff code={toGrid(commit.diff)} /> │
│ - <Pre>{" "}</Pre> │
│ - <Pre><Button label="BACK TO LOG" href={`/ │
│ git/${repo.slug}/log/`} /></Pre> │
│ + {commit.files.map((f) => ( │
│ + <Fragment> │
│ + <CodeFrame code={toGrid(f.diff)} labe │
│ l={toGrid(f.path)} diff /> │
│ + <Pre>{" "}</Pre> │
│ + </Fragment> │
│ + ))} │
│ + <Pre><Button label="BACK TO LOG" href={`/ │
│ git/${repo.slug}/log/${branch}/`} /></Pre> │
│ <ArticleFooter /> │
│ </ArticleLayout> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/git/[repo]/history/[...rest].astro ───────────────┐
│ diff --git a/ooknet-design/src/pages/git/[repo]/history/[...rest].astro b/ooknet-d │
│ esign/src/pages/git/[repo]/history/[...rest].astro │
│ new file mode 100644 │
│ index 0000000..b303792 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/git/[repo]/history/[...rest].astro │
│ @@ -0,0 +1,48 @@ │
│ +--- │
│ +import ArticleLayout from "../../../../layouts/ArticleLayout/ArticleLayout.astro" │
│ ; │
│ +import TopHeader from "../../../../components/TopHeader/TopHeader.astro"; │
│ +import ArticleHeader from "../../../../components/ArticleHeader/ArticleHeader.ast │
│ ro"; │
│ +import Pre from "../../../../components/Pre/Pre.astro"; │
│ +import KeyValue from "../../../../components/KeyValue/KeyValue.astro"; │
│ +import Timeline from "../../../../components/Timeline/Timeline.astro"; │
│ +import Button from "../../../../components/Button/Button.astro"; │
│ +import ArticleFooter from "../../../../components/ArticleFooter/ArticleFooter.ast │
│ ro"; │
│ +import { toGrid } from "../../../../lib/ascii"; │
│ +import { REPOS } from "../../../../git.config"; │
│ +import { repoAvailable, branches, repoFiles, pathLog, type GitRepo } from "../../ │
│ ../../lib/git"; │
│ + │
│ +export function getStaticPaths() { │
│ + return REPOS.filter(repoAvailable).flatMap((r) => │
│ + branches(r.path).flatMap((branch) => │
│ + repoFiles(r.path, branch).map((file) => ({ │
│ + params: { repo: r.slug, rest: `${branch}/${file}` }, │
│ + props: { repo: r, branch, file }, │
│ + })), │
│ + ), │
│ + ); │
│ +} │
│ + │
│ +const { repo, branch, file } = Astro.props as { repo: GitRepo; branch: string; fi │
│ le: string }; │
│ +const log = pathLog(repo.path, file, 200, `/git/${repo.slug}/`, branch); │
│ +const chunk = (s: string, n: number) => { │
│ + const cps = [...s]; │
│ + return Array.from({ length: Math.ceil(cps.length / n) }, (_, i) => │
│ + cps.slice(i * n, (i + 1) * n).join("")); │
│ +}; │
│ +const pathRows = chunk(toGrid(file), 38); │
│ +--- │
│ +<ArticleLayout title={`GIT - ${repo.name} - history of ${file}`} description={`Co │
│ mmits touching ${file} in ${repo.name}.`}> │
│ + <TopHeader /> │
│ + <ArticleHeader left="OOKNET GIT" right={`HISTORY @ ${branch.toUpperCase()}`} /> │
│ + <Pre>{" "}</Pre> │
│ + <KeyValue entries={[ │
│ + ["FILE", pathRows[0]], │
│ + ...pathRows.slice(1).map((c) => ["", c] as [string, string]), │
│ + ["COMMITS", `${log.length}`], │
│ + ]} /> │
│ + <Pre>{" "}</Pre> │
│ + <Timeline entries={log} /> │
│ + <Pre>{" "}</Pre> │
│ + <Pre><Button label="BACK TO FILE" href={`/git/${repo.slug}/blob/${branch}/${fil │
│ e}/`} /></Pre> │
│ + <ArticleFooter /> │
│ +</ArticleLayout> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...repo]/history/[...rest].astro ───┐
│ diff --git a/ooknet-design/src/pages/git/[re │
│ po]/history/[...rest].astro b/ooknet-design/ │
│ src/pages/git/[repo]/history/[...rest].astro │
│ new file mode 100644 │
│ index 0000000..b303792 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/git/[repo]/his │
│ tory/[...rest].astro │
│ @@ -0,0 +1,48 @@ │
│ +--- │
│ +import ArticleLayout from "../../../../layo │
│ uts/ArticleLayout/ArticleLayout.astro"; │
│ +import TopHeader from "../../../../componen │
│ ts/TopHeader/TopHeader.astro"; │
│ +import ArticleHeader from "../../../../comp │
│ onents/ArticleHeader/ArticleHeader.astro"; │
│ +import Pre from "../../../../components/Pre │
│ /Pre.astro"; │
│ +import KeyValue from "../../../../component │
│ s/KeyValue/KeyValue.astro"; │
│ +import Timeline from "../../../../component │
│ s/Timeline/Timeline.astro"; │
│ +import Button from "../../../../components/ │
│ Button/Button.astro"; │
│ +import ArticleFooter from "../../../../comp │
│ onents/ArticleFooter/ArticleFooter.astro"; │
│ +import { toGrid } from "../../../../lib/asc │
│ ii"; │
│ +import { REPOS } from "../../../../git.conf │
│ ig"; │
│ +import { repoAvailable, branches, repoFiles │
│ , pathLog, type GitRepo } from "../../../../ │
│ lib/git"; │
│ + │
│ +export function getStaticPaths() { │
│ + return REPOS.filter(repoAvailable).flatMa │
│ p((r) => │
│ + branches(r.path).flatMap((branch) => │
│ + repoFiles(r.path, branch).map((file) │
│ => ({ │
│ + params: { repo: r.slug, rest: `${br │
│ anch}/${file}` }, │
│ + props: { repo: r, branch, file }, │
│ + })), │
│ + ), │
│ + ); │
│ +} │
│ + │
│ +const { repo, branch, file } = Astro.props │
│ as { repo: GitRepo; branch: string; file: st │
│ ring }; │
│ +const log = pathLog(repo.path, file, 200, ` │
│ /git/${repo.slug}/`, branch); │
│ +const chunk = (s: string, n: number) => { │
│ + const cps = [...s]; │
│ + return Array.from({ length: Math.ceil(cps │
│ .length / n) }, (_, i) => │
│ + cps.slice(i * n, (i + 1) * n).join("")) │
│ ; │
│ +}; │
│ +const pathRows = chunk(toGrid(file), 38); │
│ +--- │
│ +<ArticleLayout title={`GIT - ${repo.name} - │
│ history of ${file}`} description={`Commits │
│ touching ${file} in ${repo.name}.`}> │
│ + <TopHeader /> │
│ + <ArticleHeader left="OOKNET GIT" right={` │
│ HISTORY @ ${branch.toUpperCase()}`} /> │
│ + <Pre>{" "}</Pre> │
│ + <KeyValue entries={[ │
│ + ["FILE", pathRows[0]], │
│ + ...pathRows.slice(1).map((c) => ["", c] │
│ as [string, string]), │
│ + ["COMMITS", `${log.length}`], │
│ + ]} /> │
│ + <Pre>{" "}</Pre> │
│ + <Timeline entries={log} /> │
│ + <Pre>{" "}</Pre> │
│ + <Pre><Button label="BACK TO FILE" href={` │
│ /git/${repo.slug}/blob/${branch}/${file}/`} │
│ /></Pre> │
│ + <ArticleFooter /> │
│ +</ArticleLayout> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/git/[repo]/index.astro ───────────────────────────┐
│ diff --git a/ooknet-design/src/pages/git/[repo]/index.astro b/ooknet-design/src/pa │
│ ges/git/[repo]/index.astro │
│ index bb1118f..469f7e3 100644 │
│ --- a/ooknet-design/src/pages/git/[repo]/index.astro │
│ +++ b/ooknet-design/src/pages/git/[repo]/index.astro │
│ @@ -1,19 +1,20 @@ │
│ --- │
│ -import { marked } from "marked"; │
│ import ArticleLayout from "../../../layouts/ArticleLayout/ArticleLayout.astro"; │
│ import TopHeader from "../../../components/TopHeader/TopHeader.astro"; │
│ import ArticleHeader from "../../../components/ArticleHeader/ArticleHeader.astro" │
│ ; │
│ import Frame from "../../../components/Frame/Frame.astro"; │
│ import Pre from "../../../components/Pre/Pre.astro"; │
│ -import KeyValue from "../../../components/KeyValue/KeyValue.astro"; │
│ +import ArticleMeta from "../../../components/ArticleMeta/ArticleMeta.astro"; │
│ +import Menu from "../../../components/Menu/Menu.astro"; │
│ import GitListing from "../../../components/GitListing/GitListing.astro"; │
│ import Timeline from "../../../components/Timeline/Timeline.astro"; │
│ import Button from "../../../components/Button/Button.astro"; │
│ import Prose from "../../../components/Prose/Prose.astro"; │
│ import ArticleFooter from "../../../components/ArticleFooter/ArticleFooter.astro" │
│ ; │
│ import { row2, ruleD } from "../../../lib/ascii"; │
│ +import { renderMarkdown } from "../../../lib/markdown"; │
│ import { REPOS } from "../../../git.config"; │
│ -import { repoAvailable, repoInfo, dirEntries, repoLog, readReadme, type GitRepo } │
│ from "../../../lib/git"; │
│ +import { repoAvailable, repoInfo, branches, dirEntries, repoLog, readReadme, type │
│ GitRepo } from "../../../lib/git"; │
│ │
│ export function getStaticPaths() { │
│ return REPOS.filter(repoAvailable).map((r) => ({ │
│ @@ -24,10 +25,12 @@ export function getStaticPaths() { │
│ │
│ const { repo } = Astro.props as { repo: GitRepo }; │
│ const info = repoInfo(repo.path); │
│ -const entries = dirEntries(repo.path); │
│ -const log = repoLog(repo.path, 5, `/git/${repo.slug}/`); │
│ -const readme = readReadme(repo.path); │
│ -const readmeHtml = readme ? marked.parse(readme) : null; │
│ +const branch = info.branch; │
│ +const allBranches = branches(repo.path); │
│ +const entries = dirEntries(repo.path, "", branch); │
│ +const log = repoLog(repo.path, 5, `/git/${repo.slug}/`, branch); │
│ +const readme = readReadme(repo.path, branch); │
│ +const readmeHtml = readme ? renderMarkdown(readme) : null; │
│ │
│ const section = (title: string, right: string) => (w: number) => │
│ [row2(title, right, w), ruleD(w)].join("\n"); │
│ @@ -36,30 +39,37 @@ const section = (title: string, right: string) => (w: number) │
│ => │
│ <TopHeader /> │
│ <ArticleHeader left="OOKNET GIT" right={`REPO: ${repo.name.toUpperCase()}`} dat │
│ e={info.lastDate} /> │
│ <Pre>{" "}</Pre> │
│ - <KeyValue entries={[ │
│ - ["BRANCH", info.branch], │
│ - ["COMMITS", `${info.commits}`], │
│ - ...(repo.clone ? [["CLONE", repo.clone.replace(/^https?:\/\//, "")] as [strin │
│ g, string]] : []), │
│ + <ArticleMeta entries={[ │
│ + { key: "COMMITS", value: `${info.commits}`, href: `/git/${repo.slug}/log/${br │
│ anch}/` }, │
│ + { key: "RECENT", value: `${info.lastHash} ${info.lastSubject}`, href: `/git/$ │
│ {repo.slug}/commit/${info.lastHash}/` }, │
│ + ...(repo.clone ? [{ key: "CLONE", value: repo.clone.replace(/^https?:\/\//, " │
│ ") }] : []), │
│ ]} /> │
│ <Pre>{" "}</Pre> │
│ │
│ - <Frame build={section("FILES", `HEAD @ ${info.branch}`)} /> │
│ + <Frame build={section("FILES", `${entries.length} ENTRIES`)} /> │
│ <Pre>{" "}</Pre> │
│ - <GitListing entries={entries} treeBase={`/git/${repo.slug}/tree/`} blobBase={`/ │
│ git/${repo.slug}/blob/`} commitBase={`/git/${repo.slug}/commit/`} /> │
│ + <Pre><Menu label={`BRANCH: ${branch}`} items={allBranches.map((b) => ({ │
│ + label: b, │
│ + href: b === branch ? `/git/${repo.slug}/` : `/git/${repo.slug}/tree/${b}/`, │
│ + }))} /></Pre> │
│ <Pre>{" "}</Pre> │
│ - │
│ - <Frame build={section("LOG", "most recent first")} /> │
│ - <Pre>{" "}</Pre> │
│ - <Timeline entries={log} /> │
│ - <Pre><Button label="FULL LOG" href={`/git/${repo.slug}/log/`} /></Pre> │
│ + <GitListing entries={entries} │
│ + treeBase={`/git/${repo.slug}/tree/${branch}/`} │
│ + blobBase={`/git/${repo.slug}/blob/${branch}/`} │
│ + commitBase={`/git/${repo.slug}/commit/`} /> │
│ <Pre>{" "}</Pre> │
│ │
│ {readmeHtml && ( │
│ <Fragment> │
│ - <Frame build={section("README", "rendered from HEAD")} /> │
│ + <Frame build={section("README", `rendered @ ${branch}`)} /> │
│ <Prose><div set:html={readmeHtml} /></Prose> │
│ <Pre>{" "}</Pre> │
│ </Fragment> │
│ )} │
│ + <Frame build={section("LOG", "most recent first")} /> │
│ + <Pre>{" "}</Pre> │
│ + <Timeline entries={log} /> │
│ + <Pre><Button label="FULL LOG" href={`/git/${repo.slug}/log/${branch}/`} /></Pre │
│ > │
│ + <Pre>{" "}</Pre> │
│ <ArticleFooter /> │
│ </ArticleLayout> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ .../pages/git/[repo]/index.astro ───┐
│ diff --git a/ooknet-design/src/pages/git/[re │
│ po]/index.astro b/ooknet-design/src/pages/gi │
│ t/[repo]/index.astro │
│ index bb1118f..469f7e3 100644 │
│ --- a/ooknet-design/src/pages/git/[repo]/ind │
│ ex.astro │
│ +++ b/ooknet-design/src/pages/git/[repo]/ind │
│ ex.astro │
│ @@ -1,19 +1,20 @@ │
│ --- │
│ -import { marked } from "marked"; │
│ import ArticleLayout from "../../../layouts │
│ /ArticleLayout/ArticleLayout.astro"; │
│ import TopHeader from "../../../components/ │
│ TopHeader/TopHeader.astro"; │
│ import ArticleHeader from "../../../compone │
│ nts/ArticleHeader/ArticleHeader.astro"; │
│ import Frame from "../../../components/Fram │
│ e/Frame.astro"; │
│ import Pre from "../../../components/Pre/Pr │
│ e.astro"; │
│ -import KeyValue from "../../../components/K │
│ eyValue/KeyValue.astro"; │
│ +import ArticleMeta from "../../../component │
│ s/ArticleMeta/ArticleMeta.astro"; │
│ +import Menu from "../../../components/Menu/ │
│ Menu.astro"; │
│ import GitListing from "../../../components │
│ /GitListing/GitListing.astro"; │
│ import Timeline from "../../../components/T │
│ imeline/Timeline.astro"; │
│ import Button from "../../../components/But │
│ ton/Button.astro"; │
│ import Prose from "../../../components/Pros │
│ e/Prose.astro"; │
│ import ArticleFooter from "../../../compone │
│ nts/ArticleFooter/ArticleFooter.astro"; │
│ import { row2, ruleD } from "../../../lib/a │
│ scii"; │
│ +import { renderMarkdown } from "../../../li │
│ b/markdown"; │
│ import { REPOS } from "../../../git.config" │
│ ; │
│ -import { repoAvailable, repoInfo, dirEntrie │
│ s, repoLog, readReadme, type GitRepo } from │
│ "../../../lib/git"; │
│ +import { repoAvailable, repoInfo, branches, │
│ dirEntries, repoLog, readReadme, type GitRe │
│ po } from "../../../lib/git"; │
│ │
│ export function getStaticPaths() { │
│ return REPOS.filter(repoAvailable).map((r │
│ ) => ({ │
│ @@ -24,10 +25,12 @@ export function getStati │
│ cPaths() { │
│ │
│ const { repo } = Astro.props as { repo: Git │
│ Repo }; │
│ const info = repoInfo(repo.path); │
│ -const entries = dirEntries(repo.path); │
│ -const log = repoLog(repo.path, 5, `/git/${r │
│ epo.slug}/`); │
│ -const readme = readReadme(repo.path); │
│ -const readmeHtml = readme ? marked.parse(re │
│ adme) : null; │
│ +const branch = info.branch; │
│ +const allBranches = branches(repo.path); │
│ +const entries = dirEntries(repo.path, "", b │
│ ranch); │
│ +const log = repoLog(repo.path, 5, `/git/${r │
│ epo.slug}/`, branch); │
│ +const readme = readReadme(repo.path, branch │
│ ); │
│ +const readmeHtml = readme ? renderMarkdown( │
│ readme) : null; │
│ │
│ const section = (title: string, right: stri │
│ ng) => (w: number) => │
│ [row2(title, right, w), ruleD(w)].join("\ │
│ n"); │
│ @@ -36,30 +39,37 @@ const section = (title: │
│ string, right: string) => (w: number) => │
│ <TopHeader /> │
│ <ArticleHeader left="OOKNET GIT" right={` │
│ REPO: ${repo.name.toUpperCase()}`} date={inf │
│ o.lastDate} /> │
│ <Pre>{" "}</Pre> │
│ - <KeyValue entries={[ │
│ - ["BRANCH", info.branch], │
│ - ["COMMITS", `${info.commits}`], │
│ - ...(repo.clone ? [["CLONE", repo.clone. │
│ replace(/^https?:\/\//, "")] as [string, str │
│ ing]] : []), │
│ + <ArticleMeta entries={[ │
│ + { key: "COMMITS", value: `${info.commit │
│ s}`, href: `/git/${repo.slug}/log/${branch}/ │
│ ` }, │
│ + { key: "RECENT", value: `${info.lastHas │
│ h} ${info.lastSubject}`, href: `/git/${repo. │
│ slug}/commit/${info.lastHash}/` }, │
│ + ...(repo.clone ? [{ key: "CLONE", value │
│ : repo.clone.replace(/^https?:\/\//, "") }] │
│ : []), │
│ ]} /> │
│ <Pre>{" "}</Pre> │
│ │
│ - <Frame build={section("FILES", `HEAD @ ${ │
│ info.branch}`)} /> │
│ + <Frame build={section("FILES", `${entries │
│ .length} ENTRIES`)} /> │
│ <Pre>{" "}</Pre> │
│ - <GitListing entries={entries} treeBase={` │
│ /git/${repo.slug}/tree/`} blobBase={`/git/${ │
│ repo.slug}/blob/`} commitBase={`/git/${repo. │
│ slug}/commit/`} /> │
│ + <Pre><Menu label={`BRANCH: ${branch}`} it │
│ ems={allBranches.map((b) => ({ │
│ + label: b, │
│ + href: b === branch ? `/git/${repo.slug} │
│ /` : `/git/${repo.slug}/tree/${b}/`, │
│ + }))} /></Pre> │
│ <Pre>{" "}</Pre> │
│ - │
│ - <Frame build={section("LOG", "most recent │
│ first")} /> │
│ - <Pre>{" "}</Pre> │
│ - <Timeline entries={log} /> │
│ - <Pre><Button label="FULL LOG" href={`/git │
│ /${repo.slug}/log/`} /></Pre> │
│ + <GitListing entries={entries} │
│ + treeBase={`/git/${repo.slug}/tree/${bra │
│ nch}/`} │
│ + blobBase={`/git/${repo.slug}/blob/${bra │
│ nch}/`} │
│ + commitBase={`/git/${repo.slug}/commit/` │
│ } /> │
│ <Pre>{" "}</Pre> │
│ │
│ {readmeHtml && ( │
│ <Fragment> │
│ - <Frame build={section("README", "rend │
│ ered from HEAD")} /> │
│ + <Frame build={section("README", `rend │
│ ered @ ${branch}`)} /> │
│ <Prose><div set:html={readmeHtml} />< │
│ /Prose> │
│ <Pre>{" "}</Pre> │
│ </Fragment> │
│ )} │
│ + <Frame build={section("LOG", "most recent │
│ first")} /> │
│ + <Pre>{" "}</Pre> │
│ + <Timeline entries={log} /> │
│ + <Pre><Button label="FULL LOG" href={`/git │
│ /${repo.slug}/log/${branch}/`} /></Pre> │
│ + <Pre>{" "}</Pre> │
│ <ArticleFooter /> │
│ </ArticleLayout> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/git/[repo]/log.astro ─────────────────────────────┐
│ diff --git a/ooknet-design/src/pages/git/[repo]/log.astro b/ooknet-design/src/page │
│ s/git/[repo]/log.astro │
│ deleted file mode 100644 │
│ index 337ed4a..0000000 │
│ --- a/ooknet-design/src/pages/git/[repo]/log.astro │
│ +++ /dev/null │
│ @@ -1,31 +0,0 @@ │
│ ---- │
│ -import ArticleLayout from "../../../layouts/ArticleLayout/ArticleLayout.astro"; │
│ -import TopHeader from "../../../components/TopHeader/TopHeader.astro"; │
│ -import ArticleHeader from "../../../components/ArticleHeader/ArticleHeader.astro" │
│ ; │
│ -import Pre from "../../../components/Pre/Pre.astro"; │
│ -import Timeline from "../../../components/Timeline/Timeline.astro"; │
│ -import Button from "../../../components/Button/Button.astro"; │
│ -import ArticleFooter from "../../../components/ArticleFooter/ArticleFooter.astro" │
│ ; │
│ -import { REPOS } from "../../../git.config"; │
│ -import { repoAvailable, repoInfo, repoLog, type GitRepo } from "../../../lib/git" │
│ ; │
│ - │
│ -export function getStaticPaths() { │
│ - return REPOS.filter(repoAvailable).map((r) => ({ │
│ - params: { repo: r.slug }, │
│ - props: { repo: r }, │
│ - })); │
│ -} │
│ - │
│ -const { repo } = Astro.props as { repo: GitRepo }; │
│ -const info = repoInfo(repo.path); │
│ -const log = repoLog(repo.path, 200, `/git/${repo.slug}/`); │
│ ---- │
│ -<ArticleLayout title={`GIT - ${repo.name} - log`} description={`Commit log for ${ │
│ repo.name}.`}> │
│ - <TopHeader /> │
│ - <ArticleHeader left="OOKNET GIT" right={`LOG: ${repo.name.toUpperCase()}`} date │
│ ={info.lastDate} /> │
│ - <Pre>{" "}</Pre> │
│ - <Timeline entries={log} /> │
│ - <Pre>{" "}</Pre> │
│ - <Pre><Button label="BACK TO REPO" href={`/git/${repo.slug}/`} /></Pre> │
│ - <ArticleFooter /> │
│ -</ArticleLayout> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...rc/pages/git/[repo]/log.astro ───┐
│ diff --git a/ooknet-design/src/pages/git/[re │
│ po]/log.astro b/ooknet-design/src/pages/git/ │
│ [repo]/log.astro │
│ deleted file mode 100644 │
│ index 337ed4a..0000000 │
│ --- a/ooknet-design/src/pages/git/[repo]/log │
│ .astro │
│ +++ /dev/null │
│ @@ -1,31 +0,0 @@ │
│ ---- │
│ -import ArticleLayout from "../../../layouts │
│ /ArticleLayout/ArticleLayout.astro"; │
│ -import TopHeader from "../../../components/ │
│ TopHeader/TopHeader.astro"; │
│ -import ArticleHeader from "../../../compone │
│ nts/ArticleHeader/ArticleHeader.astro"; │
│ -import Pre from "../../../components/Pre/Pr │
│ e.astro"; │
│ -import Timeline from "../../../components/T │
│ imeline/Timeline.astro"; │
│ -import Button from "../../../components/But │
│ ton/Button.astro"; │
│ -import ArticleFooter from "../../../compone │
│ nts/ArticleFooter/ArticleFooter.astro"; │
│ -import { REPOS } from "../../../git.config" │
│ ; │
│ -import { repoAvailable, repoInfo, repoLog, │
│ type GitRepo } from "../../../lib/git"; │
│ - │
│ -export function getStaticPaths() { │
│ - return REPOS.filter(repoAvailable).map((r │
│ ) => ({ │
│ - params: { repo: r.slug }, │
│ - props: { repo: r }, │
│ - })); │
│ -} │
│ - │
│ -const { repo } = Astro.props as { repo: Git │
│ Repo }; │
│ -const info = repoInfo(repo.path); │
│ -const log = repoLog(repo.path, 200, `/git/$ │
│ {repo.slug}/`); │
│ ---- │
│ -<ArticleLayout title={`GIT - ${repo.name} - │
│ log`} description={`Commit log for ${repo.n │
│ ame}.`}> │
│ - <TopHeader /> │
│ - <ArticleHeader left="OOKNET GIT" right={` │
│ LOG: ${repo.name.toUpperCase()}`} date={info │
│ .lastDate} /> │
│ - <Pre>{" "}</Pre> │
│ - <Timeline entries={log} /> │
│ - <Pre>{" "}</Pre> │
│ - <Pre><Button label="BACK TO REPO" href={` │
│ /git/${repo.slug}/`} /></Pre> │
│ - <ArticleFooter /> │
│ -</ArticleLayout> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/git/[repo]/log/[...branch].astro ─────────────────┐
│ diff --git a/ooknet-design/src/pages/git/[repo]/log/[...branch].astro b/ooknet-des │
│ ign/src/pages/git/[repo]/log/[...branch].astro │
│ new file mode 100644 │
│ index 0000000..9453807 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/git/[repo]/log/[...branch].astro │
│ @@ -0,0 +1,35 @@ │
│ +--- │
│ +import ArticleLayout from "../../../../layouts/ArticleLayout/ArticleLayout.astro" │
│ ; │
│ +import TopHeader from "../../../../components/TopHeader/TopHeader.astro"; │
│ +import ArticleHeader from "../../../../components/ArticleHeader/ArticleHeader.ast │
│ ro"; │
│ +import Pre from "../../../../components/Pre/Pre.astro"; │
│ +import Timeline from "../../../../components/Timeline/Timeline.astro"; │
│ +import Button from "../../../../components/Button/Button.astro"; │
│ +import ArticleFooter from "../../../../components/ArticleFooter/ArticleFooter.ast │
│ ro"; │
│ +import { REPOS } from "../../../../git.config"; │
│ +import { repoAvailable, branches, defaultBranch, repoInfo, repoLog, type GitRepo │
│ } from "../../../../lib/git"; │
│ + │
│ +export function getStaticPaths() { │
│ + return REPOS.filter(repoAvailable).flatMap((r) => │
│ + branches(r.path).map((branch) => ({ │
│ + params: { repo: r.slug, branch }, │
│ + props: { repo: r, branch }, │
│ + })), │
│ + ); │
│ +} │
│ + │
│ +const { repo, branch } = Astro.props as { repo: GitRepo; branch: string }; │
│ +const info = repoInfo(repo.path, branch); │
│ +const log = repoLog(repo.path, 200, `/git/${repo.slug}/`, branch); │
│ +const isDefault = branch === defaultBranch(repo.path); │
│ +const backHref = isDefault ? `/git/${repo.slug}/` : `/git/${repo.slug}/tree/${bra │
│ nch}/`; │
│ +--- │
│ +<ArticleLayout title={`GIT - ${repo.name} - log @ ${branch}`} description={`Commi │
│ t log for ${repo.name} @ ${branch}.`}> │
│ + <TopHeader /> │
│ + <ArticleHeader left="OOKNET GIT" right={`LOG: ${repo.name.toUpperCase()} @ ${br │
│ anch.toUpperCase()}`} date={info.lastDate} /> │
│ + <Pre>{" "}</Pre> │
│ + <Timeline entries={log} /> │
│ + <Pre>{" "}</Pre> │
│ + <Pre><Button label="BACK TO REPO" href={backHref} /></Pre> │
│ + <ArticleFooter /> │
│ +</ArticleLayout> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ .../[repo]/log/[...branch].astro ───┐
│ diff --git a/ooknet-design/src/pages/git/[re │
│ po]/log/[...branch].astro b/ooknet-design/sr │
│ c/pages/git/[repo]/log/[...branch].astro │
│ new file mode 100644 │
│ index 0000000..9453807 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/git/[repo]/log │
│ /[...branch].astro │
│ @@ -0,0 +1,35 @@ │
│ +--- │
│ +import ArticleLayout from "../../../../layo │
│ uts/ArticleLayout/ArticleLayout.astro"; │
│ +import TopHeader from "../../../../componen │
│ ts/TopHeader/TopHeader.astro"; │
│ +import ArticleHeader from "../../../../comp │
│ onents/ArticleHeader/ArticleHeader.astro"; │
│ +import Pre from "../../../../components/Pre │
│ /Pre.astro"; │
│ +import Timeline from "../../../../component │
│ s/Timeline/Timeline.astro"; │
│ +import Button from "../../../../components/ │
│ Button/Button.astro"; │
│ +import ArticleFooter from "../../../../comp │
│ onents/ArticleFooter/ArticleFooter.astro"; │
│ +import { REPOS } from "../../../../git.conf │
│ ig"; │
│ +import { repoAvailable, branches, defaultBr │
│ anch, repoInfo, repoLog, type GitRepo } from │
│ "../../../../lib/git"; │
│ + │
│ +export function getStaticPaths() { │
│ + return REPOS.filter(repoAvailable).flatMa │
│ p((r) => │
│ + branches(r.path).map((branch) => ({ │
│ + params: { repo: r.slug, branch }, │
│ + props: { repo: r, branch }, │
│ + })), │
│ + ); │
│ +} │
│ + │
│ +const { repo, branch } = Astro.props as { r │
│ epo: GitRepo; branch: string }; │
│ +const info = repoInfo(repo.path, branch); │
│ +const log = repoLog(repo.path, 200, `/git/$ │
│ {repo.slug}/`, branch); │
│ +const isDefault = branch === defaultBranch( │
│ repo.path); │
│ +const backHref = isDefault ? `/git/${repo.s │
│ lug}/` : `/git/${repo.slug}/tree/${branch}/` │
│ ; │
│ +--- │
│ +<ArticleLayout title={`GIT - ${repo.name} - │
│ log @ ${branch}`} description={`Commit log │
│ for ${repo.name} @ ${branch}.`}> │
│ + <TopHeader /> │
│ + <ArticleHeader left="OOKNET GIT" right={` │
│ LOG: ${repo.name.toUpperCase()} @ ${branch.t │
│ oUpperCase()}`} date={info.lastDate} /> │
│ + <Pre>{" "}</Pre> │
│ + <Timeline entries={log} /> │
│ + <Pre>{" "}</Pre> │
│ + <Pre><Button label="BACK TO REPO" href={b │
│ ackHref} /></Pre> │
│ + <ArticleFooter /> │
│ +</ArticleLayout> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/git/[repo]/tree/[...dir].astro ───────────────────┐
│ diff --git a/ooknet-design/src/pages/git/[repo]/tree/[...dir].astro b/ooknet-desig │
│ n/src/pages/git/[repo]/tree/[...dir].astro │
│ deleted file mode 100644 │
│ index 5ef4a03..0000000 │
│ --- a/ooknet-design/src/pages/git/[repo]/tree/[...dir].astro │
│ +++ /dev/null │
│ @@ -1,42 +0,0 @@ │
│ ---- │
│ -import ArticleLayout from "../../../../layouts/ArticleLayout/ArticleLayout.astro" │
│ ; │
│ -import TopHeader from "../../../../components/TopHeader/TopHeader.astro"; │
│ -import ArticleHeader from "../../../../components/ArticleHeader/ArticleHeader.ast │
│ ro"; │
│ -import Frame from "../../../../components/Frame/Frame.astro"; │
│ -import Pre from "../../../../components/Pre/Pre.astro"; │
│ -import GitListing from "../../../../components/GitListing/GitListing.astro"; │
│ -import Button from "../../../../components/Button/Button.astro"; │
│ -import ArticleFooter from "../../../../components/ArticleFooter/ArticleFooter.ast │
│ ro"; │
│ -import { row2, ruleD, toGrid } from "../../../../lib/ascii"; │
│ -import { REPOS } from "../../../../git.config"; │
│ -import { repoAvailable, repoFiles, repoDirs, dirEntries, type GitRepo } from "../ │
│ ../../../lib/git"; │
│ - │
│ -export function getStaticPaths() { │
│ - return REPOS.filter(repoAvailable).flatMap((r) => │
│ - repoDirs(repoFiles(r.path)).map((dir) => ({ │
│ - params: { repo: r.slug, dir }, │
│ - props: { repo: r, dir }, │
│ - })), │
│ - ); │
│ -} │
│ - │
│ -const { repo, dir } = Astro.props as { repo: GitRepo; dir: string }; │
│ -const entries = dirEntries(repo.path, dir); │
│ -const parent = dir.includes("/") ? dir.slice(0, dir.lastIndexOf("/")) : ""; │
│ -const upHref = parent ? `/git/${repo.slug}/tree/${parent}/` : `/git/${repo.slug}/ │
│ `; │
│ - │
│ -const section = (w: number) => │
│ - [row2("FILES", `/${toGrid(dir)}`.length > 40 ? "..." + `/${toGrid(dir)}`.slice( │
│ -37) : `/${toGrid(dir)}`, w), ruleD(w)].join("\n"); │
│ ---- │
│ -<ArticleLayout title={`GIT - ${repo.name} - ${dir}`} description={`${dir} in ${re │
│ po.name}.`}> │
│ - <TopHeader /> │
│ - <ArticleHeader left="OOKNET GIT" right={`REPO: ${repo.name.toUpperCase()}`} /> │
│ - <Pre>{" "}</Pre> │
│ - <Frame build={section} /> │
│ - <Pre>{" "}</Pre> │
│ - <Pre><Button label="UP" href={upHref} /></Pre> │
│ - <Pre>{" "}</Pre> │
│ - <GitListing entries={entries} treeBase={`/git/${repo.slug}/tree/`} blobBase={`/ │
│ git/${repo.slug}/blob/`} commitBase={`/git/${repo.slug}/commit/`} /> │
│ - <Pre>{" "}</Pre> │
│ - <ArticleFooter /> │
│ -</ArticleLayout> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...it/[repo]/tree/[...dir].astro ───┐
│ diff --git a/ooknet-design/src/pages/git/[re │
│ po]/tree/[...dir].astro b/ooknet-design/src/ │
│ pages/git/[repo]/tree/[...dir].astro │
│ deleted file mode 100644 │
│ index 5ef4a03..0000000 │
│ --- a/ooknet-design/src/pages/git/[repo]/tre │
│ e/[...dir].astro │
│ +++ /dev/null │
│ @@ -1,42 +0,0 @@ │
│ ---- │
│ -import ArticleLayout from "../../../../layo │
│ uts/ArticleLayout/ArticleLayout.astro"; │
│ -import TopHeader from "../../../../componen │
│ ts/TopHeader/TopHeader.astro"; │
│ -import ArticleHeader from "../../../../comp │
│ onents/ArticleHeader/ArticleHeader.astro"; │
│ -import Frame from "../../../../components/F │
│ rame/Frame.astro"; │
│ -import Pre from "../../../../components/Pre │
│ /Pre.astro"; │
│ -import GitListing from "../../../../compone │
│ nts/GitListing/GitListing.astro"; │
│ -import Button from "../../../../components/ │
│ Button/Button.astro"; │
│ -import ArticleFooter from "../../../../comp │
│ onents/ArticleFooter/ArticleFooter.astro"; │
│ -import { row2, ruleD, toGrid } from "../../ │
│ ../../lib/ascii"; │
│ -import { REPOS } from "../../../../git.conf │
│ ig"; │
│ -import { repoAvailable, repoFiles, repoDirs │
│ , dirEntries, type GitRepo } from "../../../ │
│ ../lib/git"; │
│ - │
│ -export function getStaticPaths() { │
│ - return REPOS.filter(repoAvailable).flatMa │
│ p((r) => │
│ - repoDirs(repoFiles(r.path)).map((dir) = │
│ > ({ │
│ - params: { repo: r.slug, dir }, │
│ - props: { repo: r, dir }, │
│ - })), │
│ - ); │
│ -} │
│ - │
│ -const { repo, dir } = Astro.props as { repo │
│ : GitRepo; dir: string }; │
│ -const entries = dirEntries(repo.path, dir); │
│ -const parent = dir.includes("/") ? dir.slic │
│ e(0, dir.lastIndexOf("/")) : ""; │
│ -const upHref = parent ? `/git/${repo.slug}/ │
│ tree/${parent}/` : `/git/${repo.slug}/`; │
│ - │
│ -const section = (w: number) => │
│ - [row2("FILES", `/${toGrid(dir)}`.length > │
│ 40 ? "..." + `/${toGrid(dir)}`.slice(-37) : │
│ `/${toGrid(dir)}`, w), ruleD(w)].join("\n") │
│ ; │
│ ---- │
│ -<ArticleLayout title={`GIT - ${repo.name} - │
│ ${dir}`} description={`${dir} in ${repo.nam │
│ e}.`}> │
│ - <TopHeader /> │
│ - <ArticleHeader left="OOKNET GIT" right={` │
│ REPO: ${repo.name.toUpperCase()}`} /> │
│ - <Pre>{" "}</Pre> │
│ - <Frame build={section} /> │
│ - <Pre>{" "}</Pre> │
│ - <Pre><Button label="UP" href={upHref} />< │
│ /Pre> │
│ - <Pre>{" "}</Pre> │
│ - <GitListing entries={entries} treeBase={` │
│ /git/${repo.slug}/tree/`} blobBase={`/git/${ │
│ repo.slug}/blob/`} commitBase={`/git/${repo. │
│ slug}/commit/`} /> │
│ - <Pre>{" "}</Pre> │
│ - <ArticleFooter /> │
│ -</ArticleLayout> │
└──────────────────────────────────────────────┘
┌─ ooknet-design/src/pages/git/[repo]/tree/[...rest].astro ──────────────────┐
│ diff --git a/ooknet-design/src/pages/git/[repo]/tree/[...rest].astro b/ooknet-desi │
│ gn/src/pages/git/[repo]/tree/[...rest].astro │
│ new file mode 100644 │
│ index 0000000..570a4ff │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/git/[repo]/tree/[...rest].astro │
│ @@ -0,0 +1,60 @@ │
│ +--- │
│ +// Github-shaped: tree/<branch> is the branch root, tree/<branch>/<dir> │
│ +// a directory within it. Branch names and paths both may contain │
│ +// slashes; getStaticPaths enumerates every valid combination so there │
│ +// is no ambiguity to parse. │
│ +import ArticleLayout from "../../../../layouts/ArticleLayout/ArticleLayout.astro" │
│ ; │
│ +import TopHeader from "../../../../components/TopHeader/TopHeader.astro"; │
│ +import ArticleHeader from "../../../../components/ArticleHeader/ArticleHeader.ast │
│ ro"; │
│ +import Frame from "../../../../components/Frame/Frame.astro"; │
│ +import Pre from "../../../../components/Pre/Pre.astro"; │
│ +import GitListing from "../../../../components/GitListing/GitListing.astro"; │
│ +import Button from "../../../../components/Button/Button.astro"; │
│ +import ArticleFooter from "../../../../components/ArticleFooter/ArticleFooter.ast │
│ ro"; │
│ +import { row2, ruleD, toGrid } from "../../../../lib/ascii"; │
│ +import { REPOS } from "../../../../git.config"; │
│ +import { repoAvailable, branches, defaultBranch, repoFiles, repoDirs, dirEntries, │
│ type GitRepo } from "../../../../lib/git"; │
│ + │
│ +export function getStaticPaths() { │
│ + return REPOS.filter(repoAvailable).flatMap((r) => │
│ + branches(r.path).flatMap((branch) => [ │
│ + { params: { repo: r.slug, rest: branch }, props: { repo: r, branch, dir: "" │
│ } }, │
│ + ...repoDirs(repoFiles(r.path, branch)).map((dir) => ({ │
│ + params: { repo: r.slug, rest: `${branch}/${dir}` }, │
│ + props: { repo: r, branch, dir }, │
│ + })), │
│ + ]), │
│ + ); │
│ +} │
│ + │
│ +const { repo, branch, dir } = Astro.props as { repo: GitRepo; branch: string; dir │
│ : string }; │
│ +const entries = dirEntries(repo.path, dir, branch); │
│ +const isDefault = branch === defaultBranch(repo.path); │
│ +const parent = dir.includes("/") ? dir.slice(0, dir.lastIndexOf("/")) : ""; │
│ +const upHref = dir │
│ + ? parent │
│ + ? `/git/${repo.slug}/tree/${branch}/${parent}/` │
│ + : isDefault │
│ + ? `/git/${repo.slug}/` │
│ + : `/git/${repo.slug}/tree/${branch}/` │
│ + : `/git/${repo.slug}/`; │
│ + │
│ +const where = `/${toGrid(dir)}`; │
│ +const section = (w: number) => │
│ + [row2(`FILES @ ${branch}`, where.length > 30 ? "..." + where.slice(-27) : where │
│ , w), ruleD(w)].join("\n"); │
│ +--- │
│ +<ArticleLayout title={`GIT - ${repo.name} - ${dir || branch}`} description={`${di │
│ r || branch} in ${repo.name}.`}> │
│ + <TopHeader /> │
│ + <ArticleHeader left="OOKNET GIT" right={`REPO: ${repo.name.toUpperCase()}`} /> │
│ + <Pre>{" "}</Pre> │
│ + <Frame build={section} /> │
│ + <Pre>{" "}</Pre> │
│ + <Pre><Button label="UP" href={upHref} />{" "}<Button label="LOG" href={`/git/$ │
│ {repo.slug}/log/${branch}/`} /></Pre> │
│ + <Pre>{" "}</Pre> │
│ + <GitListing entries={entries} │
│ + treeBase={`/git/${repo.slug}/tree/${branch}/`} │
│ + blobBase={`/git/${repo.slug}/blob/${branch}/`} │
│ + commitBase={`/git/${repo.slug}/commit/`} /> │
│ + <Pre>{" "}</Pre> │
│ + <ArticleFooter /> │
│ +</ArticleLayout> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...t/[repo]/tree/[...rest].astro ───┐
│ diff --git a/ooknet-design/src/pages/git/[re │
│ po]/tree/[...rest].astro b/ooknet-design/src │
│ /pages/git/[repo]/tree/[...rest].astro │
│ new file mode 100644 │
│ index 0000000..570a4ff │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/git/[repo]/tre │
│ e/[...rest].astro │
│ @@ -0,0 +1,60 @@ │
│ +--- │
│ +// Github-shaped: tree/<branch> is the bran │
│ ch root, tree/<branch>/<dir> │
│ +// a directory within it. Branch names and │
│ paths both may contain │
│ +// slashes; getStaticPaths enumerates every │
│ valid combination so there │
│ +// is no ambiguity to parse. │
│ +import ArticleLayout from "../../../../layo │
│ uts/ArticleLayout/ArticleLayout.astro"; │
│ +import TopHeader from "../../../../componen │
│ ts/TopHeader/TopHeader.astro"; │
│ +import ArticleHeader from "../../../../comp │
│ onents/ArticleHeader/ArticleHeader.astro"; │
│ +import Frame from "../../../../components/F │
│ rame/Frame.astro"; │
│ +import Pre from "../../../../components/Pre │
│ /Pre.astro"; │
│ +import GitListing from "../../../../compone │
│ nts/GitListing/GitListing.astro"; │
│ +import Button from "../../../../components/ │
│ Button/Button.astro"; │
│ +import ArticleFooter from "../../../../comp │
│ onents/ArticleFooter/ArticleFooter.astro"; │
│ +import { row2, ruleD, toGrid } from "../../ │
│ ../../lib/ascii"; │
│ +import { REPOS } from "../../../../git.conf │
│ ig"; │
│ +import { repoAvailable, branches, defaultBr │
│ anch, repoFiles, repoDirs, dirEntries, type │
│ GitRepo } from "../../../../lib/git"; │
│ + │
│ +export function getStaticPaths() { │
│ + return REPOS.filter(repoAvailable).flatMa │
│ p((r) => │
│ + branches(r.path).flatMap((branch) => [ │
│ + { params: { repo: r.slug, rest: branc │
│ h }, props: { repo: r, branch, dir: "" } }, │
│ + ...repoDirs(repoFiles(r.path, branch) │
│ ).map((dir) => ({ │
│ + params: { repo: r.slug, rest: `${br │
│ anch}/${dir}` }, │
│ + props: { repo: r, branch, dir }, │
│ + })), │
│ + ]), │
│ + ); │
│ +} │
│ + │
│ +const { repo, branch, dir } = Astro.props a │
│ s { repo: GitRepo; branch: string; dir: stri │
│ ng }; │
│ +const entries = dirEntries(repo.path, dir, │
│ branch); │
│ +const isDefault = branch === defaultBranch( │
│ repo.path); │
│ +const parent = dir.includes("/") ? dir.slic │
│ e(0, dir.lastIndexOf("/")) : ""; │
│ +const upHref = dir │
│ + ? parent │
│ + ? `/git/${repo.slug}/tree/${branch}/${p │
│ arent}/` │
│ + : isDefault │
│ + ? `/git/${repo.slug}/` │
│ + : `/git/${repo.slug}/tree/${branch}/` │
│ + : `/git/${repo.slug}/`; │
│ + │
│ +const where = `/${toGrid(dir)}`; │
│ +const section = (w: number) => │
│ + [row2(`FILES @ ${branch}`, where.length > │
│ 30 ? "..." + where.slice(-27) : where, w), │
│ ruleD(w)].join("\n"); │
│ +--- │
│ +<ArticleLayout title={`GIT - ${repo.name} - │
│ ${dir || branch}`} description={`${dir || b │
│ ranch} in ${repo.name}.`}> │
│ + <TopHeader /> │
│ + <ArticleHeader left="OOKNET GIT" right={` │
│ REPO: ${repo.name.toUpperCase()}`} /> │
│ + <Pre>{" "}</Pre> │
│ + <Frame build={section} /> │
│ + <Pre>{" "}</Pre> │
│ + <Pre><Button label="UP" href={upHref} />{ │
│ " "}<Button label="LOG" href={`/git/${repo. │
│ slug}/log/${branch}/`} /></Pre> │
│ + <Pre>{" "}</Pre> │
│ + <GitListing entries={entries} │
│ + treeBase={`/git/${repo.slug}/tree/${bra │
│ nch}/`} │
│ + blobBase={`/git/${repo.slug}/blob/${bra │
│ nch}/`} │
│ + commitBase={`/git/${repo.slug}/commit/` │
│ } /> │
│ + <Pre>{" "}</Pre> │
│ + <ArticleFooter /> │
│ +</ArticleLayout> │
└──────────────────────────────────────────────┘
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET