"use client" import { useState, useEffect } from "react" import Link from "next/link" import { Modal } from "@/components/ui/modal" import { Input } from "@/components/ui/input" import { useGlobalSearch, useIndexSearch, filterSearchHitsByTerm, useSearchConfig, } from "@/hooks/useGlobalSearch" import { CategoryTag } from "../ui/categoryTag" import { Markdown } from "../ui/markdown" import React from "react" interface SearchModalProps { open: boolean setOpen: (open: boolean) => void } interface SearchHit { objectID: string title?: string name?: string content?: string description?: string excerpt?: string url?: string locale?: string section?: string category?: string type?: string path?: string hierarchy?: { lvl0?: string [key: string]: string | undefined } [key: string]: any } interface IndexResult { indexName: string hits: SearchHit[] } function Hit({ hit, setOpen, indexName, }: { hit: SearchHit setOpen: (open: boolean) => void indexName?: string }) { const url = hit.url || `/${hit.locale || "en"}/blog/${hit.objectID}` const title = hit.title || hit.name || hit.hierarchy?.lvl0 || "Untitled Result" const section = hit.section || hit.category || hit.type || hit.hierarchy?.lvl0 || (hit.path && hit.path.split("/")[0]) || indexName || "General" const content = hit.content || hit.description || hit.excerpt || "" const snippet = content.length > 200 ? content.substring(0, 200).replace(/\S+$/, "") + "..." : content return ( setOpen(false)} >

{title}

{section}
{snippet && ( null, h2: ({ children }) => null, h3: ({ children }) => null, h4: ({ children }) => null, h5: ({ children }) => null, h6: ({ children }) => null, img: ({ src, alt }) => null, a: ({ href, children }) => { let textContent = "" React.Children.forEach(children, (child) => { if (typeof child === "string") { textContent += child } else if (React.isValidElement(child)) { const childText = child.props?.children if (typeof childText === "string") { textContent += childText } } }) return ( {textContent} ) }, }} > {snippet} )} ) } function NoResults() { return (
No results found. Try a different search term.
) } const LoadingIndicator = () => { return (
) } function DirectSearchResults({ query, setOpen, }: { query: string setOpen: (open: boolean) => void }) { const { data, isLoading, isError, error } = useGlobalSearch({ query }) if (isLoading) { return (
) } if (isError) { return (
Error: {error.message || "Search failed"}
) } if (!data || data.results.length === 0) { return } return (
{data.results.map((indexResult: IndexResult) => (
{indexResult.hits.length > 0 && (
{indexResult.indexName}
{indexResult.hits.map((hit: SearchHit) => ( ))}
)}
))}
) } function IndexSearchResults({ query, indexName, setOpen, }: { query: string indexName: string setOpen: (open: boolean) => void }) { const { data, isLoading, isError, error, refetch } = useIndexSearch({ query, indexName, }) if (query.trim() === "") { return (
Enter a search term to begin
) } if (isLoading) { return (
) } if (isError) { return (
Error searching {indexName}
) } if (!data || data.hits.length === 0) { return (
No results in {indexName}
) } // Apply special filtering for certain terms const hits = query.toLowerCase().includes("intmax") ? filterSearchHitsByTerm(data.hits, "intmax") : data.hits if (hits.length === 0) { return (
No matches for "{query}" in {indexName}
) } return (
{hits.map((hit: SearchHit) => ( ))}
) } const CustomSearchResult = ({ results, setOpen, }: { results: IndexResult[] setOpen: (open: boolean) => void }) => (
{results.map((indexResult: IndexResult) => (
{indexResult.indexName.replace(/-/g, " ")}
{indexResult.hits.map((hit: SearchHit) => ( ))}
))}
) const MultiIndexSearchView = ({ searchQuery, setOpen, }: { searchQuery: string setOpen: (open: boolean) => void }) => { const { allIndexes } = useSearchConfig() if (searchQuery.trim() === "") { return (
Enter a search term to begin
) } // Filter out empty indexes to prevent rendering issues const visibleIndexes = allIndexes.filter( (index: string) => index && index.trim() !== "" ) if (visibleIndexes.length === 0) { return } // Sort indexes to ensure projects appear first, followed by blog const sortedIndexes = [...visibleIndexes].sort((a, b) => { if (a.toLowerCase().includes("project")) return -1 if (b.toLowerCase().includes("project")) return 1 if (a.toLowerCase().includes("blog")) return 1 if (b.toLowerCase().includes("blog")) return -1 return a.localeCompare(b) }) return (
{sortedIndexes.map((indexName: string) => (
{indexName.replace(/-/g, " ")}
))}
) } export const SearchModal = ({ open, setOpen }: SearchModalProps) => { const [searchQuery, setSearchQuery] = useState("") const [directSearchMode, setDirectSearchMode] = useState(false) const { allIndexes } = useSearchConfig() useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if ((event.ctrlKey || event.metaKey) && event.key === "k") { event.preventDefault() setOpen(!open) } if (event.key === "Escape" && open) { setOpen(false) } } window.addEventListener("keydown", handleKeyDown) return () => { window.removeEventListener("keydown", handleKeyDown) } }, [open, setOpen]) useEffect(() => { if (!open) { setSearchQuery("") } }, [open]) if (allIndexes.length === 0) { return null } const handleSearchInputChange = (e: React.ChangeEvent) => { setSearchQuery(e.target.value) } return (
{directSearchMode ? ( ) : ( )}
) }