Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 6 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<repo>.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 |
Expand Down Expand Up @@ -63,14 +62,11 @@ npm run check # build + test, what CI runs
no page here: GitHub Pages serves a repository's
project site at `/<repo>/` 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 `<details>` 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
Expand Down
43 changes: 0 additions & 43 deletions public/toc.js

This file was deleted.

16 changes: 3 additions & 13 deletions src/pages/index.astro
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -17,14 +16,6 @@ const base = import.meta.env.BASE_URL;
<div class="wrap">
<h1 class="welcome">Addons &amp; Tools</h1>

<nav class="toc" aria-label="Sections">
<details>
<summary><span class="toc-hint">Jump to</span> <span class="toc-current">{groups[0].title}</span></summary>
<div class="toc-list">
{groups.map((g, i) => <a href={`#${g.id}`} aria-current={i === 0 ? "true" : undefined}>{g.title}<span>{g.entries.length}</span></a>)}
</div>
</details>
</nav>
{groups.map((g) => (
<section class="group" id={g.id}>
<h2>{g.title}</h2>
Expand All @@ -34,6 +25,5 @@ const base = import.meta.env.BASE_URL;
</section>
))}
</div>
<script is:inline src={`${base}toc.js`} defer></script>

</Page>
25 changes: 0 additions & 25 deletions src/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -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; } }
12 changes: 3 additions & 9 deletions test/site.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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(/<script\b([^>]*)>([\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, /<script\b/i, `${name}: a script`);
assert.doesNotMatch(html, /\son[a-z]+=/i, `${name}: an inline event handler`);
assert.doesNotMatch(html, /\sstyle="/i, `${name}: an inline style the policy would refuse`);
const csp = html.match(/http-equiv="Content-Security-Policy" content="([^"]+)"/);
Expand Down Expand Up @@ -55,13 +52,10 @@ test("the overview is the greeting and a tile per addon", () => {
assert.ok(hosts.some((h) => site.includes(h)), `${r}: ${site} is not one of the project's places`);
}
assert.doesNotMatch(home, /<header|<nav class="nav"|class="intro"/, "no header, no prose");
const toc = home.match(/<nav class="toc"[^>]*>([\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(`<section class="group" id="${id}"`), `#${id} is a section`);
}
assert.ok(fs.existsSync(path.join(DIST, "toc.js")), "toc.js is published");
assert.doesNotMatch(home, /<nav class="toc"|toc\.js/, "no menu of the groups");
// no search of its own: the box on the entry page finds every addon (search.json)
assert.doesNotMatch(home, /<form|role="search"|filter\.js/, "no search on the overview");
assert.ok(!fs.existsSync(path.join(DIST, "filter.js")), "filter.js is gone");
Expand Down
Loading