From 227404f6235b5af7139af3e80d0b7237ac6c3781 Mon Sep 17 00:00:00 2001 From: Hananoshika Yomaru Date: Tue, 19 May 2026 12:29:49 -0700 Subject: [PATCH] Integrate Pagefind for enhanced search functionality: update build process to include search indexing, add search page and UI components, and modify templates for search integration. Update styles for search modal and ensure proper indexing of chapter content. Enhance documentation to reflect new search capabilities. --- .agents/skills/ofb/SKILL.md | 19 +- .agents/skills/zola/SKILL.md | 11 +- .agents/skills/zola/zola-pagefind.md | 170 ++++++++++++ .gitignore | 3 + CONTRIBUTING.md | 4 +- README.md | 14 +- bun.lock | 17 ++ content/search.md | 4 + frontend/src/components/site-header.tsx | 5 +- frontend/src/main.tsx | 3 + frontend/src/theme.ts | 1 + frontend/src/types/pagefind-components.d.ts | 15 ++ og.png | Bin 0 -> 2682356 bytes package.json | 4 +- themes/openfreebooks/sass/_components.scss | 2 +- themes/openfreebooks/sass/_grain.scss | 3 +- themes/openfreebooks/sass/_search.scss | 250 ++++++++++++++++++ themes/openfreebooks/sass/main.scss | 1 + themes/openfreebooks/templates/base.html | 10 + .../openfreebooks/templates/math/chapter.html | 6 +- .../templates/partials/why-sections.html | 7 +- themes/openfreebooks/templates/search.html | 22 ++ zola.toml | 1 + 23 files changed, 553 insertions(+), 19 deletions(-) create mode 100644 .agents/skills/zola/zola-pagefind.md create mode 100644 content/search.md create mode 100644 frontend/src/types/pagefind-components.d.ts create mode 100644 og.png create mode 100644 themes/openfreebooks/sass/_search.scss create mode 100644 themes/openfreebooks/templates/search.html diff --git a/.agents/skills/ofb/SKILL.md b/.agents/skills/ofb/SKILL.md index b8ad8af..939edd6 100644 --- a/.agents/skills/ofb/SKILL.md +++ b/.agents/skills/ofb/SKILL.md @@ -33,8 +33,9 @@ frontend/ → catalog-app, canvas viewer, site chrome ```bash bun install bun run build:js # after frontend/ changes +bun run index:search # once per dev session (copies public/pagefind → static/pagefind) zola serve # http://127.0.0.1:1111 -bun run build # production: JS + Zola → public/ +bun run build # production: JS + Zola + pagefind → public/ ``` ## Non-negotiables @@ -59,6 +60,21 @@ bun run build # production: JS + Zola → public/ | New chapter (live) | Above + `content/{subject}/{slug}/_index.md` + HTML partial + `chapter.html` wiring + `main.tsx` widget mount if interactive | | Homepage subject card | `themes/.../templates/index.html` (not yet data-driven) | | Browse / nav URL | `zola.toml` `[extra] browse_url` + `base.html` site-config + `main.tsx` fallback | +| Search UI / index | `base.html` (Pagefind assets + modal), `content/search.md`, `_search.scss`; `bun run index:search` for dev | + +## Search (Pagefind) + +| Piece | Path | +|-------|------| +| Global modal + assets | `themes/.../templates/base.html` | +| Dedicated page | `/search/` — `content/search.md`, `templates/search.html` | +| Header trigger | `frontend/src/components/site-header.tsx` (`pagefind-modal-trigger`) | +| Nav link | `main.tsx` + `zola.toml` `search_url` | +| Theming | `themes/.../sass/_search.scss` (`--pf-*` → site tokens) | + +- **Component UI** only (`pagefind-component-ui.js`), not legacy `pagefind-ui.js`. +- Index runs **after** `zola build`. Production: `bun run build`. Dev: `bun run index:search` copies bundle to `static/pagefind/` (gitignored) for `zola serve`. +- v1 indexes **live chapter pages only** (`data-pagefind-body` on `templates/math/chapter.html`); home, about, catalog, credits, and other site pages are excluded. Detailed checklists: [curriculum-data.md](curriculum-data.md). Human-facing guide: [CONTRIBUTING.md](../../../CONTRIBUTING.md). @@ -114,7 +130,6 @@ bun run build ## Planned / not implemented (do not assume) -- Pagefind search (`build_search_index = false`) - Defuddle “copy as Markdown” on chapters - Markdown-based chapters (spec says HTML; README may be outdated) - Data-driven homepage cards diff --git a/.agents/skills/zola/SKILL.md b/.agents/skills/zola/SKILL.md index 4a798d4..6e408dc 100644 --- a/.agents/skills/zola/SKILL.md +++ b/.agents/skills/zola/SKILL.md @@ -29,12 +29,16 @@ See [reference.md](reference.md) for Tera pitfalls and build checks. ```bash bun run build:js # Vite → themes/openfreebooks/static/js/ (bundle.js + lazy chunks) +zola build # required before indexing +bun run index:search # pagefind on public/ → copies to static/pagefind/ for zola serve zola serve # dev at http://127.0.0.1:1111 -bun run build # build:js + zola build → public/ +bun run build # build:js + zola build + index:search ``` After Sass or template changes, run `zola build` (or `zola serve`). Do **not** copy `public/` into `themes/openfreebooks/static/`. +**Search (Pagefind):** read [zola-pagefind.md](zola-pagefind.md) before changing search UI, indexing, or `data-pagefind-*` attributes. + ## Templates (Tera) - Extend `base.html`; use `{% block content %}` for page bodies. @@ -75,6 +79,11 @@ Client behavior lives in `frontend/` and mounts from `base.html`: **When changing `frontend/` or adding interactivity**, read [zola-solid.md](zola-solid.md) before editing JS. +### Search (Pagefind) + +- Runs after `zola build`; indexes **live chapter pages** only (`data-pagefind-body` on chapter template). +- UI: global modal (⌘K) + `/search/` page. See [zola-pagefind.md](zola-pagefind.md). + ### Catalog page - `content/catalog.md` → `template = "catalog.html"` diff --git a/.agents/skills/zola/zola-pagefind.md b/.agents/skills/zola/zola-pagefind.md new file mode 100644 index 0000000..d76ad3e --- /dev/null +++ b/.agents/skills/zola/zola-pagefind.md @@ -0,0 +1,170 @@ +# Pagefind + Zola (Open Free Books) + +Reference for static search in this repo. Pagefind runs **after** Zola builds HTML to `public/` — it does not use Zola’s `build_search_index`. + +Read [Pagefind Component UI](https://pagefind.app/llms-component-ui.txt) for upstream API details. Use **Component UI** (`pagefind-component-ui.js`) only — not legacy `pagefind-ui.js` / `PagefindUI`. + +## How it fits together + +```text +bun run build:js → themes/openfreebooks/static/js/bundle.js +zola build → public/**/*.html +pagefind --site public → public/pagefind/ (index + WASM + UI assets) +``` + +| Environment | Index output | Served from | +|-------------|--------------|-------------| +| Production (`bun run build`) | `public/pagefind/` | Deployed with Wrangler | +| Dev (`zola serve`) | `static/pagefind/` (copy) | Zola copies `static/` to site root | + +`static/pagefind/` is **gitignored**. Run `bun run index:search` after `zola build` so `zola serve` can load `/pagefind/*`. + +## Commands + +```bash +bun run build:js # if frontend changed +zola build # required before indexing +bun run index:search # pagefind on public/ → copy to static/pagefind/ +zola serve # http://127.0.0.1:1111 +``` + +Production (includes index + copy): + +```bash +bun run build # build:js + zola build + index:search +``` + +`package.json` devDependency: `pagefind` (^1.5). CLI: `pagefind --site public`. + +## What gets indexed + +**Live chapter pages only.** Once any page uses `data-pagefind-body`, Pagefind **skips** all pages without it. + +| Source | Indexed? | +|--------|----------| +| `templates/math/chapter.html` → `
` | Yes | +| Home, about, catalog, credits, contributing, `/search/` | No | +| Catalog chapter cards (Solid + JSON) | No | +| Planned chapters (no `content/` page) | No | + +New subject chapter templates must include `data-pagefind-body` on the main lesson container. + +### Per-chapter markup + +[`themes/openfreebooks/templates/math/chapter.html`](../../../themes/openfreebooks/templates/math/chapter.html): + +```html +
+ … +
+
+
+``` + +- **`data-pagefind-body`** — title, lead, and HTML partial prose. +- **`data-pagefind-ignore`** — chrome and interactive widgets (not lesson text). + +Breadcrumb nav stays **outside** the article so it is not indexed. + +## UI integration + +### Global (every page) + +[`themes/openfreebooks/templates/base.html`](../../../themes/openfreebooks/templates/base.html): + +- ``: `pagefind/pagefind-component-ui.css` + `.js` via `get_url()` +- Before ``: `` +- `#site-config` includes `searchUrl` from `zola.toml` `[extra] search_url` + +### Header (Solid) + +[`frontend/src/components/site-header.tsx`](../../../frontend/src/components/site-header.tsx): + +```tsx + +``` + +[`frontend/src/main.tsx`](../../../frontend/src/main.tsx): nav link **Search** → `config.searchUrl`. + +Types: [`frontend/src/types/pagefind-components.d.ts`](../../../frontend/src/types/pagefind-components.d.ts). + +### Dedicated page + +| File | Role | +|------|------| +| `content/search.md` | `template = "search.html"` | +| `templates/search.html` | `pagefind-input`, `pagefind-summary`, `pagefind-results`, `pagefind-keyboard-hints` | +| `sass/_search.scss` | Layout + map `--pf-*` to site tokens (`:root`, `[data-theme="dark"]`) | + +URL: `/search/`. Modal and page share the default Pagefind instance (⌘K / Ctrl+K). + +## Theming + +Pagefind hosts use `all: initial` — site CSS does not cascade in. Theme via **`--pf-*`** variables in `_search.scss` (see [Pagefind CSS variables](https://pagefind.app/docs/css-variables/)). + +**Load order in `base.html`:** `pagefind-component-ui.css` first, then `main.css` so site `--pf-*` overrides Pagefind defaults (white button, blue focus ring). + +Site theme uses `data-theme` and `data-pf-theme` on `` (`theme.ts`); tokens map under `[data-theme="dark"]` and `prefers-color-scheme`. Header trigger scoped in `.site-header__search` to match `.theme-toggle`. + +## Zola config + +[`zola.toml`](../../../zola.toml): + +```toml +build_search_index = false # Zola search unused; Pagefind handles search +search_url = "/search/" +``` + +`` in `base.html` — required for Pagefind language detection. + +## Stale static HTML (common bug) + +**Never** copy `public/` into `themes/openfreebooks/static/`. Committed `themes/openfreebooks/static/**/*.html` **overrides** Zola output and breaks: + +- `data-pagefind-body` on chapters +- Pagefind assets in `base.html` +- Any template change + +If search indexes 8 pages instead of 1, delete stale `themes/openfreebooks/static/**/*.html`, then `zola build` and `bun run index:search`. + +Allowed under theme `static/`: built JS (`js/bundle.js`, hashed chunks), fonts, etc. — not generated HTML. + +## Verify + +```bash +bun run build:js && zola build && bun run index:search +``` + +Expect Pagefind output similar to: + +```text +Indexed 1 page +Indexed … words +``` + +Check: + +- `public/math//index.html` contains `data-pagefind-body` +- `public/pagefind/pagefind-component-ui.js` exists +- `static/pagefind/` exists after `index:search` (for dev) +- `/search/` and header ⌘K return chapter hits only + +## Adding a new live chapter + +1. Publish chapter per [CONTRIBUTING.md](../../../CONTRIBUTING.md) (`content/`, partial, `chapter.html` branch). +2. Ensure content lives inside `
`. +3. `zola build && bun run index:search` (or full `bun run build`). +4. Confirm indexed page count increased. + +## Do not + +- Commit `public/pagefind/` or `static/pagefind/`. +- Use `pagefind-ui.js` / `new PagefindUI()`. +- Add `data-pagefind-body` to `base.html` or marketing pages unless you intend to index them. +- Expect catalog JSON titles in search without a static HTML index block (future enhancement). + +## Related + +- [SKILL.md](SKILL.md) — Zola layout and commands +- [zola-solid.md](zola-solid.md) — header mount, `searchUrl` in site config +- [ofb/SKILL.md](../ofb/SKILL.md) — product rules, chapter workflow diff --git a/.gitignore b/.gitignore index 5a09e38..9d8fcfe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Zola build output (Wrangler deploy source) public/ +# Pagefind index copied for zola serve (run bun run index:search) +static/pagefind/ + # Dependencies node_modules/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 132cb13..f3aa55a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,6 +20,8 @@ Thank you for helping grow free, open textbooks. **Browse the catalog locally:** `bun run build:js && zola serve` → [http://127.0.0.1:1111/catalog/](http://127.0.0.1:1111/catalog/) +**Search locally:** After `bun run build:js`, run `bun run index:search` once (or again after changing chapter HTML), then `zola serve`. Try [http://127.0.0.1:1111/search/](http://127.0.0.1:1111/search/) or press ⌘K / Ctrl+K. Pagefind indexes **live chapter pages only** (not home, about, catalog, or credits). Without `index:search`, the search UI loads but returns no results. + --- ## How the catalog fits together @@ -110,7 +112,7 @@ chapter_id = "your-chapter-slug" Today, chapter bodies live as HTML partials under the theme, not Markdown: 1. Add `themes/openfreebooks/templates/partials/math/your-chapter-slug-content.html` with the lesson HTML. -2. In `themes/openfreebooks/templates/math/chapter.html`, include it when `chapter_id` matches: +2. In `themes/openfreebooks/templates/math/chapter.html`, include it when `chapter_id` matches (chapter content is inside `
` for search indexing): ```html {% if section.extra.chapter_id == "your-chapter-slug" %} diff --git a/README.md b/README.md index df58426..823ef5a 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Free, open-source textbooks for every learner — from elementary school through | Theme | Custom theme in `themes/openfreebooks/` | | Interactivity | [Solid.js](https://www.solidjs.com/) (header, marquee, scroll reveal, theme toggle) | | JS build | [Bun](https://bun.sh/) + [Vite](https://vite.dev/) | +| Search | [Pagefind](https://pagefind.app/) (post-build index, Component UI) | | Hosting | Cloudflare Workers (static assets via Wrangler) | Chapter content is **HTML partials** in the theme today (not Markdown). Canvas maps use the [JSON Canvas](https://jsoncanvas.org/) standard (Obsidian-compatible) via [json-canvas-viewer](https://github.com/Hesprs/JSON-Canvas-Viewer). @@ -47,14 +48,17 @@ Install JS dependencies: bun install ``` -Build the Solid bundle and start Zola’s dev server: +Build the Solid bundle, index search (once per session or after content changes), then start Zola’s dev server: ```bash bun run build:js +bun run index:search zola serve ``` -Open [http://127.0.0.1:1111](http://127.0.0.1:1111). Re-run `bun run build:js` (or `bun run dev:js` in another terminal) when you change `frontend/`. +Open [http://127.0.0.1:1111](http://127.0.0.1:1111). Re-run `bun run build:js` (or `bun run dev:js` in another terminal) when you change `frontend/`. Re-run `bun run index:search` when you change chapter HTML or add pages so search stays up to date. + +Search UI is available immediately; results require the index step above (`static/pagefind/` is gitignored and copied from the build output). ## Production build @@ -62,7 +66,7 @@ Open [http://127.0.0.1:1111](http://127.0.0.1:1111). Re-run `bun run build:js` ( bun run build ``` -This runs `vite build` (outputs to `themes/openfreebooks/static/js/bundle.js`) and then `zola build` (outputs to `public/`). +This runs `vite build` (outputs to `themes/openfreebooks/static/js/bundle.js`), `zola build` (outputs to `public/`), then Pagefind (`public/pagefind/`). ## Deploy to Cloudflare @@ -78,7 +82,7 @@ The Worker name is `openfreebooks` (see `wrangler.jsonc`). Assets are served fro Site-wide settings live in `zola.toml`: - `base_url` — canonical URL for feeds and absolute links -- `[extra]` — `site_title`, `github_url`, `browse_url`, `about_url` (used in templates and the Solid config block) +- `[extra]` — `site_title`, `github_url`, `browse_url`, `search_url`, `about_url` (used in templates and the Solid config block) ## Theme notes @@ -108,7 +112,7 @@ These issues make contributions harder than they should be. PRs that fix any ite ### Site & discoverability - [ ] **Data-driven homepage cards** — “Browse by subject” / featured blocks duplicate manual edits in `index.html`. -- [ ] **Pagefind search** — spec mentions search; `build_search_index = false` and no index step in build. +- [x] **Pagefind search** — `/search/`, header modal (⌘K), `bun run index:search` for local dev. - [ ] **CONTRIBUTING wizard or checklist in CI** — fail PR if live chapter missing partial or catalog entry. ### Build & repo hygiene diff --git a/bun.lock b/bun.lock index ef37cca..2cf57c7 100644 --- a/bun.lock +++ b/bun.lock @@ -13,6 +13,7 @@ "three": "^0.184.0", }, "devDependencies": { + "pagefind": "^1.3.0", "typescript": "^5.8.3", "vite": "^6.3.5", "vite-plugin-solid": "^2.11.6", @@ -132,6 +133,20 @@ "@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="], + "@pagefind/darwin-arm64": ["@pagefind/darwin-arm64@1.5.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ=="], + + "@pagefind/darwin-x64": ["@pagefind/darwin-x64@1.5.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-IojxFWMEJe0RQ7PQ3KXQsPIImNsbpPYpoZ+QUDrL8fAl/O27IX+LVLs74/UzEZy5uA2LD8Nz1AiwKr72vrkZQw=="], + + "@pagefind/freebsd-x64": ["@pagefind/freebsd-x64@1.5.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-7EVzo9+0w+2cbe671BtMj10UlNo83I+HrLVLfRxO731svHRJKUfJ/mo05gU14pe9PCfpKNQT8FS3Xc/oDN6pOA=="], + + "@pagefind/linux-arm64": ["@pagefind/linux-arm64@1.5.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Ovt9+K35sqzn8H3ZMXGwls4TD/wMJuvRtShHIsmUQREmaxjrDEX7gHckRCrwYJ4XE1H1p6HkLz3wukrAnsfXQw=="], + + "@pagefind/linux-x64": ["@pagefind/linux-x64@1.5.2", "", { "os": "linux", "cpu": "x64" }, "sha512-V+tFqHKXhQKq/WqPBD67AFy7scn1/aZID00ws4fSDd+1daSi5UHR9VVlRrOUYKxn3VuFQYRD7lYXdZK1WED1YA=="], + + "@pagefind/windows-arm64": ["@pagefind/windows-arm64@1.5.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-hN9Nh90fNW61nNRCW9ZyQrAj/mD0eRvmJ8NlTUzkbuW8kIzGJUi3cxjFkEcMZ5h/8FsKWD/VcouZl4yo1F7B6g=="], + + "@pagefind/windows-x64": ["@pagefind/windows-x64@1.5.2", "", { "os": "win32", "cpu": "x64" }, "sha512-Fa2Iyw7kaDRzGMfNYNUXNW2zbL5FQVDgSOcbDHdzBrDEdpqOqg8TcZ68F22ol6NJ9IGzvUdmeyZypLW5dyhqsg=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.4", "", { "os": "android", "cpu": "arm" }, "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.4", "", { "os": "android", "cpu": "arm64" }, "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw=="], @@ -446,6 +461,8 @@ "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], + "pagefind": ["pagefind@1.5.2", "", { "optionalDependencies": { "@pagefind/darwin-arm64": "1.5.2", "@pagefind/darwin-x64": "1.5.2", "@pagefind/freebsd-x64": "1.5.2", "@pagefind/linux-arm64": "1.5.2", "@pagefind/linux-x64": "1.5.2", "@pagefind/windows-arm64": "1.5.2", "@pagefind/windows-x64": "1.5.2" }, "bin": { "pagefind": "lib/runner/bin.cjs" } }, "sha512-XTUaK0hXMCu2jszWE584JGQT7y284TmMV9l/HX3rnG5uo3rHI/uHU56XTyyyPFjeWEBxECbAi0CaFDJOONtG0Q=="], + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], diff --git a/content/search.md b/content/search.md new file mode 100644 index 0000000..d573f60 --- /dev/null +++ b/content/search.md @@ -0,0 +1,4 @@ ++++ +title = "Search" +template = "search.html" ++++ diff --git a/frontend/src/components/site-header.tsx b/frontend/src/components/site-header.tsx index 9cea670..64d20b9 100644 --- a/frontend/src/components/site-header.tsx +++ b/frontend/src/components/site-header.tsx @@ -22,7 +22,7 @@ export function SiteHeader(props: SiteHeaderProps) { window.addEventListener("scroll", onScroll, { passive: true }); const onResize = () => { - if (window.innerWidth > 467 && navOpen()) { + if (window.innerWidth > 768 && navOpen()) { setNavOpen(false); document.body.style.overflow = ""; } @@ -60,6 +60,9 @@ export function SiteHeader(props: SiteHeaderProps) { +