layouts/_default/search.html (178 lines of code) (raw):

{{ define "main" }} <!-- Source: https://makewithhugo.com/add-search-to-a-hugo-site/ --> <main> <div id="search-results"></div> <div class="search-loading">Loading...</div> <script id="search-result-template" type="text/x-js-template"> <div id="summary-${key}"> <h3><a href="${link}">${title}</a></h3> <p class="pb-0 mb-0">${snippet}</p> <p class="opacity-50 pt-0 mt-0"><small>Score: ${score}</small></p> <p> <small> ${ isset tags }Tags: ${tags}<br>${ end } </small> </p> </div> </script> <script src="/js/fuse.min.js" type="text/javascript" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="/js/mark.min.js" type="text/javascript" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script type="text/javascript"> (function() { const summaryInclude = 180; // See: https://fusejs.io/api/options.html const fuseOptions = { // Indicates whether comparisons should be case sensitive. isCaseSensitive: false, // Whether the score should be included in the result set. // A score of 0 indicates a perfect match, while a score of 1 indicates a complete mismatch. includeScore: true, // Whether the matches should be included in the result set. // When true, each record in the result set will include the indices of the matched characters. // These can consequently be used for highlighting purposes. includeMatches: true, // Only the matches whose length exceeds this value will be returned. // (For instance, if you want to ignore single character matches in the result, set it to 2). minMatchCharLength: 2, // Whether to sort the result list, by score. shouldSort: true, // List of keys that will be searched. // This supports nested paths, weighted search, searching in arrays of strings and objects. keys: [ {name: "title", weight: 0.8}, {name: "contents", weight: 0.7}, // {name: "tags", weight: 0.95}, // {name: "categories", weight: 0.05} ], // --- Fuzzy Matching Options // Determines approximately where in the text is the pattern expected to be found. location: 0, // At what point does the match algorithm give up. // A threshold of 0.0 requires a perfect match (of both letters and location), // a threshold of 1.0 would match anything. threshold: 0.2, // Determines how close the match must be to the fuzzy location (specified by location). // An exact letter match which is distance characters away from the fuzzy location would // score as a complete mismatch. A distance of 0 requires the match be at the exact // location specified. A distance of 1000 would require a perfect match to be within 800 // characters of the location to be found using a threshold of 0.8. distance: 100, // When true, search will ignore location and distance, so it won't matter where in // the string the pattern appears. // // NOTE: These settings are used to calculate the Fuzziness Score (Bitap algorithm) in Fuse.js. // It calculates threshold (default 0.6) * distance (default (100), which gives 60 by // default, meaning it will search for the query-term within 60 characters from the location // (default 0). Since Jena docs may have very long text that includes the query term anywhere // we disable it with ignoreLocation: true. // For more: https://fusejs.io/concepts/scoring-theory.html#scoring-theory ignoreLocation: true, }; // ============================= // Search // ============================= const inputBox = document.getElementById('search-query'); if (inputBox !== null) { const searchQuery = param("q"); if (searchQuery) { inputBox.value = searchQuery || ""; executeSearch(searchQuery, false); } else { document.getElementById('search-results').innerHTML = '<p class="search-results-empty">Please enter a word or phrase above, or see <a href="/tags/">all tags</a>.</p>'; } } function executeSearch(searchQuery) { show(document.querySelector('.search-loading')); fetch('/index.json').then(function (response) { if (response.status !== 200) { console.log('Looks like there was a problem. Status Code: ' + response.status); return; } // Examine the text in the response response.json().then(function (pages) { const fuse = new Fuse(pages, fuseOptions); const result = fuse.search(searchQuery); if (result.length > 0) { populateResults(result); } else { document.getElementById('search-results').innerHTML = '<p class=\"search-results-empty\">No matches found</p>'; } hide(document.querySelector('.search-loading')); }) .catch(function (err) { console.log('Fetch Error :-S', err); }); }); } function populateResults(results) { const searchQuery = document.getElementById("search-query").value; const searchResults = document.getElementById("search-results"); searchResults.innerHTML += `<p>Search returned ${results.length} matches.</p>`; // pull template from hugo template definition const templateDefinition = document.getElementById("search-result-template").innerHTML; results.forEach(function (value, key) { const contents = value.item.contents; let snippet = ""; const snippetHighlights = []; snippetHighlights.push(searchQuery); snippet = contents.substring(0, summaryInclude * 2) + '&hellip;'; //replace values let tags = "" if (value.item.tags) { value.item.tags.forEach(function (element) { tags = tags + "<a href='/tags/" + element + "'>" + "#" + element + "</a> " }); } const output = render(templateDefinition, { key: key, title: value.item.title, link: value.item.permalink, tags: tags, categories: value.item.categories, snippet: snippet, score: value.score }); searchResults.innerHTML += output; snippetHighlights.forEach(function (snipvalue, snipkey) { const instance = new Mark(document.getElementById('summary-' + key)); instance.mark(snipvalue); }); }); } function render(templateString, data) { let conditionalMatches, conditionalPattern, copy; conditionalPattern = /\$\{\s*isset ([a-zA-Z]*) \s*\}(.*)\$\{\s*end\s*}/g; //since loop below depends on re.lastInxdex, we use a copy to capture any manipulations whilst inside the loop copy = templateString; while ((conditionalMatches = conditionalPattern.exec(templateString)) !== null) { if (data[conditionalMatches[1]]) { //valid key, remove conditionals, leave contents. copy = copy.replace(conditionalMatches[0], conditionalMatches[2]); } else { //not valid, remove entire section copy = copy.replace(conditionalMatches[0], ''); } } templateString = copy; //now any conditionals removed we can do simple substitution let key, find, re; for (key in data) { find = '\\$\\{\\s*' + key + '\\s*\\}'; re = new RegExp(find, 'g'); templateString = templateString.replace(re, data[key]); } return templateString; } // Helper Functions function show(elem) { elem.style.display = 'block'; } function hide(elem) { elem.style.display = 'none'; } function param(name) { return decodeURIComponent((location.search.split(name + '=')[1] || '').split('&')[0]).replace(/\+/g, ' '); } })() </script> </main> {{ end }}