2023-01-09 17:53:13 +01:00

230 lines
9.0 KiB
Plaintext

<script>
document.addEventListener('DOMContentLoaded', init, false);
document.addEventListener('DOMContentLoaded', setCorrectShortcut, false);
window.toggleSearch=function(){
if(document.getElementById('globalsearch').classList.contains('active')){
document.getElementById('globalsearch').classList.remove('active');
}else{
document.getElementById('globalsearch').classList.add('active');
document.getElementById('term').focus();
}
}
window.toggleTagSearch=function(evt) {
console.log(evt.textContent);
const term = evt.textContent;
if(term){
window.document.getElementById('term').value = term.trim();
window.toggleSearch();
window.search();
}
}
const loadingSvg = `
<svg width="100" height="100" viewBox="0 0 45 45" xmlns="http://www.w3.org/2000/svg" stroke="#fff">
<g fill="none" fill-rule="evenodd" transform="translate(1 1)" stroke-width="2">
<circle cx="22" cy="22" r="6" stroke-opacity="0">
<animate attributeName="r"
begin="1.5s" dur="3s"
values="6;22"
calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="stroke-opacity"
begin="1.5s" dur="3s"
values="1;0" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="stroke-width"
begin="1.5s" dur="3s"
values="2;0" calcMode="linear"
repeatCount="indefinite" />
</circle>
<circle cx="22" cy="22" r="6" stroke-opacity="0">
<animate attributeName="r"
begin="3s" dur="3s"
values="6;22"
calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="stroke-opacity"
begin="3s" dur="3s"
values="1;0" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="stroke-width"
begin="3s" dur="3s"
values="2;0" calcMode="linear"
repeatCount="indefinite" />
</circle>
<circle cx="22" cy="22" r="8">
<animate attributeName="r"
begin="0s" dur="1.5s"
values="6;1;2;3;4;5;6"
calcMode="linear"
repeatCount="indefinite" />
</circle>
</g>
</svg>`;
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
function setCorrectShortcut(){
if(navigator.platform.toUpperCase().indexOf('MAC')>=0){
document.querySelectorAll(".search-keys").forEach(x=>x.innerHTML = "⌘ + K");
}
}
async function init() {
//open searchmodal when ctrl + k is pressed, cmd + k on mac
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
toggleSearch();
}
if (e.key === 'Escape') {
document.getElementById('globalsearch').classList.remove('active');
}
//navigate search results with arrow keys
if (document.getElementById('globalsearch').classList.contains('active')) {
if (e.key === 'ArrowDown') {
e.preventDefault();
let active = document.querySelector('.searchresult.active');
if (active) {
active.classList.remove('active');
if (active.nextElementSibling) {
active.nextElementSibling.classList.add('active');
} else {
document.querySelector('.searchresult').classList.add('active');
}
} else {
document.querySelector('.searchresult').classList.add('active');
}
let currentActive = document.querySelector('.searchresult.active');
if (currentActive) {
currentActive .scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'start',
});
}
}
if (e.key === 'ArrowUp') {
e.preventDefault();
let active = document.querySelector('.searchresult.active');
if (active) {
active.classList.remove('active');
if (active.previousElementSibling) {
active.previousElementSibling.classList.add('active');
} else {
document.querySelectorAll('.searchresult').forEach((el) => {
if (!el.nextElementSibling) {
el.classList.add('active');
}
});
}
} else {
document.querySelectorAll('.searchresult').forEach((el) => {
if (el.nextElementSibling) {
el.classList.add('active');
}
});
}
let currentActive = document.querySelector('.searchresult.active');
if (currentActive) {
currentActive .scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'start',
});
}
}
if (e.key === 'Enter') {
e.preventDefault();
let active = document.querySelector('.searchresult.active');
if (active) {
window.location.href = active.querySelector("a").href;
}
}
}
});
const debouncedSearch = debounce(search, 200, false);
field = document.querySelector('#term');
field.addEventListener('keydown', (e) => {
if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') {
debouncedSearch();
}
});
resultsDiv = document.querySelector('#search-results');
const params = new URL(location.href).searchParams;
if (params.get('q')) {
field.setAttribute('value', params.get('q'));
toggleSearch();
search();
}
}
window.lastSearch = '';
async function search() {
let search = field
.value
.trim();
if (!search)
return;
if(search == lastSearch) return;
console.log(`search for ${search}`);
window.lastSearch = search;
resultsDiv.innerHTML = loadingSvg;
let searchRequest = await fetch(`/api/search?term=${encodeURIComponent(search)}`);
let results = await searchRequest.json();
let resultsHTML = '';
if (!results.length) {
resultsHTML += `<p>No results for "${search}"</p>`;
resultsDiv.innerHTML = resultsHTML;
return;
}
resultsHTML += '<div>';
// we need to add title, url from ref
results.forEach(r => {
if(r.tags && r.tags.length > 0){
resultsHTML += `<div class="searchresult">
<a class="search-link" href="${r.url}">${r.title}</a>
<div onclick="window.location='${r.url}'">
<div class="header-meta">
<div class="header-tags">
${r.tags.map(tag=>'<a class="tag" href="JavaScript:Void(0);">#'+tag+'</a>').join("")}
</div>
</div>
${r.content}
</div>
</div>`;
} else {
resultsHTML += `<div class="searchresult">
<a class="search-link" href="${r.url}">${r.title}</a>
<div onclick="window.location='${r.url}'">
${r.content}
</div>
</div>`;
}
});
resultsHTML += '</div>';
resultsDiv.innerHTML = resultsHTML;
}
</script>