import { useEffect, useState, useRef } from 'react'; import lunr from 'lunr'; interface SearchResult { id: string; title: string; description: string; category: string; tags: string[]; url: string; pubDate: string; } interface IndexedResult extends SearchResult { score: number; } export default function SearchDialog() { const [isOpen, setIsOpen] = useState(false); const [query, setQuery] = useState(''); const [results, setResults] = useState([]); const [selectedIndex, setSelectedIndex] = useState(0); const [searchData, setSearchData] = useState([]); const [searchIndex, setSearchIndex] = useState(null); const [isLoading, setIsLoading] = useState(true); const inputRef = useRef(null); const resultsRef = useRef(null); // Load search data and build index useEffect(() => { fetch('/search.json') .then((res) => res.json()) .then((data: SearchResult[]) => { setSearchData(data); // Build Lunr index const idx = lunr(function () { this.ref('id'); this.field('title', { boost: 10 }); this.field('description', { boost: 5 }); this.field('content'); this.field('category', { boost: 3 }); this.field('tags', { boost: 3 }); data.forEach((doc) => { this.add(doc); }); }); setSearchIndex(idx); setIsLoading(false); }) .catch((err) => { console.error('Failed to load search data:', err); setIsLoading(false); }); }, []); // Keyboard shortcut to open search useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); setIsOpen(true); } if (e.key === 'Escape' && isOpen) { closeSearch(); } }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [isOpen]); // Focus input when dialog opens useEffect(() => { if (isOpen) { inputRef.current?.focus(); document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = ''; } }, [isOpen]); // Real-time search useEffect(() => { if (!query.trim() || !searchIndex || !searchData) { setResults([]); setSelectedIndex(0); return; } try { // Add wildcards for partial matching const searchQuery = query .trim() .split(/\s+/) .map((term) => `${term}* ${term}~1`) .join(' '); const searchResults = searchIndex.search(searchQuery); const matchedResults = searchResults .map((result) => { const data = searchData.find((d) => d.id === result.ref); return data ? { ...data, score: result.score } : null; }) .filter((r): r is IndexedResult => r !== null) .slice(0, 8); setResults(matchedResults); setSelectedIndex(0); } catch (err) { // Fallback to simple search if query syntax is invalid try { const searchResults = searchIndex.search(query); const matchedResults = searchResults .map((result) => { const data = searchData.find((d) => d.id === result.ref); return data ? { ...data, score: result.score } : null; }) .filter((r): r is IndexedResult => r !== null) .slice(0, 8); setResults(matchedResults); setSelectedIndex(0); } catch { setResults([]); } } }, [query, searchIndex, searchData]); const closeSearch = () => { setIsOpen(false); setQuery(''); setResults([]); setSelectedIndex(0); }; // Keyboard navigation const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'ArrowDown') { e.preventDefault(); setSelectedIndex((prev) => Math.min(prev + 1, results.length - 1)); } else if (e.key === 'ArrowUp') { e.preventDefault(); setSelectedIndex((prev) => Math.max(prev - 1, 0)); } else if (e.key === 'Enter' && results[selectedIndex]) { window.location.href = results[selectedIndex].url; } }; // Scroll selected item into view useEffect(() => { if (resultsRef.current && results.length > 0) { const selectedElement = resultsRef.current.children[selectedIndex] as HTMLElement; selectedElement?.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } }, [selectedIndex, results]); if (!isOpen) { return ( ); } return (
{/* Backdrop with scan line effect */}
{/* Search Dialog */}
{/* Header Bar */}
/// SEARCH_QUERY
{/* Search Input */}
setQuery(e.target.value)} onKeyDown={handleKeyDown} placeholder="ENTER SEARCH QUERY..." className="flex-1 bg-transparent border-none outline-none text-[var(--theme-text-primary)] placeholder:text-[var(--theme-text-subtle)] font-mono text-base tracking-wide uppercase" /> {query && ( )}
{/* Results */}
{isLoading ? (
/// INITIALIZING SEARCH PROTOCOL
) : results.length > 0 ? ( <> {results.map((result, index) => ( setSelectedIndex(index)} >

{result.title}

{result.category && ( {result.category} )}

{result.description}

{result.tags && result.tags.length > 0 && (
{result.tags.slice(0, 4).map((tag) => ( #{tag} ))}
)}
))} ) : query ? (
/// NO RESULTS FOUND

Query returned 0 matches. Try different keywords.

) : (
/// AWAITING INPUT

Begin typing to search all blog content

)}
{/* Footer */} {results.length > 0 && (
↑↓ Navigate Select ESC Close
{results.length} RESULT{results.length !== 1 ? 'S' : ''}
)}
); }