Merge branch 'feature/search-modal' into dev
This commit is contained in:
commit
1910a3c3c4
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user