invidious/assets/js/suggestions.js
2026-07-07 22:45:06 -07:00

117 lines
3.2 KiB
JavaScript

"use strict";
const searchbox = document.getElementById("searchbox");
const searchContainer = document.getElementById("search-container");
const suggestions = document.getElementById("suggestions");
let selectedSuggestion = -1;
let suggestionController;
let queuedSuggestions = 0;
function resetSuggestions() {
suggestions.textContent = "";
selectedSuggestion = -1;
}
searchbox?.addEventListener("input", function () {
// If there is already a request in progress,
// abort. The user has made another input,
// invalidating the old query.
if (suggestionController != undefined) {
suggestionController.abort();
}
// Only make a request for suggestions after
// there is a delay of at least 150 milliseconds
// between inputs.
queuedSuggestions++;
setTimeout(async function () {
queuedSuggestions--;
if (queuedSuggestions != 0) {
return;
}
// Only continue for queries of more than one
// character.
if (searchbox.value.length < 2) {
resetSuggestions();
return;
}
// Create the controller which will be
// used to abort this request if another
// is made before it finishes.
suggestionController = new AbortController();
const signal = suggestionController.signal;
// Make the request for suggestions.
try {
const resp = await fetch(
`/api/v1/search/suggestions?q=${encodeURIComponent(searchbox.value)}`,
{ signal },
);
const body = await resp.json();
// As a sanity check, ensure the
// suggestions match the query.
if (body.query != searchbox.value) {
return;
}
// Put the results into DOM.
resetSuggestions();
const results = body.suggestions;
for (let i = 0; i < results.length; i++) {
const s = document.createElement("a");
s.href = `/search?q=${encodeURIComponent(results[i])}`;
s.innerText = results[i];
suggestions.appendChild(s);
}
} catch (err) {
// Discard AbortError as it is expected.
if (err.name !== "AbortError") {
console.error(err);
}
}
}, 150);
});
searchbox?.addEventListener("keydown", function (e) {
const currentSuggestions = suggestions.children;
// Navigate suggestions by key.
switch (e.key) {
case "ArrowDown":
selectedSuggestion++;
e.preventDefault();
break;
case "ArrowUp":
selectedSuggestion--;
e.preventDefault();
break;
case "Enter":
if (selectedSuggestion != -1) {
currentSuggestions[selectedSuggestion].click();
e.preventDefault();
return;
}
break;
}
// Loop around if you go beyond the end
// or before -1 (the resting position).
if (selectedSuggestion >= currentSuggestions.length) {
selectedSuggestion = -1;
} else if (selectedSuggestion < -1) {
selectedSuggestion = currentSuggestions.length - 1;
}
// Remove .active class from all elements
// except the active one.
for (let i = 0; i < currentSuggestions.length; i++) {
if (i == selectedSuggestion) {
currentSuggestions[i].classList.add("active");
} else if (currentSuggestions[i].classList.contains("active")) {
currentSuggestions[i].classList.remove("active");
}
}
});