Public: anyone can read this; the crew can change it.
The browse and org pages' filters draw §5's filters, and .filters leaves their markup #4342
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/4294-the-browse-and-org-pages-filters-draw-5 → main 2 commits fork 1 thread · 1 unresolved
5 files
changed +79
−4
src/web/pages/browse.ts +2 −2
| @@ -147,7 +147,7 @@ function filterBar( | ||
| 147 | 147 | labels: Label[], |
| 148 | 148 | counts: { open: number; closed: number } | undefined, |
| 149 | 149 | ): Html { |
| 150 | return html`<div class="filters"> | |
| 150 | return html`<div class="bar mb"> | |
| 151 | 151 | ${seg([ |
| 152 | 152 | { |
| 153 | 153 | label: "Open", |
| @@ -167,7 +167,7 @@ function filterBar( | ||
| 167 | 167 | current: query.state === "all", |
| 168 | 168 | }, |
| 169 | 169 | ])} |
| 170 | <form method="get" action="${base}" role="search"> | |
| 170 | <form class="bar" method="get" action="${base}" role="search"> | |
| 171 | 171 | ${when(query.state !== "open", () => hidden("state", query.state))} |
| 172 | 172 | ${when(query.label !== "", () => hidden("label", query.label))} |
| 173 | 173 | ${when(query.author !== "", () => hidden("author", query.author))} |
src/web/pages/org.ts +2 −2
| @@ -100,8 +100,8 @@ function repoSection( | ||
| 100 | 100 | const lead = (repo: Repo) => |
| 101 | 101 | icon(repo.forkParentId !== null ? "fork" : "repo"); |
| 102 | 102 | |
| 103 | return html`<div class="filters mt"> | |
| 104 | <form method="get" action="/${owner.username}" role="search"> | |
| 103 | return html`<div class="bar mt mb"> | |
| 104 | <form class="bar" method="get" action="/${owner.username}" role="search"> | |
| 105 | 105 | <input |
| 106 | 106 | class="input search" |
| 107 | 107 | type="search" |
src/web/style/content.ts +4
| @@ -260,6 +260,10 @@ a.origin-chip:hover { border-color: var(--border-strong); color: var(--fg); } | ||
| 260 | 260 | little less padding there, and a scroll inside the bar as the last resort. */ |
| 261 | 261 | .filters .seg { max-width: 100%; overflow-x: auto; scrollbar-width: none; } |
| 262 | 262 | @media (max-width: 420px) { .filters .seg > a { padding: 0 8px; } } |
| 263 | /* The same three reaches for a filter bar drawn with §5's .bar. */ | |
| 264 | .bar .search { min-width: 180px; flex: 1; max-width: 320px; } | |
| 265 | .bar .seg { max-width: 100%; overflow-x: auto; scrollbar-width: none; } | |
| 266 | @media (max-width: 420px) { .bar .seg > a { padding: 0 8px; } } | |
| 263 | 267 | |
| 264 | 268 | .merge-box { margin-top: 14px; } |
| 265 | 269 | .merge-box .card-body { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; } |
test/filters-markup.test.ts added +62
| @@ -0,0 +1,62 @@ | ||
| 1 | /** | |
| 2 | * The browse and org pages' filter bars are drawn with §5's `.bar`, not the | |
| 3 | * page-local `.filters`: the words, links and forms stay, the old name goes. | |
| 4 | */ | |
| 5 | import { afterAll, beforeAll, describe, expect, test } from "bun:test"; | |
| 6 | import { mkdtemp, rm } from "node:fs/promises"; | |
| 7 | import { tmpdir } from "node:os"; | |
| 8 | import { join } from "node:path"; | |
| 9 | import { createOrg } from "../src/actions/orgs.ts"; | |
| 10 | import { createApp, type App } from "../src/app.ts"; | |
| 11 | import { createRepo } from "../src/core/repos.ts"; | |
| 12 | import { createUser } from "../src/core/users.ts"; | |
| 13 | import { serve, type Servers } from "../src/http/server.ts"; | |
| 14 | ||
| 15 | describe("the org page's filter bar uses §5's bar", () => { | |
| 16 | let dataDir: string; | |
| 17 | let app: App; | |
| 18 | let servers: Servers; | |
| 19 | let base: string; | |
| 20 | ||
| 21 | beforeAll(async () => { | |
| 22 | dataDir = await mkdtemp(join(tmpdir(), "forge-filters-markup-")); | |
| 23 | app = createApp({ dataDir, port: 0 }); | |
| 24 | servers = await serve(app); | |
| 25 | base = servers.url; | |
| 26 | const made = await createUser(app.db, { | |
| 27 | origin: "seed", | |
| 28 | trusted: true, | |
| 29 | username: "alice", | |
| 30 | email: "[email protected]", | |
| 31 | password: "a-good-passphrase", | |
| 32 | adminIfFirst: true, | |
| 33 | }); | |
| 34 | if (!made.ok) throw new Error(made.error); | |
| 35 | const org = createOrg(app, made.value, "acme"); | |
| 36 | if (!org.ok) throw new Error(String(org.error)); | |
| 37 | const repo = await createRepo(app.db, app.reposRoot, { | |
| 38 | ownerId: org.value.id, | |
| 39 | name: "svc", | |
| 40 | visibility: "public", | |
| 41 | }); | |
| 42 | if (!repo.ok) throw new Error(String(repo.error)); | |
| 43 | }, 60_000); | |
| 44 | ||
| 45 | afterAll(async () => { | |
| 46 | servers.stop(); | |
| 47 | app.close(); | |
| 48 | await rm(dataDir, { recursive: true, force: true }); | |
| 49 | }); | |
| 50 | ||
| 51 | test("the org page's filter is a bar with the same GET form", async () => { | |
| 52 | const page = await (await fetch(`${base}/acme`)).text(); | |
| 53 | expect(page).not.toMatch(/class="[^"]*\bfilters\b/); | |
| 54 | expect(page).toContain('class="bar mt mb"'); | |
| 55 | expect(page).toMatch( | |
| 56 | /<form class="bar" method="get" action="\/acme" role="search">/, | |
| 57 | ); | |
| 58 | expect(page).toContain('name="q"'); | |
| 59 | expect(page).toContain("Find a repository"); | |
| 60 | expect(page).toContain(">Filter<"); | |
| 61 | }); | |
| 62 | }); |
test/web-browse.test.ts +9
| @@ -437,6 +437,15 @@ describe("browsing an origin repository on the web", () => { | ||
| 437 | 437 | expect(list).toContain("mona"); |
| 438 | 438 | expect(list).toContain("bug"); |
| 439 | 439 | |
| 440 | // The filter bar is §5's `.bar`; the page-local `.filters` is gone. | |
| 441 | expect(list).not.toMatch(/class="[^"]*\bfilters\b/); | |
| 442 | expect(list).toMatch( | |
| 443 | /<form class="bar" method="get" action="[^"]*\/issues" role="search">/, | |
| 444 | ); | |
| 445 | for (const word of ["Open", "Closed", "All", "Search"]) | |
| 446 | expect(list).toContain(word); | |
| 447 | expect(list).toContain('name="q"'); | |
| 448 | ||
| 440 | 449 | const one = await bodyOf(alice, `${browse}/issues/7`); |
| 441 | 450 | expect(one).toContain("The gear slips"); |
| 442 | 451 | expect(one).toContain("under load"); |
new above
offline
These rules are global to every .bar, not only filter bars. Fine today (only .seg/.search inside a bar), but name them in your area doc, and remove the .filters twins once the last page moves.