From 0bca6d73b18d6d34740838b16010b66cb9476112 Mon Sep 17 00:00:00 2001 From: Untone Date: Sun, 28 Jan 2024 08:57:05 +0300 Subject: [PATCH 1/4] search-using-resource --- .../Nav/SearchModal/SearchModal.tsx | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/src/components/Nav/SearchModal/SearchModal.tsx b/src/components/Nav/SearchModal/SearchModal.tsx index 3c786608..85176702 100644 --- a/src/components/Nav/SearchModal/SearchModal.tsx +++ b/src/components/Nav/SearchModal/SearchModal.tsx @@ -1,8 +1,12 @@ import type { Shout } from '../../../graphql/schema/core.gen' -import { createSignal, Show, For } from 'solid-js' +import { createResource, createSignal, For, Show } from 'solid-js' +import { debounce } from 'throttle-debounce' import { useLocalize } from '../../../context/localize' +import { loadShoutsSearch } from '../../../stores/zine/articles' +// import { restoreScrollPosition, saveScrollPosition } from '../../../utils/scroll' +import { byScore } from '../../../utils/sortby' import { Button } from '../../_shared/Button' import { Icon } from '../../_shared/Icon' import { FEED_PAGE_SIZE } from '../../Views/Feed/Feed' @@ -10,13 +14,11 @@ import { FEED_PAGE_SIZE } from '../../Views/Feed/Feed' import { SearchResultItem } from './SearchResultItem' import styles from './SearchModal.module.scss' -import { restoreScrollPosition, saveScrollPosition } from '../../../utils/scroll' -import { loadShoutsSearch, useArticlesStore } from '../../../stores/zine/articles' -import { byScore } from '../../../utils/sortby' // @@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( @@ -24,16 +26,16 @@ const getSearchCoincidences = ({ str, intersection }: { str: string; intersectio (casePreservedMatch) => `${casePreservedMatch}`, )}` -const prepareSearchResults = (list, searchValue) => - list.map((article, index) => ({ +const prepareSearchResults = (list: Shout[], searchValue: string) => + list.sort(byScore()).map((article, index) => ({ ...article, - body: '', - cover: '', - createdAt: '', + body: article.body, + cover: article.cover, + created_at: article.created_at, id: index, slug: article.slug, - authors: [], - topics: [], + authors: article.authors, + topics: article.topics, title: article.title ? getSearchCoincidences({ str: article.title, @@ -50,38 +52,35 @@ const prepareSearchResults = (list, searchValue) => export const SearchModal = () => { const { t } = useLocalize() - const { sortedArticles } = useArticlesStore() const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) - const [offset, setOffset] = createSignal(0) const [inputValue, setInputValue] = createSignal('') - //const [searchResultsList, setSearchResultsList] = createSignal<[] | null>([]) const [isLoading, setIsLoading] = createSignal(false) - // const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) - - let searchEl: HTMLInputElement - const handleQueryChange = async (_ev) => { - setInputValue(searchEl.value) - - if (inputValue() && inputValue().length > 2) await loadMore() - } - - const loadMore = async () => { - setIsLoading(true) - saveScrollPosition() - if (inputValue() && inputValue().length > 2) { - console.log(inputValue()) - const { hasMore } = await loadShoutsSearch({ - text: inputValue(), - offset: offset(), + const [searchResultsList, { refetch: loadSearchResults, mutate: setSearchResultsList }] = createResource< + Shout[] + >( + async () => { + setIsLoading(true) + const { hasMore, newShouts } = await loadShoutsSearch({ limit: FEED_PAGE_SIZE, + text: inputValue(), + offset: searchResultsList().length, }) setIsLoadMoreButtonVisible(hasMore) - setOffset(offset() + FEED_PAGE_SIZE) - } else { - console.warn('[SaerchView] no query found') - } - restoreScrollPosition() - setIsLoading(false) + return newShouts + }, + { + ssrLoadFrom: 'initial', + initialValue: [], + }, + ) + + let searchEl: HTMLInputElement + const debouncedLoadMore = debounce(500, loadSearchResults) + const handleQueryInput = () => { + const inp = searchEl.value + setInputValue(inp) + if (inp?.length > 2) debouncedLoadMore() + else setSearchResultsList([]) } return ( @@ -90,13 +89,14 @@ export const SearchModal = () => { type="search" placeholder={t('Site search')} class={styles.searchInput} - onInput={handleQueryChange} + onInput={handleQueryInput} + onChange={debouncedLoadMore} ref={searchEl} />

- +

