(function() { let searchIndex = null; let flexSearch = null; // Initialize search when DOM is ready document.addEventListener('DOMContentLoaded', initializeSearch); async function initializeSearch() { const searchInput = document.getElementById('q'); const searchResults = document.getElementById('search-results'); if (!searchInput) return; try { // FlexSearch should already be loaded via script tag if (typeof FlexSearch === 'undefined') { throw new Error('FlexSearch library not found. Make sure flexsearch.min.js is loaded.'); } // Load search index const response = await fetch('/search.json'); if (!response.ok) { throw new Error(`Failed to load search index: ${response.status}`); } searchIndex = await response.json(); if (!Array.isArray(searchIndex) || searchIndex.length === 0) { console.warn('Search index is empty or invalid'); return; } // Initialize FlexSearch flexSearch = new FlexSearch.Index({ tokenize: 'forward', cache: true, resolution: 9 }); // Add documents to search index searchIndex.forEach((item, index) => { if (item && item.title) { const searchText = `${item.title} ${item.description || ''} ${item.content || ''} ${(item.tags || []).join(' ')}`; flexSearch.add(index, searchText); } }); // Set up search event listeners setupSearchListeners(searchInput, searchResults); console.log(`Search initialized with ${searchIndex.length} items`); } catch (error) { console.error('Search initialization failed:', error); } } function setupSearchListeners(searchInput, searchResults) { let searchTimeout; searchInput.addEventListener('input', (e) => { clearTimeout(searchTimeout); const query = e.target.value.trim(); if (query.length < 2) { hideSearchResults(searchResults); return; } searchTimeout = setTimeout(() => { performSearch(query, searchResults); }, 300); }); searchInput.addEventListener('focus', (e) => { if (e.target.value.trim().length >= 2) { performSearch(e.target.value.trim(), searchResults); } }); // Hide results when clicking outside document.addEventListener('click', (e) => { if (!searchInput.contains(e.target) && !searchResults?.contains(e.target)) { hideSearchResults(searchResults); } }); // Keyboard navigation searchInput.addEventListener('keydown', (e) => { if (!searchResults) return; const items = searchResults.querySelectorAll('a'); const currentFocus = document.activeElement; let currentIndex = Array.from(items).indexOf(currentFocus); if (e.key === 'ArrowDown') { e.preventDefault(); currentIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0; items[currentIndex]?.focus(); } else if (e.key === 'ArrowUp') { e.preventDefault(); currentIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1; items[currentIndex]?.focus(); } else if (e.key === 'Escape') { hideSearchResults(searchResults); searchInput.blur(); } }); } function performSearch(query, searchResults) { if (!flexSearch || !searchIndex) return; try { const results = flexSearch.search(query, { limit: 8 }); const items = results.map(index => searchIndex[index]); displaySearchResults(items, query, searchResults); } catch (error) { console.warn('Search failed:', error); } } function displaySearchResults(items, query, searchResults) { if (!searchResults) { // Create search results container if it doesn't exist searchResults = createSearchResultsContainer(); } if (items.length === 0) { searchResults.innerHTML = `
No results found for "${query}"
`; } else { searchResults.innerHTML = items.map(item => `
${highlightMatch(item.title, query)}
${highlightMatch(item.description || item.content, query)}
${item.tags.length > 0 ? `
${item.tags.slice(0, 3).map(tag => `#${tag}`).join('')}
` : ''}
`).join(''); } searchResults.classList.remove('hidden'); } function createSearchResultsContainer() { const searchForm = document.querySelector('form[role="search"]'); if (!searchForm) return null; const container = document.createElement('div'); container.id = 'search-results'; container.className = 'absolute top-full left-0 right-0 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-b-md shadow-lg z-50 max-h-96 overflow-y-auto hidden'; searchForm.style.position = 'relative'; searchForm.appendChild(container); return container; } function hideSearchResults(searchResults) { if (searchResults) { searchResults.classList.add('hidden'); } } function highlightMatch(text, query) { if (!text || !query) return text || ''; const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'); return text.replace(regex, '$1'); } function loadScript(src) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = src; script.onload = () => { console.log('Script loaded successfully:', src); resolve(); }; script.onerror = (error) => { console.error('Script failed to load:', src, error); reject(error); }; document.head.appendChild(script); }); } })();