From 53c9ba85bf18e53a7d998c048d10e578b5a19983 Mon Sep 17 00:00:00 2001 From: johannst Date: Tue, 11 May 2021 01:48:40 +0200 Subject: added search input with simple search result preview --- static/search.js | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++ templates/index.html | 25 +++++++++++- 2 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 static/search.js diff --git a/static/search.js b/static/search.js new file mode 100644 index 0000000..02f1814 --- /dev/null +++ b/static/search.js @@ -0,0 +1,111 @@ +// 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 => { return word.startsWith(term); })) { + return { idx : idx, end : idx + word.length }; + } else { + return null; + } + }).filter(match => { return 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 evaluteSearch = (ctx) => { + let term = ctx.input.value.trim(); + + // 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 = new class { + constructor() { + // Get HTML DOM elements. + this.input = document.getElementById("search"); + this.results = document.getElementById("search-results"); + this.results_items = document.getElementById("search-results-items"); + this.pages = document.getElementById("pages"); + // Load search index. + this.search_index = elasticlunr.Index.load(window.searchIndex); + // Last search term. + this.last_search = ""; + } + }; + + if (ctx.search_index) { + ctx.input.onkeyup = debounce(() => { evaluteSearch(ctx); }, 200); + } +} diff --git a/templates/index.html b/templates/index.html index bc5cca2..ad0d413 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,3 +1,5 @@ + + @@ -44,14 +46,24 @@ {% endfor %} {% endblock sidebar_nav %} + + +
+ +
+ {% endblock sidebar %} -
+ +
+ {% block content %}
- {% for page in section.pages | reverse %} + + {% for page in section.pages %} +

@@ -66,6 +78,15 @@ {% endblock content %}

+ +
+
+
+ + + + + -- cgit v1.2.3