175 lines
6.6 KiB
JavaScript
175 lines
6.6 KiB
JavaScript
// Credits to search implementation: https://gist.github.com/cmod/5410eae147e4318164258742dd053993
|
|
|
|
var fuse; // holds our search engine
|
|
var searchVisible = false;
|
|
var firstRun = true; // allow us to delay loading json data unless search activated
|
|
var list = document.querySelector('.search-list'); // targets the <ul>
|
|
var first = list.firstChild; // first child of search list
|
|
var last = list.lastChild; // last child of search list
|
|
var maininput = document.querySelector('.search-ui input'); // input box for search
|
|
var searchResultsHeading = document.querySelector('.search-results'); // input box for search
|
|
var noResults = document.querySelector('.no-results'); // input box for search
|
|
var resultsAvailable = false; // Did we get any search results?
|
|
|
|
// ==========================================
|
|
// The main keyboard event listener running the show
|
|
//
|
|
document.querySelector('.open-search').addEventListener('click', openSearch);
|
|
document.querySelector('.close-search').addEventListener('click', closeSearch);
|
|
|
|
function closeSearch() {
|
|
document.querySelector('.search-ui').classList.add("hidden");
|
|
document.activeElement.blur(); // remove focus from search box
|
|
searchVisible = false; // search not visible
|
|
searchResultsHeading.classList.add('hidden');
|
|
}
|
|
|
|
function openSearch() {
|
|
// Load json search index if first time invoking search
|
|
// Means we don't load json unless searches are going to happen; keep user payload small unless needed
|
|
if (firstRun) {
|
|
loadSearch(); // loads our json data and builds fuse.js search index
|
|
firstRun = false; // let's never do this again
|
|
}
|
|
|
|
//Close the mobile menu when search is click.
|
|
mobileMenu.classList.toggle('hidden');
|
|
|
|
// Toggle visibility of search box
|
|
if (!searchVisible) {
|
|
document.querySelector('.search-ui').classList.remove("hidden");
|
|
document.querySelector('.search-ui input').focus(); // put focus in input box so you can just start typing
|
|
searchVisible = true; // search visible
|
|
}
|
|
}
|
|
|
|
document.addEventListener('keydown', function (event) {
|
|
|
|
if (event.metaKey && event.which === 191) {
|
|
openSearch()
|
|
}
|
|
|
|
// Allow ESC (27) to close search box
|
|
if (event.keyCode == 27) {
|
|
if (searchVisible) {
|
|
document.querySelector('.search-ui').classList.add("hidden");
|
|
document.activeElement.blur();
|
|
searchVisible = false;
|
|
searchResultsHeading.classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
// DOWN (40) arrow
|
|
if (event.keyCode == 40) {
|
|
if (searchVisible && resultsAvailable) {
|
|
console.log("down");
|
|
event.preventDefault(); // stop window from scrolling
|
|
if (document.activeElement == maininput) { first.focus(); } // if the currently focused element is the main input --> focus the first <li>
|
|
else if (document.activeElement == last) { last.focus(); } // if we're at the bottom, stay there
|
|
else { document.activeElement.parentElement.nextSibling.firstElementChild.focus(); } // otherwise select the next search result
|
|
}
|
|
}
|
|
|
|
// UP (38) arrow
|
|
if (event.keyCode == 38) {
|
|
if (searchVisible && resultsAvailable) {
|
|
event.preventDefault(); // stop window from scrolling
|
|
if (document.activeElement == maininput) { maininput.focus(); } // If we're in the input box, do nothing
|
|
else if (document.activeElement == first) { maininput.focus(); } // If we're at the first item, go to input box
|
|
else { document.activeElement.parentElement.previousSibling.firstElementChild.focus(); } // Otherwise, select the search result above the current active one
|
|
}
|
|
}
|
|
})
|
|
|
|
|
|
// ==========================================
|
|
// execute search as each character is typed
|
|
//
|
|
document.querySelector('.search-ui input').onkeyup = function (e) {
|
|
executeSearch(this.value);
|
|
}
|
|
|
|
|
|
// ==========================================
|
|
// fetch some json without jquery
|
|
//
|
|
function fetchJSONFile(path, callback) {
|
|
var httpRequest = new XMLHttpRequest();
|
|
httpRequest.onreadystatechange = function () {
|
|
if (httpRequest.readyState === 4) {
|
|
if (httpRequest.status === 200) {
|
|
var data = JSON.parse(httpRequest.responseText);
|
|
if (callback) callback(data);
|
|
}
|
|
}
|
|
};
|
|
httpRequest.open('GET', path);
|
|
httpRequest.send();
|
|
}
|
|
|
|
|
|
// ==========================================
|
|
// load our search index, only executed once
|
|
// on first call of search box (CMD-/)
|
|
//
|
|
function loadSearch() {
|
|
const lang = document.querySelector('head > meta[name="lang"]')?.getAttribute?.('content')
|
|
fetchJSONFile(`${lang ? "/" + lang : ""}/index.json`, function (data) {
|
|
|
|
var options = { // fuse.js options; check fuse.js website for details
|
|
shouldSort: true,
|
|
location: 0,
|
|
distance: 100,
|
|
threshold: 0.4,
|
|
minMatchCharLength: 2,
|
|
keys: [
|
|
'title',
|
|
'permalink',
|
|
'contents'
|
|
]
|
|
};
|
|
fuse = new Fuse(data, options); // build the index from the json file
|
|
});
|
|
}
|
|
|
|
|
|
// ==========================================
|
|
// using the index we loaded on CMD-/, run
|
|
// a search query (for "term") every time a letter is typed
|
|
// in the search box
|
|
//
|
|
function executeSearch(term) {
|
|
let results = fuse.search(term); // the actual query being run using fuse.js
|
|
let searchitems = ''; // our results bucket
|
|
|
|
if (results.length === 0) { // no results based on what was typed into the input box
|
|
resultsAvailable = false;
|
|
searchitems = '';
|
|
if (term !== "") {
|
|
noResults.classList.remove('hidden')
|
|
} else {
|
|
noResults.classList.add('hidden')
|
|
}
|
|
} else { // build our html
|
|
noResults.classList.add('hidden')
|
|
if (term !== "") {
|
|
searchResultsHeading.classList.remove('hidden');
|
|
}
|
|
|
|
for (let item in results.slice(0, 5)) { // only show first 5 results
|
|
const title = '<div class="text-2xl mb-2 font-bold">' + results[item].item.title + '</div>';
|
|
const date = results[item].item.date ? '<div><em class="px-4">' + new Date(results[item].item.date).toUTCString().substring(0, 16) + '</em></div>' : '';
|
|
const contents = '<div class="prose px-4">' + results[item].item.contents + '</div>';
|
|
|
|
searchitems = searchitems + '<li><a class="block mb-2 px-4 py-2 rounded pb-2 border-b border-gray-200 dark:border-gray-600 focus:bg-gray-100 dark:focus:bg-gray-700 focus:outline-none" href="' + results[item].item.permalink + '" tabindex="0">' + title + '</a>' + date + contents + '</li>';
|
|
}
|
|
resultsAvailable = true;
|
|
}
|
|
|
|
list.innerHTML = searchitems;
|
|
if (results.length > 0) {
|
|
first = list.firstChild.firstElementChild; // first result container — used for checking against keyboard up/down location
|
|
last = list.lastChild.firstElementChild; // last result container — used for checking against keyboard up/down location
|
|
}
|
|
}
|