mirror of
https://github.com/HananoshikaYomaru/openfreebooks.git
synced 2026-11-12 20:45:52 +00:00
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.
This commit is contained in:
parent
c66f8850a9
commit
227404f623
23 changed files with 553 additions and 19 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
170
.agents/skills/zola/zola-pagefind.md
Normal file
170
.agents/skills/zola/zola-pagefind.md
Normal file
|
|
@ -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` → `<article data-pagefind-body>` | 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
|
||||
<article class="book-chapter" data-pagefind-body>
|
||||
…
|
||||
<div id="copy-page-button" … data-pagefind-ignore></div>
|
||||
<div id="quadratic-explorer" … data-pagefind-ignore></div>
|
||||
</article>
|
||||
```
|
||||
|
||||
- **`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):
|
||||
|
||||
- `<head>`: `pagefind/pagefind-component-ui.css` + `.js` via `get_url()`
|
||||
- Before `</body>`: `<pagefind-modal reset-on-close>`
|
||||
- `#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
|
||||
<pagefind-modal-trigger compact placeholder="Search" />
|
||||
```
|
||||
|
||||
[`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 `<html>` (`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/"
|
||||
```
|
||||
|
||||
`<html lang="en">` 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/<slug>/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 `<article data-pagefind-body>`.
|
||||
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
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -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/
|
||||
|
||||
|
|
|
|||
|
|
@ -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 `<article data-pagefind-body>` for search indexing):
|
||||
|
||||
```html
|
||||
{% if section.extra.chapter_id == "your-chapter-slug" %}
|
||||
|
|
|
|||
14
README.md
14
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
|
||||
|
|
|
|||
17
bun.lock
17
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=="],
|
||||
|
|
|
|||
4
content/search.md
Normal file
4
content/search.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
+++
|
||||
title = "Search"
|
||||
template = "search.html"
|
||||
+++
|
||||
|
|
@ -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) {
|
|||
<nav class="main-nav" aria-label="Primary navigation">
|
||||
<For each={props.links}>{(link) => <a href={link.href}>{link.label}</a>}</For>
|
||||
</nav>
|
||||
<div class="site-header__search">
|
||||
<pagefind-modal-trigger compact placeholder="Search" />
|
||||
</div>
|
||||
<ThemeToggle />
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ function readConfig() {
|
|||
homeUrl: "/",
|
||||
browseUrl: "/catalog/",
|
||||
aboutUrl: "/about/",
|
||||
searchUrl: "/search/",
|
||||
contributingUrl: "/contributing/",
|
||||
githubUrl: "https://github.com/openfreebooks/openfreebooks",
|
||||
};
|
||||
|
|
@ -22,6 +23,7 @@ function readConfig() {
|
|||
homeUrl: string;
|
||||
browseUrl: string;
|
||||
aboutUrl: string;
|
||||
searchUrl: string;
|
||||
contributingUrl: string;
|
||||
githubUrl: string;
|
||||
};
|
||||
|
|
@ -39,6 +41,7 @@ initTheme();
|
|||
const config = readConfig();
|
||||
const navLinks = [
|
||||
{ label: "Browse", href: config.browseUrl },
|
||||
{ label: "Search", href: config.searchUrl },
|
||||
{ label: "About", href: config.aboutUrl },
|
||||
{ label: "GitHub", href: config.githubUrl },
|
||||
];
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export function resolveTheme(): ThemeChoice {
|
|||
|
||||
export function applyTheme(theme: ThemeChoice) {
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
document.documentElement.setAttribute("data-pf-theme", theme);
|
||||
}
|
||||
|
||||
export function setTheme(theme: ThemeChoice) {
|
||||
|
|
|
|||
15
frontend/src/types/pagefind-components.d.ts
vendored
Normal file
15
frontend/src/types/pagefind-components.d.ts
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import "solid-js";
|
||||
|
||||
declare module "solid-js" {
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
"pagefind-modal-trigger": {
|
||||
compact?: boolean;
|
||||
placeholder?: string;
|
||||
shortcut?: string;
|
||||
"hide-shortcut"?: boolean;
|
||||
instance?: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
og.png
Normal file
BIN
og.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 MiB |
|
|
@ -5,7 +5,8 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"build:js": "vite build",
|
||||
"build": "rm -f themes/openfreebooks/static/main.css public/main.css && bun run build:js && zola build",
|
||||
"build": "rm -f themes/openfreebooks/static/main.css public/main.css && bun run build:js && zola build && bun run index:search",
|
||||
"index:search": "pagefind --site public && rm -rf static/pagefind && cp -R public/pagefind static/pagefind",
|
||||
"dev:js": "vite build --watch",
|
||||
"scan:size": "bun scripts/scan-output-size.ts",
|
||||
"test": "bun test frontend/src/lib"
|
||||
|
|
@ -19,6 +20,7 @@
|
|||
"three": "^0.184.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"pagefind": "^1.5.0",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-solid": "^2.11.6"
|
||||
|
|
|
|||
|
|
@ -440,7 +440,7 @@ h3 {
|
|||
}
|
||||
}
|
||||
|
||||
@media (max-width: 467px) {
|
||||
@media (max-width: 768px) {
|
||||
.nav-burger {
|
||||
display: inline-flex;
|
||||
margin-left: auto;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ body::before {
|
|||
|
||||
/* Top-layer modals must stay opaque; grain blend would otherwise show through. */
|
||||
.contributor-dialog[open],
|
||||
.contributor-dialog[open] .contributor-dialog-panel {
|
||||
.contributor-dialog[open] .contributor-dialog-panel,
|
||||
pagefind-modal dialog.pf-modal[open] {
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
|
||||
|
|
|
|||
250
themes/openfreebooks/sass/_search.scss
Normal file
250
themes/openfreebooks/sass/_search.scss
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
// Pagefind Component UI — override defaults from pagefind-component-ui.css (load main.css after Pagefind).
|
||||
// Modal surface matches .contributor-dialog / .contributor-dialog-panel (_contributors.scss).
|
||||
|
||||
$dialog-surface-light: #f5f4f1;
|
||||
$dialog-surface-dark: #1a1917;
|
||||
$dialog-backdrop-light: rgba(26, 26, 26, 0.45);
|
||||
$dialog-backdrop-dark: rgba(0, 0, 0, 0.62);
|
||||
|
||||
@mixin pagefind-tokens {
|
||||
--pf-text: var(--ink);
|
||||
--pf-text-secondary: var(--soft-ink);
|
||||
--pf-text-muted: var(--muted);
|
||||
--pf-background: #{$dialog-surface-light};
|
||||
--pf-border: var(--line);
|
||||
--pf-border-focus: color-mix(in srgb, var(--accent) 45%, var(--line));
|
||||
--pf-skeleton: color-mix(in srgb, var(--line) 50%, #{$dialog-surface-light});
|
||||
--pf-skeleton-shine: color-mix(in srgb, var(--line) 25%, #{$dialog-surface-light});
|
||||
--pf-hover: color-mix(in srgb, var(--accent) 10%, #{$dialog-surface-light});
|
||||
--pf-mark: var(--accent);
|
||||
--pf-scroll-shadow: var(--shadow-soft);
|
||||
--pf-shadow-sm: 0 2px 8px var(--shadow-soft);
|
||||
--pf-shadow-md: 0 4px 16px var(--shadow-soft);
|
||||
--pf-shadow-lg: 0 18px 48px var(--shadow-soft);
|
||||
--pf-error-bg: color-mix(in srgb, #dc2626 12%, var(--paper));
|
||||
--pf-error-border: color-mix(in srgb, #dc2626 35%, var(--line));
|
||||
--pf-error-text: #b91c1c;
|
||||
--pf-error-text-secondary: #991b1b;
|
||||
--pf-outline-focus: var(--accent);
|
||||
--pf-outline-width: 2px;
|
||||
--pf-outline-offset: 2px;
|
||||
--pf-font: var(--font-body);
|
||||
--pf-input-height: 40px;
|
||||
--pf-input-font-size: 16px;
|
||||
--pf-summary-font-size: 0.75rem;
|
||||
--pf-result-title-font-size: 0.95rem;
|
||||
--pf-result-excerpt-font-size: 0.85rem;
|
||||
--pf-border-radius: 8px;
|
||||
--pf-modal-backdrop: #{$dialog-backdrop-light};
|
||||
--pf-modal-max-width: min(92vw, 440px);
|
||||
--pf-modal-max-height: min(80dvh, 800px);
|
||||
--pf-modal-top: 10dvh;
|
||||
}
|
||||
|
||||
@mixin pagefind-dialog-dark-tokens {
|
||||
--pf-background: #{$dialog-surface-dark};
|
||||
--pf-skeleton: color-mix(in srgb, var(--line) 50%, #{$dialog-surface-dark});
|
||||
--pf-skeleton-shine: color-mix(in srgb, var(--line) 25%, #{$dialog-surface-dark});
|
||||
--pf-hover: color-mix(in srgb, var(--accent) 12%, #{$dialog-surface-dark});
|
||||
--pf-modal-backdrop: #{$dialog-backdrop-dark};
|
||||
}
|
||||
|
||||
:root {
|
||||
@include pagefind-tokens;
|
||||
}
|
||||
|
||||
[data-theme="dark"],
|
||||
html[data-pf-theme="dark"] {
|
||||
@include pagefind-tokens;
|
||||
@include pagefind-dialog-dark-tokens;
|
||||
|
||||
--pf-error-bg: color-mix(in srgb, #dc2626 18%, var(--paper));
|
||||
--pf-error-text: #fca5a5;
|
||||
--pf-error-text-secondary: #fecaca;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) {
|
||||
@include pagefind-tokens;
|
||||
@include pagefind-dialog-dark-tokens;
|
||||
|
||||
--pf-error-bg: color-mix(in srgb, #dc2626 18%, var(--paper));
|
||||
--pf-error-text: #fca5a5;
|
||||
--pf-error-text-secondary: #fecaca;
|
||||
}
|
||||
}
|
||||
|
||||
// Match Pagefind's specificity so modal styles beat component defaults.
|
||||
@mixin pf-host {
|
||||
:is(*, #\#):is(*, #\#):is(*, #\#) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
pagefind-modal {
|
||||
--pf-background: #{$dialog-surface-light};
|
||||
--pf-modal-backdrop: #{$dialog-backdrop-light};
|
||||
}
|
||||
|
||||
[data-theme="dark"] pagefind-modal,
|
||||
html[data-pf-theme="dark"] pagefind-modal {
|
||||
--pf-background: #{$dialog-surface-dark};
|
||||
--pf-modal-backdrop: #{$dialog-backdrop-dark};
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) pagefind-modal {
|
||||
--pf-background: #{$dialog-surface-dark};
|
||||
--pf-modal-backdrop: #{$dialog-backdrop-dark};
|
||||
}
|
||||
}
|
||||
|
||||
@include pf-host {
|
||||
dialog.pf-modal {
|
||||
background-color: #{$dialog-surface-light};
|
||||
color: var(--ink);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 0;
|
||||
box-shadow: 0 18px 48px var(--shadow-soft);
|
||||
isolation: isolate;
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
|
||||
dialog.pf-modal::backdrop {
|
||||
background: #{$dialog-backdrop-light};
|
||||
}
|
||||
|
||||
pagefind-modal-header,
|
||||
.pf-modal-header,
|
||||
pagefind-modal-body,
|
||||
.pf-modal-body,
|
||||
pagefind-modal-footer,
|
||||
.pf-modal-footer {
|
||||
background-color: #{$dialog-surface-light};
|
||||
}
|
||||
|
||||
pagefind-modal-header .pf-input,
|
||||
.pf-modal-header .pf-input {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.pf-modal-close {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
color: var(--muted);
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.pf-modal-close:hover,
|
||||
.pf-modal-close:focus-visible {
|
||||
color: var(--ink);
|
||||
background: var(--paper);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
@include pf-host {
|
||||
dialog.pf-modal {
|
||||
background-color: #{$dialog-surface-dark};
|
||||
}
|
||||
|
||||
dialog.pf-modal::backdrop {
|
||||
background: #{$dialog-backdrop-dark};
|
||||
}
|
||||
|
||||
pagefind-modal-header,
|
||||
.pf-modal-header,
|
||||
pagefind-modal-body,
|
||||
.pf-modal-body,
|
||||
pagefind-modal-footer,
|
||||
.pf-modal-footer {
|
||||
background-color: #{$dialog-surface-dark};
|
||||
}
|
||||
|
||||
.pf-modal-close:hover,
|
||||
.pf-modal-close:focus-visible {
|
||||
background: color-mix(in srgb, var(--ink) 8%, #{$dialog-surface-dark});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) {
|
||||
@include pf-host {
|
||||
dialog.pf-modal {
|
||||
background-color: #{$dialog-surface-dark};
|
||||
}
|
||||
|
||||
dialog.pf-modal::backdrop {
|
||||
background: #{$dialog-backdrop-dark};
|
||||
}
|
||||
|
||||
pagefind-modal-header,
|
||||
.pf-modal-header,
|
||||
pagefind-modal-body,
|
||||
.pf-modal-body,
|
||||
pagefind-modal-footer,
|
||||
.pf-modal-footer {
|
||||
background-color: #{$dialog-surface-dark};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Header compact trigger — align with .theme-toggle
|
||||
.site-header__search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
--pf-background: transparent;
|
||||
--pf-border: var(--line);
|
||||
--pf-border-focus: color-mix(in srgb, var(--accent) 35%, var(--line));
|
||||
--pf-hover: color-mix(in srgb, var(--accent) 10%, transparent);
|
||||
--pf-text-muted: var(--soft-ink);
|
||||
--pf-text-secondary: var(--soft-ink);
|
||||
--pf-input-height: 36px;
|
||||
--pf-border-radius: 8px;
|
||||
--pf-font: var(--mono);
|
||||
}
|
||||
|
||||
.search-page {
|
||||
padding-block: clamp(2.5rem, 6vh, 4rem) clamp(3rem, 8vh, 5rem);
|
||||
}
|
||||
|
||||
.search-page__layout {
|
||||
max-width: 42rem;
|
||||
}
|
||||
|
||||
.search-page__title {
|
||||
margin-top: 0.35rem;
|
||||
}
|
||||
|
||||
.search-page__lead {
|
||||
margin: 0.75rem 0 2rem;
|
||||
color: var(--soft-ink);
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.search-page__ui {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
|
||||
--pf-background: #{$dialog-surface-light};
|
||||
}
|
||||
|
||||
[data-theme="dark"] .search-page__ui {
|
||||
--pf-background: #{$dialog-surface-dark};
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) .search-page__ui {
|
||||
--pf-background: #{$dialog-surface-dark};
|
||||
}
|
||||
}
|
||||
|
|
@ -10,4 +10,5 @@
|
|||
@import "about-manifest";
|
||||
@import "book";
|
||||
@import "catalog";
|
||||
@import "search";
|
||||
@import "math-widget";
|
||||
|
|
|
|||
|
|
@ -20,7 +20,15 @@
|
|||
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;700&family=Instrument+Serif:ital@0;1&family=Inter:wght@300;400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="{{ get_url(path='pagefind/pagefind-component-ui.css', trailing_slash=false) | safe }}"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="stylesheet" href="{{ get_url(path='main.css', trailing_slash=false) | safe }}" />
|
||||
<script
|
||||
src="{{ get_url(path='pagefind/pagefind-component-ui.js', trailing_slash=false) | safe }}"
|
||||
type="module"
|
||||
></script>
|
||||
{% block extra_head %}{% endblock extra_head %}
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -30,6 +38,7 @@
|
|||
"homeUrl": {{ get_url(path="/") | json_encode() | safe }},
|
||||
"browseUrl": {{ config.extra.browse_url | json_encode() | safe }},
|
||||
"aboutUrl": {{ config.extra.about_url | json_encode() | safe }},
|
||||
"searchUrl": {{ config.extra.search_url | json_encode() | safe }},
|
||||
"githubUrl": {{ config.extra.github_url | json_encode() | safe }}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -54,6 +63,7 @@
|
|||
</footer>
|
||||
|
||||
<script type="module" src="{{ get_url(path='js/bundle.js', trailing_slash=false) | safe }}"></script>
|
||||
<pagefind-modal reset-on-close></pagefind-modal>
|
||||
{% block extra_scripts %}{% endblock extra_scripts %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
<span aria-current="page">{{ section.title }}</span>
|
||||
</nav>
|
||||
|
||||
<article class="book-chapter">
|
||||
<article class="book-chapter" data-pagefind-body>
|
||||
<header class="book-chapter__header">
|
||||
<div class="book-chapter__header-main">
|
||||
<p class="section-kicker">Number & Algebra</p>
|
||||
|
|
@ -31,12 +31,12 @@
|
|||
<p class="book-chapter__lead">{{ section.description }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="copy-page-button" class="book-chapter__actions"></div>
|
||||
<div id="copy-page-button" class="book-chapter__actions" data-pagefind-ignore></div>
|
||||
</header>
|
||||
|
||||
{% if section.extra.chapter_id == "quadratic-equations" %}
|
||||
{% include "partials/math/quadratic-equations-content.html" %}
|
||||
<div id="quadratic-explorer" class="math-widget-mount"></div>
|
||||
<div id="quadratic-explorer" class="math-widget-mount" data-pagefind-ignore></div>
|
||||
{% endif %}
|
||||
</article>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -93,9 +93,10 @@
|
|||
<p class="section-kicker">Planet</p>
|
||||
<h2 class="section-title">Open textbooks are gentler on the Earth</h2>
|
||||
<p>
|
||||
Core curriculum changes slowly. Newton’s laws, the structure of a cell, and how fractions work do not
|
||||
need a new AI run every September. Regenerating the same chapter thousands of times burns electricity
|
||||
in data centers — for text that could have been written once and shared openly.
|
||||
Everybody learns the same basics. Everybody prompts AI the same thing. Core curriculum changes slowly.
|
||||
Newton’s laws, the structure of a cell, and how fractions work do not need a new AI run every September.
|
||||
Regenerating the same chapter thousands of times burns electricity in data centers — for text that could
|
||||
have been written once and shared openly.
|
||||
</p>
|
||||
<p>
|
||||
<strong>One carefully reviewed book</strong>, published on the web for everyone, means less redundant
|
||||
|
|
|
|||
22
themes/openfreebooks/templates/search.html
Normal file
22
themes/openfreebooks/templates/search.html
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ page.title }} | {{ config.extra.site_title }}{% endblock title %}
|
||||
{% block meta_description %}
|
||||
<meta name="description" content="Search Open Free Books chapters and site pages." />
|
||||
{% endblock meta_description %}
|
||||
|
||||
{% block content %}
|
||||
<section class="search-page">
|
||||
<div class="container search-page__layout">
|
||||
<p class="section-kicker">Search</p>
|
||||
<h1 class="section-title search-page__title">{{ page.title }}</h1>
|
||||
<p class="search-page__lead">Find chapters and pages across Open Free Books.</p>
|
||||
<div class="search-page__ui">
|
||||
<pagefind-input autofocus placeholder="Search books and chapters…"></pagefind-input>
|
||||
<pagefind-summary></pagefind-summary>
|
||||
<pagefind-results></pagefind-results>
|
||||
<pagefind-keyboard-hints></pagefind-keyboard-hints>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock content %}
|
||||
|
|
@ -23,3 +23,4 @@ browse_url = "/catalog/"
|
|||
about_url = "/about/"
|
||||
credits_url = "/credits/"
|
||||
contributing_url = "/contributing/"
|
||||
search_url = "/search/"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue