Merge branch 'feature/search-modal' into dev

This commit is contained in:
Untone 2024-01-29 12:13:49 +03:00
commit 1910a3c3c4
3 changed files with 63 additions and 38 deletions

View File

@ -128,7 +128,7 @@ export const HeaderAuth = (props: Props) => {
<Show when={!isSaveButtonVisible()}> <Show when={!isSaveButtonVisible()}>
<div class={styles.userControlItem}> <div class={styles.userControlItem}>
<a href="?modal=search"> <a href="?m=search">
<Icon name="search" class={styles.icon} /> <Icon name="search" class={styles.icon} />
<Icon name="search" class={clsx(styles.icon, styles.iconHover)} /> <Icon name="search" class={clsx(styles.icon, styles.iconHover)} />
</a> </a>

View File

@ -1,9 +1,10 @@
import type { Shout } from '../../../graphql/schema/core.gen' import type { Shout } from '../../../graphql/schema/core.gen'
import { createSignal, Show, For } from 'solid-js' import { createResource, createSignal, For, onCleanup, Show } from 'solid-js'
import { debounce } from 'throttle-debounce'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
import { loadShoutsSearch, useArticlesStore } from '../../../stores/zine/articles' import { loadShoutsSearch } from '../../../stores/zine/articles'
import { restoreScrollPosition, saveScrollPosition } from '../../../utils/scroll' import { restoreScrollPosition, saveScrollPosition } from '../../../utils/scroll'
import { byScore } from '../../../utils/sortby' import { byScore } from '../../../utils/sortby'
import { Button } from '../../_shared/Button' import { Button } from '../../_shared/Button'
@ -17,6 +18,7 @@ import styles from './SearchModal.module.scss'
// @@TODO handle empty article options after backend support (subtitle, cover, etc.) // @@TODO handle empty article options after backend support (subtitle, cover, etc.)
// @@TODO implement load more // @@TODO implement load more
// @@TODO implement FILTERS & TOPICS // @@TODO implement FILTERS & TOPICS
// @@TODO use save/restoreScrollPosition if needed
const getSearchCoincidences = ({ str, intersection }: { str: string; intersection: string }) => const getSearchCoincidences = ({ str, intersection }: { str: string; intersection: string }) =>
`<span>${str.replaceAll( `<span>${str.replaceAll(
@ -24,16 +26,16 @@ const getSearchCoincidences = ({ str, intersection }: { str: string; intersectio
(casePreservedMatch) => `<span class="blackModeIntersection">${casePreservedMatch}</span>`, (casePreservedMatch) => `<span class="blackModeIntersection">${casePreservedMatch}</span>`,
)}</span>` )}</span>`
const prepareSearchResults = (list, searchValue) => const prepareSearchResults = (list: Shout[], searchValue: string) =>
list.map((article, index) => ({ list.sort(byScore()).map((article, index) => ({
...article, ...article,
body: '', body: article.body,
cover: '', cover: article.cover,
createdAt: '', created_at: article.created_at,
id: index, id: index,
slug: article.slug, slug: article.slug,
authors: [], authors: article.authors,
topics: [], topics: article.topics,
title: article.title title: article.title
? getSearchCoincidences({ ? getSearchCoincidences({
str: article.title, str: article.title,
@ -50,53 +52,76 @@ const prepareSearchResults = (list, searchValue) =>
export const SearchModal = () => { export const SearchModal = () => {
const { t } = useLocalize() const { t } = useLocalize()
const { sortedArticles } = useArticlesStore()
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
const [offset, setOffset] = createSignal(0)
const [inputValue, setInputValue] = createSignal('') const [inputValue, setInputValue] = createSignal('')
//const [searchResultsList, setSearchResultsList] = createSignal<[] | null>([])
const [isLoading, setIsLoading] = createSignal(false) const [isLoading, setIsLoading] = createSignal(false)
// const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) const [offset, setOffset] = createSignal<number>(0)
const [searchResultsList, { refetch: loadSearchResults, mutate: setSearchResultsList }] = createResource<
let searchEl: HTMLInputElement Shout[] | null
const handleQueryChange = async (_ev) => { >(
setInputValue(searchEl.value) async () => {
if (inputValue() && inputValue().length > 2) await loadMore()
}
const loadMore = async () => {
setIsLoading(true) setIsLoading(true)
saveScrollPosition() const { hasMore, newShouts } = await loadShoutsSearch({
if (inputValue() && inputValue().length > 2) { limit: FEED_PAGE_SIZE,
console.log(inputValue())
const { hasMore } = await loadShoutsSearch({
text: inputValue(), text: inputValue(),
offset: offset(), offset: offset(),
limit: FEED_PAGE_SIZE,
}) })
setIsLoading(false)
setOffset(newShouts.length)
setIsLoadMoreButtonVisible(hasMore) setIsLoadMoreButtonVisible(hasMore)
setOffset(offset() + FEED_PAGE_SIZE) return newShouts
},
{
ssrLoadFrom: 'initial',
initialValue: null,
},
)
let searchEl: HTMLInputElement
const debouncedLoadMore = debounce(500, loadSearchResults)
const handleQueryInput = async () => {
setInputValue(searchEl.value)
if (searchEl.value?.length > 2) {
await debouncedLoadMore()
} else { } else {
console.warn('[SaerchView] no query found') setIsLoading(false)
setSearchResultsList(null)
}
}
const enterQuery = async (ev: KeyboardEvent) => {
setIsLoading(true)
if (ev.key === 'Enter' && inputValue().length > 2) {
await debouncedLoadMore()
} else {
setIsLoading(false)
setSearchResultsList(null)
} }
restoreScrollPosition() restoreScrollPosition()
setIsLoading(false) setIsLoading(false)
} }
// Cleanup the debounce timer when the component unmounts
onCleanup(() => {
debouncedLoadMore.cancel()
// console.debug('[SearchModal] cleanup debouncing search')
})
return ( return (
<div class={styles.searchContainer}> <div class={styles.searchContainer}>
<input <input
type="search" type="search"
placeholder={t('Site search')} placeholder={t('Site search')}
class={styles.searchInput} class={styles.searchInput}
onInput={handleQueryChange} onInput={handleQueryInput}
onKeyDown={enterQuery}
ref={searchEl} ref={searchEl}
/> />
<Button <Button
class={styles.searchButton} class={styles.searchButton}
onClick={loadMore} onClick={debouncedLoadMore}
value={isLoading() ? <div class={styles.searchLoader} /> : <Icon name="search" />} value={isLoading() ? <div class={styles.searchLoader} /> : <Icon name="search" />}
/> />
@ -108,8 +133,8 @@ export const SearchModal = () => {
/> />
<Show when={!isLoading()}> <Show when={!isLoading()}>
<Show when={sortedArticles()}> <Show when={searchResultsList()}>
<For each={sortedArticles().sort(byScore())}> <For each={prepareSearchResults(searchResultsList(), inputValue())}>
{(article: Shout) => ( {(article: Shout) => (
<div> <div>
<SearchResultItem <SearchResultItem
@ -126,14 +151,14 @@ export const SearchModal = () => {
<Show when={isLoadMoreButtonVisible()}> <Show when={isLoadMoreButtonVisible()}>
<p class="load-more-container"> <p class="load-more-container">
<button class="button" onClick={loadMore}> <button class="button" onClick={loadSearchResults}>
{t('Load more')} {t('Load more')}
</button> </button>
</p> </p>
</Show> </Show>
</Show> </Show>
<Show when={!sortedArticles()}> <Show when={Array.isArray(searchResultsList()) && searchResultsList().length === 0}>
<p class={styles.searchDescription} innerHTML={t("We couldn't find anything for your request")} /> <p class={styles.searchDescription} innerHTML={t("We couldn't find anything for your request")} />
</Show> </Show>
</Show> </Show>

View File

@ -190,7 +190,7 @@ export const ProfileSettings = () => {
<h4>{t('Userpic')}</h4> <h4>{t('Userpic')}</h4>
<div class="pretty-form__item"> <div class="pretty-form__item">
<div <div
class={clsx(styles.userpic, { [styles.hasControls]: form.userpic })} class={clsx(styles.userpic, { [styles.hasControls]: form.pic })}
onClick={handleCropAvatar} onClick={handleCropAvatar}
> >
<Switch> <Switch>