docs: improved search results (#11909)
This commit is contained in:
1 parent
ee014e063e
commit
98e630bce5
12 files changed
+196
-152
No files matched your search
@@ -47,7 +47,6 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
|
|||||||
),
|
),
|
||||||
filterOptions: searchFilters,
|
filterOptions: searchFilters,
|
||||||
}}
|
}}
|
||||||
initialDefaultFilters={["guides"]}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</UiSearchProvider>
|
</UiSearchProvider>
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
|
|||||||
checkInternalPattern: new RegExp(`^${config.baseUrl}/resources/.*`),
|
checkInternalPattern: new RegExp(`^${config.baseUrl}/resources/.*`),
|
||||||
filterOptions: searchFilters,
|
filterOptions: searchFilters,
|
||||||
}}
|
}}
|
||||||
initialDefaultFilters={["guides"]}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</UiSearchProvider>
|
</UiSearchProvider>
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ const sidebarMappings: {
|
|||||||
"/medusa-cli",
|
"/medusa-cli",
|
||||||
"/js-sdk",
|
"/js-sdk",
|
||||||
"/nextjs-starter",
|
"/nextjs-starter",
|
||||||
|
"/references/js-sdk",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
|
|||||||
checkInternalPattern: new RegExp(`^${absoluteUrl()}/ui`),
|
checkInternalPattern: new RegExp(`^${absoluteUrl()}/ui`),
|
||||||
filterOptions: searchFilters,
|
filterOptions: searchFilters,
|
||||||
}}
|
}}
|
||||||
initialDefaultFilters={["ui"]}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</UiSearchProvider>
|
</UiSearchProvider>
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
|
|||||||
checkInternalPattern: new RegExp(`^${config.baseUrl}/user-guide`),
|
checkInternalPattern: new RegExp(`^${config.baseUrl}/user-guide`),
|
||||||
filterOptions: searchFilters,
|
filterOptions: searchFilters,
|
||||||
}}
|
}}
|
||||||
// TODO change later when we have a user guide filter
|
|
||||||
initialDefaultFilters={["guides"]}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</UiSearchProvider>
|
</UiSearchProvider>
|
||||||
|
|||||||
@@ -28,11 +28,13 @@ export type HitType = {
|
|||||||
}
|
}
|
||||||
_tags: string[]
|
_tags: string[]
|
||||||
url: string
|
url: string
|
||||||
|
url_without_anchor: string
|
||||||
type?: "lvl1" | "lvl2" | "lvl3" | "lvl4" | "lvl5" | "content"
|
type?: "lvl1" | "lvl2" | "lvl3" | "lvl4" | "lvl5" | "content"
|
||||||
content?: string
|
content?: string
|
||||||
__position: number
|
__position: number
|
||||||
__queryID?: string
|
__queryID?: string
|
||||||
objectID: string
|
objectID: string
|
||||||
|
description?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GroupedHitType = {
|
export type GroupedHitType = {
|
||||||
@@ -103,7 +105,7 @@ export const SearchHits = ({
|
|||||||
const { setIsOpen } = useSearch()
|
const { setIsOpen } = useSearch()
|
||||||
|
|
||||||
// group by lvl0
|
// group by lvl0
|
||||||
const grouped = useMemo(() => {
|
const grouped: GroupedHitType = useMemo(() => {
|
||||||
const grouped: GroupedHitType = {}
|
const grouped: GroupedHitType = {}
|
||||||
hits.forEach((hit) => {
|
hits.forEach((hit) => {
|
||||||
if (hit.hierarchy.lvl0) {
|
if (hit.hierarchy.lvl0) {
|
||||||
@@ -114,7 +116,16 @@ export const SearchHits = ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return grouped
|
// sort groups by number of hits
|
||||||
|
const sortedGroups = Object.fromEntries(
|
||||||
|
Object.entries(grouped).sort(([, a], [, b]) => {
|
||||||
|
const lvl1CountA = a.filter((hit) => hit.type === "lvl1").length
|
||||||
|
const lvl1CountB = b.filter((hit) => hit.type === "lvl1").length
|
||||||
|
|
||||||
|
return lvl1CountB - lvl1CountA
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return sortedGroups
|
||||||
}, [hits])
|
}, [hits])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -123,18 +134,6 @@ export const SearchHits = ({
|
|||||||
}
|
}
|
||||||
}, [hits, status])
|
}, [hits, status])
|
||||||
|
|
||||||
const getLastAvailableHeirarchy = (item: HitType) => {
|
|
||||||
return (
|
|
||||||
Object.keys(item.hierarchy)
|
|
||||||
.reverse()
|
|
||||||
.find(
|
|
||||||
(key) =>
|
|
||||||
item.hierarchy[key as Hierarchy] !== null &&
|
|
||||||
item.hierarchy[key as Hierarchy] !== item.content
|
|
||||||
) || ""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkIfInternal = (url: string): boolean => {
|
const checkIfInternal = (url: string): boolean => {
|
||||||
if (!checkInternalPattern) {
|
if (!checkInternalPattern) {
|
||||||
return false
|
return false
|
||||||
@@ -153,75 +152,71 @@ export const SearchHits = ({
|
|||||||
{Object.keys(grouped).map((groupName, index) => (
|
{Object.keys(grouped).map((groupName, index) => (
|
||||||
<Fragment key={index}>
|
<Fragment key={index}>
|
||||||
<SearchHitGroupName name={groupName} />
|
<SearchHitGroupName name={groupName} />
|
||||||
{grouped[groupName].map((item, index) => (
|
{grouped[groupName].map((item, index) => {
|
||||||
<div
|
const hierarchies = Object.values(item.hierarchy)
|
||||||
className={clsx(
|
.filter(Boolean)
|
||||||
"gap-docs_0.25 relative flex flex-1 flex-col p-docs_0.5",
|
.join(" › ")
|
||||||
"overflow-x-hidden text-ellipsis whitespace-nowrap break-words",
|
return (
|
||||||
"hover:bg-medusa-bg-base-hover",
|
<div
|
||||||
"focus:bg-medusa-bg-base-hover",
|
|
||||||
"last:mb-docs_1 focus:outline-none"
|
|
||||||
)}
|
|
||||||
key={index}
|
|
||||||
tabIndex={index}
|
|
||||||
data-hit
|
|
||||||
onClick={(e) => {
|
|
||||||
const target = e.target as Element
|
|
||||||
if (target.tagName.toLowerCase() === "div") {
|
|
||||||
target.querySelector("a")?.click()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"text-compact-small-plus text-medusa-fg-base",
|
"gap-docs_0.25 relative flex flex-1 flex-col p-docs_0.5",
|
||||||
"max-w-full"
|
"overflow-x-hidden text-ellipsis whitespace-nowrap break-words",
|
||||||
|
"hover:bg-medusa-bg-base-hover",
|
||||||
|
"focus:bg-medusa-bg-base-hover",
|
||||||
|
"last:mb-docs_1 focus:outline-none"
|
||||||
)}
|
)}
|
||||||
>
|
key={index}
|
||||||
{/* @ts-expect-error React v19 doesn't see this type as a React element */}
|
tabIndex={index}
|
||||||
<Snippet
|
data-hit
|
||||||
attribute={[
|
|
||||||
"hierarchy",
|
|
||||||
item.type && item.type !== "content"
|
|
||||||
? item.type
|
|
||||||
: getLastAvailableHeirarchy(item) ||
|
|
||||||
item.hierarchy.lvl1 ||
|
|
||||||
"",
|
|
||||||
]}
|
|
||||||
hit={item}
|
|
||||||
separator="."
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
{item.type !== "lvl1" && (
|
|
||||||
<span className="text-compact-small text-medusa-fg-subtle">
|
|
||||||
{/* @ts-expect-error React v19 doesn't see this type as a React element */}
|
|
||||||
<Snippet
|
|
||||||
attribute={
|
|
||||||
item.content
|
|
||||||
? "content"
|
|
||||||
: [
|
|
||||||
"hierarchy",
|
|
||||||
item.type || getLastAvailableHeirarchy(item),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
hit={item}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<Link
|
|
||||||
href={item.url}
|
|
||||||
className="absolute top-0 left-0 h-full w-full"
|
|
||||||
target="_self"
|
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (checkIfInternal(item.url)) {
|
const target = e.target as Element
|
||||||
e.preventDefault()
|
if (target.tagName.toLowerCase() === "div") {
|
||||||
window.location.href = item.url
|
target.querySelector("a")?.click()
|
||||||
setIsOpen(false)
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
</div>
|
<span
|
||||||
))}
|
className={clsx(
|
||||||
|
"text-compact-small-plus text-medusa-fg-base",
|
||||||
|
"max-w-full"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{/* @ts-expect-error React v19 doesn't see this type as a React element */}
|
||||||
|
<Snippet attribute={"hierarchy.lvl1"} hit={item} />
|
||||||
|
</span>
|
||||||
|
<span className="text-compact-small text-medusa-fg-subtle text-ellipsis overflow-hidden">
|
||||||
|
{item.type === "content" && (
|
||||||
|
<>
|
||||||
|
{/* @ts-expect-error React v19 doesn't see this type as a React element */}
|
||||||
|
<Snippet attribute={"content"} hit={item} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{item.type !== "content" && item.description}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
className={clsx(
|
||||||
|
"text-ellipsis overflow-hidden",
|
||||||
|
"text-medusa-fg-muted items-center text-compact-x-small"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{hierarchies}
|
||||||
|
</span>
|
||||||
|
<Link
|
||||||
|
href={item.url}
|
||||||
|
className="absolute top-0 left-0 h-full w-full"
|
||||||
|
target="_self"
|
||||||
|
onClick={(e) => {
|
||||||
|
if (checkIfInternal(item.url)) {
|
||||||
|
e.preventDefault()
|
||||||
|
window.location.href = item.url
|
||||||
|
setIsOpen(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import React, { useEffect, useRef, useState } from "react"
|
import React, { useEffect, useMemo, useRef, useState } from "react"
|
||||||
import { InstantSearch, SearchBox } from "react-instantsearch"
|
import { InstantSearch, SearchBox } from "react-instantsearch"
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { SearchEmptyQueryBoundary } from "./EmptyQueryBoundary"
|
import { SearchEmptyQueryBoundary } from "./EmptyQueryBoundary"
|
||||||
@@ -8,8 +8,7 @@ import { SearchSuggestions, type SearchSuggestionType } from "./Suggestions"
|
|||||||
import { AlgoliaProps, useSearch } from "@/providers"
|
import { AlgoliaProps, useSearch } from "@/providers"
|
||||||
import { checkArraySameElms } from "@/utils"
|
import { checkArraySameElms } from "@/utils"
|
||||||
import { SearchHitsWrapper } from "./Hits"
|
import { SearchHitsWrapper } from "./Hits"
|
||||||
import { Button, SelectBadge, SpinnerLoading } from "@/components"
|
import { SelectBadge, SpinnerLoading } from "@/components"
|
||||||
import { XMark } from "@medusajs/icons"
|
|
||||||
import { useSearchNavigation, type OptionType } from "@/hooks"
|
import { useSearchNavigation, type OptionType } from "@/hooks"
|
||||||
import { SearchFooter } from "./Footer"
|
import { SearchFooter } from "./Footer"
|
||||||
|
|
||||||
@@ -28,8 +27,7 @@ export const Search = ({
|
|||||||
checkInternalPattern,
|
checkInternalPattern,
|
||||||
filterOptions = [],
|
filterOptions = [],
|
||||||
}: SearchProps) => {
|
}: SearchProps) => {
|
||||||
const { isOpen, setIsOpen, defaultFilters, searchClient, modalRef } =
|
const { isOpen, defaultFilters, searchClient, modalRef } = useSearch()
|
||||||
useSearch()
|
|
||||||
const [filters, setFilters] = useState<string[]>(defaultFilters)
|
const [filters, setFilters] = useState<string[]>(defaultFilters)
|
||||||
const searchBoxRef = useRef<HTMLFormElement>(null)
|
const searchBoxRef = useRef<HTMLFormElement>(null)
|
||||||
|
|
||||||
@@ -59,6 +57,14 @@ export const Search = ({
|
|||||||
}
|
}
|
||||||
}, [isOpen])
|
}, [isOpen])
|
||||||
|
|
||||||
|
const facetFilters = useMemo(() => {
|
||||||
|
const filtersToUse =
|
||||||
|
!filters.length || filters[0] === "all"
|
||||||
|
? filterOptions.map((option) => option.value)
|
||||||
|
: filters
|
||||||
|
return filtersToUse.map((filter) => "_tags:" + filter)
|
||||||
|
}, [filters, defaultFilters])
|
||||||
|
|
||||||
useSearchNavigation({
|
useSearchNavigation({
|
||||||
getInputElm: () =>
|
getInputElm: () =>
|
||||||
searchBoxRef.current?.querySelector("input") as HTMLInputElement,
|
searchBoxRef.current?.querySelector("input") as HTMLInputElement,
|
||||||
@@ -70,7 +76,7 @@ export const Search = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
{filterOptions.length && (
|
{filterOptions.length > 0 && (
|
||||||
<SelectBadge
|
<SelectBadge
|
||||||
multiple
|
multiple
|
||||||
options={filterOptions}
|
options={filterOptions}
|
||||||
@@ -134,7 +140,15 @@ export const Search = ({
|
|||||||
loadingIconComponent={() => <SpinnerLoading />}
|
loadingIconComponent={() => <SpinnerLoading />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="md:flex-initial h-[calc(100%-95px)] lg:max-h-[calc(100%-140px)] lg:min-h-[calc(100%-140px)]">
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"md:flex-initial",
|
||||||
|
filterOptions.length > 0 &&
|
||||||
|
"h-[calc(100%-95px)] lg:max-h-[calc(100%-140px)] lg:min-h-[calc(100%-140px)]",
|
||||||
|
filterOptions.length === 0 &&
|
||||||
|
"h-[calc(100%-75px)] lg:max-h-[calc(100%-100px)] lg:min-h-[calc(100%-100px)]"
|
||||||
|
)}
|
||||||
|
>
|
||||||
<SearchEmptyQueryBoundary
|
<SearchEmptyQueryBoundary
|
||||||
fallback={<SearchSuggestions suggestions={suggestions} />}
|
fallback={<SearchSuggestions suggestions={suggestions} />}
|
||||||
>
|
>
|
||||||
@@ -143,7 +157,8 @@ export const Search = ({
|
|||||||
// filters array has to be wrapped
|
// filters array has to be wrapped
|
||||||
// in another array for an OR condition
|
// in another array for an OR condition
|
||||||
// to be applied between the items.
|
// to be applied between the items.
|
||||||
tagFilters: [filters],
|
facetFilters: [facetFilters],
|
||||||
|
getRankingInfo: true,
|
||||||
}}
|
}}
|
||||||
indices={algolia.indices}
|
indices={algolia.indices}
|
||||||
checkInternalPattern={checkInternalPattern}
|
checkInternalPattern={checkInternalPattern}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export const SelectBadge = ({
|
|||||||
(!Array.isArray(value) && !value) ||
|
(!Array.isArray(value) && !value) ||
|
||||||
(Array.isArray(value) && !value.length)
|
(Array.isArray(value) && !value.length)
|
||||||
) {
|
) {
|
||||||
str = "None selected"
|
str = "No Filters Selected"
|
||||||
} else {
|
} else {
|
||||||
str = selectedOptions[0].label
|
str = selectedOptions[0].label
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,11 +168,12 @@ export const navDropdownItems: NavigationItem[] = [
|
|||||||
|
|
||||||
export const searchFilters: OptionType[] = [
|
export const searchFilters: OptionType[] = [
|
||||||
{
|
{
|
||||||
value: "guides",
|
value: "concepts-guides",
|
||||||
label: "Guides",
|
label: "Concepts & Guides",
|
||||||
|
hitsPerPage: 8,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "references-v2",
|
value: "references",
|
||||||
label: "References",
|
label: "References",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -184,7 +185,11 @@ export const searchFilters: OptionType[] = [
|
|||||||
label: "Store API",
|
label: "Store API",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "ui",
|
value: "user-guide",
|
||||||
label: "Medusa UI",
|
label: "User Guide",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "troubleshooting",
|
||||||
|
label: "Troubleshooting",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -5,6 +5,7 @@ export type OptionType = {
|
|||||||
label: string
|
label: string
|
||||||
index?: string
|
index?: string
|
||||||
isAllOption?: boolean
|
isAllOption?: boolean
|
||||||
|
hitsPerPage?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SelectOptions = {
|
export type SelectOptions = {
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export const MainContentLayout = ({
|
|||||||
"pt-docs_4 lg:pt-docs_6 pb-docs_8 lg:pb-docs_4",
|
"pt-docs_4 lg:pt-docs_6 pb-docs_8 lg:pb-docs_4",
|
||||||
contentClassName
|
contentClassName
|
||||||
)}
|
)}
|
||||||
|
id="content"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
// @ts-expect-error can't install the types package because it doesn't support React v19
|
// @ts-expect-error can't install the types package because it doesn't support React v19
|
||||||
import { CSSTransition, SwitchTransition } from "react-transition-group"
|
import { CSSTransition, SwitchTransition } from "react-transition-group"
|
||||||
|
import { searchFilters } from "../.."
|
||||||
|
|
||||||
export type SearchCommand = {
|
export type SearchCommand = {
|
||||||
name: string
|
name: string
|
||||||
@@ -87,36 +88,8 @@ export const SearchProvider = ({
|
|||||||
"requests" in searchParams ? searchParams.requests : searchParams
|
"requests" in searchParams ? searchParams.requests : searchParams
|
||||||
// always send this request, which is the main request with no filters
|
// always send this request, which is the main request with no filters
|
||||||
const mainRequest = requests[0]
|
const mainRequest = requests[0]
|
||||||
|
const params = (mainRequest.params || {}) as Record<string, unknown>
|
||||||
// retrieve only requests that have filters
|
if (!params.query) {
|
||||||
// this is to ensure that we show no result if no filter is selected
|
|
||||||
const requestsWithFilters = requests.filter((item) => {
|
|
||||||
if (
|
|
||||||
!item.params ||
|
|
||||||
typeof item.params !== "object" ||
|
|
||||||
!("tagFilters" in item.params)
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const tagFilters = item.params.tagFilters as string[]
|
|
||||||
|
|
||||||
// if no tag filters are specified, there's still one item,
|
|
||||||
// which is an empty array
|
|
||||||
return tagFilters.length >= 1 && tagFilters[0].length > 0
|
|
||||||
})
|
|
||||||
|
|
||||||
// check whether a query is entered in the search box
|
|
||||||
const noQueries = requestsWithFilters.every(
|
|
||||||
(item) =>
|
|
||||||
!item.facetQuery &&
|
|
||||||
(!item.params ||
|
|
||||||
typeof item.params !== "object" ||
|
|
||||||
!("query" in item.params) ||
|
|
||||||
!item.params.query)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (noQueries) {
|
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
results: requests.map(() => ({
|
results: requests.map(() => ({
|
||||||
hits: [],
|
hits: [],
|
||||||
@@ -132,36 +105,75 @@ export const SearchProvider = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// split requests per tags
|
// retrieve only requests that have filters
|
||||||
const newRequests: typeof requestsWithFilters = [mainRequest]
|
// this is to ensure that we show no result if no filter is selected
|
||||||
for (const request of requestsWithFilters) {
|
const requestsWithFilters = requests.filter((item) => {
|
||||||
const params = request.params as Record<string, unknown>
|
if (
|
||||||
const tagFilters = (params.tagFilters as string[][])[0]
|
!item.params ||
|
||||||
|
typeof item.params !== "object" ||
|
||||||
// if only one tag is selected, keep the request as-is
|
!("facetFilters" in item.params)
|
||||||
if (tagFilters.length === 1) {
|
) {
|
||||||
newRequests.push(request)
|
return false
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if multiple tags are selected, split the tags
|
const facetFilters = item.params.facetFilters as string[]
|
||||||
// to retrieve a small subset of results per each tag.
|
|
||||||
newRequests.push(
|
// if no tag filters are specified, there's still one item,
|
||||||
...tagFilters.map((tag) => ({
|
// which is an empty array
|
||||||
...request,
|
return facetFilters.length >= 1 && facetFilters[0].length > 0
|
||||||
params: {
|
})
|
||||||
...params,
|
|
||||||
tagFilters: [tag],
|
// check whether a query is entered in the search box
|
||||||
},
|
const noQueries = requestsWithFilters.every(
|
||||||
hitsPerPage: 4,
|
(item) =>
|
||||||
}))
|
!item.facetQuery &&
|
||||||
)
|
(!item.params ||
|
||||||
|
typeof item.params !== "object" ||
|
||||||
|
!("query" in item.params) ||
|
||||||
|
!item.params.query)
|
||||||
|
)
|
||||||
|
|
||||||
|
const newRequests: typeof requestsWithFilters = [mainRequest]
|
||||||
|
if (!noQueries) {
|
||||||
|
// split requests per tags
|
||||||
|
for (const request of requestsWithFilters) {
|
||||||
|
const params = request.params as Record<string, unknown>
|
||||||
|
const facetFilters = (params.facetFilters as string[][])[0]
|
||||||
|
|
||||||
|
// if only one tag is selected, keep the request as-is
|
||||||
|
if (facetFilters.length === 1) {
|
||||||
|
newRequests.push(request)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// if multiple tags are selected, split the tags
|
||||||
|
// to retrieve a small subset of results per each tag.
|
||||||
|
newRequests.push(
|
||||||
|
...facetFilters.map((tag) => {
|
||||||
|
// get the filter's details in case it has custom hitsPerPage
|
||||||
|
const filterDetails = searchFilters.find(
|
||||||
|
(item) => `_tags:${item.value}` === tag
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
...request,
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
facetFilters: [tag],
|
||||||
|
},
|
||||||
|
hitsPerPage: filterDetails?.hitsPerPage || 3,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return algoliaClient
|
return algoliaClient
|
||||||
.search<SearchHits>(newRequests)
|
.search<SearchHits>(newRequests)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
if (newRequests.length === 1) {
|
||||||
|
return response
|
||||||
|
}
|
||||||
// combine results of the same index and return the results
|
// combine results of the same index and return the results
|
||||||
const resultsByIndex: {
|
const resultsByIndex: {
|
||||||
[indexName: string]: SearchResponse<SearchHits>
|
[indexName: string]: SearchResponse<SearchHits>
|
||||||
@@ -194,9 +206,28 @@ export const SearchProvider = ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const newResults = Object.values(resultsByIndex).flatMap(
|
||||||
|
(result) => ({
|
||||||
|
...result,
|
||||||
|
hits: ("hits" in result ? result.hits : []).sort((a, b) => {
|
||||||
|
const typosA = a._rankingInfo?.nbTypos || 0
|
||||||
|
const typosB = b._rankingInfo?.nbTypos || 0
|
||||||
|
if (a.type === "lvl1" && typosA <= typosB) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b.type === "lvl1" && typosB <= typosA) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// append the results with the main request's results
|
// append the results with the main request's results
|
||||||
results: [mainResult, ...Object.values(resultsByIndex)],
|
results: [mainResult, ...newResults],
|
||||||
} as SearchResponses<any>
|
} as SearchResponses<any>
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -239,7 +270,7 @@ export const SearchProvider = ({
|
|||||||
"!p-0 overflow-hidden relative h-full",
|
"!p-0 overflow-hidden relative h-full",
|
||||||
"flex flex-col justify-between"
|
"flex flex-col justify-between"
|
||||||
)}
|
)}
|
||||||
modalContainerClassName="!h-[480px] max-h-[480px]"
|
modalContainerClassName="!h-[95%] max-h-[95%] md:!h-[480px] md:max-h-[480px]"
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
onClose={() => setIsOpen(false)}
|
onClose={() => setIsOpen(false)}
|
||||||
passedRef={modalRef}
|
passedRef={modalRef}
|
||||||
|
|||||||
Reference in new issue
Block a user