Search json index (#1998)

* search: Add support for a JSON index

* docs: Document JSON index for search

* docs: Use lazy-loaded JSON index

* Add elasticlunr prefix to search engine format configuration

This will be useful if support for more search libraries are added in the future
This commit is contained in:
Sosthene 2022-10-30 21:55:47 +01:00 committed by Vincent Prouillet
parent 291c93e4ba
commit 7000f787b3
8 changed files with 73 additions and 26 deletions

View file

@ -1,5 +1,18 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum IndexFormat {
ElasticlunrJson,
ElasticlunrJavascript,
}
impl Default for IndexFormat {
fn default() -> IndexFormat {
IndexFormat::ElasticlunrJavascript
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct Search {
@ -15,6 +28,8 @@ pub struct Search {
pub include_description: bool,
/// Include the path of the page in the search index. `false` by default.
pub include_path: bool,
/// Foramt of the search index to be produced. Javascript by default
pub index_format: IndexFormat,
}
impl Default for Search {
@ -25,6 +40,7 @@ impl Default for Search {
include_description: false,
include_path: false,
truncate_content_length: None,
index_format: Default::default(),
}
}
}

View file

@ -5,8 +5,13 @@ mod theme;
use std::path::Path;
pub use crate::config::{
languages::LanguageOptions, link_checker::LinkChecker, link_checker::LinkCheckerLevel,
search::Search, slugify::Slugify, taxonomies::TaxonomyConfig, Config,
languages::LanguageOptions,
link_checker::LinkChecker,
link_checker::LinkCheckerLevel,
search::{IndexFormat, Search},
slugify::Slugify,
taxonomies::TaxonomyConfig,
Config,
};
use errors::Result;

View file

@ -15,7 +15,7 @@ use libs::rayon::prelude::*;
use libs::tera::{Context, Tera};
use libs::walkdir::{DirEntry, WalkDir};
use config::{get_config, Config};
use config::{get_config, Config, IndexFormat};
use content::{Library, Page, Paginator, Section, Taxonomy};
use errors::{anyhow, bail, Context as ErrorContext, Result};
use libs::relative_path::RelativePathBuf;
@ -764,32 +764,36 @@ impl Site {
Ok(())
}
fn index_for_lang(&self, lang: &str) -> Result<()> {
let index_json = search::build_index(
&self.config.default_language,
&self.library.read().unwrap(),
&self.config,
)?;
let (path, content) = match &self.config.search.index_format {
IndexFormat::ElasticlunrJson => {
let path = self.output_path.join(&format!("search_index.{}.json", lang));
(path, index_json)
}
IndexFormat::ElasticlunrJavascript => {
let path = self.output_path.join(&format!("search_index.{}.js", lang));
let content = format!("window.searchIndex = {};", index_json);
(path, content)
}
};
create_file(&path, &content)
}
pub fn build_search_index(&self) -> Result<()> {
ensure_directory_exists(&self.output_path)?;
// TODO: add those to the SITE_CONTENT map
// index first
create_file(
&self.output_path.join(&format!("search_index.{}.js", self.config.default_language)),
&format!(
"window.searchIndex = {};",
search::build_index(
&self.config.default_language,
&self.library.read().unwrap(),
&self.config
)?
),
)?;
self.index_for_lang(&self.config.default_language)?;
for (code, language) in &self.config.other_languages() {
if code != &self.config.default_language && language.build_search_index {
create_file(
&self.output_path.join(&format!("search_index.{}.js", &code)),
&format!(
"window.searchIndex = {};",
search::build_index(code, &self.library.read().unwrap(), &self.config)?
),
)?;
self.index_for_lang(code)?;
}
}

View file

@ -5,6 +5,9 @@ description = "Everything you need to make a static site engine in one binary."
compile_sass = true
build_search_index = true
[search]
index_format = "elasticlunr_json"
[markdown]
highlight_code = true
highlight_theme = "kronuz"

View file

@ -17,6 +17,9 @@ After `zola build` or `zola serve`, you should see two files in your public dire
- `search_index.${default_language}.js`: so `search_index.en.js` for a default setup
- `elasticlunr.min.js`
If you set `index_format = "elasticlunr_json"` in your `config.toml`, a `search_index.${default_language}.json` is generated
instead of the default `search_index.${default_language}.js`.
As each site will be different, Zola makes no assumptions about your search function and doesn't provide
the JavaScript/CSS code to do an actual search and display results. You can look at how this site
implements it to get an idea: [search.js](https://github.com/getzola/zola/tree/master/docs/static/search.js).

View file

@ -160,6 +160,10 @@ include_content = true
# become too big to load on the site. Defaults to not being set.
# truncate_content_length = 100
# Wether to produce the search index as a javascript file or as a JSON file
# Accepted value "elasticlunr_javascript" or "elasticlunr_json"
index_format = "elasticlunr_javascript"
# Optional translation object for the default language
# Example:
# default_language = "fr"

21
docs/static/search.js vendored
View file

@ -142,11 +142,24 @@ function initSearch() {
}
};
var currentTerm = "";
var index = elasticlunr.Index.load(window.searchIndex);
var index;
var initIndex = async function () {
if (index === undefined) {
index = fetch("/search_index.en.json")
.then(
async function(response) {
return await elasticlunr.Index.load(await response.json());
}
);
}
let res = await index;
return res;
}
$searchInput.addEventListener("keyup", debounce(function() {
$searchInput.addEventListener("keyup", debounce(async function() {
var term = $searchInput.value.trim();
if (term === currentTerm || !index) {
if (term === currentTerm) {
return;
}
$searchResults.style.display = term === "" ? "none" : "block";
@ -156,7 +169,7 @@ function initSearch() {
return;
}
var results = index.search(term, options);
var results = (await initIndex()).search(term, options);
if (results.length === 0) {
$searchResults.style.display = "none";
return;

View file

@ -103,7 +103,6 @@
</footer>
<script type="text/javascript" src="{{ get_url(path="elasticlunr.min.js") }}"></script>
<script type="text/javascript" src="{{ get_url(path="search_index.en.js") }}"></script>
<script type="text/javascript" src="{{ get_url(path="search.js") }}"></script>
</body>
</html>