jaspr_search 0.2.0
jaspr_search: ^0.2.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 example #
jaspr_search has two halves that run in different places: a build-time
indexer (plain Dart, runs anywhere) and a client-side search dialog (a Jaspr
@client component, runs inside a Jaspr app). This directory has a runnable
example of the first and a code sample of the second.
1. Build an index — runnable #
build_index_example.dart writes a couple of
sample markdown pages to a temp directory, indexes them with
buildSearchIndex, and runs a query against the result with searchIndex —
the same ranking function the dialog uses in the browser. Run it directly:
dart run example/build_index_example.dart
For a real site, this is the shape of a tool/build_search_index.dart script:
import 'dart:io';
import 'package:jaspr_search/builder.dart';
void main(List<String> args) async {
final build = await buildSearchIndex(Directory('content'));
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);
}
return;
}
writeSearchIndex(build, output);
}
Or, for a site with no per-page grouping, skip the script entirely and use
the bundled CLI: dart run jaspr_search. Run dart run jaspr_search --help
for its flags.
2. Render the dialog — inside a Jaspr app #
SearchDialog is a @client component: it needs a Jaspr app to render into
and a browser to interact with, so it isn't something a standalone
example/ script can execute. In your site's header component:
import 'package:jaspr_search/jaspr_search.dart';
// wherever your header lives
const SearchDialog()
It fetches search-index.json (the file buildSearchIndex produces) the
first time a reader opens it, and scores it in the browser with the same
searchIndex function shown above. Its parameters are all String, bool
and int — that is a hard requirement of @client, which serializes every
constructor parameter across the server/client boundary.
Customising it #
For anything structural — your own trigger button, a translated empty state,
a result row with a different layout — use SearchDialogView instead. It is
the same dialog with no @client annotation, which is what lets it accept
components and callbacks. Build it inside a @client wrapper of your own:
import 'package:jaspr/dom.dart'; // div
import 'package:jaspr/jaspr.dart';
import 'package:jaspr_search/jaspr_search.dart';
@client
class DocsSearch extends StatelessComponent {
const DocsSearch({super.key});
@override
Component build(BuildContext context) => SearchDialogView(
emptyState: (pageCount) => Component.text('Search $pageCount pages.'),
resultContent: (hit, tokens) =>
div(classes: 'my-row', highlightQuery(hit.title, tokens)),
);
}
Then render const DocsSearch() where const SearchDialog() would have
gone. See the package README for
the full list of constructor options, the CSS classes and data-state hooks
for styling, and why the two components exist.