web-ansol.org/themes/academic/assets/js/academic-search.js

183 lines
6.1 KiB
JavaScript

/*************************************************
* Academic
* https://github.com/gcushen/hugo-academic
*
* In-built Fuse based search algorithm.
**************************************************/
/* ---------------------------------------------------------------------------
* Configuration.
* --------------------------------------------------------------------------- */
// Configure Fuse.
let fuseOptions = {
shouldSort: true,
includeMatches: true,
tokenize: true,
threshold: search_config.threshold, // Set to ~0.3 for parsing diacritics and CJK languages.
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: search_config.minLength, // Set to 1 for parsing CJK languages.
keys: [
{name:'title', weight:0.99}, /* 1.0 doesn't work o_O */
{name:'summary', weight:0.6},
{name:'authors', weight:0.5},
{name:'content', weight:0.2},
{name:'tags', weight:0.5},
{name:'categories', weight:0.5}
]
};
// Configure summary.
let summaryLength = 60;
/* ---------------------------------------------------------------------------
* Functions.
* --------------------------------------------------------------------------- */
// Get query from URI.
function getSearchQuery(name) {
return decodeURIComponent((location.search.split(name + '=')[1] || '').split('&')[0]).replace(/\+/g, ' ');
}
// Set query in URI without reloading the page.
function updateURL(url) {
if (history.replaceState) {
window.history.replaceState({path:url}, '', url);
}
}
// Pre-process new search query.
function initSearch(force, fuse) {
let query = document.querySelector("#search-query").value;
// If query deleted, clear results.
if (query.length < 1) {
document.querySelector('#search-hits').innerHTML = "";
}
// Check for timer event (enter key not pressed) and query less than minimum length required.
if (!force && query.length < fuseOptions.minMatchCharLength)
return;
// Do search.
document.querySelector('#search-hits').innerHTML = "";
searchAcademic(query, fuse);
let newURL = window.location.protocol + "//" + window.location.host + window.location.pathname + '?q=' + encodeURIComponent(query) + window.location.hash;
updateURL(newURL);
}
// Perform search.
function searchAcademic(query, fuse) {
let results = fuse.search(query);
// console.log({"results": results});
if (results.length > 0) {
document.querySelector('#search-hits').insertAdjacentHTML('beforeend', '<h3 class="mt-0">' + results.length + ' ' + i18n.results + '</h3>');
parseResults(query, results);
} else {
document.querySelector('#search-hits').insertAdjacentHTML('beforeend', '<div class="search-no-results">' + i18n.no_results + '</div>');
}
}
// Parse search results.
function parseResults(query, results) {
console.log(results);
results.forEach(function (value, key) {
let content_key = value.item.section;
let content = "";
let snippet = "";
let snippetHighlights = [];
// Show abstract in results for content types where the abstract is often the primary content.
if (["publication", "talk"].includes(content_key)) {
content = value.item.summary;
} else {
content = value.item.content;
}
if ( fuseOptions.tokenize ) {
snippetHighlights.push(query);
} else {
value.matches.forEach(function(matchValue, matchKey) {
if (matchValue.key == "content") {
let start = (matchValue.indices[0][0]-summaryLength>0) ? matchValue.indices[0][0]-summaryLength : 0;
let end = (matchValue.indices[0][1]+summaryLength<content.length) ? matchValue.indices[0][1]+summaryLength : content.length;
snippet += content.substring(start, end);
snippetHighlights.push(matchValue.value.substring(matchValue.indices[0][0], matchValue.indices[0][1]-matchValue.indices[0][0]+1));
}
});
}
if (snippet.length < 1) {
snippet += value.item.summary; // Alternative fallback: `content.substring(0, summaryLength*2);`
}
// Load template.
let template = document.querySelector('#search-hit-fuse-template').innerHTML;
// Localize content types.
if (content_key in content_type) {
content_key = content_type[content_key];
}
// Parse template.
let templateData = {
key: key,
title: value.item.title,
type: content_key,
relpermalink: value.item.relpermalink,
snippet: snippet,
};
let output = render(template, templateData);
document.querySelector('#search-hits').insertAdjacentHTML('beforeend', output);
// Highlight search terms in result.
snippetHighlights.forEach(function (hlValue, hlKey) {
new Mark(document.querySelector("#summary-" + key)).mark(hlValue);
});
});
}
function render(template, data) {
// Replace placeholders with their values.
let key, find, re;
for (key in data) {
find = '\\{\\{\\s*' + key + '\\s*\\}\\}'; // Expect placeholder in the form `{{x}}`.
re = new RegExp(find, 'g');
template = template.replace(re, data[key]);
}
return template;
}
/* ---------------------------------------------------------------------------
* Initialize.
* --------------------------------------------------------------------------- */
// If Academic's in-built search is enabled and Fuse loaded, then initialize it.
if (typeof Fuse === 'function') {
// Wait for Fuse to initialize.
fetch(search_config.indexURI).then((response) => response.json()).then(function (search_index) {
let fuse = new Fuse(search_index, fuseOptions);
// On page load, check for search query in URL.
if (query = getSearchQuery('q')) {
document.querySelector("body").classList.add('searching');
document.querySelector("#search-query").value = query;
document.querySelector("#search-query").focus();
initSearch(true, fuse);
}
// On search box key up, process query.
document.querySelector('#search-query').addEventListener('keyup', function (e) {
clearTimeout(this.dataset.searchTimer);
if (e.keyCode == 13) {
initSearch(true, fuse);
} else {
this.dataset.searchTimer = setTimeout(function () { initSearch(false, fuse); }, 250);
}
});
});
}