import { Input } from "@stratakit/bricks/TextBox"; import { useEffect, useMemo, useRef, useState } from "react"; import "../pagefind-client.d.ts"; import { getBaseUrl } from "./util/getBaseUrl.ts"; import { GalleryItem } from "./Gallery.tsx"; import { Root } from "@stratakit/foundations"; import { Select } from "@stratakit/bricks"; const GALLERY_BASE = __GALLERY_BASE_URL__; // Copied/extracted from https://github.com/Pagefind/pagefind/blob/main/pagefind_web_js/lib/coupled_search.ts // and https://github.com/Pagefind/pagefind/blob/main/pagefind_web_js/types/index.d.ts type Pagefind = { init: (overrideLanguage?: string) => void; search: ( term: string, options?: PagefindSearchOptions, ) => Promise; preload: (term: string, options?: PagefindSearchOptions) => Promise; filters: () => Promise; options: (options: PagefindIndexOptions) => Promise; }; function GallerySearch({ setSearchResults, setTag, }: { setSearchResults: (newSearchResults: PagefindSearchFragment[] | null) => void; setTag: (newTag: string | "All") => void; }) { const pagefind = useRef(null); const isFirstLoad = useRef(true); const [pagefindLoaded, setPagefindLoaded] = useState(false); const [availableFilters, setAvailableFilters] = useState({ tags: {} }); const [internalSearchResults, setInternalSearchResults] = useState< PagefindSearchFragment[] >([]); const [searchTerm, setSearchTerm] = useState(""); const [currentTag, setCurrentTag] = useState("All"); const requestId = useRef(0); useEffect(() => { async function doSearch() { if (!pagefind.current) { return; } const searchString = searchTerm; console.log("doSearch", { searchString, currentTag }); if (!searchString || searchString === "") { setSearchResults(null); return; } const thisRequestId = ++requestId.current; console.time(searchString); const search = await pagefind.current.search(searchString, { filters: { tags: currentTag === "All" ? undefined : currentTag, }, }); console.log(search); console.log(search.results.length, "results"); for (const result of search.results) { console.log(await result.data()); } console.timeEnd(searchString); const resultDataPromises = await Promise.allSettled( search.results.map((result) => { return result.data(); }), ); const resultData = resultDataPromises .map((result) => { return result.status === "fulfilled" ? result.value : undefined; }) .filter((result) => !!result); if (thisRequestId === requestId.current) { setInternalSearchResults(resultData); setSearchResults(resultData); } } doSearch(); }, [searchTerm, currentTag, setSearchResults]); useEffect(() => { async function loadPageFind() { const pagefindImport: Pagefind = await import( /* @vite-ignore */ `${GALLERY_BASE}/pagefind/pagefind.js` ); console.log(pagefindImport); pagefindImport.init(); await pagefindImport.options({ baseUrl: getBaseUrl(), }); pagefind.current = pagefindImport; const filters = await pagefind.current.filters(); setAvailableFilters(filters); console.log("filters", filters); setPagefindLoaded(true); } if (isFirstLoad.current) { // This prevents double activation when using StrictMode isFirstLoad.current = false; loadPageFind(); } }, []); const debounceTimeout = useRef(null); return ( <> { if (debounceTimeout.current) { clearInterval(debounceTimeout.current); } const newSearchTerm = e.target.value; pagefind.current?.preload(newSearchTerm); debounceTimeout.current = setTimeout(() => { setSearchTerm(newSearchTerm); }, 300); }} /> { setCurrentTag(e.target.value); setTag(e.target.value); }} > {Object.keys(availableFilters.tags).map((label) => ( ))} {internalSearchResults.map((result) => { const snippets = []; const content = result.content; for (const location of result.locations) { snippets.push(content.split(" ")[location]); } return (

{result.meta.title} {result.url}

{/*

{result.meta.description}

*/} {/*
{content}
*/} {/*
{result.excerpt}
*/} {/* TODO: newlines are stripped from the content it seems. This is annoying for trying to render a snippet... */}

{/*
    {snippets.map((snippet, i) => { return
  • {snippet}
  • ; })}
*/}
); return

{result.meta.title}

; })} ); } export default function TestApp() { const [, setLegacyIdMap] = useState>({}); const [galleryItems, setGalleryItems] = useState([]); const [, setGalleryLoaded] = useState(false); useEffect(() => { let ignore = false; async function fetchGallery() { const req = await fetch(`${GALLERY_BASE}/list.json`); const resp = await req.json(); if (!ignore) { setGalleryItems(resp.entries); setLegacyIdMap(resp.legacyIdMap); setGalleryLoaded(true); } } fetchGallery(); return () => { ignore = true; }; }, []); const [searchResults, setSearchResults] = useState< PagefindSearchFragment[] | null >(null); const [currentTag, setCurrentTag] = useState("All"); const filteredList = useMemo(() => { console.log("useMemo"); const taggedItems = currentTag === "All" ? galleryItems : galleryItems.filter((item) => item.labels.includes(currentTag)); if (searchResults === null) { console.log(" return tagged only"); return taggedItems; } if (searchResults.length === 0) { console.log(" return empty, no results"); return []; } console.log(" filter results"); const searchedIds = searchResults.map((result) => result.meta.id); return searchedIds .map((id) => taggedItems.find((item) => item.id === id)) .filter((item) => item !== undefined); }, [galleryItems, searchResults, currentTag]); return ( setCurrentTag(newTag)} />

Gallery:

{filteredList.length === 0 &&

No items found

} {filteredList.map((item) => { return (
{item.title} - {item.labels.join(", ")}
); })}
{/* { // Load the gallery item every time it's clicked loadGalleryItem(item.id); }} /> */}
); }