blob: 191b5e04e081c252d6bdb9ef9673df20e3c39ffe [file] [log] [blame]
{{ 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 }}