jaspr_search 0.1.0
jaspr_search: ^0.1.0 copied to clipboard
Client-side full-text search for static Jaspr sites — a search dialog component plus a build-time indexer, with no backend to query.
jaspr_search #
Client-side full-text search for static Jaspr sites built with jaspr_content — a search dialog component plus a build-time indexer. No backend, no crawler, no API key: the index is a JSON file your site already ships, fetched once and scored in the browser.
Extracted from two documentation sites (Zonai, Revali) that had independently built and then separately bug-fixed the same thing.
What you get #
SearchDialog— a@clientcomponent: a trigger button plus a modal dialog. Opens on click,⌘K/Ctrl+K, or/. Arrow keys move the selection,Enternavigates,Esccloses. Ships with default styles that respectjaspr_content's theme colors; override or opt out.jaspr_searchCLI /buildSearchIndex— walks acontent/directory of markdown files, splits each page on its headings, and produces the doc list that becomessearch-index.json.searchIndex— the ranking function both sides use: every query word must match somewhere in a section (AND, not OR), with score boosts for title/heading/URL matches and whole-phrase hits.
Usage #
1. Build the index #
For a site with no per-page grouping, the bundled CLI is enough:
dart run jaspr_search --content content --output web/search-index.json
Both flags default to those paths, so dart run jaspr_search alone works for
the common layout. Add --check for CI, so a content change that forgot to
regenerate the index fails the build instead of silently going stale:
dart run jaspr_search --check
Run dart run jaspr_search --help for the rest of the flags
(--max-section-length among them).
If your site groups pages under sections and wants that label shown on
each result, the CLI can't express it — groupFor is a Dart closure reaching
into the site's own navigation code, not something a flag can carry. Write a
tiny entrypoint instead:
// tool/build_search_index.dart
import 'dart:io';
import 'package:jaspr_search/builder.dart';
void main(List<String> args) async {
final build = await buildSearchIndex(
Directory('content'),
groupFor: (route) => sectionOf(route)?.title ?? '',
);
final output = File('web/search-index.json');
if (args.contains('--check')) {
if (!searchIndexIsCurrent(build, output)) {
stderr.writeln('web/search-index.json is stale. Run: dart run tool/build_search_index.dart');
exit(1);
}
stdout.writeln('Search index is up to date (${build.docs.length} pages).');
return;
}
writeSearchIndex(build, output);
stdout.writeln('Wrote ${output.path} — ${build.docs.length} pages, ${build.sectionCount} sections.');
}
See the doc comment on buildSearchIndex for the rest of the hooks (routeOf, titleFor, descriptionFor, compare).
Either way, run it before jaspr build / jaspr serve.
2. Render the dialog #
import 'package:jaspr_search/jaspr_search.dart';
// wherever your header lives
const SearchDialog()
Customize text, the index path, or the result caps through its constructor — see the doc comment on SearchDialog for the full list. Set includeDefaultStyles: false to supply your own CSS for the jaspr-search-* classes instead.
Why client-side #
Both source sites are static, deployed to GitHub Pages — there is nothing to query server-side, and one of them dropped an Algolia DocSearch integration for this (a crawler, an API key in the config, and results that lagged a deploy). The index is fetched once, on first use, so it costs nothing for readers who never search.