diff --git a/AGENTS.md b/AGENTS.md index b39551d..3a60887 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,8 +16,7 @@ build does not depend on them. | `src/data/catalog.ts` | The catalogue: every addon, in groups, in order; which addon has a site of its own | | `src/data/addons/.json` | The facts of one addon - title, tagline, summary, features, requirements, install notes, usage snippet, links, status, license | | `src/data/addons.ts` | Reads the JSON at build time and holds it to the shape; a missing or empty file fails the build | -| `src/pages/index.astro` | The overview: the heading, a folded menu of the groups that stays in view at the top, a tile per addon across the full width - no prose | -| `public/toc.js` | Names the current group on the folded menu while the page scrolls and closes the list after a jump | +| `src/pages/index.astro` | The overview: the heading, then the groups with a tile per addon across the full width - no menu, no search, no prose | | `src/pages/[repo].astro` | The addon page, one shape for all: title and tagline, the two buttons, then what it does, requirements, installation, usage, links | | `src/components/Tile.astro` | One tile of the overview: title, tagline, badges | | `src/components/Badges.astro` | The badge row, the same on the tile and on the addon page | @@ -63,14 +62,11 @@ npm run check # build + test, what CI runs no page here: GitHub Pages serves a repository's project site at `//` in front of anything this site puts there. The tile leads to that site and says so (`label`, default "own site"). -- **One script, and only this.** The policy is `script-src 'self'`. The - overview loads `public/toc.js`, which names the group the reader is in on - the folded menu; the menu works without it - a `
` with plain - anchors. There is no search on the overview: the search box on - https://abap2ui5.github.io/ finds every addon and tool, from this site's - `search.json`. No other script, - no inline script, no `on…=` handler, no inline `style=""` - the test - refuses them. +- **No script.** The policy is `script-src 'self'` and no page ships one - + no search, no filter, no menu of the groups on the overview: the search + box on https://abap2ui5.github.io/ finds every addon and tool, from this + site's `search.json`. No inline script, no `on…=` handler, no inline + `style=""` - the test refuses them. - **Nothing from another origin.** No CDN, no web font service, no analytics, no image from GitHub; the font, the logo and any screenshot are in `public/`. - **The look is the entry page's.** `global.css` starts with a copy of diff --git a/public/toc.js b/public/toc.js deleted file mode 100644 index ac575e1..0000000 --- a/public/toc.js +++ /dev/null @@ -1,43 +0,0 @@ -// The menu of the overview: names the group the reader is in on the folded -// menu, marks it in the list, and closes the list after a jump, on Escape -// and on a click elsewhere. The menu works without it - it is a
-// with plain anchors. The one script of the site: loaded from this origin, -// no inline code, so the Content-Security-Policy (script-src 'self') allows it. -(() => { - const menu = document.querySelector(".toc details"); - if (!menu) return; - const label = menu.querySelector(".toc-current"); - const links = [...menu.querySelectorAll("a[href^='#']")]; - const sections = links.map((a) => document.getElementById(a.hash.slice(1))).filter(Boolean); - let ticking = false; - // a group jumped to stays current until the reader scrolls away from it, - // even near the end of the page, where it cannot reach the top - let pinned = null, pinnedAt = null; - const mark = () => { - ticking = false; - if (pinned && pinnedAt !== null && Math.abs(window.scrollY - pinnedAt) > 40) pinned = null; - const line = window.innerHeight * 0.3; - let current = sections[0]; - for (const s of sections) if (s.getBoundingClientRect().top <= line) current = s; - if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 2) current = sections[sections.length - 1]; - if (pinned) current = pinned; - for (const a of links) { - const on = a.hash.slice(1) === current.id; - if (on) { a.setAttribute("aria-current", "true"); label.textContent = a.firstChild.textContent; } - else a.removeAttribute("aria-current"); - } - }; - addEventListener("scroll", () => { if (!ticking) { ticking = true; requestAnimationFrame(mark); } }, { passive: true }); - addEventListener("resize", mark); - for (const a of links) a.addEventListener("click", () => { - menu.open = false; - pinned = document.getElementById(a.hash.slice(1)); - pinnedAt = null; - // the jump lands after this handler; remember where it landed - setTimeout(() => { pinnedAt = window.scrollY; mark(); }, 120); - }); - addEventListener("hashchange", () => { if (pinned) { pinnedAt = window.scrollY; mark(); } }); - addEventListener("keydown", (e) => { if (e.key === "Escape") menu.open = false; }); - addEventListener("click", (e) => { if (menu.open && !menu.contains(e.target)) menu.open = false; }); - mark(); -})(); diff --git a/src/pages/index.astro b/src/pages/index.astro index 8152a28..4532465 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,9 +1,8 @@ --- /* The overview: every addon, in groups, one tile each. A tile opens the - * addon's page on this site, or its own site where it has one. A menu at the - * top folds out to every group and stays in view while the page scrolls; - * public/toc.js names the group the reader is in. The tiles run in two - * columns, as on the entry page. There is no search of its own here: the + * addon's page on this site, or its own site where it has one. The tiles run + * in two columns, as on the entry page; the groups follow each other with + * their headings, no menu. There is no search of its own here: the * search box on https://abap2ui5.github.io/ finds every addon and tool, from * the search.json this site publishes. */ import Page from "../layouts/Page.astro"; @@ -17,14 +16,6 @@ const base = import.meta.env.BASE_URL;

Addons & Tools

- {groups.map((g) => (

{g.title}

@@ -34,6 +25,5 @@ const base = import.meta.env.BASE_URL;
))}
- diff --git a/src/styles/global.css b/src/styles/global.css index eecbf6a..8201611 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -204,28 +204,3 @@ main { flex: 1 0 auto; } .foot a { color: var(--muted); text-decoration: none; } .foot a:hover { color: var(--brand); } -/* ---- the menu of the overview: folded at the top, always in view -------- */ -.toc { position: sticky; top: 0; z-index: 5; margin: 0 0 24px; padding: 12px 0; background: var(--bg); } -.toc details { position: relative; display: inline-block; } -.toc summary { - display: inline-flex; align-items: center; gap: 8px; padding: 8px 14px; cursor: pointer; list-style: none; - border: 1px solid var(--line); border-radius: 10px; background: var(--card); - font-size: 14px; font-weight: 600; color: var(--ink); - transition: border-color .15s; -} -.toc summary::-webkit-details-marker { display: none; } -.toc summary::after { content: ""; width: 7px; height: 7px; margin-left: 4px; border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted); transform: translateY(-2px) rotate(45deg); transition: transform .15s; } -.toc details[open] summary::after { transform: translateY(1px) rotate(-135deg); } -.toc summary:hover, .toc details[open] summary { border-color: var(--brand); } -.toc .toc-hint { color: var(--muted); font-weight: 500; } -.toc-list { - position: absolute; top: calc(100% + 6px); left: 0; min-width: 260px; padding: 6px; - display: flex; flex-direction: column; border: 1px solid var(--line); border-radius: 12px; - background: var(--card); box-shadow: var(--shadow); -} -.toc-list a { display: flex; justify-content: space-between; gap: 16px; padding: 8px 10px; border-radius: 8px; color: var(--text); font-size: 14px; font-weight: 500; text-decoration: none; } -.toc-list a span { color: var(--muted); font-variant-numeric: tabular-nums; } -.toc-list a:hover { background: var(--bg); color: var(--ink); } -.toc-list a[aria-current="true"] { color: var(--brand); font-weight: 600; } -.group { scroll-margin-top: 72px; } -@media (prefers-reduced-motion: reduce) { .toc summary, .toc summary::after { transition: none; } } diff --git a/test/site.test.mjs b/test/site.test.mjs index 29836cd..60a3306 100644 --- a/test/site.test.mjs +++ b/test/site.test.mjs @@ -17,11 +17,8 @@ const hosts = ["abap2ui5.github.io", "abap2ui5-addons.github.io", "github.com/ab const pages = facts.filter((r) => !own.some(([o]) => o === r)); function noScript(html, name) { - // the one script allowed is the menu marker, a file of this origin - for (const m of html.matchAll(/]*)>([\s\S]*?)<\/script>/gi)) { - assert.match(m[1], /\ssrc="\/toc\.js"/, `${name}: a script other than /toc.js`); - assert.equal(m[2].trim(), "", `${name}: inline script code`); - } + // no script at all: the search of the family is on the entry page + assert.doesNotMatch(html, / { assert.ok(hosts.some((h) => site.includes(h)), `${r}: ${site} is not one of the project's places`); } assert.doesNotMatch(home, /]*>([\s\S]*?)<\/nav>/); - assert.ok(toc, "a menu of the groups"); for (const id of [...catalog.matchAll(/id: "([^"]+)"/g)].map((m) => m[1])) { - assert.match(toc[1], new RegExp(`href="#${id}"`), `the menu links #${id}`); assert.match(home, new RegExp(`