Add search functionality across documentation pages

- Implemented a search button in the header of each documentation page.
- Added a search modal that allows users to input search queries.
- Integrated a JavaScript search feature that fetches and displays results from a new `pages-data.json` file.
- Each documentation page now includes a search overlay for improved navigation.
- Updated the main JavaScript file to handle search logic, including result highlighting and navigation.
- Created a `pages-data.json` file containing metadata for all documentation pages to facilitate search functionality.
This commit is contained in:
yhirose
2026-03-01 23:37:51 -05:00
parent cda9680cdc
commit 843ad379c7
31 changed files with 1154 additions and 0 deletions

View File

@@ -15,6 +15,17 @@ struct PageContext {
status: Option<String>,
}
/// Entry for pages-data.json used by client-side search.
#[derive(Debug, Serialize)]
struct PageDataEntry {
title: String,
url: String,
lang: String,
section: String,
/// Plain-text body with HTML tags stripped (truncated to ~500 chars).
body: String,
}
#[derive(Debug, Serialize, Clone)]
struct NavItem {
title: String,
@@ -62,6 +73,9 @@ pub fn build(src: &Path, out: &Path) -> Result<()> {
copy_dir_recursive(&static_dir, out)?;
}
// Collect page data entries for pages-data.json across all languages
let mut page_data_entries: Vec<PageDataEntry> = Vec::new();
// Build each language
for lang in &config.i18n.langs {
let pages_dir = src.join("pages").join(lang);
@@ -74,6 +88,17 @@ pub fn build(src: &Path, out: &Path) -> Result<()> {
let nav = build_nav(&pages);
for page in &pages {
// Collect search data for pages-data.json
let plain_body = strip_html_tags(&page.html_content);
let truncated_body: String = plain_body.chars().take(500).collect();
page_data_entries.push(PageDataEntry {
title: page.frontmatter.title.clone(),
url: page.url.clone(),
lang: lang.clone(),
section: page.section.clone(),
body: truncated_body,
});
let template_name = if page.section.is_empty() {
"portal.html"
} else {
@@ -136,6 +161,11 @@ pub fn build(src: &Path, out: &Path) -> Result<()> {
}
}
// Generate pages-data.json for client-side search
let pages_json = serde_json::to_string(&page_data_entries)
.context("Failed to serialize pages-data.json")?;
fs::write(out.join("pages-data.json"), pages_json)?;
// Generate root redirect
generate_root_redirect(out, &config)?;
@@ -392,3 +422,27 @@ fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<()> {
}
Ok(())
}
/// Strip HTML tags from a string and collapse whitespace into a single space,
/// producing a plain-text representation suitable for search indexing.
fn strip_html_tags(html: &str) -> String {
let mut result = String::with_capacity(html.len());
let mut in_tag = false;
for ch in html.chars() {
match ch {
'<' => in_tag = true,
'>' => {
in_tag = false;
// Insert a space to avoid words being glued across tags
result.push(' ');
}
_ if !in_tag => result.push(ch),
_ => {}
}
}
// Collapse whitespace
let collapsed: String = result.split_whitespace().collect::<Vec<_>>().join(" ");
collapsed
}