Skip to content
Open
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
5 changes: 5 additions & 0 deletions .github/workflows/build_and_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ jobs:
# Downloads to '/home/runner/work/docs/docs/html'
path: html

- name: Build search index
# Consumed by napari-search-widget.js, which merges this index with
# sibling napari.org sub-sites' indexes at query time.
run: npx --yes pagefind@1.5.2 --site html --exclude-selectors ".headerlink"

- name: Deploy Docs
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/main'))
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0
Expand Down
41 changes: 41 additions & 0 deletions docs/_static/napari-search-widget.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* napari brand overrides for Pagefind's default search UI.
Full variable list: https://pagefind.app/docs/ui/#customising-the-ui-css */
.napari-search {
position: relative;
--pagefind-ui-primary: #4d9de0;
--pagefind-ui-text: #23252f;
--pagefind-ui-background: #ffffff;
--pagefind-ui-border: #d9dbe1;
--pagefind-ui-tag: #e9ebf1;
--pagefind-ui-border-radius: 6px;
--pagefind-ui-border-width: 1px;
--pagefind-ui-font: inherit;
}

@media (prefers-color-scheme: dark) {
.napari-search {
--pagefind-ui-text: #f1f1f1;
--pagefind-ui-background: #1e1e21;
--pagefind-ui-border: #3c3e46;
--pagefind-ui-tag: #2c2e36;
}
}

/* Pagefind's stock UI leaves the results dropdown transparent, relying on
an opaque ancestor to back it — true when embedded in a themed navbar,
false when floating standalone (as on workshops), where it reads as a
broken translucent panel. Always give it its own solid backing. */
.napari-search .pagefind-ui__results-area {
position: absolute;
top: calc(100% + 0.5rem);
left: 0;
right: 0;
z-index: 1;
max-height: 70vh;
overflow-y: auto;
background: var(--pagefind-ui-background);
border: var(--pagefind-ui-border-width) solid var(--pagefind-ui-border);
border-radius: var(--pagefind-ui-border-radius);
box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.15);
padding: 1rem;
}
90 changes: 90 additions & 0 deletions docs/_static/napari-search-widget.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// napari-search-widget.js
//
// Drop-in replacement for a site's native search box. Mounts Pagefind's
// stock UI against this site's own Pagefind bundle, and merges in the
// Pagefind bundles of sibling napari.org sub-sites so results span all of
// them from one search box.
//
// Usage (append to a page, once at least one element matching data-mount
// exists and a Pagefind bundle already exists for this site):
//
// <div class="napari-search"></div>
// <script
// src="/_static/napari-search-widget.js"
// data-bundle-path="/dev/pagefind/"
// data-merge-paths="/workshops/pagefind/,/island-dispatch/pagefind/"
// data-mount=".napari-search"
// defer
// ></script>
//
// - data-bundle-path: root-relative path to *this* page's own Pagefind
// bundle directory (the folder containing pagefind-ui.js). Required.
// - data-merge-paths: comma-separated root-relative paths to sibling
// sites' Pagefind bundle directories to merge into results. Optional.
// - data-mount: CSS selector matching every element to mount a search UI
// into. Defaults to ".napari-search". Some themes render their navbar
// more than once per page (e.g. a desktop and a mobile variant), and
// this script tag may itself be included more than once as a result —
// every match gets its own independent PagefindUI instance, and each
// match/script pair is only ever initialized once.

(function () {
const thisScript = document.currentScript;
const bundlePath = thisScript.dataset.bundlePath;
const mergePaths = (thisScript.dataset.mergePaths || '')
.split(',')
.map((s) => s.trim())
.filter(Boolean);
const mountSelector = thisScript.dataset.mount || '.napari-search';
// Directory this very <script> was loaded from — used to find its
// sibling CSS file without assuming any particular site layout/prefix.
const ownDir = new URL('.', thisScript.src).href;

if (!bundlePath) {
console.error('napari-search-widget: data-bundle-path is required');
return;
}

function loadStylesheet(href) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
document.head.appendChild(link);
}

// Cached across multiple script instances on the same page, so the
// pagefind-ui bundle is only ever fetched once.
function loadPagefindUiOnce() {
if (!window.__napariSearchPagefindUiPromise) {
loadStylesheet(`${ownDir}napari-search-widget.css`);
loadStylesheet(`${bundlePath}pagefind-ui.css`);
window.__napariSearchPagefindUiPromise = new Promise((resolve, reject) => {
const s = document.createElement('script');
s.src = `${bundlePath}pagefind-ui.js`;
s.onload = resolve;
s.onerror = () => reject(new Error(`Failed to load ${s.src}`));
document.head.appendChild(s);
});
}
return window.__napariSearchPagefindUiPromise;
}

loadPagefindUiOnce().then(() => {
const mounts = document.querySelectorAll(mountSelector);
if (!mounts.length) {
console.error(`napari-search-widget: no element matches ${mountSelector}`);
return;
}
mounts.forEach((mount) => {
if (mount.dataset.napariSearchMounted) return;
mount.dataset.napariSearchMounted = 'true';
new PagefindUI({
element: mount,
showSubResults: true,
showImages: false,
excerptLength: 15,
mergeIndex: mergePaths.map((path) => ({ bundlePath: path })),
});
});
});
})();
16 changes: 16 additions & 0 deletions docs/_templates/search-button-field.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{# Overrides napari-sphinx-theme's (and pydata-sphinx-theme's) default
search-button-field navbar component (referenced via the theme's
navbar_persistent option). Mounts a Pagefind-based widget instead,
which also merges in results from sibling napari.org sub-sites (see
napari-search-widget.js). The theme renders its navbar more than once
per page (desktop/mobile variants), so this template — and therefore
this script tag — is included more than once; napari-search-widget.js
is written to handle that. #}
<div class="napari-search bd-search d-flex align-items-center"></div>
<script
src="{{ pathto('_static/napari-search-widget.js', 1) }}"
data-bundle-path="{{ pathto('', 1) }}pagefind/"
data-merge-paths="/workshops/pagefind/"
data-mount=".napari-search"
defer
></script>
3 changes: 3 additions & 0 deletions pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ name = "napari-docs"
platforms = ["win-64", "linux-64", "osx-arm64"] # Mac Intel support is not available
version = "0.1.0"

[system-requirements]
macos = "14.0"

[feature.base.dependencies]
python = "3.13.*"
make = "*"
Expand Down
Loading