import { For, Show, createResource, createSignal, onCleanup } from 'solid-js' import { debounce } from 'throttle-debounce' import { Button } from '~/components/_shared/Button' import { Icon } from '~/components/_shared/Icon' import { useFeed } from '~/context/feed' import { useLocalize } from '~/context/localize' import type { Shout } from '~/graphql/schema/core.gen' import { byScore } from '~/lib/sort' import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll' import { FEED_PAGE_SIZE } from '../../Views/Feed/Feed' import styles from './SearchModal.module.scss' import { SearchResultItem } from './SearchResultItem' // @@TODO handle empty article options after backend support (subtitle, cover, etc.) // @@TODO implement load more // @@TODO implement FILTERS & TOPICS // @@TODO use save/restoreScrollPosition if needed const getSearchCoincidences = ({ str, intersection }: { str: string; intersection: string }) => `${str.replaceAll( new RegExp(intersection, 'gi'), (casePreservedMatch) => `${casePreservedMatch}` )}` const prepareSearchResults = (list: Shout[], searchValue: string) => list.sort(byScore() as (a: Shout, b: Shout) => number).map((article, index) => ({ ...article, id: index, title: article.title ? getSearchCoincidences({ str: article.title, intersection: searchValue }) : '', subtitle: article.subtitle ? getSearchCoincidences({ str: article.subtitle, intersection: searchValue }) : '' })) export const SearchModal = () => { const { t } = useLocalize() const { loadShoutsSearch } = useFeed() const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) const [inputValue, setInputValue] = createSignal('') const [isLoading, setIsLoading] = createSignal(false) const [offset, setOffset] = createSignal(0) const [searchResultsList, { refetch: loadSearchResults, mutate: setSearchResultsList }] = createResource< Shout[] >( async () => { setIsLoading(true) saveScrollPosition() const { hasMore, newShouts } = await loadShoutsSearch({ limit: FEED_PAGE_SIZE, text: inputValue(), offset: offset() }) setIsLoading(false) setOffset(newShouts.length) setIsLoadMoreButtonVisible(hasMore) return newShouts }, { ssrLoadFrom: 'initial', initialValue: [] } ) let searchEl: HTMLInputElement const debouncedLoadMore = debounce(500, loadSearchResults) const handleQueryInput = async () => { setInputValue(searchEl.value) if (searchEl.value?.length > 2) { await debouncedLoadMore() } else { setIsLoading(false) setSearchResultsList([]) } } const enterQuery = async (ev: KeyboardEvent) => { setIsLoading(true) if (ev.key === 'Enter' && inputValue().length > 2) { await debouncedLoadMore() } else { setIsLoading(false) setSearchResultsList([]) } restoreScrollPosition() setIsLoading(false) } // Cleanup the debounce timer when the component unmounts onCleanup(() => { debouncedLoadMore.cancel() // console.debug('[SearchModal] cleanup debouncing search') }) return (
(searchEl = el)} />

{/* @@TODO handle filter */} {/*

{(filter) => ( )}
*/} {/* @@TODO handle topics */} {/*
{(topic) => ( )}
*/}
) }