127 lines
4.8 KiB
JavaScript
127 lines
4.8 KiB
JavaScript
/*
|
|
This script fetches and caches search suggestions to reduce server requests with an LRU cache.
|
|
*/
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const searchInput = document.getElementById('search-input');
|
|
const searchWrapper = document.querySelector('.wrapper, .wrapper-results');
|
|
const resultsWrapper = document.querySelector('.autocomplete');
|
|
|
|
let suggestions = [];
|
|
let currentIndex = -1;
|
|
const cacheLimit = 10000; // This should take about 3.6 MB of memory
|
|
const suggestionCache = new Map(); // Map to store suggestions with LRU capability
|
|
|
|
// Fetch suggestions from server or cache
|
|
async function getSuggestions(query) {
|
|
if (suggestionCache.has(query)) {
|
|
// Move the recently accessed item to the end of Map to mark it as most recently used
|
|
const cachedResult = suggestionCache.get(query);
|
|
suggestionCache.delete(query);
|
|
suggestionCache.set(query, cachedResult);
|
|
return cachedResult;
|
|
}
|
|
try {
|
|
const response = await fetch(`/suggestions?q=${encodeURIComponent(query)}`);
|
|
const data = await response.json();
|
|
// Add result to cache and enforce cache limit
|
|
suggestionCache.set(query, data[1]);
|
|
if (suggestionCache.size > cacheLimit) {
|
|
// Remove the oldest (first) item in the Map
|
|
const firstKey = suggestionCache.keys().next().value;
|
|
suggestionCache.delete(firstKey);
|
|
}
|
|
return data[1];
|
|
} catch (error) {
|
|
console.error('Error fetching suggestions:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Function to render results
|
|
function renderSuggestions(results) {
|
|
if (!results || !results.length) {
|
|
searchWrapper.classList.remove('wrapper-searching');
|
|
resultsWrapper.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
let content = '';
|
|
results.forEach((item, index) => {
|
|
content += `<li data-index="${index}">${item}</li>`;
|
|
});
|
|
|
|
resultsWrapper.innerHTML = `<ul>${content}</ul>`;
|
|
searchWrapper.classList.add('wrapper-searching');
|
|
}
|
|
|
|
// Fetch suggestions when input is focused
|
|
searchInput.addEventListener('focus', async () => {
|
|
const query = searchInput.value.trim();
|
|
suggestions = await getSuggestions(query);
|
|
renderSuggestions(suggestions);
|
|
currentIndex = -1;
|
|
});
|
|
|
|
// Handle input event
|
|
searchInput.addEventListener('input', async () => {
|
|
const query = searchInput.value.trim();
|
|
if (query.length > 0) {
|
|
suggestions = await getSuggestions(query);
|
|
} else {
|
|
suggestions = [];
|
|
}
|
|
currentIndex = -1; // Reset index when new results come in
|
|
renderSuggestions(suggestions);
|
|
});
|
|
|
|
// Handle keydown events for navigation
|
|
searchInput.addEventListener('keydown', (event) => {
|
|
const items = resultsWrapper.querySelectorAll('li');
|
|
if (event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'Tab') {
|
|
event.preventDefault();
|
|
if (items[currentIndex]) items[currentIndex].classList.remove('selected');
|
|
|
|
currentIndex = event.key === 'ArrowUp'
|
|
? (currentIndex > 0 ? currentIndex - 1 : items.length - 1)
|
|
: (currentIndex + 1) % items.length;
|
|
|
|
if (items[currentIndex]) {
|
|
items[currentIndex].classList.add('selected');
|
|
searchInput.value = items[currentIndex].textContent;
|
|
}
|
|
} else if (event.key === 'Enter' && currentIndex > -1 && items[currentIndex]) {
|
|
event.preventDefault();
|
|
selectSuggestion(items[currentIndex]);
|
|
}
|
|
});
|
|
|
|
// Function to handle suggestion selection
|
|
function selectSuggestion(item) {
|
|
const query = item.textContent;
|
|
searchInput.value = query;
|
|
resultsWrapper.innerHTML = '';
|
|
searchWrapper.classList.remove('wrapper-searching');
|
|
|
|
// Retrieve the `t` parameter from the hidden input or set a default
|
|
const currentTemplateType = document.querySelector('input[name="t"]').value || 'web';
|
|
|
|
// Redirect to the appropriate results page
|
|
window.location.href = `/search?q=${encodeURIComponent(query)}&t=${encodeURIComponent(currentTemplateType)}`;
|
|
}
|
|
|
|
// Handle clicks on search suggestions
|
|
resultsWrapper.addEventListener('click', (event) => {
|
|
if (event.target.tagName === 'LI') {
|
|
selectSuggestion(event.target);
|
|
}
|
|
});
|
|
|
|
// Close the suggestions when clicking outside
|
|
document.addEventListener('click', (event) => {
|
|
if (!searchWrapper.contains(event.target)) {
|
|
searchWrapper.classList.remove('wrapper-searching');
|
|
resultsWrapper.innerHTML = '';
|
|
currentIndex = -1;
|
|
}
|
|
});
|
|
});
|