import React, { useState, useEffect, useCallback } from 'react'; import ReactDOM from 'react-dom/client'; // Badge Component const Badge = ({ children, variant = "default", className = "" }) => { const variants = { default: "bg-gray-100 text-gray-800", link: "bg-blue-100 text-blue-800", page: "bg-green-100 text-green-800", post: "bg-purple-100 text-purple-800" }; return ( {children} ); }; // Card Component const Card = ({ children, className = "", hover = false }) => (
{children}
); // Input Component const Input = ({ className = "", ...props }) => ( ); // Select Component const Select = ({ children, className = "", ...props }) => ( ); // Button Component const Button = ({ children, variant = "default", className = "", ...props }) => { const variants = { default: "bg-blue-600 text-white hover:bg-blue-700", outline: "border-2 border-gray-300 bg-white hover:bg-gray-50", ghost: "hover:bg-gray-100" }; return ( ); }; // Skeleton Loader const Skeleton = ({ className = "" }) => (
); // Search Result Item Component const SearchResultItem = ({ result }) => { const typeIcons = { link: "fa-link", page: "fa-file-alt", post: "fa-newspaper" }; const formatDate = (dateString) => { return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); }; return (
{result.title} {result.type}
{result.type === 'link' && result.original_url && ( {result.original_url} )} {(result.description || result.summary) && (

{result.description || result.summary}

)}
{formatDate(result.created_at)} {result.click_count !== undefined && ( {result.click_count} clicks )}
{result.tags && result.tags.length > 0 && (
{result.tags.map(tag => ( {tag.name} ))}
)}
); }; // Main Search App Component const SearchApp = () => { const [query, setQuery] = useState(''); const [type, setType] = useState(''); const [sort, setSort] = useState('relevance'); const [isVector, setIsVector] = useState(false); const [results, setResults] = useState([]); const [loading, setLoading] = useState(false); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [error, setError] = useState(null); const perPage = 20; const performSearch = useCallback(async (searchQuery, searchType, searchSort, searchPage) => { if (!searchQuery.trim()) { setResults([]); setTotal(0); return; } setLoading(true); setError(null); try { const params = new URLSearchParams({ q: searchQuery, type: searchType, sort: searchSort, page: searchPage, per_page: perPage }); const endpoint = isVector ? "/search/api/vector/" : "/search/api/v2/"; const response = await fetch(`${endpoint}?${params}`); const data = await response.json(); if (response.ok) { setResults(data.results); setTotal(data.total); } else { setError(data.error || 'Search failed'); setResults([]); } } catch (err) { setError('Network error. Please try again.'); setResults([]); } finally { setLoading(false); } }, [isVector]); const handleSearch = useCallback((e) => { e.preventDefault(); setPage(1); performSearch(query, type, sort, 1); }, [query, type, sort, performSearch]); useEffect(() => { const params = new URLSearchParams(window.location.search); const urlQuery = params.get('q') || ''; const urlType = params.get('type') || ''; const urlSort = params.get('sort') || 'relevance'; setQuery(urlQuery); setType(urlType); setSort(urlSort); if (urlQuery) { performSearch(urlQuery, urlType, urlSort, 1); } }, [performSearch]); useEffect(() => { if (query) { const params = new URLSearchParams({ q: query, ...(type && { type }), ...(sort !== 'relevance' && { sort }) }); window.history.replaceState({}, '', `?${params}`); } }, [query, type, sort]); const handlePageChange = (newPage) => { setPage(newPage); performSearch(query, type, sort, newPage); window.scrollTo({ top: 0, behavior: 'smooth' }); }; const totalPages = Math.ceil(total / perPage); return (
{/* Header */}

Advanced Search

Search through all Links, Pages, and Posts

{/* Search Form */}
{/* Search Input */}
setQuery(e.target.value)} placeholder="Enter keywords to search..." className="pl-10" />
{/* Filters */}
setIsVector(e.target.checked)} className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" />
{/* Search Button */}
{/* Results */} {query && (
{/* Results Header */}

{loading ? ( 'Searching...' ) : error ? ( {error} ) : ( <> Search results for: "{query}" {total > 0 && ( ({total} {total === 1 ? 'result' : 'results'}) )} )}

{/* Loading Skeletons */} {loading && (
{[1, 2, 3].map(i => (
))}
)} {/* Results List */} {!loading && !error && results.length > 0 && (
{results.map(result => ( ))}
)} {/* No Results */} {!loading && !error && results.length === 0 && query && (

No results found

Try adjusting your search terms or filters

)} {/* Pagination */} {!loading && totalPages > 1 && (
{[...Array(Math.min(5, totalPages))].map((_, i) => { let pageNum; if (totalPages <= 5) { pageNum = i + 1; } else if (page <= 3) { pageNum = i + 1; } else if (page >= totalPages - 2) { pageNum = totalPages - 4 + i; } else { pageNum = page - 2 + i; } return ( ); })}
)}
)} {/* Empty State */} {!query && (

Start Searching

Enter a search query to find links, pages, and posts. You can search by title, content, URL, or tags.

)}
); }; // Mount app const root = ReactDOM.createRoot(document.getElementById('root')); root.render();