106 lines
3.3 KiB
TypeScript
106 lines
3.3 KiB
TypeScript
import useSWR from 'swr'
|
|
import { useRouter } from 'next/router'
|
|
import Head from 'next/head'
|
|
import { Heading } from '@primer/react'
|
|
|
|
import { sendEvent, EventType } from 'components/lib/events'
|
|
import { useTranslation } from 'components/hooks/useTranslation'
|
|
import { DEFAULT_VERSION, useVersion } from 'components/hooks/useVersion'
|
|
import { useNumberFormatter } from 'components/hooks/useNumberFormatter'
|
|
import type { SearchResultsT } from 'components/search/types'
|
|
import { SearchResults } from 'components/search/SearchResults'
|
|
import { SearchError } from 'components/search/SearchError'
|
|
import { NoQuery } from 'components/search/NoQuery'
|
|
import { Loading } from 'components/search/Loading'
|
|
import { useQuery } from 'components/hooks/useQuery'
|
|
import { usePage } from 'components/hooks/usePage'
|
|
import { useMainContext } from 'components/context/MainContext'
|
|
|
|
export function Search() {
|
|
const { locale } = useRouter()
|
|
const { formatInteger } = useNumberFormatter()
|
|
const { t } = useTranslation('search')
|
|
const { currentVersion } = useVersion()
|
|
const { query, debug } = useQuery()
|
|
const { page } = usePage()
|
|
|
|
// A reference to the `content/search/index.md` Page object.
|
|
// Not to be confused with the "page" that is for paginating
|
|
// results.
|
|
const { allVersions, page: documentPage } = useMainContext()
|
|
const searchVersion = allVersions[currentVersion].versionTitle
|
|
|
|
const sp = new URLSearchParams()
|
|
const hasQuery = Boolean(query.trim())
|
|
if (hasQuery) {
|
|
sp.set('query', query.trim())
|
|
sp.set('language', locale || 'en')
|
|
if (debug) sp.set('debug', 'true')
|
|
sp.set('version', currentVersion)
|
|
if (page !== 1) {
|
|
sp.set('page', `${page}`)
|
|
}
|
|
}
|
|
|
|
const inDebugMode = process.env.NODE_ENV === 'development'
|
|
|
|
const { data: results, error } = useSWR<SearchResultsT | null, Error | null>(
|
|
hasQuery ? `/api/search/v1?${sp.toString()}` : null,
|
|
async (url) => {
|
|
const response = await fetch(url)
|
|
if (!response.ok) {
|
|
throw new Error(`${response.status} on ${url}`)
|
|
}
|
|
return await response.json()
|
|
},
|
|
{
|
|
onSuccess: () => {
|
|
sendEvent({
|
|
type: EventType.search,
|
|
search_query: query,
|
|
})
|
|
},
|
|
// Because the backend never changes between fetches, we can treat
|
|
// it as an immutable resource and disable these revalidation
|
|
// checks.
|
|
revalidateIfStale: inDebugMode,
|
|
revalidateOnFocus: inDebugMode,
|
|
revalidateOnReconnect: inDebugMode,
|
|
}
|
|
)
|
|
|
|
let pageTitle = documentPage.fullTitle
|
|
if (hasQuery) {
|
|
pageTitle = `${t('search_results_for')} "${query.trim()}"`
|
|
if (currentVersion !== DEFAULT_VERSION) {
|
|
pageTitle += ` (${searchVersion})`
|
|
}
|
|
if (results) {
|
|
pageTitle = `${formatInteger(results.meta.found.value)} ${pageTitle}`
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="container-xl px-3 px-md-6 my-4" data-testid="search-results">
|
|
<Head>
|
|
<title>{pageTitle}</title>
|
|
</Head>
|
|
{hasQuery && (
|
|
<Heading as="h1" className="mb-2">
|
|
{t('search_results_for')} "{query.trim()}"
|
|
</Heading>
|
|
)}
|
|
|
|
{error ? (
|
|
<SearchError error={error} />
|
|
) : results ? (
|
|
<SearchResults results={results} query={query} />
|
|
) : hasQuery ? (
|
|
<Loading />
|
|
) : (
|
|
<NoQuery />
|
|
)}
|
|
</div>
|
|
)
|
|
}
|