"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 ? (
) : (
)}
)
}