docs: remove custom searching mechanism (#11984)

* docs: remove custom searching mechanism

* remove grouping

* changes to grouping

* small title change

* update resources sitemap
This commit is contained in:
Shahed Nasser authored and GitHub committed 2025-03-26 10:56:02 +02:00
1 parent 87b75ea959
commit c96560ccb7
10 files changed
+316 -250

No files matched your search

+8 -2
View File
@@ -21,8 +21,14 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
apiKey: process.env.NEXT_PUBLIC_ALGOLIA_API_KEY || "temp", apiKey: process.env.NEXT_PUBLIC_ALGOLIA_API_KEY || "temp",
mainIndexName: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp", mainIndexName: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
indices: [ indices: [
process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp", {
process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp", name: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
title: "Store & Admin API",
},
{
name: process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
title: "Docs",
},
], ],
}} }}
searchProps={{ searchProps={{
@@ -1,5 +1,5 @@
export const metadata = { export const metadata = {
title: `${pageNumber} Module Link`, title: `${pageNumber} Define Module Link`,
} }
# {metadata.title} # {metadata.title}
+8 -2
View File
@@ -16,8 +16,14 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
mainIndexName: mainIndexName:
process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp", process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
indices: [ indices: [
process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp", {
process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp", name: process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
title: "Docs",
},
{
name: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
title: "Store & Admin API",
},
], ],
}} }}
searchProps={{ searchProps={{
+46 -1
View File
@@ -5,9 +5,54 @@ import { config } from "../config"
import { basePathUrl } from "../utils/base-path-url" import { basePathUrl } from "../utils/base-path-url"
export default function sitemap(): MetadataRoute.Sitemap { export default function sitemap(): MetadataRoute.Sitemap {
return retrieveMdxPages({ const items = retrieveMdxPages({
basePath: path.resolve("app"), basePath: path.resolve("app"),
}).map((filePath) => ({ }).map((filePath) => ({
url: `${config.baseUrl}${basePathUrl(filePath)}`, url: `${config.baseUrl}${basePathUrl(filePath)}`,
})) }))
// add some references
items.push(
{
url: `${config.baseUrl}${basePathUrl("/references/file-provider-module")}`,
},
{
url: `${config.baseUrl}${basePathUrl("/references/file-service")}`,
},
{
url: `${config.baseUrl}${basePathUrl("/references/locking-module-provider")}`,
},
{
url: `${config.baseUrl}${basePathUrl("/references/locking-service")}`,
},
{
url: `${config.baseUrl}${basePathUrl("/references/notification-provider-module")}`,
},
{
url: `${config.baseUrl}${basePathUrl("/references/notification-service")}`,
},
{
url: `${config.baseUrl}${basePathUrl("/references/event-service")}`,
},
{
url: `${config.baseUrl}${basePathUrl("/references/cache-service")}`,
},
{
url: `${config.baseUrl}${basePathUrl("/references/file-service")}`,
},
{
url: `${config.baseUrl}${basePathUrl("/references/auth/provider")}`,
},
{
url: `${config.baseUrl}${basePathUrl("/references/fulfillment/provider")}`,
},
{
url: `${config.baseUrl}${basePathUrl("/references/tax/provider")}`,
},
{
url: `${config.baseUrl}${basePathUrl("/references/payment/provider")}`,
}
)
return items
} }
+8 -2
View File
@@ -16,8 +16,14 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
mainIndexName: mainIndexName:
process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp", process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
indices: [ indices: [
process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp", {
process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp", name: process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
title: "Docs",
},
{
name: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
title: "Store & Admin API",
},
], ],
}} }}
searchProps={{ searchProps={{
+8 -2
View File
@@ -16,8 +16,14 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
mainIndexName: mainIndexName:
process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp", process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
indices: [ indices: [
process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp", {
process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp", name: process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
title: "Docs",
},
{
name: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
title: "Store & Admin API",
},
], ],
}} }}
searchProps={{ searchProps={{
+8 -2
View File
@@ -16,8 +16,14 @@ const SearchProvider = ({ children }: SearchProviderProps) => {
mainIndexName: mainIndexName:
process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp", process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
indices: [ indices: [
process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp", {
process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp", name: process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
title: "Docs",
},
{
name: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
title: "Store & Admin API",
},
], ],
}} }}
searchProps={{ searchProps={{
@@ -11,9 +11,8 @@ import {
useInstantSearch, useInstantSearch,
} from "react-instantsearch" } from "react-instantsearch"
import { SearchNoResult } from "../NoResults" import { SearchNoResult } from "../NoResults"
import { SearchHitGroupName } from "./GroupName" import { AlgoliaIndex, useSearch } from "@/providers"
import { useSearch } from "@/providers" import { Link, SearchHitGroupName } from "@/components"
import { Link } from "@/components"
export type Hierarchy = "lvl0" | "lvl1" | "lvl2" | "lvl3" | "lvl4" | "lvl5" export type Hierarchy = "lvl0" | "lvl1" | "lvl2" | "lvl3" | "lvl4" | "lvl5"
@@ -43,7 +42,7 @@ export type GroupedHitType = {
export type SearchHitWrapperProps = { export type SearchHitWrapperProps = {
configureProps: ConfigureProps configureProps: ConfigureProps
indices: string[] indices: AlgoliaIndex[]
} & Omit<SearchHitsProps, "indexName" | "setNoResults"> } & Omit<SearchHitsProps, "indexName" | "setNoResults">
export type IndexResults = { export type IndexResults = {
@@ -57,8 +56,8 @@ export const SearchHitsWrapper = ({
}: SearchHitWrapperProps) => { }: SearchHitWrapperProps) => {
const { status } = useInstantSearch() const { status } = useInstantSearch()
const [hasNoResults, setHashNoResults] = useState<IndexResults>({ const [hasNoResults, setHashNoResults] = useState<IndexResults>({
[indices[0]]: false, [indices[0].name]: false,
[indices[1]]: false, [indices[1].name]: false,
}) })
const showNoResults = useMemo(() => { const showNoResults = useMemo(() => {
return Object.values(hasNoResults).every((value) => value === true) return Object.values(hasNoResults).every((value) => value === true)
@@ -74,11 +73,14 @@ export const SearchHitsWrapper = ({
return ( return (
<div className="h-full overflow-auto px-docs_0.5"> <div className="h-full overflow-auto px-docs_0.5">
{status !== "loading" && showNoResults && <SearchNoResult />} {status !== "loading" && showNoResults && <SearchNoResult />}
{indices.map((indexName, index) => ( {indices.map((index, key) => (
// @ts-expect-error React v19 doesn't see this type as a React element // @ts-expect-error React v19 doesn't see this type as a React element
<Index indexName={indexName} key={index}> <Index indexName={index.name} key={key}>
{!hasNoResults[index.name] && (
<SearchHitGroupName name={index.title} />
)}
<SearchHits <SearchHits
indexName={indexName} indexName={index.name}
setNoResults={setNoResults} setNoResults={setNoResults}
{...rest} {...rest}
/> />
@@ -104,30 +106,6 @@ export const SearchHits = ({
const { status } = useInstantSearch() const { status } = useInstantSearch()
const { setIsOpen } = useSearch() const { setIsOpen } = useSearch()
// group by lvl0
const grouped: GroupedHitType = useMemo(() => {
const grouped: GroupedHitType = {}
hits.forEach((hit) => {
if (hit.hierarchy.lvl0) {
if (!grouped[hit.hierarchy.lvl0]) {
grouped[hit.hierarchy.lvl0] = []
}
grouped[hit.hierarchy.lvl0].push(hit)
}
})
// 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])
useEffect(() => { useEffect(() => {
if (status !== "loading" && status !== "stalled") { if (status !== "loading" && status !== "stalled") {
setNoResults(indexName, hits.length === 0) setNoResults(indexName, hits.length === 0)
@@ -149,76 +127,71 @@ export const SearchHits = ({
"[&_mark]:text-medusa-fg-interactive" "[&_mark]:text-medusa-fg-interactive"
)} )}
> >
{Object.keys(grouped).map((groupName, index) => ( {hits.map((item, index) => {
<Fragment key={index}> const hierarchies = Object.values(item.hierarchy)
<SearchHitGroupName name={groupName} /> .filter(Boolean)
{grouped[groupName].map((item, index) => { .join(" › ")
const hierarchies = Object.values(item.hierarchy) return (
.filter(Boolean) <div
.join(" › ") className={clsx(
return ( "gap-docs_0.25 relative flex flex-1 flex-col p-docs_0.5",
<div "overflow-x-hidden text-ellipsis whitespace-nowrap break-words",
className={clsx( "hover:bg-medusa-bg-base-hover",
"gap-docs_0.25 relative flex flex-1 flex-col p-docs_0.5", "focus:bg-medusa-bg-base-hover",
"overflow-x-hidden text-ellipsis whitespace-nowrap break-words", "focus:outline-none"
"hover:bg-medusa-bg-base-hover", )}
"focus:bg-medusa-bg-base-hover", key={index}
"last:mb-docs_1 focus:outline-none" tabIndex={index}
)} data-hit
key={index} onClick={(e) => {
tabIndex={index} const target = e.target as Element
data-hit if (target.tagName.toLowerCase() === "div") {
onClick={(e) => { target.querySelector("a")?.click()
const target = e.target as Element }
if (target.tagName.toLowerCase() === "div") { }}
target.querySelector("a")?.click() >
} <span
}} className={clsx(
> "text-compact-small-plus text-medusa-fg-base",
<span "max-w-full"
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 */} {/* @ts-expect-error React v19 doesn't see this type as a React element */}
<Snippet attribute={"hierarchy.lvl1"} hit={item} /> <Snippet attribute={"content"} hit={item} />
</span> </>
<span className="text-compact-small text-medusa-fg-subtle text-ellipsis overflow-hidden"> )}
{item.type === "content" && ( {item.type !== "content" && item.description}
<> </span>
{/* @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 <span
className={clsx( className={clsx(
"text-ellipsis overflow-hidden", "text-ellipsis overflow-hidden",
"text-medusa-fg-muted items-center text-compact-x-small" "text-medusa-fg-muted items-center text-compact-x-small"
)} )}
> >
{hierarchies} {hierarchies}
</span> </span>
<Link <Link
href={item.url} href={item.url}
className="absolute top-0 left-0 h-full w-full" className="absolute top-0 left-0 h-full w-full"
target="_self" target="_self"
onClick={(e) => { onClick={(e) => {
if (checkIfInternal(item.url)) { if (checkIfInternal(item.url)) {
e.preventDefault() e.preventDefault()
window.location.href = item.url window.location.href = item.url
setIsOpen(false) setIsOpen(false)
} }
}} }}
/> />
</div> </div>
) )
})} })}
</Fragment>
))}
</div> </div>
) )
} }
@@ -159,6 +159,7 @@ export const Search = ({
// to be applied between the items. // to be applied between the items.
facetFilters: [facetFilters], facetFilters: [facetFilters],
getRankingInfo: true, getRankingInfo: true,
hitsPerPage: 3,
}} }}
indices={algolia.indices} indices={algolia.indices}
checkInternalPattern={checkInternalPattern} checkInternalPattern={checkInternalPattern}
@@ -13,14 +13,10 @@ import { checkArraySameElms } from "../../utils"
import { import {
liteClient as algoliasearch, liteClient as algoliasearch,
LiteClient as SearchClient, LiteClient as SearchClient,
type SearchResponses,
type SearchHits,
SearchResponse,
} from "algoliasearch/lite" } from "algoliasearch/lite"
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
@@ -46,11 +42,16 @@ export type SearchContextType = {
const SearchContext = createContext<SearchContextType | null>(null) const SearchContext = createContext<SearchContextType | null>(null)
export type AlgoliaIndex = {
name: string
title: string
}
export type AlgoliaProps = { export type AlgoliaProps = {
appId: string appId: string
apiKey: string apiKey: string
mainIndexName: string mainIndexName: string
indices: string[] indices: AlgoliaIndex[]
} }
export type SearchProviderProps = { export type SearchProviderProps = {
@@ -83,154 +84,170 @@ export const SearchProvider = ({
const algoliaClient = algoliasearch(algolia.appId, algolia.apiKey) const algoliaClient = algoliasearch(algolia.appId, algolia.apiKey)
return { return {
...algoliaClient, ...algoliaClient,
async search(searchParams) { // async search(searchParams) {
const requests = // const requests =
"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> // const params = (mainRequest.params || {}) as Record<string, unknown>
if (!params.query) { // if (!params.query) {
return Promise.resolve({ // return Promise.resolve({
results: requests.map(() => ({ // results: requests.map(() => ({
hits: [], // hits: [],
nbHits: 0, // nbHits: 0,
nbPages: 0, // nbPages: 0,
page: 0, // page: 0,
processingTimeMS: 0, // processingTimeMS: 0,
hitsPerPage: 0, // hitsPerPage: 0,
exhaustiveNbHits: false, // exhaustiveNbHits: false,
query: "", // query: "",
params: "", // params: "",
})), // })),
}) // })
} // }
// retrieve only requests that have filters // // retrieve only requests that have filters
// this is to ensure that we show no result if no filter is selected // // this is to ensure that we show no result if no filter is selected
const requestsWithFilters = requests.filter((item) => { // const requestsWithFilters = requests.filter((item) => {
if ( // if (
!item.params || // !item.params ||
typeof item.params !== "object" || // typeof item.params !== "object" ||
!("facetFilters" in item.params) // !("facetFilters" in item.params)
) { // ) {
return false // return false
} // }
const facetFilters = item.params.facetFilters as string[] // const facetFilters = item.params.facetFilters as string[]
// if no tag filters are specified, there's still one item, // // if no tag filters are specified, there's still one item,
// which is an empty array // // which is an empty array
return facetFilters.length >= 1 && facetFilters[0].length > 0 // return facetFilters.length >= 1 && facetFilters[0].length > 0
}) // })
// check whether a query is entered in the search box // // check whether a query is entered in the search box
const noQueries = requestsWithFilters.every( // const noQueries = requestsWithFilters.every(
(item) => // (item) =>
!item.facetQuery && // !item.facetQuery &&
(!item.params || // (!item.params ||
typeof item.params !== "object" || // typeof item.params !== "object" ||
!("query" in item.params) || // !("query" in item.params) ||
!item.params.query) // !item.params.query)
) // )
const newRequests: typeof requestsWithFilters = [mainRequest] // const newRequests: typeof requestsWithFilters = [mainRequest]
if (!noQueries) { // if (!noQueries) {
// split requests per tags // // split requests per tags
for (const request of requestsWithFilters) { // for (const request of requestsWithFilters) {
const params = request.params as Record<string, unknown> // const params = request.params as Record<string, unknown>
const facetFilters = (params.facetFilters as string[][])[0] // const facetFilters = (params.facetFilters as string[][])[0]
// if only one tag is selected, keep the request as-is // // if only one tag is selected, keep the request as-is
if (facetFilters.length === 1) { // if (facetFilters.length === 1) {
newRequests.push(request) // newRequests.push(request)
continue // continue
} // }
// if multiple tags are selected, split the tags // // if multiple tags are selected, split the tags
// to retrieve a small subset of results per each tag. // // to retrieve a small subset of results per each tag.
newRequests.push( // newRequests.push(
...facetFilters.map((tag) => { // ...facetFilters.map((tag) => {
// get the filter's details in case it has custom hitsPerPage // // get the filter's details in case it has custom hitsPerPage
const filterDetails = searchFilters.find( // const filterDetails = searchFilters.find(
(item) => `_tags:${item.value}` === tag // (item) => `_tags:${item.value}` === tag
) // )
return { // return {
...request, // ...request,
params: { // params: {
...params, // ...params,
facetFilters: [tag], // facetFilters: [tag],
}, // },
hitsPerPage: filterDetails?.hitsPerPage || 3, // hitsPerPage: filterDetails?.hitsPerPage || 3,
} // }
}) // })
) // )
} // }
} // }
return algoliaClient // return algoliaClient
.search<SearchHits>(newRequests) // .search<ExpandedHits>(newRequests)
.then((response) => { // .then((response) => {
if (newRequests.length === 1) { // if (newRequests.length === 1) {
return response // 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<ExpandedHits>
} = {} // } = {}
// extract the response of the main request // // extract the response of the main request
const mainResult = response.results[0] // const mainResult = response.results[0]
response.results.forEach((result, indexNum) => { // response.results.forEach((result, indexNum) => {
if (indexNum === 0) { // if (indexNum === 0) {
// ignore the main request's result // // ignore the main request's result
return // return
} // }
const resultIndex = "index" in result ? result.index : undefined // const resultIndex = "index" in result ? result.index : undefined
const resultHits = "hits" in result ? result.hits : [] // const resultHits = "hits" in result ? result.hits : []
if (!resultIndex) { // if (!resultIndex) {
return // return
} // }
resultsByIndex[resultIndex] = { // resultsByIndex[resultIndex] = {
...result, // ...result,
...(resultsByIndex[resultIndex] || {}), // ...(resultsByIndex[resultIndex] || {}),
hits: [ // hits: [
...(resultsByIndex[resultIndex]?.hits || []), // ...(resultsByIndex[resultIndex]?.hits || []),
...resultHits, // ...resultHits,
], // ],
nbHits: // nbHits:
(resultsByIndex[resultIndex]?.nbHits || 0) + // (resultsByIndex[resultIndex]?.nbHits || 0) +
resultHits.length, // resultHits.length,
} // }
}) // })
const newResults = Object.values(resultsByIndex).flatMap( // const newResults = Object.values(resultsByIndex).flatMap(
(result) => ({ // (result) => ({
...result, // ...result,
hits: ("hits" in result ? result.hits : []).sort((a, b) => { // hits: ("hits" in result ? result.hits : []).sort((a, b) => {
const typosA = a._rankingInfo?.nbTypos || 0 // const typosA = a._rankingInfo?.nbTypos || 0
const typosB = b._rankingInfo?.nbTypos || 0 // const typosB = b._rankingInfo?.nbTypos || 0
if (a.type === "lvl1" && typosA <= typosB) { // const tagASortOrder =
return -1 // searchFilters.find((item) =>
} // a._tags.find((tag) => tag === item.value)
// )?.internalSortOrder || 0
// const tagBSortorder =
// searchFilters.find((item) =>
// b._tags.find((tag) => tag === item.value)
// )?.internalSortOrder || 0
// if (
// a.type === "lvl1" &&
// typosA <= typosB &&
// tagASortOrder >= tagBSortorder
// ) {
// return -1
// }
if (b.type === "lvl1" && typosB <= typosA) { // if (
return 1 // b.type === "lvl1" &&
} // typosB <= typosA &&
// tagBSortorder >= tagASortOrder
// ) {
// return 1
// }
return 0 // return 0
}), // }),
}) // })
) // )
return { // return {
// append the results with the main request's results // // append the results with the main request's results
results: [mainResult, ...newResults], // results: [mainResult, ...newResults],
} as SearchResponses<any> // } as SearchResponses<any>
}) // })
}, // },
} }
}, [algolia.appId, algolia.apiKey]) }, [algolia.appId, algolia.apiKey])