From cff98ab68307c7e5d656b214091df7f9b4617709 Mon Sep 17 00:00:00 2001 From: Lukas Heumos Date: Sun, 23 Aug 2026 23:03:57 +0200 Subject: [PATCH 1/3] Add site-wide search Pagefind indexes the built HTML after hugo runs, which matters here because the ecosystem grid and the tutorial cards are rendered by layouts from remote JSON rather than living in content files, so anything reading the markdown would miss the 120 packages and 23 tutorials that people most want to find. The dialog opens from the nav, from / and from ctrl+k, and is driven with the arrow keys and enter. Results are filtered before display: Pagefind matches when an indexed word is a prefix of the search term, so "xylophone" otherwise came back matching "x" on seventeen pages. Closes #269 Signed-off-by: Lukas Heumos --- .github/workflows/gh-pages.yml | 3 + README.md | 10 +- assets/main.scss | 146 ++++++++++++++++++++++++++++ layouts/partials/footer.html | 2 +- layouts/partials/header.html | 31 +++++- netlify.toml | 5 + static/main.js | 170 +++++++++++++++++++++++++++++++++ 7 files changed, 364 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 8dc16698..adc3b555 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -25,6 +25,9 @@ jobs: - name: Build run: hugo --buildFuture --minify + - name: Build search index + run: npx --yes pagefind@1.5.2 --site public + - name: Deploy uses: peaceiris/actions-gh-pages@v4 if: github.ref == 'refs/heads/main' diff --git a/README.md b/README.md index 4460cee8..c13794c2 100644 --- a/README.md +++ b/README.md @@ -24,5 +24,13 @@ Look inside [`/layout`](/layout) for that. [Hugo Templating](https://gohugo.io/t To build the website (with hot reload), [install Hugo](https://gohugo.io/getting-started/installing/) and run ```sh -hugo server --buildFuture -D +hugo server -D +``` + +Site search is powered by [Pagefind](https://pagefind.app/), which indexes the built HTML +rather than the content files, so `hugo server` alone has no index and the search dialog +will say so. To try it locally, build and index the site, then serve `public/`: + +```sh +hugo && npx pagefind --site public && python3 -m http.server -d public ``` diff --git a/assets/main.scss b/assets/main.scss index be29950e..3d7ec806 100644 --- a/assets/main.scss +++ b/assets/main.scss @@ -2781,3 +2781,149 @@ $eco-hues: .page-item-author { font-weight: 600; } + +// ---- Global search ------------------------------------------------------ + +.nav-search-button { + display: flex; + align-items: center; + gap: 0.4rem; + background: none; + border: 0; + color: $greyheader; + font-size: inherit; + + &:hover { + color: $greyheaderhover; + } + + .nav-search-kbd { + padding: 0.05rem 0.4rem; + border: 1px solid $tilebg2; + border-radius: 0.25rem; + background-color: $tilebg4; + color: $greydesc; + font-size: 0.75rem; + line-height: 1.2; + + @media (max-width: 62rem) { + display: none; + } + } +} + +.search-overlay { + position: fixed; + inset: 0; + z-index: 1080; + display: flex; + justify-content: center; + padding: 4rem 1rem 1rem; + background-color: rgba(0, 0, 0, 0.45); + + &[hidden] { + display: none; + } + + @media (max-width: 50rem) { + padding: 1rem 0.5rem; + } +} + +.search-dialog { + display: flex; + flex-direction: column; + width: 100%; + max-width: 42rem; + max-height: 100%; + overflow: hidden; + border-radius: 0.75rem; + background-color: white; + box-shadow: 0 1.5rem 3rem rgba(0, 0, 0, 0.3); +} + +.search-field { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 1rem 1.25rem; + border-bottom: 1px solid $tilebg; + color: $greydesc; +} + +.search-input { + flex: 1; + border: 0; + outline: none; + font-size: 1.15rem; + color: $tiletext; + + &::-webkit-search-cancel-button { + display: none; + } +} + +.search-close { + padding: 0.1rem 0.45rem; + border: 1px solid $tilebg2; + border-radius: 0.25rem; + background-color: $tilebg4; + color: $greydesc; + font-size: 0.8rem; +} + +.search-status { + margin: 0; + padding: 0.6rem 1.25rem; + color: $greydesc; + font-size: 0.9rem; + + &:empty { + display: none; + } +} + +.search-results { + margin: 0; + padding: 0 0 0.5rem; + overflow-y: auto; + list-style: none; + + a { + display: block; + padding: 0.75rem 1.25rem; + color: inherit; + text-decoration: none; + + &:hover, + &:focus-visible, + &[aria-selected="true"] { + background-color: $tilebg4; + outline: none; + } + } + + .search-result-title { + color: $tiletext; + font-weight: 600; + } + + .search-result-url { + color: $mudatagreen; + font-size: 0.8rem; + } + + .search-result-excerpt { + display: block; + margin-top: 0.15rem; + color: $greydesc; + font-size: 0.9rem; + line-height: 1.35rem; + + mark { + padding: 0; + background-color: rgba($scviyellow, 0.35); + color: inherit; + } + } +} diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html index feff8d05..d4540935 100644 --- a/layouts/partials/footer.html +++ b/layouts/partials/footer.html @@ -1,4 +1,4 @@ -