import type { SearchResult } from "@/services/FlexSearchService"; import type { JSX, Accessor, Setter } from "solid-js"; import { createContext, useContext, For, createEffect, createSignal, } from "solid-js"; import { Highlighter } from "solid-highlight-words"; import { useDebounce } from "@/hooks/useDebounce"; import { Dialog, DialogPanel } from "terracotta"; import { navigation } from "@/libs/navigation"; import { navigate } from "vike/client/router"; import { useId } from "@/hooks/useId"; import clsx from "clsx"; const SearchContext = createContext<{ query: Accessor; close: () => void; results: Accessor; isLoading: Accessor; isOpened: Accessor; setQuery: Setter; setIsOpened: Setter; setIsLoading: Setter; setResults: Setter; }>({ query: () => "", close: () => {}, results: () => [], isLoading: () => false, isOpened: () => false, setQuery: () => {}, setIsOpened: () => {}, setIsLoading: () => {}, setResults: () => {}, }); function SearchIcon(props: JSX.IntrinsicElements["svg"]) { return ( ); } function LoadingIcon(props: JSX.IntrinsicElements["svg"]) { const id = useId(); return ( ); } function SearchInput() { const { close, setQuery, query, isLoading } = useContext(SearchContext); return (
{ if (event.key === "Escape") { // In Safari, closing the dialog with the escape key can sometimes cause the scroll position to jump to the // bottom of the page. This is a workaround for that until we can figure out a proper fix in Headless UI. if (document.activeElement instanceof HTMLElement) { document.activeElement.blur(); } close(); } }} value={query()} onInput={(event) => { const { value } = event.currentTarget; setQuery(value); }} /> {isLoading() && (
)}
); } function HighlightQuery({ text, query }: { text: string; query: string }) { return ( ); } function SearchResultItem(props: { result: SearchResult; query: string }) { const { close } = useContext(SearchContext); const id = useId(); const getHierarchy = (): string[] => { const sectionTitle = navigation.find((section) => { return section.links.find( (link) => link.href === props.result.url.split("#")[0], ); })?.title; return [sectionTitle, props.result.pageTitle].filter( (x): x is string => typeof x === "string", ); }; return (
  • { if (event.key === "Enter") { navigate(props.result.url); close(); } }} onClick={() => { navigate(props.result.url); close(); }} > {getHierarchy().length > 0 && ( )}
  • ); } function SearchResults() { const { results, query } = useContext(SearchContext); if (results().length === 0) { return (

    Aucun résultat pour “ {query()}

    ); } return (
      {(result) => (
    • )}
    ); } function SearchDialog(props: { class?: string }) { const { close, isOpened, setIsOpened, results } = useContext(SearchContext); createEffect(() => { if (isOpened()) return; function onKeyDown(event: KeyboardEvent) { if (event.key === "k" && (event.metaKey || event.ctrlKey)) { event.preventDefault(); setIsOpened(true); } } window.addEventListener("keydown", onKeyDown); return () => { window.removeEventListener("keydown", onKeyDown); }; }, [isOpened, setIsOpened]); const handleClickOutside = (event: MouseEvent) => { const { target, currentTarget } = event; if (target instanceof Node && currentTarget instanceof Node) { if (target === currentTarget) close(); } }; return ( <>
    { if (event.key === "Escape") close(); }} >
    event.preventDefault()}>
    {results().length > 0 && }
    ); } export function Search() { const [results, setResults] = createSignal([]); const [modifierKey, setModifierKey] = createSignal(); const [isLoading, setIsLoading] = createSignal(false); const [isOpened, setIsOpened] = createSignal(false); const [query, setQuery] = createSignal(""); const debouncedQuery = useDebounce(query, 300); const onSearch = async (query: string) => { const response = await fetch(`/search?query=${query}`); if (!response.ok) { throw new Error("Network response was not ok"); } const data = await response.json(); return data; }; createEffect(() => { const platform = navigator.userAgentData?.platform || navigator.platform; setModifierKey(/(Mac|iPhone|iPod|iPad)/i.test(platform) ? "⌘" : "Ctrl "); }, []); createEffect(() => { const query = debouncedQuery(); if (query.length === 0) { setIsLoading(false); setResults([]); return; } setIsLoading(true); onSearch(query) .then(setResults) .finally(() => setIsLoading(false)); }); return ( setIsOpened(false), results, isLoading, isOpened, setQuery, setIsOpened, setIsLoading, setResults, }} > ); }