HASH d63e516e0799
DATE 2026-07-18
SUBJECT web: /git section, repos rendered by the site itself, forgejo demoted to
plumbing
FILES 20 CHANGED HASH d63e516e0799
DATE 2026-07-18
SUBJECT web: /git section, repos rendered by
the site itself, forgejo demoted to
plumbing
FILES 20 CHANGED ┌─ README.md ─────────────────────────────────────────────────────────────── [ COPY ] ─┐
│ diff --git a/README.md b/README.md │
│ new file mode 100644 │
│ index 0000000..fb0f028 │
│ --- /dev/null │
│ +++ b/README.md │
│ @@ -0,0 +1,40 @@ │
│ +# ooknet.org │
│ + │
│ +Personal website. A text-mode knowledge base and the ascii design │
│ +system that renders it - every border, chart, and frame on the site is │
│ +a real character on a strict monospace grid. │
│ + │
│ +## Layout │
│ + │
│ +| PATH | WHAT | │
│ +| ---------------- | ------------------------------------------- | │
│ +| `ooknet-design/` | the astro site and the ascii design system | │
│ +| `nix/` | dev shell | │
│ + │
│ +## The rules │
│ + │
│ +- og ascii: no em dashes, no ellipsis glyphs, no curly quotes. │
│ + `checkGrid` fails the build on off-grid characters. │
│ +- every frame renders twice: 86 cells wide for desktop, 48 for mobile. │
│ +- no smooth animation. motion is discrete, like a terminal. │
│ +- components are pure builders with tests; chrome is text, not css │
│ + borders. │
│ + │
│ +## Working on it │
│ + │
│ +```sh │
│ +cd ooknet-design │
│ +npm run dev # dev server on :4321 │
│ +npm test # vitest │
│ +npm run build # static build + grid checks across every page │
│ +``` │
│ + │
│ +The dev shell comes from the flake; `direnv allow` and the tooling is │
│ +there. │
│ + │
│ +## Content │
│ + │
│ +Three collections under `ooknet-design/src/content/`: `notes/` │
│ +(personal writing, by subject), `kb/` (vendor docs, credited), and │
│ +`docs/` (project documentation). All three cross-link with │
│ +`[[wikilinks]]` and feed a REFERENCED BY index on every page. │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ README.md ───────────────────────── [ COPY ] ─┐
│ diff --git a/README.md b/README.md │
│ new file mode 100644 │
│ index 0000000..fb0f028 │
│ --- /dev/null │
│ +++ b/README.md │
│ @@ -0,0 +1,40 @@ │
│ +# ooknet.org │
│ + │
│ +Personal website. A text-mode knowledge bas │
│ e and the ascii design │
│ +system that renders it - every border, char │
│ t, and frame on the site is │
│ +a real character on a strict monospace grid │
│ . │
│ + │
│ +## Layout │
│ + │
│ +| PATH | WHAT │
│ | │
│ +| ---------------- | ---------------------- │
│ --------------------- | │
│ +| `ooknet-design/` | the astro site and the │
│ ascii design system | │
│ +| `nix/` | dev shell │
│ | │
│ + │
│ +## The rules │
│ + │
│ +- og ascii: no em dashes, no ellipsis glyph │
│ s, no curly quotes. │
│ + `checkGrid` fails the build on off-grid c │
│ haracters. │
│ +- every frame renders twice: 86 cells wide │
│ for desktop, 48 for mobile. │
│ +- no smooth animation. motion is discrete, │
│ like a terminal. │
│ +- components are pure builders with tests; │
│ chrome is text, not css │
│ + borders. │
│ + │
│ +## Working on it │
│ + │
│ +```sh │
│ +cd ooknet-design │
│ +npm run dev # dev server on :4321 │
│ +npm test # vitest │
│ +npm run build # static build + grid check │
│ s across every page │
│ +``` │
│ + │
│ +The dev shell comes from the flake; `direnv │
│ allow` and the tooling is │
│ +there. │
│ + │
│ +## Content │
│ + │
│ +Three collections under `ooknet-design/src/ │
│ content/`: `notes/` │
│ +(personal writing, by subject), `kb/` (vend │
│ or docs, credited), and │
│ +`docs/` (project documentation). All three │
│ cross-link with │
│ +`[[wikilinks]]` and feed a REFERENCED BY in │
│ dex on every page. │
└──────────────────────────────────────────────┘ ┌─ ooknet-design/package-lock.json ───────────────────────────────────────── [ COPY ] ─┐
│ diff --git a/ooknet-design/package-lock.json b/ooknet-design/package-lock.json │
│ index d17bd9b..d58ec8a 100644 │
│ --- a/ooknet-design/package-lock.json │
│ +++ b/ooknet-design/package-lock.json │
│ @@ -10,7 +10,8 @@ │
│ "dependencies": { │
│ "@fontsource/cascadia-mono": "^5.2.3", │
│ "@fontsource/ibm-plex-mono": "^5.2.7", │
│ - "astro": "^6.1.9" │
│ + "astro": "^6.1.9", │
│ + "marked": "^18.0.6" │
│ }, │
│ "devDependencies": { │
│ "sass": "^1.99.0", │
│ @@ -3291,6 +3292,18 @@ │
│ "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 ─── [ COPY ] ─┐
│ diff --git a/ooknet-design/package-lock.json │
│ b/ooknet-design/package-lock.json │
│ index d17bd9b..d58ec8a 100644 │
│ --- a/ooknet-design/package-lock.json │
│ +++ b/ooknet-design/package-lock.json │
│ @@ -10,7 +10,8 @@ │
│ "dependencies": { │
│ "@fontsource/cascadia-mono": "^5.2. │
│ 3", │
│ "@fontsource/ibm-plex-mono": "^5.2. │
│ 7", │
│ - "astro": "^6.1.9" │
│ + "astro": "^6.1.9", │
│ + "marked": "^18.0.6" │
│ }, │
│ "devDependencies": { │
│ "sass": "^1.99.0", │
│ @@ -3291,6 +3292,18 @@ │
│ "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 ────────────────────────────────────────────── [ COPY ] ─┐
│ diff --git a/ooknet-design/package.json b/ooknet-design/package.json │
│ index 31126d6..112a66e 100644 │
│ --- a/ooknet-design/package.json │
│ +++ b/ooknet-design/package.json │
│ @@ -15,7 +15,8 @@ │
│ "dependencies": { │
│ "@fontsource/cascadia-mono": "^5.2.3", │
│ "@fontsource/ibm-plex-mono": "^5.2.7", │
│ - "astro": "^6.1.9" │
│ + "astro": "^6.1.9", │
│ + "marked": "^18.0.6" │
│ }, │
│ "devDependencies": { │
│ "sass": "^1.99.0", │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ ooknet-design/package.json ──────── [ COPY ] ─┐
│ diff --git a/ooknet-design/package.json b/oo │
│ knet-design/package.json │
│ index 31126d6..112a66e 100644 │
│ --- a/ooknet-design/package.json │
│ +++ b/ooknet-design/package.json │
│ @@ -15,7 +15,8 @@ │
│ "dependencies": { │
│ "@fontsource/cascadia-mono": "^5.2.3", │
│ "@fontsource/ibm-plex-mono": "^5.2.7", │
│ - "astro": "^6.1.9" │
│ + "astro": "^6.1.9", │
│ + "marked": "^18.0.6" │
│ }, │
│ "devDependencies": { │
│ "sass": "^1.99.0", │
└──────────────────────────────────────────────┘ ┌─ ooknet-design/src/components/Breadcrumbs/Breadcrumbs.ts ───────────────── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/components/Breadcrumbs/Breadcrumbs.ts b/ooknet-desi │
│ gn/src/components/Breadcrumbs/Breadcrumbs.ts │
│ index 3150f9f..1f40af1 100644 │
│ --- a/ooknet-design/src/components/Breadcrumbs/Breadcrumbs.ts │
│ +++ b/ooknet-design/src/components/Breadcrumbs/Breadcrumbs.ts │
│ @@ -9,6 +9,7 @@ const ROOTS: Record<string, string> = { │
│ NOTES: "/notes/", │
│ KB: "/kb/", │
│ DOCS: "/docs/", │
│ + GIT: "/git/", │
│ }; │
│ │
│ export interface Crumb { │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ ...ts/Breadcrumbs/Breadcrumbs.ts ── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/components/Br │
│ eadcrumbs/Breadcrumbs.ts b/ooknet-design/src │
│ /components/Breadcrumbs/Breadcrumbs.ts │
│ index 3150f9f..1f40af1 100644 │
│ --- a/ooknet-design/src/components/Breadcrum │
│ bs/Breadcrumbs.ts │
│ +++ b/ooknet-design/src/components/Breadcrum │
│ bs/Breadcrumbs.ts │
│ @@ -9,6 +9,7 @@ const ROOTS: Record<string, │
│ string> = { │
│ NOTES: "/notes/", │
│ KB: "/kb/", │
│ DOCS: "/docs/", │
│ + GIT: "/git/", │
│ }; │
│ │
│ export interface Crumb { │
└──────────────────────────────────────────────┘ ┌─ ooknet-design/src/components/GitListing/GitListing.astro ──────────────── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/components/GitListing/GitListing.astro b/ooknet-des │
│ ign/src/components/GitListing/GitListing.astro │
│ new file mode 100644 │
│ index 0000000..8c82ca8 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/components/GitListing/GitListing.astro │
│ @@ -0,0 +1,21 @@ │
│ +--- │
│ +import Pre from "../Pre/Pre.astro"; │
│ +import { FRAME_W, MOBILE_FRAME_W } from "../../lib/config"; │
│ +import { layoutListing, type ListingRow, type Props } from "./GitListing"; │
│ +import "./GitListing.scss"; │
│ + │
│ +const { entries, treeBase, blobBase, commitBase } = Astro.props as Props; │
│ +const variants: [string, ListingRow[]][] = [ │
│ + ["frame-wide", layoutListing(entries, FRAME_W, treeBase, blobBase, commitBase)] │
│ , │
│ + ["frame-narrow", layoutListing(entries, MOBILE_FRAME_W, treeBase, blobBase, com │
│ mitBase)], │
│ +]; │
│ +--- │
│ +<div class="git-listing"> │
│ + {variants.map(([variant, rows]) => │
│ + rows.map((r) => ( │
│ + <Pre class={variant}><a class="listing-link" href={r.href}>{r.name}</a>{r.g │
│ ap1}{r.subject │
│ + ? <a class="listing-subject" href={r.subjectHref}>{r.subject}</a> │
│ + : ""}{r.gap2}{r.date}</Pre> │
│ + )) │
│ + )} │
│ +</div> │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ ...s/GitListing/GitListing.astro ── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/components/Gi │
│ tListing/GitListing.astro b/ooknet-design/sr │
│ c/components/GitListing/GitListing.astro │
│ new file mode 100644 │
│ index 0000000..8c82ca8 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/components/GitListin │
│ g/GitListing.astro │
│ @@ -0,0 +1,21 @@ │
│ +--- │
│ +import Pre from "../Pre/Pre.astro"; │
│ +import { FRAME_W, MOBILE_FRAME_W } from ".. │
│ /../lib/config"; │
│ +import { layoutListing, type ListingRow, ty │
│ pe Props } from "./GitListing"; │
│ +import "./GitListing.scss"; │
│ + │
│ +const { entries, treeBase, blobBase, commit │
│ Base } = Astro.props as Props; │
│ +const variants: [string, ListingRow[]][] = │
│ [ │
│ + ["frame-wide", layoutListing(entries, FRA │
│ ME_W, treeBase, blobBase, commitBase)], │
│ + ["frame-narrow", layoutListing(entries, M │
│ OBILE_FRAME_W, treeBase, blobBase, commitBas │
│ e)], │
│ +]; │
│ +--- │
│ +<div class="git-listing"> │
│ + {variants.map(([variant, rows]) => │
│ + rows.map((r) => ( │
│ + <Pre class={variant}><a class="listin │
│ g-link" href={r.href}>{r.name}</a>{r.gap1}{r │
│ .subject │
│ + ? <a class="listing-subject" href={ │
│ r.subjectHref}>{r.subject}</a> │
│ + : ""}{r.gap2}{r.date}</Pre> │
│ + )) │
│ + )} │
│ +</div> │
└──────────────────────────────────────────────┘ ┌─ ooknet-design/src/components/GitListing/GitListing.scss ───────────────── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/components/GitListing/GitListing.scss b/ooknet-desi │
│ gn/src/components/GitListing/GitListing.scss │
│ new file mode 100644 │
│ index 0000000..556e2cd │
│ --- /dev/null │
│ +++ b/ooknet-design/src/components/GitListing/GitListing.scss │
│ @@ -0,0 +1,28 @@ │
│ +.git-listing { │
│ + display: block; │
│ + │
│ + .listing-link { │
│ + color: var(--ink); │
│ + text-decoration: none; │
│ + │
│ + &:hover { │
│ + text-decoration: underline; │
│ + text-underline-offset: 3px; │
│ + } │
│ + │
│ + &:focus-visible { │
│ + outline: 1px dashed var(--ink); │
│ + outline-offset: 1px; │
│ + } │
│ + } │
│ + │
│ + .listing-subject { │
│ + color: var(--ink-soft); │
│ + text-decoration: none; │
│ + │
│ + &:hover { │
│ + text-decoration: underline; │
│ + text-underline-offset: 3px; │
│ + } │
│ + } │
│ +} │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ ...ts/GitListing/GitListing.scss ── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/components/Gi │
│ tListing/GitListing.scss b/ooknet-design/src │
│ /components/GitListing/GitListing.scss │
│ new file mode 100644 │
│ index 0000000..556e2cd │
│ --- /dev/null │
│ +++ b/ooknet-design/src/components/GitListin │
│ g/GitListing.scss │
│ @@ -0,0 +1,28 @@ │
│ +.git-listing { │
│ + display: block; │
│ + │
│ + .listing-link { │
│ + color: var(--ink); │
│ + text-decoration: none; │
│ + │
│ + &:hover { │
│ + text-decoration: underline; │
│ + text-underline-offset: 3px; │
│ + } │
│ + │
│ + &:focus-visible { │
│ + outline: 1px dashed var(--ink); │
│ + outline-offset: 1px; │
│ + } │
│ + } │
│ + │
│ + .listing-subject { │
│ + color: var(--ink-soft); │
│ + text-decoration: none; │
│ + │
│ + &:hover { │
│ + text-decoration: underline; │
│ + text-underline-offset: 3px; │
│ + } │
│ + } │
│ +} │
└──────────────────────────────────────────────┘ ┌─ ooknet-design/src/components/GitListing/GitListing.test.ts ────────────── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/components/GitListing/GitListing.test.ts b/ooknet-d │
│ esign/src/components/GitListing/GitListing.test.ts │
│ new file mode 100644 │
│ index 0000000..fa3ed56 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/components/GitListing/GitListing.test.ts │
│ @@ -0,0 +1,35 @@ │
│ +import { describe, expect, it } from "vitest"; │
│ +import { layoutListing } from "./GitListing"; │
│ +import { len } from "../../lib/ascii"; │
│ + │
│ +const entries = [ │
│ + { name: "src", path: "src", kind: "tree" as const, hash: "abc1234", date: "2026 │
│ -07-18", subject: "web: restructure into collections" }, │
│ + { name: "a-very-long-file-name-indeed.config.ts", path: "a-very-long-file-name- │
│ indeed.config.ts", kind: "blob" as const, hash: "def5678", date: "2026-07-17", sub │
│ ject: "short" }, │
│ +]; │
│ + │
│ +describe("layoutListing", () => { │
│ + it("fills the exact width at both frames", () => { │
│ + for (const w of [86, 48]) { │
│ + for (const r of layoutListing(entries, w, "/t/", "/b/")) { │
│ + expect(len(r.name + r.gap1 + r.subject + r.gap2 + r.date)).toBe(w); │
│ + } │
│ + } │
│ + }); │
│ + │
│ + it("marks directories with a slash and links by kind", () => { │
│ + const rows = layoutListing(entries, 86, "/t/", "/b/"); │
│ + expect(rows[0].name).toBe("src/"); │
│ + expect(rows[0].href).toBe("/t/src/"); │
│ + expect(rows[1].href).toBe("/b/a-very-long-file-name-indeed.config.ts/"); │
│ + }); │
│ + │
│ + it("links subjects to their commit", () => { │
│ + const rows = layoutListing(entries, 86, "/t/", "/b/", "/c/"); │
│ + expect(rows[0].subjectHref).toBe("/c/abc1234/"); │
│ + }); │
│ + │
│ + it("truncates names and subjects rather than overflowing", () => { │
│ + const rows = layoutListing(entries, 48, "/t/", "/b/"); │
│ + expect(rows[1].name.endsWith("...")).toBe(true); │
│ + }); │
│ +}); │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ ...GitListing/GitListing.test.ts ── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/components/Gi │
│ tListing/GitListing.test.ts b/ooknet-design/ │
│ src/components/GitListing/GitListing.test.ts │
│ new file mode 100644 │
│ index 0000000..fa3ed56 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/components/GitListin │
│ g/GitListing.test.ts │
│ @@ -0,0 +1,35 @@ │
│ +import { describe, expect, it } from "vites │
│ t"; │
│ +import { layoutListing } from "./GitListing │
│ "; │
│ +import { len } from "../../lib/ascii"; │
│ + │
│ +const entries = [ │
│ + { name: "src", path: "src", kind: "tree" │
│ as const, hash: "abc1234", date: "2026-07-18 │
│ ", subject: "web: restructure into collectio │
│ ns" }, │
│ + { name: "a-very-long-file-name-indeed.con │
│ fig.ts", path: "a-very-long-file-name-indeed │
│ .config.ts", kind: "blob" as const, hash: "d │
│ ef5678", date: "2026-07-17", subject: "short │
│ " }, │
│ +]; │
│ + │
│ +describe("layoutListing", () => { │
│ + it("fills the exact width at both frames" │
│ , () => { │
│ + for (const w of [86, 48]) { │
│ + for (const r of layoutListing(entries │
│ , w, "/t/", "/b/")) { │
│ + expect(len(r.name + r.gap1 + r.subj │
│ ect + r.gap2 + r.date)).toBe(w); │
│ + } │
│ + } │
│ + }); │
│ + │
│ + it("marks directories with a slash and li │
│ nks by kind", () => { │
│ + const rows = layoutListing(entries, 86, │
│ "/t/", "/b/"); │
│ + expect(rows[0].name).toBe("src/"); │
│ + expect(rows[0].href).toBe("/t/src/"); │
│ + expect(rows[1].href).toBe("/b/a-very-lo │
│ ng-file-name-indeed.config.ts/"); │
│ + }); │
│ + │
│ + it("links subjects to their commit", () = │
│ > { │
│ + const rows = layoutListing(entries, 86, │
│ "/t/", "/b/", "/c/"); │
│ + expect(rows[0].subjectHref).toBe("/c/ab │
│ c1234/"); │
│ + }); │
│ + │
│ + it("truncates names and subjects rather t │
│ han overflowing", () => { │
│ + const rows = layoutListing(entries, 48, │
│ "/t/", "/b/"); │
│ + expect(rows[1].name.endsWith("...")).to │
│ Be(true); │
│ + }); │
│ +}); │
└──────────────────────────────────────────────┘ ┌─ ooknet-design/src/components/GitListing/GitListing.ts ─────────────────── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/components/GitListing/GitListing.ts b/ooknet-design │
│ /src/components/GitListing/GitListing.ts │
│ new file mode 100644 │
│ index 0000000..e2bbc3b │
│ --- /dev/null │
│ +++ b/ooknet-design/src/components/GitListing/GitListing.ts │
│ @@ -0,0 +1,62 @@ │
│ +// Directory listing rows, github-shape: NAME LAST COMMIT DATE. │
│ +// The name is a link; widths flex per frame and the subject column │
│ +// drops when the narrow frame can't seat it. │
│ +import { checkGrid, len, pad } from "../../lib/ascii"; │
│ +import type { DirEntry } from "../../lib/git"; │
│ + │
│ +export interface Props { │
│ + entries: DirEntry[]; │
│ + /** Url prefixes for links. */ │
│ + treeBase: string; │
│ + blobBase: string; │
│ + commitBase: string; │
│ +} │
│ + │
│ +export interface ListingRow { │
│ + name: string; │
│ + href: string; │
│ + gap1: string; │
│ + subject: string; │
│ + subjectHref: string; │
│ + gap2: string; │
│ + date: string; │
│ +} │
│ + │
│ +const DATE_W = 10; │
│ +const GAP = 2; │
│ + │
│ +export function layoutListing( │
│ + entries: DirEntry[], │
│ + width: number, │
│ + treeBase: string, │
│ + blobBase: string, │
│ + commitBase = "", │
│ +): ListingRow[] { │
│ + const nameW = Math.min( │
│ + Math.max(8, ...entries.map((e) => len(e.name) + (e.kind === "tree" ? 1 : 0))) │
│ , │
│ + Math.floor(width / 2) - GAP, │
│ + ); │
│ + const subjectW = Math.max(0, width - nameW - DATE_W - GAP * 2); │
│ + return entries.map((e) => { │
│ + const rawName = e.kind === "tree" ? `${e.name}/` : e.name; │
│ + const name = │
│ + len(rawName) > nameW ? [...rawName].slice(0, nameW - 3).join("") + "..." : │
│ rawName; │
│ + const subject = │
│ + subjectW < 8 │
│ + ? "" │
│ + : len(e.subject) > subjectW │
│ + ? [...e.subject].slice(0, subjectW - 3).join("") + "..." │
│ + : e.subject; │
│ + const row: ListingRow = { │
│ + name, │
│ + href: `${e.kind === "tree" ? treeBase : blobBase}${e.path}/`, │
│ + gap1: " ".repeat(nameW - len(name) + GAP), │
│ + subject, │
│ + subjectHref: `${commitBase}${e.hash}/`, │
│ + gap2: " ".repeat(Math.max(GAP, width - nameW - GAP - len(subject) - len(e.d │
│ ate))), │
│ + date: e.date, │
│ + }; │
│ + checkGrid(row.name + row.gap1 + row.subject + row.gap2 + row.date, width, "gi │
│ t listing"); │
│ + return row; │
│ + }); │
│ +} │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ ...ents/GitListing/GitListing.ts ── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/components/Gi │
│ tListing/GitListing.ts b/ooknet-design/src/c │
│ omponents/GitListing/GitListing.ts │
│ new file mode 100644 │
│ index 0000000..e2bbc3b │
│ --- /dev/null │
│ +++ b/ooknet-design/src/components/GitListin │
│ g/GitListing.ts │
│ @@ -0,0 +1,62 @@ │
│ +// Directory listing rows, github-shape: NA │
│ ME LAST COMMIT DATE. │
│ +// The name is a link; widths flex per fram │
│ e and the subject column │
│ +// drops when the narrow frame can't seat i │
│ t. │
│ +import { checkGrid, len, pad } from "../../ │
│ lib/ascii"; │
│ +import type { DirEntry } from "../../lib/gi │
│ t"; │
│ + │
│ +export interface Props { │
│ + entries: DirEntry[]; │
│ + /** Url prefixes for links. */ │
│ + treeBase: string; │
│ + blobBase: string; │
│ + commitBase: string; │
│ +} │
│ + │
│ +export interface ListingRow { │
│ + name: string; │
│ + href: string; │
│ + gap1: string; │
│ + subject: string; │
│ + subjectHref: string; │
│ + gap2: string; │
│ + date: string; │
│ +} │
│ + │
│ +const DATE_W = 10; │
│ +const GAP = 2; │
│ + │
│ +export function layoutListing( │
│ + entries: DirEntry[], │
│ + width: number, │
│ + treeBase: string, │
│ + blobBase: string, │
│ + commitBase = "", │
│ +): ListingRow[] { │
│ + const nameW = Math.min( │
│ + Math.max(8, ...entries.map((e) => len(e │
│ .name) + (e.kind === "tree" ? 1 : 0))), │
│ + Math.floor(width / 2) - GAP, │
│ + ); │
│ + const subjectW = Math.max(0, width - name │
│ W - DATE_W - GAP * 2); │
│ + return entries.map((e) => { │
│ + const rawName = e.kind === "tree" ? `${ │
│ e.name}/` : e.name; │
│ + const name = │
│ + len(rawName) > nameW ? [...rawName].s │
│ lice(0, nameW - 3).join("") + "..." : rawNam │
│ e; │
│ + const subject = │
│ + subjectW < 8 │
│ + ? "" │
│ + : len(e.subject) > subjectW │
│ + ? [...e.subject].slice(0, subject │
│ W - 3).join("") + "..." │
│ + : e.subject; │
│ + const row: ListingRow = { │
│ + name, │
│ + href: `${e.kind === "tree" ? treeBase │
│ : blobBase}${e.path}/`, │
│ + gap1: " ".repeat(nameW - len(name) + │
│ GAP), │
│ + subject, │
│ + subjectHref: `${commitBase}${e.hash}/ │
│ `, │
│ + gap2: " ".repeat(Math.max(GAP, width │
│ - nameW - GAP - len(subject) - len(e.date))) │
│ , │
│ + date: e.date, │
│ + }; │
│ + checkGrid(row.name + row.gap1 + row.sub │
│ ject + row.gap2 + row.date, width, "git list │
│ ing"); │
│ + return row; │
│ + }); │
│ +} │
└──────────────────────────────────────────────┘ ┌─ ooknet-design/src/components/NavBar/NavBar.test.ts ────────────────────── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/components/NavBar/NavBar.test.ts b/ooknet-design/sr │
│ c/components/NavBar/NavBar.test.ts │
│ index 6a9fbc9..d895ccf 100644 │
│ --- a/ooknet-design/src/components/NavBar/NavBar.test.ts │
│ +++ b/ooknet-design/src/components/NavBar/NavBar.test.ts │
│ @@ -1,10 +1,10 @@ │
│ import { describe, expect, it } from "vitest"; │
│ -import { NAV_ITEMS, NAV_LANG_W, itemsWidth, navRows } from "./NavBar"; │
│ +import { NAV_GAP, NAV_ITEMS, NAV_LANG_W, itemsWidth, navRows } from "./NavBar"; │
│ import { MOBILE_FRAME_W, FRAME_W } from "../../lib/config"; │
│ │
│ describe("navRows", () => { │
│ - it("keeps all five items on one row at desktop width", () => { │
│ - const rows = navRows(NAV_ITEMS, FRAME_W, 4, NAV_LANG_W + 1); │
│ + it("keeps all seven items on one row at desktop width", () => { │
│ + const rows = navRows(NAV_ITEMS, FRAME_W, NAV_GAP, NAV_LANG_W + 1); │
│ expect(rows).toHaveLength(1); │
│ }); │
│ │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ ...ponents/NavBar/NavBar.test.ts ── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/components/Na │
│ vBar/NavBar.test.ts b/ooknet-design/src/comp │
│ onents/NavBar/NavBar.test.ts │
│ index 6a9fbc9..d895ccf 100644 │
│ --- a/ooknet-design/src/components/NavBar/Na │
│ vBar.test.ts │
│ +++ b/ooknet-design/src/components/NavBar/Na │
│ vBar.test.ts │
│ @@ -1,10 +1,10 @@ │
│ import { describe, expect, it } from "vites │
│ t"; │
│ -import { NAV_ITEMS, NAV_LANG_W, itemsWidth, │
│ navRows } from "./NavBar"; │
│ +import { NAV_GAP, NAV_ITEMS, NAV_LANG_W, it │
│ emsWidth, navRows } from "./NavBar"; │
│ import { MOBILE_FRAME_W, FRAME_W } from ".. │
│ /../lib/config"; │
│ │
│ describe("navRows", () => { │
│ - it("keeps all five items on one row at de │
│ sktop width", () => { │
│ - const rows = navRows(NAV_ITEMS, FRAME_W │
│ , 4, NAV_LANG_W + 1); │
│ + it("keeps all seven items on one row at d │
│ esktop width", () => { │
│ + const rows = navRows(NAV_ITEMS, FRAME_W │
│ , NAV_GAP, NAV_LANG_W + 1); │
│ expect(rows).toHaveLength(1); │
│ }); │
│ │
└──────────────────────────────────────────────┘ ┌─ ooknet-design/src/components/NavBar/NavBar.ts ─────────────────────────── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/components/NavBar/NavBar.ts b/ooknet-design/src/com │
│ ponents/NavBar/NavBar.ts │
│ index 6eb9a15..e0c92fa 100644 │
│ --- a/ooknet-design/src/components/NavBar/NavBar.ts │
│ +++ b/ooknet-design/src/components/NavBar/NavBar.ts │
│ @@ -21,12 +21,13 @@ export const NAV_ITEMS: NavItem[] = [ │
│ { label: "NOTES", href: "/notes/" }, │
│ { label: "KB", href: "/kb/" }, │
│ { label: "DOCS", href: "/docs/" }, │
│ + { label: "GIT", href: "/git/" }, │
│ { label: "PROJECTS", href: "/projects/" }, │
│ { label: "RESOURCES", href: "/resources/" }, │
│ { label: "ABOUT", href: "/about/" }, │
│ ]; │
│ │
│ -export const NAV_GAP = 4; // spaces between buttons │
│ +export const NAV_GAP = 3; // spaces between buttons - seven items fit one 8 │
│ 6-cell row │
│ export const NAV_LANG_W = 2; // visible width of "EN" │
│ │
│ /** Total width of all buttons + inter-button gaps. */ │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ ...c/components/NavBar/NavBar.ts ── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/components/Na │
│ vBar/NavBar.ts b/ooknet-design/src/component │
│ s/NavBar/NavBar.ts │
│ index 6eb9a15..e0c92fa 100644 │
│ --- a/ooknet-design/src/components/NavBar/Na │
│ vBar.ts │
│ +++ b/ooknet-design/src/components/NavBar/Na │
│ vBar.ts │
│ @@ -21,12 +21,13 @@ export const NAV_ITEMS: │
│ NavItem[] = [ │
│ { label: "NOTES", href: "/notes/" }, │
│ { label: "KB", href: "/kb/" }, │
│ { label: "DOCS", href: "/docs/" }, │
│ + { label: "GIT", href: "/git/" }, │
│ { label: "PROJECTS", href: "/projects/" │
│ }, │
│ { label: "RESOURCES", href: "/resources/" │
│ }, │
│ { label: "ABOUT", href: "/about/" }, │
│ ]; │
│ │
│ -export const NAV_GAP = 4; // spaces b │
│ etween buttons │
│ +export const NAV_GAP = 3; // spaces b │
│ etween buttons - seven items fit one 86-cell │
│ row │
│ export const NAV_LANG_W = 2; // visible │
│ width of "EN" │
│ │
│ /** Total width of all buttons + inter-butt │
│ on gaps. */ │
└──────────────────────────────────────────────┘ ┌─ ooknet-design/src/git.config.ts ───────────────────────────────────────── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/git.config.ts b/ooknet-design/src/git.config.ts │
│ new file mode 100644 │
│ index 0000000..e9795bd │
│ --- /dev/null │
│ +++ b/ooknet-design/src/git.config.ts │
│ @@ -0,0 +1,15 @@ │
│ +import type { GitRepo } from "./lib/git"; │
│ + │
│ +// Repos rendered by /git/. Paths resolve from ooknet-design/ at build │
│ +// time; missing paths are skipped so any machine can build the site. │
│ +// On ooknode these point at forgejo's bare repositories │
│ +// (.../forgejo/data/forgejo-repositories/<owner>/<repo>.git). │
│ +export const REPOS: GitRepo[] = [ │
│ + { │
│ + slug: "ooknet-org", │
│ + name: "ooknet.org", │
│ + path: "..", │
│ + desc: "personal website - a text-mode knowledge base and the ascii design sys │
│ tem that renders it", │
│ + clone: "https://git.ooknet.org/ooks/ooknet.org.git", │
│ + }, │
│ +]; │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ ooknet-design/src/git.config.ts ─── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/git.config.ts │
│ b/ooknet-design/src/git.config.ts │
│ new file mode 100644 │
│ index 0000000..e9795bd │
│ --- /dev/null │
│ +++ b/ooknet-design/src/git.config.ts │
│ @@ -0,0 +1,15 @@ │
│ +import type { GitRepo } from "./lib/git"; │
│ + │
│ +// Repos rendered by /git/. Paths resolve f │
│ rom ooknet-design/ at build │
│ +// time; missing paths are skipped so any m │
│ achine can build the site. │
│ +// On ooknode these point at forgejo's bare │
│ repositories │
│ +// (.../forgejo/data/forgejo-repositories/< │
│ owner>/<repo>.git). │
│ +export const REPOS: GitRepo[] = [ │
│ + { │
│ + slug: "ooknet-org", │
│ + name: "ooknet.org", │
│ + path: "..", │
│ + desc: "personal website - a text-mode k │
│ nowledge base and the ascii design system th │
│ at renders it", │
│ + clone: "https://git.ooknet.org/ooks/ook │
│ net.org.git", │
│ + }, │
│ +]; │
└──────────────────────────────────────────────┘ ┌─ ooknet-design/src/lib/ascii.ts ────────────────────────────────────────── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/lib/ascii.ts b/ooknet-design/src/lib/ascii.ts │
│ index 22e7fff..31f24ef 100644 │
│ --- a/ooknet-design/src/lib/ascii.ts │
│ +++ b/ooknet-design/src/lib/ascii.ts │
│ @@ -34,6 +34,21 @@ export function checkGrid(text: string, maxW: number, ctx: stri │
│ ng): string { │
│ return text; │
│ } │
│ │
│ +// For text from outside the pipeline (git history, external feeds): │
│ +// transliterate typographic glyphs to their og-ascii forms and blank │
│ +// anything else off-grid, so checkGrid never fails on foreign input. │
│ +const GRID_MAP: Record<string, string> = { │
│ + "-": "-", "-": "-", "...": "...", "-": "-", │
│ + "": "", """: '"', """: '"', "'": "'", "'": "'", │
│ +}; │
│ + │
│ +export function toGrid(s: string): string { │
│ + return s │
│ + .split("\n") │
│ + .map((line) => [...line].map((c) => GRID_MAP[c] ?? (ON_GRID.test(c) ? c : "?" │
│ )).join("")) │
│ + .join("\n"); │
│ +} │
│ + │
│ export type PadSide = "start" | "end" | "center"; │
│ export function pad(s: unknown, n: number, side: PadSide = "end"): string { │
│ const str = String(s ?? ""); │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ ooknet-design/src/lib/ascii.ts ──── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/lib/ascii.ts │
│ b/ooknet-design/src/lib/ascii.ts │
│ index 22e7fff..31f24ef 100644 │
│ --- a/ooknet-design/src/lib/ascii.ts │
│ +++ b/ooknet-design/src/lib/ascii.ts │
│ @@ -34,6 +34,21 @@ export function checkGrid │
│ (text: string, maxW: number, ctx: string): s │
│ tring { │
│ return text; │
│ } │
│ │
│ +// For text from outside the pipeline (git │
│ history, external feeds): │
│ +// transliterate typographic glyphs to thei │
│ r og-ascii forms and blank │
│ +// anything else off-grid, so checkGrid nev │
│ er fails on foreign input. │
│ +const GRID_MAP: Record<string, string> = { │
│ + "-": "-", "-": "-", "...": "...", "-": "- │
│ ", │
│ + "": "", """: '"', """: '"', "'": "'", "'" │
│ : "'", │
│ +}; │
│ + │
│ +export function toGrid(s: string): string { │
│ + return s │
│ + .split("\n") │
│ + .map((line) => [...line].map((c) => GRI │
│ D_MAP[c] ?? (ON_GRID.test(c) ? c : "?")).joi │
│ n("")) │
│ + .join("\n"); │
│ +} │
│ + │
│ export type PadSide = "start" | "end" | "ce │
│ nter"; │
│ export function pad(s: unknown, n: number, │
│ side: PadSide = "end"): string { │
│ const str = String(s ?? ""); │
└──────────────────────────────────────────────┘ ┌─ ooknet-design/src/lib/git.test.ts ─────────────────────────────────────── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/lib/git.test.ts b/ooknet-design/src/lib/git.test.ts │
│ new file mode 100644 │
│ index 0000000..fed2655 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/lib/git.test.ts │
│ @@ -0,0 +1,42 @@ │
│ +import { describe, expect, it } from "vitest"; │
│ +import { filesTree, repoAvailable, repoInfo } from "./git"; │
│ +import { toGrid } from "./ascii"; │
│ + │
│ +describe("toGrid", () => { │
│ + it("transliterates typographic glyphs to og ascii", () => { │
│ + expect(toGrid("a - b... "c"")).toBe('a - b... "c"'); │
│ + }); │
│ + it("blanks anything else off-grid", () => { │
│ + expect(toGrid("na?ve ?")).toBe("na?ve ?"); │
│ + }); │
│ + it("passes frame glyphs and ascii through", () => { │
│ + expect(toGrid("[ ok ] ─ █")).toBe("[ ok ] ─ █"); │
│ + }); │
│ + it("preserves line structure", () => { │
│ + expect(toGrid("a\nb - c\n")).toBe("a\nb - c\n"); │
│ + }); │
│ +}); │
│ + │
│ +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("repoInfo", () => { │
│ + it("reads this repository", () => { │
│ + expect(repoAvailable({ slug: "x", name: "x", path: "..", desc: "" })).toBe(tr │
│ ue); │
│ + const info = repoInfo(".."); │
│ + expect(info.commits).toBeGreaterThan(30); │
│ + expect(info.branch).toBe("master"); │
│ + expect(info.lastDate).toMatch(/^\d{4}-\d{2}-\d{2}$/); │
│ + }); │
│ +}); │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ ...et-design/src/lib/git.test.ts ── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/lib/git.test. │
│ ts b/ooknet-design/src/lib/git.test.ts │
│ new file mode 100644 │
│ index 0000000..fed2655 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/lib/git.test.ts │
│ @@ -0,0 +1,42 @@ │
│ +import { describe, expect, it } from "vites │
│ t"; │
│ +import { filesTree, repoAvailable, repoInfo │
│ } from "./git"; │
│ +import { toGrid } from "./ascii"; │
│ + │
│ +describe("toGrid", () => { │
│ + it("transliterates typographic glyphs to │
│ og ascii", () => { │
│ + expect(toGrid("a - b... "c"")).toBe('a │
│ - b... "c"'); │
│ + }); │
│ + it("blanks anything else off-grid", () => │
│ { │
│ + expect(toGrid("na?ve ?")).toBe("na?ve ? │
│ "); │
│ + }); │
│ + it("passes frame glyphs and ascii through │
│ ", () => { │
│ + expect(toGrid("[ ok ] ─ █")).toBe("[ ok │
│ ] ─ █"); │
│ + }); │
│ + it("preserves line structure", () => { │
│ + expect(toGrid("a\nb - c\n")).toBe("a\nb │
│ - c\n"); │
│ + }); │
│ +}); │
│ + │
│ +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("repoInfo", () => { │
│ + it("reads this repository", () => { │
│ + expect(repoAvailable({ slug: "x", name: │
│ "x", path: "..", desc: "" })).toBe(true); │
│ + const info = repoInfo(".."); │
│ + expect(info.commits).toBeGreaterThan(30 │
│ ); │
│ + expect(info.branch).toBe("master"); │
│ + expect(info.lastDate).toMatch(/^\d{4}-\ │
│ d{2}-\d{2}$/); │
│ + }); │
│ +}); │
└──────────────────────────────────────────────┘ ┌─ ooknet-design/src/lib/git.ts ──────────────────────────────────────────── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/lib/git.ts b/ooknet-design/src/lib/git.ts │
│ new file mode 100644 │
│ index 0000000..489be48 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/lib/git.ts │
│ @@ -0,0 +1,201 @@ │
│ +// Build-time git readers for the /git/ section. Pages are generated │
│ +// 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. │
│ +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 { │
│ + /** Url segment. */ │
│ + slug: string; │
│ + /** Display name. */ │
│ + name: string; │
│ + /** Local checkout or bare repo path, resolved from ooknet-design/. */ │
│ + path: string; │
│ + desc: string; │
│ + clone?: string; │
│ +} │
│ + │
│ +const MAX_BLOB = 200 * 1024; │
│ + │
│ +const git = (path: string, args: string[]): string => │
│ + execFileSync("git", ["-C", path, ...args], { │
│ + encoding: "utf8", │
│ + maxBuffer: 32 * 1024 * 1024, │
│ + }); │
│ + │
│ +export const repoAvailable = (r: GitRepo): boolean => existsSync(r.path); │
│ + │
│ +export interface RepoInfo { │
│ + branch: string; │
│ + commits: number; │
│ + lastDate: string; │
│ + lastSubject: string; │
│ +} │
│ + │
│ +export function repoInfo(path: string): RepoInfo { │
│ + const [lastDate, ...subject] = git(path, ["log", "-1", "--format=%as|%s"]) │
│ + .trim() │
│ + .split("|"); │
│ + return { │
│ + branch: git(path, ["symbolic-ref", "--short", "HEAD"]).trim(), │
│ + commits: parseInt(git(path, ["rev-list", "--count", "HEAD"]).trim(), 10), │
│ + lastDate, │
│ + lastSubject: toGrid(subject.join("|")), │
│ + }; │
│ +} │
│ + │
│ +export function repoFiles(path: string): string[] { │
│ + return git(path, ["ls-tree", "-r", "--name-only", "HEAD"]) │
│ + .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"]) │
│ + .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}/`, │
│ + }; │
│ + }); │
│ +} │
│ + │
│ +export interface DirEntry { │
│ + name: string; │
│ + path: string; │
│ + kind: "tree" | "blob"; │
│ + /** Last commit touching this path. */ │
│ + hash: string; │
│ + date: string; │
│ + subject: string; │
│ +} │
│ + │
│ +/** 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"; │
│ + const rows = git(path, ["ls-tree", spec]) │
│ + .split("\n") │
│ + .filter(Boolean) │
│ + .map((line) => { │
│ + const [meta, name] = line.split("\t"); │
│ + const kind = meta.split(" ")[1] as "tree" | "blob" | "commit"; │
│ + return { name, kind }; │
│ + }) │
│ + .filter((e): e is { name: string; kind: "tree" | "blob" } => e.kind !== "comm │
│ it"); │
│ + return rows │
│ + .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]) │
│ + .trim() │
│ + .split("|"); │
│ + return { name: toGrid(name), path: full, kind, hash, date, subject: toGrid( │
│ subject.join("|")) }; │
│ + }); │
│ +} │
│ + │
│ +/** Every directory path in the repo, for tree page generation. */ │
│ +export function repoDirs(files: string[]): string[] { │
│ + const dirs = new Set<string>(); │
│ + for (const f of files) { │
│ + const segments = f.split("/"); │
│ + for (let i = 1; i < segments.length; i++) { │
│ + dirs.add(segments.slice(0, i).join("/")); │
│ + } │
│ + } │
│ + return [...dirs].sort(); │
│ +} │
│ + │
│ +/** Repo README content at HEAD, if present. */ │
│ +export function readReadme(path: string): string | null { │
│ + for (const name of ["README.md", "readme.md", "README"]) { │
│ + try { │
│ + return git(path, ["show", `HEAD:${name}`]); │
│ + } catch { │
│ + continue; │
│ + } │
│ + } │
│ + return null; │
│ +} │
│ + │
│ +export interface Blob { │
│ + text: string; │
│ + binary: boolean; │
│ + truncated: boolean; │
│ + lines: number; │
│ +} │
│ + │
│ +export function readBlob(path: string, file: string): Blob { │
│ + const raw = execFileSync("git", ["-C", path, "show", `HEAD:${file}`], { │
│ + maxBuffer: 32 * 1024 * 1024, │
│ + }); │
│ + if (raw.subarray(0, 8000).includes(0)) { │
│ + return { text: "", binary: true, truncated: false, lines: 0 }; │
│ + } │
│ + const truncated = raw.length > MAX_BLOB; │
│ + const text = raw.subarray(0, MAX_BLOB).toString("utf8"); │
│ + return { │
│ + text, │
│ + binary: false, │
│ + truncated, │
│ + lines: text.split("\n").length, │
│ + }; │
│ +} │
│ + │
│ +export interface Commit { │
│ + hash: string; │
│ + date: string; │
│ + subject: string; │
│ + body: string; │
│ + diff: string; │
│ +} │
│ + │
│ +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]); │
│ + 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, │
│ + }; │
│ +} │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ ooknet-design/src/lib/git.ts ────── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/lib/git.ts b/ │
│ ooknet-design/src/lib/git.ts │
│ new file mode 100644 │
│ index 0000000..489be48 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/lib/git.ts │
│ @@ -0,0 +1,201 @@ │
│ +// Build-time git readers for the /git/ sec │
│ tion. Pages are generated │
│ +// 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. │
│ +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 { │
│ + /** Url segment. */ │
│ + slug: string; │
│ + /** Display name. */ │
│ + name: string; │
│ + /** Local checkout or bare repo path, res │
│ olved from ooknet-design/. */ │
│ + path: string; │
│ + desc: string; │
│ + clone?: string; │
│ +} │
│ + │
│ +const MAX_BLOB = 200 * 1024; │
│ + │
│ +const git = (path: string, args: string[]): │
│ string => │
│ + execFileSync("git", ["-C", path, ...args] │
│ , { │
│ + encoding: "utf8", │
│ + maxBuffer: 32 * 1024 * 1024, │
│ + }); │
│ + │
│ +export const repoAvailable = (r: GitRepo): │
│ boolean => existsSync(r.path); │
│ + │
│ +export interface RepoInfo { │
│ + branch: string; │
│ + commits: number; │
│ + lastDate: string; │
│ + lastSubject: string; │
│ +} │
│ + │
│ +export function repoInfo(path: string): Rep │
│ oInfo { │
│ + const [lastDate, ...subject] = git(path, │
│ ["log", "-1", "--format=%as|%s"]) │
│ + .trim() │
│ + .split("|"); │
│ + return { │
│ + branch: git(path, ["symbolic-ref", "--s │
│ hort", "HEAD"]).trim(), │
│ + commits: parseInt(git(path, ["rev-list" │
│ , "--count", "HEAD"]).trim(), 10), │
│ + lastDate, │
│ + lastSubject: toGrid(subject.join("|")), │
│ + }; │
│ +} │
│ + │
│ +export function repoFiles(path: string): st │
│ ring[] { │
│ + return git(path, ["ls-tree", "-r", "--nam │
│ e-only", "HEAD"]) │
│ + .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"]) │
│ + .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}/`, │
│ + }; │
│ + }); │
│ +} │
│ + │
│ +export interface DirEntry { │
│ + name: string; │
│ + path: string; │
│ + kind: "tree" | "blob"; │
│ + /** Last commit touching this path. */ │
│ + hash: string; │
│ + date: string; │
│ + subject: string; │
│ +} │
│ + │
│ +/** 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" │
│ ; │
│ + const rows = git(path, ["ls-tree", spec]) │
│ + .split("\n") │
│ + .filter(Boolean) │
│ + .map((line) => { │
│ + const [meta, name] = line.split("\t") │
│ ; │
│ + const kind = meta.split(" ")[1] as "t │
│ ree" | "blob" | "commit"; │
│ + return { name, kind }; │
│ + }) │
│ + .filter((e): e is { name: string; kind: │
│ "tree" | "blob" } => e.kind !== "commit"); │
│ + return rows │
│ + .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]) │
│ + .trim() │
│ + .split("|"); │
│ + return { name: toGrid(name), path: fu │
│ ll, kind, hash, date, subject: toGrid(subjec │
│ t.join("|")) }; │
│ + }); │
│ +} │
│ + │
│ +/** Every directory path in the repo, for t │
│ ree page generation. */ │
│ +export function repoDirs(files: string[]): │
│ string[] { │
│ + const dirs = new Set<string>(); │
│ + for (const f of files) { │
│ + const segments = f.split("/"); │
│ + for (let i = 1; i < segments.length; i+ │
│ +) { │
│ + dirs.add(segments.slice(0, i).join("/ │
│ ")); │
│ + } │
│ + } │
│ + return [...dirs].sort(); │
│ +} │
│ + │
│ +/** Repo README content at HEAD, if present │
│ . */ │
│ +export function readReadme(path: string): s │
│ tring | null { │
│ + for (const name of ["README.md", "readme. │
│ md", "README"]) { │
│ + try { │
│ + return git(path, ["show", `HEAD:${nam │
│ e}`]); │
│ + } catch { │
│ + continue; │
│ + } │
│ + } │
│ + return null; │
│ +} │
│ + │
│ +export interface Blob { │
│ + text: string; │
│ + binary: boolean; │
│ + truncated: boolean; │
│ + lines: number; │
│ +} │
│ + │
│ +export function readBlob(path: string, file │
│ : string): Blob { │
│ + const raw = execFileSync("git", ["-C", pa │
│ th, "show", `HEAD:${file}`], { │
│ + maxBuffer: 32 * 1024 * 1024, │
│ + }); │
│ + if (raw.subarray(0, 8000).includes(0)) { │
│ + return { text: "", binary: true, trunca │
│ ted: false, lines: 0 }; │
│ + } │
│ + const truncated = raw.length > MAX_BLOB; │
│ + const text = raw.subarray(0, MAX_BLOB).to │
│ String("utf8"); │
│ + return { │
│ + text, │
│ + binary: false, │
│ + truncated, │
│ + lines: text.split("\n").length, │
│ + }; │
│ +} │
│ + │
│ +export interface Commit { │
│ + hash: string; │
│ + date: string; │
│ + subject: string; │
│ + body: string; │
│ + diff: string; │
│ +} │
│ + │
│ +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]); │
│ + 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, │
│ + }; │
│ +} │
└──────────────────────────────────────────────┘ ┌─ ooknet-design/src/pages/git/[repo]/blob/[...path].astro ───────────────── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/pages/git/[repo]/blob/[...path].astro b/ooknet-desi │
│ gn/src/pages/git/[repo]/blob/[...path].astro │
│ new file mode 100644 │
│ index 0000000..6f33a4e │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/git/[repo]/blob/[...path].astro │
│ @@ -0,0 +1,52 @@ │
│ +--- │
│ +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 Diff from "../../../../components/Diff/Diff.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"; │
│ + │
│ +export function getStaticPaths() { │
│ + return REPOS.filter(repoAvailable).flatMap((r) => │
│ + repoFiles(r.path).map((file) => ({ │
│ + params: { repo: r.slug, path: file }, │
│ + props: { repo: r, file }, │
│ + })), │
│ + ); │
│ +} │
│ + │
│ +const { repo, file } = Astro.props as { repo: GitRepo; file: string }; │
│ +const blob = readBlob(repo.path, file); │
│ +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 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} - ${name}`} description={`${file} in ${ │
│ repo.name}.`}> │
│ + <TopHeader /> │
│ + <ArticleHeader left="OOKNET GIT" right={`REPO: ${repo.name.toUpperCase()}`} /> │
│ + <Pre>{" "}</Pre> │
│ + <KeyValue entries={[ │
│ + ["FILE", pathRows[0]], │
│ + ...pathRows.slice(1).map((c) => ["", c] as [string, string]), │
│ + ["LINES", blob.binary ? "BINARY" : `${blob.lines}`], │
│ + ]} /> │
│ + <Pre>{" "}</Pre> │
│ + {blob.binary │
│ + ? <Pre>*** BINARY FILE - NOT RENDERED ***</Pre> │
│ + : <Diff code={toGrid(blob.text)} />} │
│ + {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/[...path].astro ── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/pages/git/[re │
│ po]/blob/[...path].astro b/ooknet-design/src │
│ /pages/git/[repo]/blob/[...path].astro │
│ new file mode 100644 │
│ index 0000000..6f33a4e │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/git/[repo]/blo │
│ b/[...path].astro │
│ @@ -0,0 +1,52 @@ │
│ +--- │
│ +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 Diff from "../../../../components/Di │
│ ff/Diff.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"; │
│ + │
│ +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 }, │
│ + })), │
│ + ); │
│ +} │
│ + │
│ +const { repo, file } = Astro.props as { rep │
│ o: GitRepo; file: string }; │
│ +const blob = readBlob(repo.path, file); │
│ +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 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} - │
│ ${name}`} description={`${file} in ${repo.n │
│ ame}.`}> │
│ + <TopHeader /> │
│ + <ArticleHeader left="OOKNET GIT" right={` │
│ REPO: ${repo.name.toUpperCase()}`} /> │
│ + <Pre>{" "}</Pre> │
│ + <KeyValue entries={[ │
│ + ["FILE", pathRows[0]], │
│ + ...pathRows.slice(1).map((c) => ["", c] │
│ as [string, string]), │
│ + ["LINES", blob.binary ? "BINARY" : `${b │
│ lob.lines}`], │
│ + ]} /> │
│ + <Pre>{" "}</Pre> │
│ + {blob.binary │
│ + ? <Pre>*** BINARY FILE - NOT RENDERED * │
│ **</Pre> │
│ + : <Diff code={toGrid(blob.text)} />} │
│ + {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 ────────────────── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/pages/git/[repo]/commit/[hash].astro b/ooknet-desig │
│ n/src/pages/git/[repo]/commit/[hash].astro │
│ new file mode 100644 │
│ index 0000000..391cea9 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/git/[repo]/commit/[hash].astro │
│ @@ -0,0 +1,41 @@ │
│ +--- │
│ +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 Diff from "../../../../components/Diff/Diff.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"; │
│ + │
│ +export function getStaticPaths() { │
│ + return REPOS.filter(repoAvailable).flatMap((r) => │
│ + repoLog(r.path, 500, `/git/${r.slug}/`).map((c) => ({ │
│ + params: { repo: r.slug, hash: c.title }, │
│ + props: { repo: r }, │
│ + })), │
│ + ); │
│ +} │
│ + │
│ +const { repo } = Astro.props as { repo: GitRepo }; │
│ +const { hash } = Astro.params; │
│ +const commit = readCommit(repo.path, hash!); │
│ +--- │
│ +<ArticleLayout title={`GIT - ${repo.name} @ ${hash}`} description={commit.subject │
│ }> │
│ + <TopHeader /> │
│ + <ArticleHeader left="OOKNET GIT" right={`COMMIT: ${hash!.toUpperCase()}`} date= │
│ {commit.date} /> │
│ + <Pre>{" "}</Pre> │
│ + <KeyValue entries={[ │
│ + ["HASH", commit.hash.slice(0, 12)], │
│ + ["DATE", commit.date], │
│ + ["SUBJECT", commit.subject], │
│ + ]} /> │
│ + <Pre>{" "}</Pre> │
│ + <Diff code={toGrid(commit.diff)} /> │
│ + <Pre>{" "}</Pre> │
│ + <Pre><Button label="BACK TO LOG" href={`/git/${repo.slug}/log/`} /></Pre> │
│ + <ArticleFooter /> │
│ +</ArticleLayout> │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ ...it/[repo]/commit/[hash].astro ── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/pages/git/[re │
│ po]/commit/[hash].astro b/ooknet-design/src/ │
│ pages/git/[repo]/commit/[hash].astro │
│ new file mode 100644 │
│ index 0000000..391cea9 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/git/[repo]/com │
│ mit/[hash].astro │
│ @@ -0,0 +1,41 @@ │
│ +--- │
│ +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 Diff from "../../../../components/Di │
│ ff/Diff.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"; │
│ + │
│ +export function getStaticPaths() { │
│ + return REPOS.filter(repoAvailable).flatMa │
│ p((r) => │
│ + repoLog(r.path, 500, `/git/${r.slug}/`) │
│ .map((c) => ({ │
│ + params: { repo: r.slug, hash: c.title │
│ }, │
│ + props: { repo: r }, │
│ + })), │
│ + ); │
│ +} │
│ + │
│ +const { repo } = Astro.props as { repo: Git │
│ Repo }; │
│ +const { hash } = Astro.params; │
│ +const commit = readCommit(repo.path, hash!) │
│ ; │
│ +--- │
│ +<ArticleLayout title={`GIT - ${repo.name} @ │
│ ${hash}`} description={commit.subject}> │
│ + <TopHeader /> │
│ + <ArticleHeader left="OOKNET GIT" right={` │
│ COMMIT: ${hash!.toUpperCase()}`} date={commi │
│ t.date} /> │
│ + <Pre>{" "}</Pre> │
│ + <KeyValue entries={[ │
│ + ["HASH", commit.hash.slice(0, 12)], │
│ + ["DATE", commit.date], │
│ + ["SUBJECT", commit.subject], │
│ + ]} /> │
│ + <Pre>{" "}</Pre> │
│ + <Diff code={toGrid(commit.diff)} /> │
│ + <Pre>{" "}</Pre> │
│ + <Pre><Button label="BACK TO LOG" href={`/ │
│ git/${repo.slug}/log/`} /></Pre> │
│ + <ArticleFooter /> │
│ +</ArticleLayout> │
└──────────────────────────────────────────────┘ ┌─ ooknet-design/src/pages/git/[repo]/index.astro ────────────────────────── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/pages/git/[repo]/index.astro b/ooknet-design/src/pa │
│ ges/git/[repo]/index.astro │
│ new file mode 100644 │
│ index 0000000..bb1118f │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/git/[repo]/index.astro │
│ @@ -0,0 +1,65 @@ │
│ +--- │
│ +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 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 { REPOS } from "../../../git.config"; │
│ +import { repoAvailable, repoInfo, dirEntries, repoLog, readReadme, 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 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 section = (title: string, right: string) => (w: number) => │
│ + [row2(title, right, w), ruleD(w)].join("\n"); │
│ +--- │
│ +<ArticleLayout title={`GIT - ${repo.name}`} description={repo.desc}> │
│ + <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]] : []), │
│ + ]} /> │
│ + <Pre>{" "}</Pre> │
│ + │
│ + <Frame build={section("FILES", `HEAD @ ${info.branch}`)} /> │
│ + <Pre>{" "}</Pre> │
│ + <GitListing entries={entries} treeBase={`/git/${repo.slug}/tree/`} blobBase={`/ │
│ git/${repo.slug}/blob/`} commitBase={`/git/${repo.slug}/commit/`} /> │
│ + <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> │
│ + <Pre>{" "}</Pre> │
│ + │
│ + {readmeHtml && ( │
│ + <Fragment> │
│ + <Frame build={section("README", "rendered from HEAD")} /> │
│ + <Prose><div set:html={readmeHtml} /></Prose> │
│ + <Pre>{" "}</Pre> │
│ + </Fragment> │
│ + )} │
│ + <ArticleFooter /> │
│ +</ArticleLayout> │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ .../pages/git/[repo]/index.astro ── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/pages/git/[re │
│ po]/index.astro b/ooknet-design/src/pages/gi │
│ t/[repo]/index.astro │
│ new file mode 100644 │
│ index 0000000..bb1118f │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/git/[repo]/ind │
│ ex.astro │
│ @@ -0,0 +1,65 @@ │
│ +--- │
│ +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 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 { REPOS } from "../../../git.config" │
│ ; │
│ +import { repoAvailable, repoInfo, dirEntrie │
│ s, repoLog, readReadme, 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 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 section = (title: string, right: stri │
│ ng) => (w: number) => │
│ + [row2(title, right, w), ruleD(w)].join("\ │
│ n"); │
│ +--- │
│ +<ArticleLayout title={`GIT - ${repo.name}`} │
│ description={repo.desc}> │
│ + <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]] : []), │
│ + ]} /> │
│ + <Pre>{" "}</Pre> │
│ + │
│ + <Frame build={section("FILES", `HEAD @ ${ │
│ info.branch}`)} /> │
│ + <Pre>{" "}</Pre> │
│ + <GitListing entries={entries} treeBase={` │
│ /git/${repo.slug}/tree/`} blobBase={`/git/${ │
│ repo.slug}/blob/`} commitBase={`/git/${repo. │
│ slug}/commit/`} /> │
│ + <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> │
│ + <Pre>{" "}</Pre> │
│ + │
│ + {readmeHtml && ( │
│ + <Fragment> │
│ + <Frame build={section("README", "rend │
│ ered from HEAD")} /> │
│ + <Prose><div set:html={readmeHtml} />< │
│ /Prose> │
│ + <Pre>{" "}</Pre> │
│ + </Fragment> │
│ + )} │
│ + <ArticleFooter /> │
│ +</ArticleLayout> │
└──────────────────────────────────────────────┘ ┌─ ooknet-design/src/pages/git/[repo]/log.astro ──────────────────────────── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/pages/git/[repo]/log.astro b/ooknet-design/src/page │
│ s/git/[repo]/log.astro │
│ new file mode 100644 │
│ index 0000000..337ed4a │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/git/[repo]/log.astro │
│ @@ -0,0 +1,31 @@ │
│ +--- │
│ +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 ── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/pages/git/[re │
│ po]/log.astro b/ooknet-design/src/pages/git/ │
│ [repo]/log.astro │
│ new file mode 100644 │
│ index 0000000..337ed4a │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/git/[repo]/log │
│ .astro │
│ @@ -0,0 +1,31 @@ │
│ +--- │
│ +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]/tree/[...dir].astro ────────────────── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/pages/git/[repo]/tree/[...dir].astro b/ooknet-desig │
│ n/src/pages/git/[repo]/tree/[...dir].astro │
│ new file mode 100644 │
│ index 0000000..5ef4a03 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/git/[repo]/tree/[...dir].astro │
│ @@ -0,0 +1,42 @@ │
│ +--- │
│ +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 ── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/pages/git/[re │
│ po]/tree/[...dir].astro b/ooknet-design/src/ │
│ pages/git/[repo]/tree/[...dir].astro │
│ new file mode 100644 │
│ index 0000000..5ef4a03 │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/git/[repo]/tre │
│ e/[...dir].astro │
│ @@ -0,0 +1,42 @@ │
│ +--- │
│ +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/index.astro ───────────────────────────────── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/pages/git/index.astro b/ooknet-design/src/pages/git │
│ /index.astro │
│ new file mode 100644 │
│ index 0000000..d5b1cbe │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/git/index.astro │
│ @@ -0,0 +1,49 @@ │
│ +--- │
│ +import ArticleLayout from "../../layouts/ArticleLayout/ArticleLayout.astro"; │
│ +import TopHeader from "../../components/TopHeader/TopHeader.astro"; │
│ +import Frame from "../../components/Frame/Frame.astro"; │
│ +import Pre from "../../components/Pre/Pre.astro"; │
│ +import Panel from "../../components/Panel/Panel.astro"; │
│ +import Button from "../../components/Button/Button.astro"; │
│ +import ArticleFooter from "../../components/ArticleFooter/ArticleFooter.astro"; │
│ +import { kv, row2, ruleD } from "../../lib/ascii"; │
│ +import { REPOS } from "../../git.config"; │
│ +import { repoAvailable, repoInfo } from "../../lib/git"; │
│ + │
│ +const repos = REPOS.filter(repoAvailable).map((r) => ({ ...r, info: repoInfo(r.pa │
│ th) })); │
│ +const KV_W = 38; │
│ +--- │
│ +<ArticleLayout title="OOKNET KB - Git" │
│ + description="Source on file: the ooknet repositories, browsable in place."> │
│ + <TopHeader /> │
│ + <Frame build={(w) => [row2("OOKNET GIT", `${repos.length} ON FILE`, w), ruleD(w │
│ )].join("\n")} /> │
│ + <Pre>{" "}</Pre> │
│ + {repos.map((r) => ( │
│ + <Fragment> │
│ + <Panel title={r.name.toUpperCase()}> │
│ + <p class="repo-desc">{r.desc}</p> │
│ + <Pre>{kv("BRANCH", r.info.branch, 10, KV_W)}</Pre> │
│ + <Pre>{kv("COMMITS", String(r.info.commits), 10, KV_W)}</Pre> │
│ + <Pre>{kv("UPDATED", r.info.lastDate, 10, KV_W)}</Pre> │
│ + {r.clone && <p class="repo-clone">{r.clone}</p>} │
│ + <Pre><Button label="BROWSE" href={`/git/${r.slug}/`} />{" "}<Button labe │
│ l="LOG" href={`/git/${r.slug}/log/`} /></Pre> │
│ + </Panel> │
│ + <Pre>{" "}</Pre> │
│ + </Fragment> │
│ + ))} │
│ + <ArticleFooter /> │
│ +</ArticleLayout> │
│ + │
│ +<style lang="scss"> │
│ + .repo-desc, │
│ + .repo-clone { │
│ + margin: 0 0 0.7em; │
│ + font-family: var(--mono); │
│ + font-size: var(--fs); │
│ + line-height: var(--lh); │
│ + overflow-wrap: anywhere; │
│ + } │
│ + .repo-clone { │
│ + color: var(--ink-soft); │
│ + } │
│ +</style> │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ ...ign/src/pages/git/index.astro ── [ COPY ] ─┐
│ diff --git a/ooknet-design/src/pages/git/ind │
│ ex.astro b/ooknet-design/src/pages/git/index │
│ .astro │
│ new file mode 100644 │
│ index 0000000..d5b1cbe │
│ --- /dev/null │
│ +++ b/ooknet-design/src/pages/git/index.astr │
│ o │
│ @@ -0,0 +1,49 @@ │
│ +--- │
│ +import ArticleLayout from "../../layouts/Ar │
│ ticleLayout/ArticleLayout.astro"; │
│ +import TopHeader from "../../components/Top │
│ Header/TopHeader.astro"; │
│ +import Frame from "../../components/Frame/F │
│ rame.astro"; │
│ +import Pre from "../../components/Pre/Pre.a │
│ stro"; │
│ +import Panel from "../../components/Panel/P │
│ anel.astro"; │
│ +import Button from "../../components/Button │
│ /Button.astro"; │
│ +import ArticleFooter from "../../components │
│ /ArticleFooter/ArticleFooter.astro"; │
│ +import { kv, row2, ruleD } from "../../lib/ │
│ ascii"; │
│ +import { REPOS } from "../../git.config"; │
│ +import { repoAvailable, repoInfo } from ".. │
│ /../lib/git"; │
│ + │
│ +const repos = REPOS.filter(repoAvailable).m │
│ ap((r) => ({ ...r, info: repoInfo(r.path) }) │
│ ); │
│ +const KV_W = 38; │
│ +--- │
│ +<ArticleLayout title="OOKNET KB - Git" │
│ + description="Source on file: the ooknet r │
│ epositories, browsable in place."> │
│ + <TopHeader /> │
│ + <Frame build={(w) => [row2("OOKNET GIT", │
│ `${repos.length} ON FILE`, w), ruleD(w)].joi │
│ n("\n")} /> │
│ + <Pre>{" "}</Pre> │
│ + {repos.map((r) => ( │
│ + <Fragment> │
│ + <Panel title={r.name.toUpperCase()}> │
│ + <p class="repo-desc">{r.desc}</p> │
│ + <Pre>{kv("BRANCH", r.info.branch, 1 │
│ 0, KV_W)}</Pre> │
│ + <Pre>{kv("COMMITS", String(r.info.c │
│ ommits), 10, KV_W)}</Pre> │
│ + <Pre>{kv("UPDATED", r.info.lastDate │
│ , 10, KV_W)}</Pre> │
│ + {r.clone && <p class="repo-clone">{ │
│ r.clone}</p>} │
│ + <Pre><Button label="BROWSE" href={` │
│ /git/${r.slug}/`} />{" "}<Button label="LOG │
│ " href={`/git/${r.slug}/log/`} /></Pre> │
│ + </Panel> │
│ + <Pre>{" "}</Pre> │
│ + </Fragment> │
│ + ))} │
│ + <ArticleFooter /> │
│ +</ArticleLayout> │
│ + │
│ +<style lang="scss"> │
│ + .repo-desc, │
│ + .repo-clone { │
│ + margin: 0 0 0.7em; │
│ + font-family: var(--mono); │
│ + font-size: var(--fs); │
│ + line-height: var(--lh); │
│ + overflow-wrap: anywhere; │
│ + } │
│ + .repo-clone { │
│ + color: var(--ink-soft); │
│ + } │
│ +</style> │
└──────────────────────────────────────────────┘ [ BACK TO LOG ] ──────────────────────────────────────────────────────────────────────────────────────
OOKNET ────────────────────────────────────────────────
OOKNET