Search/static/js/autocomplete.js
2024-08-28 23:10:18 +02:00

188 lines
6.6 KiB
JavaScript

/**
* @source: ./script.js (originally from araa-search on Github)
*
* @licstart The following is the entire license notice for the
* JavaScript code in this page.
*
* Copyright (C) 2023 Extravi
*
* The JavaScript code in this page is free software: you can
* redistribute it and/or modify it under the terms of the GNU Affero
* General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* The code is distributed WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
*
* As additional permission under GNU Affero General Public License
* section 7, you may distribute non-source (e.g., minimized or compacted)
* forms of that code without the copy of the GNU Affero General Public
* License normally required by section 4, provided you include this
* license notice and a URL through which recipients can access the
* Corresponding Source.
*
* @licend The above is the entire license notice
* for the JavaScript code in this page.
*/
// Removes the 'Apply Settings' button for Javascript users,
// since changing any of the elements causes the settings to apply
// automatically.
let resultsSave = document.querySelector(".results-save");
if (resultsSave != null) {
resultsSave.style.display = "none";
}
const searchInput = document.getElementById('search-input');
const searchWrapper = document.querySelectorAll('.wrapper, .wrapper-results')[0];
const resultsWrapper = document.querySelector('.autocomplete');
// const clearSearch = document.querySelector("#clearSearch");
async function getSuggestions(query) {
try {
const params = new URLSearchParams({ "q": query }).toString();
const response = await fetch(`/suggestions?${params}`);
const data = await response.json();
return data[1]; // Return only the array of suggestion strings
} catch (error) {
console.error(error);
}
}
let currentIndex = -1; // Keep track of the currently selected suggestion
let results = [];
searchInput.addEventListener('input', async () => {
let input = searchInput.value;
if (input.length) {
results = await getSuggestions(input);
}
renderResults(results);
currentIndex = -1; // Reset index when we return new results
});
searchInput.addEventListener("focus", async () => {
let input = searchInput.value;
if (results.length === 0 && input.length != 0) {
results = await getSuggestions(input);
}
renderResults(results);
})
// clearSearch.style.visibility = "visible"; // Only show the clear search button for JS users.
// clearSearch.addEventListener("click", () => {
// searchInput.value = "";
// searchInput.focus();
// })
searchInput.addEventListener('keydown', (event) => {
if (event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'Tab') {
event.preventDefault(); // Prevent the default behavior, such as moving the cursor
// Find the currently selected suggestion element
const selectedSuggestion = resultsWrapper.querySelector('.selected');
if (selectedSuggestion) {
selectedSuggestion.classList.remove('selected'); // Deselect the currently selected suggestion
}
// Increment current index when ArrowUp is pressed otherwise hen Tab OR ArrowDown decrement
if (event.key === 'ArrowUp') {
currentIndex--;
} else {
currentIndex++;
}
// Wrap around the index if it goes out of bounds
if (currentIndex < 0) {
currentIndex = resultsWrapper.querySelectorAll('li').length - 1;
} else if (currentIndex >= resultsWrapper.querySelectorAll('li').length) {
currentIndex = 0;
}
// Select the new suggestion
resultsWrapper.querySelectorAll('li')[currentIndex].classList.add('selected');
// Update the value of the search input
searchInput.value = resultsWrapper.querySelectorAll('li')[currentIndex].textContent;
}
});
// Default to the currently selected type or fallback to 'text'
let selectedType = document.querySelector('.search-active')?.value || 'text';
// Function to render results
function renderResults(results) {
if (!results || !results.length || !searchInput.value) {
searchWrapper.classList.remove('show');
return;
}
let content = '';
results.forEach((item) => {
content += `<li>${item}</li>`;
});
if (searchInput.value) {
searchWrapper.classList.add('show');
}
resultsWrapper.innerHTML = `<ul>${content}</ul>`;
}
// Function to handle search input
searchInput.addEventListener('input', async () => {
let input = searchInput.value;
if (input.length) {
const results = await getSuggestions(input);
renderResults(results);
}
});
// Handle click events on the type buttons
const typeButtons = document.querySelectorAll('[name="t"]');
typeButtons.forEach(button => {
button.addEventListener('click', function() {
selectedType = this.value;
typeButtons.forEach(btn => btn.classList.remove('search-active'));
this.classList.add('search-active');
});
});
// Handle clicks on search results
resultsWrapper.addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
const query = event.target.textContent;
window.location.href = `/search?q=${encodeURIComponent(query)}&t=${encodeURIComponent(selectedType)}`;
}
});
document.addEventListener("keypress", (event) => {
if (document.activeElement == searchInput) {
// Allow the '/' character to be pressed when searchInput is active
} else if (document.querySelector(".calc") != null) {
// Do nothing if the calculator is available, so the division keybinding
// will still work
}
else if (event.key == "/") {
event.preventDefault();
searchInput.focus();
searchInput.selectionStart = searchInput.selectionEnd = searchInput.value.length;
}
})
// Add event listener to hide autocomplete suggestions when clicking outside of search-input or wrapper
document.addEventListener('click', (event) => {
// Check if the target of the event is the search-input or any of its ancestors
if (!searchInput.contains(event.target) && !searchWrapper.contains(event.target)) {
// Remove the show class from the search wrapper
searchWrapper.classList.remove('show');
}
});
// Update visual feedback for selected type on page load
document.addEventListener("DOMContentLoaded", () => {
const activeButton = document.querySelector(`[name="t"][value="${selectedType}"]`);
if (activeButton) {
typeButtons.forEach(btn => btn.classList.remove('search-active'));
activeButton.classList.add('search-active');
}
});