From b866af2ff79537b6b57be4eb002f611e32f84a85 Mon Sep 17 00:00:00 2001 From: Untone Date: Sun, 28 Jan 2024 10:06:31 +0300 Subject: [PATCH 2/4] reasource-based-search-results-load --- .../Feed/ArticleCard/ArticleCard.tsx | 1 + src/components/Nav/Header/Header.tsx | 3 -- src/components/Nav/HeaderAuth.tsx | 2 +- .../Nav/SearchModal/SearchModal.tsx | 41 +++++++++++++++---- src/utils/apiClient.ts | 0 5 files changed, 34 insertions(+), 13 deletions(-) delete mode 100644 src/utils/apiClient.ts diff --git a/src/components/Feed/ArticleCard/ArticleCard.tsx b/src/components/Feed/ArticleCard/ArticleCard.tsx index a6a9939d..d2c15c13 100644 --- a/src/components/Feed/ArticleCard/ArticleCard.tsx +++ b/src/components/Feed/ArticleCard/ArticleCard.tsx @@ -2,6 +2,7 @@ import type { Author, Shout, Topic } from '../../../graphql/schema/core.gen' import { getPagePath, openPage } from '@nanostores/router' import { clsx } from 'clsx' +import { createMemo, createSignal, For, Show } from 'solid-js' import { useLocalize } from '../../../context/localize' import { useSession } from '../../../context/session' diff --git a/src/components/Nav/Header/Header.tsx b/src/components/Nav/Header/Header.tsx index 0b6a75ee..fb597f05 100644 --- a/src/components/Nav/Header/Header.tsx +++ b/src/components/Nav/Header/Header.tsx @@ -4,8 +4,6 @@ import { getPagePath, redirectPage } from '@nanostores/router' import { clsx } from 'clsx' import { Show, createSignal, createEffect, onMount, onCleanup, For } from 'solid-js' -import { apiClient } from '../../../utils/apiClient' - import { useLocalize } from '../../../context/localize' import { useSession } from '../../../context/session' import { apiClient } from '../../../graphql/client/core' @@ -22,7 +20,6 @@ import { HeaderAuth } from '../HeaderAuth' import { Modal } from '../Modal' import { SearchModal } from '../SearchModal/SearchModal' import { Snackbar } from '../Snackbar' -import { SearchModal } from '../SearchModal/SearchModal' import { Link } from './Link' diff --git a/src/components/Nav/HeaderAuth.tsx b/src/components/Nav/HeaderAuth.tsx index fb8ee22a..d8ca5f6b 100644 --- a/src/components/Nav/HeaderAuth.tsx +++ b/src/components/Nav/HeaderAuth.tsx @@ -128,7 +128,7 @@ export const HeaderAuth = (props: Props) => {

- + diff --git a/src/components/Nav/SearchModal/SearchModal.tsx b/src/components/Nav/SearchModal/SearchModal.tsx index 85176702..781dea50 100644 --- a/src/components/Nav/SearchModal/SearchModal.tsx +++ b/src/components/Nav/SearchModal/SearchModal.tsx @@ -1,6 +1,6 @@ import type { Shout } from '../../../graphql/schema/core.gen' -import { createResource, createSignal, For, Show } from 'solid-js' +import { createEffect, createResource, createSignal, For, onCleanup, Show } from 'solid-js' import { debounce } from 'throttle-debounce' import { useLocalize } from '../../../context/localize' @@ -55,6 +55,7 @@ export const SearchModal = () => { 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[] >( @@ -63,8 +64,10 @@ export const SearchModal = () => { const { hasMore, newShouts } = await loadShoutsSearch({ limit: FEED_PAGE_SIZE, text: inputValue(), - offset: searchResultsList().length, + offset: offset(), }) + setIsLoading(false) + setOffset(newShouts.length) setIsLoadMoreButtonVisible(hasMore) return newShouts }, @@ -76,13 +79,33 @@ export const SearchModal = () => { let searchEl: HTMLInputElement const debouncedLoadMore = debounce(500, loadSearchResults) - const handleQueryInput = () => { - const inp = searchEl.value - setInputValue(inp) - if (inp?.length > 2) debouncedLoadMore() - else setSearchResultsList([]) + + 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([]) + } + } + + // Cleanup the debounce timer when the component unmounts + onCleanup(() => { + debouncedLoadMore.cancel() + console.log('cleanup search') + }) + return (
{ placeholder={t('Site search')} class={styles.searchInput} onInput={handleQueryInput} - onChange={debouncedLoadMore} + onKeyDown={enterQuery} ref={searchEl} />