/* 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 += `
  • ${item}
  • `; }); resultsWrapper.innerHTML = ``; 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'); const form = searchInput.closest('form'); form ? form.submit() : window.location.href = `/search?q=${encodeURIComponent(query)}&t=web`; } // 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; } }); });