From fcf2abdf175e5c6a7492362411ca90652593b10b Mon Sep 17 00:00:00 2001 From: lilleman Date: Mon, 15 Jun 2026 13:10:24 +0200 Subject: [PATCH] =?UTF-8?q?Add=20data-driven=20pagination=20partial=20(tod?= =?UTF-8?q?o=20=C2=A71);=20rows-per-page=20GET=20form=20+=20page-number=20?= =?UTF-8?q?links,=20zero-JS,=20query-param=20driven?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/pagination.test.ts | 69 +++++++++++++++++++++++++++++++++++ todo.md | 2 +- views/partials/pagination.ejs | 60 ++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 src/pagination.test.ts create mode 100644 views/partials/pagination.ejs diff --git a/README.md b/README.md index 8417b19..8cdce24 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. app shell, nav tree, filter bar, data table, icon sprite) +views/ Core EJS templates (index, 403/404/500, partials/ incl. app shell, nav tree, filter bar, data table, pagination, 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/pagination.test.ts b/src/pagination.test.ts new file mode 100644 index 0000000..9c6e591 --- /dev/null +++ b/src/pagination.test.ts @@ -0,0 +1,69 @@ +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 pagination = join(dirname(fileURLToPath(import.meta.url)), "..", "views", "partials", "pagination.ejs"); +const render = (data: Record = {}): Promise => ejs.renderFile(pagination, data); +const flat = (s: string): string => s.replace(/>\s+<").replace(/\s+/g, " ").trim(); + +const config = { + summary: { from: "1", to: 12, total: "1,284" }, + rows: { + name: "rows", + value: 25, // active option + options: [12, 25, 50, 100], + hidden: [{ name: "q", value: "ada" }, { name: "status", value: "active" }], // list state carried forward + }, + prev: {}, // first page → disabled (no href) + pages: [ + { label: "1", current: true }, + { label: "2", href: "?sort=name&page=2" }, // & must be escaped + { label: "3", href: "?page=3" }, + { ellipsis: true }, + { label: "107", href: "?page=107" }, + ], + next: { href: "?page=2" }, +}; + +test("pagination renders summary, rows-per-page form, page links, current, ellipsis and prev/next", async () => { + const html = flat(await render(config)); + + assert.match(html, /
1–12 of 1,284<\/b><\/span>/); + + // Rows-per-page: GET form carrying list state, active option selected, zero-JS submit. + assert.match(html, /
/); + assert.match(html, /