diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 8dc1669..adc3b55 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/.prettierrc.cjs b/.prettierrc.cjs index 1085a96..1c2ef06 100644 --- a/.prettierrc.cjs +++ b/.prettierrc.cjs @@ -7,7 +7,7 @@ module.exports = { plugins: [require.resolve("prettier-plugin-go-template")], overrides: [ { - files: ["*.html"], + files: ["*.html", "layouts/**/*.json"], options: { parser: "go-template", }, diff --git a/README.md b/README.md index 4460cee..dd64748 100644 --- a/README.md +++ b/README.md @@ -24,5 +24,12 @@ 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 be29950..4faf62d 100644 --- a/assets/main.scss +++ b/assets/main.scss @@ -514,8 +514,12 @@ body { } } } + // The wide gap under the hero used to live on #packages, which put it + // between whatever section came after the demo and the packages. + #interactive-demo-section { + margin-bottom: 15%; + } #packages { - margin-top: 15%; #packages-list { .card { display: flex; @@ -2547,7 +2551,6 @@ $eco-hues: // Interactive UMAP Visualization Styles #interactive-demo-section { margin-top: 8%; - margin-bottom: 5%; } .interactive-container { @@ -2781,3 +2784,164 @@ $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-kind { + padding: 0.05rem 0.4rem; + border-radius: 0.25rem; + background-color: rgba($scviyellow, 0.25); + color: $tiletext2; + font-size: 0.75rem; + white-space: nowrap; + } + + .search-result-entity .search-result-title::after { + content: " ↗"; + color: $greydesc; + font-weight: 400; + } + + .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/config.toml b/config.toml index 434cfdc..b1912d0 100644 --- a/config.toml +++ b/config.toml @@ -12,6 +12,15 @@ enableRobotsTXT = true images = ["img/scverse-social-card.png"] description = "Foundational tools for omics data in the life sciences" +[outputs] +home = ["html", "rss", "entities"] + +[outputFormats.entities] +mediaType = "application/json" +baseName = "search-entities" +isPlainText = true +notAlternative = true + [markup.goldmark.renderer] unsafe= true diff --git a/layouts/index.entities.json b/layouts/index.entities.json new file mode 100644 index 0000000..6c4d813 --- /dev/null +++ b/layouts/index.entities.json @@ -0,0 +1,65 @@ +{{- $entities := slice -}} + +{{- with site.GetPage "/packages" -}} + {{- range (append (.Params.packages | default slice) (.Params.datastructures | default slice)) -}} + {{- $docs := .url -}} + {{- range .links -}} + {{- if hasPrefix (lower .text) "documentation" -}} + {{- $docs = .url -}} + {{- end -}} + {{- end -}} + {{- $entities = $entities | append (dict + "kind" "Core package" + "name" .name + "detail" .description + "url" $docs + "site" "/packages/#core-packages") + -}} + {{- end -}} +{{- end -}} + +{{- $registry := resources.GetRemote "https://scverse.org/ecosystem-packages/packages.json" -}} +{{- with $registry -}} + {{- range (transform.Unmarshal .Content) -}} + {{- $desc := trim (.description | markdownify | plainify | htmlUnescape | replaceRE `\s+` " ") " " -}} + {{- $entities = $entities | append (dict + "kind" "Ecosystem package" + "name" .name + "detail" $desc + "url" (or .documentation_home .project_home) + "site" "/packages/#ecosystem") + -}} + {{- end -}} +{{- end -}} + +{{- with site.GetPage "/people" -}} + {{- range .Params.teams -}} + {{- $team := .name -}} + {{- range .members -}} + {{- $detail := $team -}} + {{- with .role -}}{{- $detail = printf "%s · %s" $team . -}}{{- end -}} + {{- with .affiliation -}} + {{- $detail = printf "%s · %s" $detail . -}} + {{- end -}} + {{- $entities = $entities | append (dict + "kind" $team + "name" .name + "detail" $detail + "url" .url + "site" "/people/") + -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* The registry repeats the core packages and people sit on several teams. */ -}} +{{- $seen := slice -}} +{{- $unique := slice -}} +{{- range $entities -}} + {{- $key := lower .name -}} + {{- if not (in $seen $key) -}} + {{- $seen = $seen | append $key -}} + {{- $unique = $unique | append . -}} + {{- end -}} +{{- end -}} +{{- $unique | jsonify -}} diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html index feff8d0..d454093 100644 --- a/layouts/partials/footer.html +++ b/layouts/partials/footer.html @@ -1,4 +1,4 @@ -