// Copyright (c) 2021 Johannes Stoelp // file: search.js const formatResultPreview = (body, terms) => { let body_idx = 0; // Collect indexes into `body` that match any term. let matches = body.toLowerCase().split(" ").map(word => { let idx = body_idx; // Update global index into body. body_idx += word.length + 1 /* for the ' ' split char */; if (terms.some(term => word.startsWith(term))) { return { idx : idx, end : idx + word.length }; } else { return null; } }).filter(match => match !== null); // Format preview, by making each term bold and adding a short slice // following after the term. let preview = []; matches.forEach(match => { let end = Math.min(match.end + 40, body.length - 1); preview.push(`${body.substring(match.idx, match.end)}` + body.substring(match.end, end) + "..."); }); return preview.join(" "); } const formatResult = (result, terms) => { return "
" + `${result.doc.title}` + `
${formatResultPreview(result.doc.body, terms)}
` + "
"; } const evaluteSearchTerm = (term, ctx) => { // Nothing todo if current search term is the same as last one. if (term === ctx.last_search) { return; } // Empty search term, show list of blog pages again. if (term === "") { ctx.pages.style.display = "block"; ctx.results.style.display = "none"; return; } // Search term entered, show result list and hide list with blog posts. ctx.pages.style.display = "none"; ctx.results.style.display = "block"; // Perform actual search. let results = ctx.search_index.search(term, { bool: "AND", expand: true, // Also match if words starts with search query. fields: { title: {boost: 2}, body : {boost: 1}, } }); if (results.length !== 0) { // Update last search term. ctx.last_search = term; // Reset HTML of results items (previous search results). ctx.results_items.innerHTML = ""; // Generate HTML for search results. results.forEach(result => { let item = document.createElement("li"); item.innerHTML = formatResult(result, term.split(" ")); ctx.results_items.appendChild(item); }); } }; const debounce = (func, wait) => { let timeout = null; return () => { clearTimeout(timeout); timeout = setTimeout(func , wait); }; } window.onload = () => { let ctx = { // Get HTML DOM elements. results : document.getElementById("search-results"), results_items : document.getElementById("search-results-items"), pages : document.getElementById("pages"), // Load search index. search_index : elasticlunr.Index.load(window.searchIndex), // Last search term. last_search : null, }; if (ctx.search_index) { const input = document.getElementById("search"); input.onkeyup = debounce(() => evaluteSearchTerm(input.value.trim(), ctx), 200); } }