import { useEffect, useRef } from "preact/hooks"; import useDebouncedCallback from "@lib/hooks/useDebouncedCallback.ts"; import { IconLoader2, IconSearch } from "@components/icons.tsx"; import { useEventListener } from "@lib/hooks/useEventListener.ts"; import { SearchResult } from "@lib/types.ts"; import { resources } from "@lib/resources.ts"; import { getCookie } from "@lib/string.ts"; import { IS_BROWSER } from "$fresh/runtime.ts"; import Checkbox from "@components/Checkbox.tsx"; import { Rating } from "@components/Rating.tsx"; import { useSignal } from "@preact/signals"; import Image from "@components/Image.tsx"; import { Emoji } from "@components/Emoji.tsx"; export async function fetchQueryResource(url: URL, type = "") { const query = url.searchParams.get("q"); const status = url.searchParams.get("status"); try { url.pathname = "/api/resources"; url.searchParams.set("q", encodeURIComponent(query || "*")); if (status) { url.searchParams.set("status", "not-seen"); } if (type) { url.searchParams.set("type", type); } const response = await fetch(url); const jsonData = await response.json(); return jsonData; } catch (error) { console.error("Error fetching data:", error); } } export const RedirectSearchHandler = () => { if (getCookie("session_cookie")) { useEventListener("keydown", (e: KeyboardEvent) => { if (e?.target?.nodeName == "INPUT") return; if ( e.key === "?" && window.location.search === "" ) { window.location.href += "?q=*"; } }, IS_BROWSER ? document?.body : undefined); } return <>>; }; const SearchResultImage = ({ src }: { src: string }) => { const imageSrc = `/api/images?image=${src}&width=50&height=50`; return ( ); }; export const SearchResultItem = ( { item, showEmoji = false }: { item: NonNullable[number]; showEmoji?: boolean; }, ) => { const doc = item.document; const resourceType = resources[doc.type]; const href = resourceType ? `${resourceType.link}/${doc.id}` : ""; return ( {showEmoji && resourceType ? : ""} {doc?.image && } {doc?.name} ); }; export const SearchResultList = ( { result, showEmoji }: { result: SearchResult; showEmoji?: boolean }, ) => { return ( {result?.hits ? ( {result.hits.map((hit) => ( ))} ) : No Results} ); }; const Search = ( { q = "*", type, results }: { q: string; type?: string; results?: SearchResult; }, ) => { const searchQuery = useSignal(q); const data = useSignal(results); const isLoading = useSignal(false); const showSeenStatus = useSignal(false); const inputRef = useRef(null); if ("history" in globalThis) { const u = new URL(window.location.href); if (u.searchParams.get("q") !== searchQuery.value) { u.searchParams.set("q", searchQuery.value); } if (showSeenStatus.value) { u.searchParams.set("rating", "0"); } else { u.searchParams.delete("rating"); } window.history.replaceState({}, "", u); } const fetchData = async () => { try { isLoading.value = true; const jsonData = await fetchQueryResource( new URL(window?.location.href), type, ); data.value = jsonData; isLoading.value = false; } catch (error) { console.error("Error fetching data:", error); isLoading.value = false; } }; const debouncedFetchData = useDebouncedCallback(fetchData, 500); // Debounce the fetchData function with a delay of 500ms useEffect(() => { if (inputRef.current && searchQuery?.value.length === 0) { inputRef.current?.focus(); } }, [inputRef.current, searchQuery]); const handleInputChange = (event: Event) => { const target = event.target as HTMLInputElement; if (target.value !== searchQuery.value) { searchQuery.value = target.value; } }; useEffect(() => { debouncedFetchData(); // Call the debounced fetch function with the updated search query }, [searchQuery.value, showSeenStatus.value]); useEffect(() => { debouncedFetchData(); }, []); return ( {isLoading.value && searchQuery.value ? : } {data?.value?.hits?.length && !isLoading.value ? : isLoading.value ? : ( No Results )} ); }; export default Search;