From 67743cad239395142cc5ff6a3f31640ebaa6cf24 Mon Sep 17 00:00:00 2001 From: lilleman Date: Mon, 15 Jun 2026 11:59:26 +0200 Subject: [PATCH] =?UTF-8?q?Add=20recursive=20nav-tree=20partial=20(todo=20?= =?UTF-8?q?=C2=A71);=20header/leaf=20=C3=97=20clickable/static,=20counts?= =?UTF-8?q?=20+=20aria-current?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/nav-tree.test.ts | 68 +++++++++++++++++++++++++++++++++++++ todo.md | 3 +- views/partials/nav-tree.ejs | 28 +++++++++++++++ 4 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 src/nav-tree.test.ts create mode 100644 views/partials/nav-tree.ejs diff --git a/README.md b/README.md index 57b1b41..94f23d1 100644 --- a/README.md +++ b/README.md @@ -362,7 +362,7 @@ src/context.ts RequestContext handed to handlers + buildContext() src/config.ts Env loader — Ory endpoints, cookie/CSRF secrets, JWKS, port; validated at boot src/icons.ts Used-icon registry + sprite builder from lucide-static (regenerates partials/icons.ejs) src/plugin.ts definePlugin() + the host's plugin discovery/router (planned) -views/ Core EJS templates (index, 403/404/500, partials/ incl. the app shell + icon sprite) +views/ Core EJS templates (index, 403/404/500, partials/ incl. app shell, nav tree, icon sprite) public/ Static assets under /public/ (css/styles.css + auth.css, favicon, robots.txt) config/menu.ts Central menu override + branding (planned) plugins/ Drop-in plugin folders, auto-discovered (planned) diff --git a/src/nav-tree.test.ts b/src/nav-tree.test.ts new file mode 100644 index 0000000..01e9b4c --- /dev/null +++ b/src/nav-tree.test.ts @@ -0,0 +1,68 @@ +import assert from "node:assert/strict"; +import { dirname, join } from "node:path"; +import { test } from "node:test"; +import { fileURLToPath } from "node:url"; +import * as ejs from "ejs"; + +const navTree = join(dirname(fileURLToPath(import.meta.url)), "..", "views", "partials", "nav-tree.ejs"); +const render = (data: Record = {}): Promise => ejs.renderFile(navTree, data); +const flat = (s: string): string => s.replace(/>\s+<").replace(/\s+/g, " ").trim(); + +const nodes = [ + { label: "Overview", href: "/overview", icon: "i-grid" }, // leaf · clickable · icon + { + label: "Workspace", + open: true, // header · static · open + children: [ + { + label: "Directory", + href: "/dir", + icon: "i-users", + count: 4, + open: true, // header · clickable · icon · count + children: [ + { label: "People", href: "/people", count: "1,284", current: true }, // leaf · clickable · current + { label: "Webhooks (soon)" }, // leaf · static + ], + }, + { label: "Roles & Access", children: [{ label: "Roles", href: "/roles" }] }, // header · static · closed + ], + }, +]; + +test("nav-tree renders the header/leaf × clickable/static matrix with counts, icons and aria-current", async () => { + const html = flat(await render({ nodes })); + + // Root list vs. recursive child lists. + assert.match(html, /