@docusaurus/theme-search-algolia 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,4 +4,6 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
+ export { useAlgoliaThemeConfig } from './useAlgoliaThemeConfig';
7
8
  export { useAlgoliaContextualFacetFilters } from './useAlgoliaContextualFacetFilters';
9
+ export { useSearchResultUrlProcessor } from './useSearchResultUrlProcessor';
@@ -4,4 +4,6 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
+ export { useAlgoliaThemeConfig } from './useAlgoliaThemeConfig';
7
8
  export { useAlgoliaContextualFacetFilters } from './useAlgoliaContextualFacetFilters';
9
+ export { useSearchResultUrlProcessor } from './useSearchResultUrlProcessor';
@@ -0,0 +1,2 @@
1
+ import type { ThemeConfig } from '@docusaurus/theme-search-algolia';
2
+ export declare function useAlgoliaThemeConfig(): ThemeConfig;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
8
+ export function useAlgoliaThemeConfig() {
9
+ const { siteConfig: { themeConfig }, } = useDocusaurusContext();
10
+ return themeConfig;
11
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ /**
8
+ * Process the search result url from Algolia to its final form, ready to be
9
+ * navigated to or used as a link
10
+ */
11
+ export declare function useSearchResultUrlProcessor(): (url: string) => string;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import { useCallback } from 'react';
8
+ import { isRegexpStringMatch } from '@docusaurus/theme-common';
9
+ import { useBaseUrlUtils } from '@docusaurus/useBaseUrl';
10
+ import { useAlgoliaThemeConfig } from './useAlgoliaThemeConfig';
11
+ function replacePathname(pathname, replaceSearchResultPathname) {
12
+ return replaceSearchResultPathname
13
+ ? pathname.replaceAll(new RegExp(replaceSearchResultPathname.from, 'g'), replaceSearchResultPathname.to)
14
+ : pathname;
15
+ }
16
+ /**
17
+ * Process the search result url from Algolia to its final form, ready to be
18
+ * navigated to or used as a link
19
+ */
20
+ export function useSearchResultUrlProcessor() {
21
+ const { withBaseUrl } = useBaseUrlUtils();
22
+ const { algolia: { externalUrlRegex, replaceSearchResultPathname }, } = useAlgoliaThemeConfig();
23
+ return useCallback((url) => {
24
+ const parsedURL = new URL(url);
25
+ // Algolia contains an external domain => navigate to URL
26
+ if (isRegexpStringMatch(externalUrlRegex, parsedURL.href)) {
27
+ return url;
28
+ }
29
+ // Otherwise => transform to relative URL for SPA navigation
30
+ const relativeUrl = `${parsedURL.pathname + parsedURL.hash}`;
31
+ return withBaseUrl(replacePathname(relativeUrl, replaceSearchResultPathname));
32
+ }, [withBaseUrl, externalUrlRegex, replaceSearchResultPathname]);
33
+ }
@@ -4,18 +4,20 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
- import React, {useState, useRef, useCallback, useMemo} from 'react';
8
- import {createPortal} from 'react-dom';
9
- import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
10
- import {useHistory} from '@docusaurus/router';
11
- import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
12
- import Link from '@docusaurus/Link';
7
+ import React, {useCallback, useMemo, useRef, useState} from 'react';
8
+ import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
13
9
  import Head from '@docusaurus/Head';
10
+ import Link from '@docusaurus/Link';
11
+ import {useHistory} from '@docusaurus/router';
14
12
  import {isRegexpStringMatch} from '@docusaurus/theme-common';
15
13
  import {useSearchPage} from '@docusaurus/theme-common/internal';
16
- import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
17
- import {useAlgoliaContextualFacetFilters} from '@docusaurus/theme-search-algolia/client';
14
+ import {
15
+ useAlgoliaContextualFacetFilters,
16
+ useSearchResultUrlProcessor,
17
+ } from '@docusaurus/theme-search-algolia/client';
18
18
  import Translate from '@docusaurus/Translate';
19
+ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
20
+ import {createPortal} from 'react-dom';
19
21
  import translations from '@theme/SearchTranslations';
20
22
  let DocSearchModal = null;
21
23
  function Hit({hit, children}) {
@@ -39,6 +41,7 @@ function mergeFacetFilters(f1, f2) {
39
41
  }
40
42
  function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
41
43
  const {siteMetadata} = useDocusaurusContext();
44
+ const processSearchResultUrl = useSearchResultUrlProcessor();
42
45
  const contextualSearchFacetFilters = useAlgoliaContextualFacetFilters();
43
46
  const configFacetFilters = props.searchParameters?.facetFilters ?? [];
44
47
  const facetFilters = contextualSearch
@@ -51,7 +54,6 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
51
54
  ...props.searchParameters,
52
55
  facetFilters,
53
56
  };
54
- const {withBaseUrl} = useBaseUrlUtils();
55
57
  const history = useHistory();
56
58
  const searchContainer = useRef(null);
57
59
  const searchButtonRef = useRef(null);
@@ -104,19 +106,14 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
104
106
  },
105
107
  }).current;
106
108
  const transformItems = useRef((items) =>
107
- items.map((item) => {
108
- // If Algolia contains a external domain, we should navigate without
109
- // relative URL
110
- if (isRegexpStringMatch(externalUrlRegex, item.url)) {
111
- return item;
112
- }
113
- // We transform the absolute URL into a relative URL.
114
- const url = new URL(item.url);
115
- return {
116
- ...item,
117
- url: withBaseUrl(`${url.pathname}${url.hash}`),
118
- };
119
- }),
109
+ props.transformItems
110
+ ? // Custom transformItems
111
+ props.transformItems(items)
112
+ : // Default transformItems
113
+ items.map((item) => ({
114
+ ...item,
115
+ url: processSearchResultUrl(item.url),
116
+ })),
120
117
  ).current;
121
118
  const resultsFooterComponent = useMemo(
122
119
  () =>
@@ -5,26 +5,29 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
  /* eslint-disable jsx-a11y/no-autofocus */
8
- import React, {useEffect, useState, useReducer, useRef} from 'react';
8
+ import React, {useEffect, useReducer, useRef, useState} from 'react';
9
9
  import clsx from 'clsx';
10
- import algoliaSearch from 'algoliasearch/lite';
11
10
  import algoliaSearchHelper from 'algoliasearch-helper';
11
+ import algoliaSearch from 'algoliasearch/lite';
12
+ import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
12
13
  import Head from '@docusaurus/Head';
13
14
  import Link from '@docusaurus/Link';
14
- import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
15
+ import {useAllDocsData} from '@docusaurus/plugin-content-docs/client';
15
16
  import {
16
17
  HtmlClassNameProvider,
17
- usePluralForm,
18
- isRegexpStringMatch,
19
18
  useEvent,
19
+ usePluralForm,
20
20
  } from '@docusaurus/theme-common';
21
21
  import {
22
- useTitleFormatter,
23
22
  useSearchPage,
23
+ useTitleFormatter,
24
24
  } from '@docusaurus/theme-common/internal';
25
- import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
26
- import {useAllDocsData} from '@docusaurus/plugin-content-docs/client';
27
25
  import Translate, {translate} from '@docusaurus/Translate';
26
+ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
27
+ import {
28
+ useAlgoliaThemeConfig,
29
+ useSearchResultUrlProcessor,
30
+ } from '@docusaurus/theme-search-algolia/client';
28
31
  import Layout from '@theme/Layout';
29
32
  import styles from './styles.module.css';
30
33
  // Very simple pluralization: probably good enough for now
@@ -114,12 +117,12 @@ function SearchVersionSelectList({docsSearchVersionsHelpers}) {
114
117
  }
115
118
  function SearchPageContent() {
116
119
  const {
117
- siteConfig: {themeConfig},
118
120
  i18n: {currentLocale},
119
121
  } = useDocusaurusContext();
120
122
  const {
121
- algolia: {appId, apiKey, indexName, externalUrlRegex},
122
- } = themeConfig;
123
+ algolia: {appId, apiKey, indexName},
124
+ } = useAlgoliaThemeConfig();
125
+ const processSearchResultUrl = useSearchResultUrlProcessor();
123
126
  const documentsFoundPlural = useDocumentsFoundPlural();
124
127
  const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers();
125
128
  const {searchQuery, setSearchQuery} = useSearchPage();
@@ -191,15 +194,12 @@ function SearchPageContent() {
191
194
  _highlightResult: {hierarchy},
192
195
  _snippetResult: snippet = {},
193
196
  }) => {
194
- const parsedURL = new URL(url);
195
197
  const titles = Object.keys(hierarchy).map((key) =>
196
198
  sanitizeValue(hierarchy[key].value),
197
199
  );
198
200
  return {
199
201
  title: titles.pop(),
200
- url: isRegexpStringMatch(externalUrlRegex, parsedURL.href)
201
- ? parsedURL.href
202
- : parsedURL.pathname + parsedURL.hash,
202
+ url: processSearchResultUrl(url),
203
203
  summary: snippet.content
204
204
  ? `${sanitizeValue(snippet.content.value)}...`
205
205
  : '',
@@ -7,6 +7,7 @@
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.validateThemeConfig = exports.Schema = exports.DEFAULT_CONFIG = void 0;
10
+ const utils_1 = require("@docusaurus/utils");
10
11
  const utils_validation_1 = require("@docusaurus/utils-validation");
11
12
  exports.DEFAULT_CONFIG = {
12
13
  // Enabled by default, as it makes sense in most cases
@@ -33,6 +34,18 @@ exports.Schema = utils_validation_1.Joi.object({
33
34
  .try(utils_validation_1.Joi.boolean().invalid(true), utils_validation_1.Joi.string())
34
35
  .allow(null)
35
36
  .default(exports.DEFAULT_CONFIG.searchPagePath),
37
+ replaceSearchResultPathname: utils_validation_1.Joi.object({
38
+ from: utils_validation_1.Joi.custom((from) => {
39
+ if (typeof from === 'string') {
40
+ return (0, utils_1.escapeRegexp)(from);
41
+ }
42
+ else if (from instanceof RegExp) {
43
+ return from.source;
44
+ }
45
+ throw new Error(`it should be a RegExp or a string, but received ${from}`);
46
+ }).required(),
47
+ to: utils_validation_1.Joi.string().required(),
48
+ }).optional(),
36
49
  })
37
50
  .label('themeConfig.algolia')
38
51
  .required()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docusaurus/theme-search-algolia",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Algolia search component for Docusaurus.",
5
5
  "main": "lib/index.js",
6
6
  "sideEffects": [
@@ -34,13 +34,13 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@docsearch/react": "^3.1.1",
37
- "@docusaurus/core": "2.2.0",
38
- "@docusaurus/logger": "2.2.0",
39
- "@docusaurus/plugin-content-docs": "2.2.0",
40
- "@docusaurus/theme-common": "2.2.0",
41
- "@docusaurus/theme-translations": "2.2.0",
42
- "@docusaurus/utils": "2.2.0",
43
- "@docusaurus/utils-validation": "2.2.0",
37
+ "@docusaurus/core": "2.3.0",
38
+ "@docusaurus/logger": "2.3.0",
39
+ "@docusaurus/plugin-content-docs": "2.3.0",
40
+ "@docusaurus/theme-common": "2.3.0",
41
+ "@docusaurus/theme-translations": "2.3.0",
42
+ "@docusaurus/utils": "2.3.0",
43
+ "@docusaurus/utils-validation": "2.3.0",
44
44
  "algoliasearch": "^4.13.1",
45
45
  "algoliasearch-helper": "^3.10.0",
46
46
  "clsx": "^1.2.1",
@@ -51,7 +51,7 @@
51
51
  "utility-types": "^3.10.0"
52
52
  },
53
53
  "devDependencies": {
54
- "@docusaurus/module-type-aliases": "2.2.0"
54
+ "@docusaurus/module-type-aliases": "2.3.0"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "react": "^16.8.4 || ^17.0.0",
@@ -60,5 +60,5 @@
60
60
  "engines": {
61
61
  "node": ">=16.14"
62
62
  },
63
- "gitHead": "a308fb7c81832cca354192fe2984f52749441249"
63
+ "gitHead": "ad477781bdca6a11fa9c6daef5048bdcec0ee37e"
64
64
  }
@@ -5,4 +5,6 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
+ export {useAlgoliaThemeConfig} from './useAlgoliaThemeConfig';
8
9
  export {useAlgoliaContextualFacetFilters} from './useAlgoliaContextualFacetFilters';
10
+ export {useSearchResultUrlProcessor} from './useSearchResultUrlProcessor';
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
8
+ import type {ThemeConfig} from '@docusaurus/theme-search-algolia';
9
+
10
+ export function useAlgoliaThemeConfig(): ThemeConfig {
11
+ const {
12
+ siteConfig: {themeConfig},
13
+ } = useDocusaurusContext();
14
+ return themeConfig as ThemeConfig;
15
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import {useCallback} from 'react';
9
+ import {isRegexpStringMatch} from '@docusaurus/theme-common';
10
+ import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
11
+ import {useAlgoliaThemeConfig} from './useAlgoliaThemeConfig';
12
+ import type {ThemeConfig} from '@docusaurus/theme-search-algolia';
13
+
14
+ function replacePathname(
15
+ pathname: string,
16
+ replaceSearchResultPathname: ThemeConfig['algolia']['replaceSearchResultPathname'],
17
+ ): string {
18
+ return replaceSearchResultPathname
19
+ ? pathname.replaceAll(
20
+ new RegExp(replaceSearchResultPathname.from, 'g'),
21
+ replaceSearchResultPathname.to,
22
+ )
23
+ : pathname;
24
+ }
25
+
26
+ /**
27
+ * Process the search result url from Algolia to its final form, ready to be
28
+ * navigated to or used as a link
29
+ */
30
+ export function useSearchResultUrlProcessor(): (url: string) => string {
31
+ const {withBaseUrl} = useBaseUrlUtils();
32
+ const {
33
+ algolia: {externalUrlRegex, replaceSearchResultPathname},
34
+ } = useAlgoliaThemeConfig();
35
+
36
+ return useCallback(
37
+ (url: string) => {
38
+ const parsedURL = new URL(url);
39
+
40
+ // Algolia contains an external domain => navigate to URL
41
+ if (isRegexpStringMatch(externalUrlRegex, parsedURL.href)) {
42
+ return url;
43
+ }
44
+
45
+ // Otherwise => transform to relative URL for SPA navigation
46
+ const relativeUrl = `${parsedURL.pathname + parsedURL.hash}`;
47
+
48
+ return withBaseUrl(
49
+ replacePathname(relativeUrl, replaceSearchResultPathname),
50
+ );
51
+ },
52
+ [withBaseUrl, externalUrlRegex, replaceSearchResultPathname],
53
+ );
54
+ }
@@ -5,20 +5,23 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import React, {useState, useRef, useCallback, useMemo} from 'react';
9
- import {createPortal} from 'react-dom';
10
- import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
11
- import {useHistory} from '@docusaurus/router';
12
- import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
13
- import Link from '@docusaurus/Link';
8
+ import React, {useCallback, useMemo, useRef, useState} from 'react';
9
+ import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
14
10
  import Head from '@docusaurus/Head';
11
+ import Link from '@docusaurus/Link';
12
+ import {useHistory} from '@docusaurus/router';
15
13
  import {isRegexpStringMatch} from '@docusaurus/theme-common';
16
14
  import {useSearchPage} from '@docusaurus/theme-common/internal';
17
- import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
18
- import {useAlgoliaContextualFacetFilters} from '@docusaurus/theme-search-algolia/client';
15
+ import {
16
+ useAlgoliaContextualFacetFilters,
17
+ useSearchResultUrlProcessor,
18
+ } from '@docusaurus/theme-search-algolia/client';
19
19
  import Translate from '@docusaurus/Translate';
20
+ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
21
+ import {createPortal} from 'react-dom';
20
22
  import translations from '@theme/SearchTranslations';
21
23
 
24
+ import type {AutocompleteState} from '@algolia/autocomplete-core';
22
25
  import type {
23
26
  DocSearchModal as DocSearchModalType,
24
27
  DocSearchModalProps,
@@ -28,7 +31,6 @@ import type {
28
31
  StoredDocSearchHit,
29
32
  } from '@docsearch/react/dist/esm/types';
30
33
  import type {SearchClient} from 'algoliasearch/lite';
31
- import type {AutocompleteState} from '@algolia/autocomplete-core';
32
34
 
33
35
  type DocSearchProps = Omit<
34
36
  DocSearchModalProps,
@@ -88,6 +90,7 @@ function DocSearch({
88
90
  ...props
89
91
  }: DocSearchProps) {
90
92
  const {siteMetadata} = useDocusaurusContext();
93
+ const processSearchResultUrl = useSearchResultUrlProcessor();
91
94
 
92
95
  const contextualSearchFacetFilters =
93
96
  useAlgoliaContextualFacetFilters() as FacetFilters;
@@ -107,7 +110,6 @@ function DocSearch({
107
110
  facetFilters,
108
111
  };
109
112
 
110
- const {withBaseUrl} = useBaseUrlUtils();
111
113
  const history = useHistory();
112
114
  const searchContainer = useRef<HTMLDivElement | null>(null);
113
115
  const searchButtonRef = useRef<HTMLButtonElement>(null);
@@ -172,20 +174,14 @@ function DocSearch({
172
174
 
173
175
  const transformItems = useRef<DocSearchModalProps['transformItems']>(
174
176
  (items) =>
175
- items.map((item) => {
176
- // If Algolia contains a external domain, we should navigate without
177
- // relative URL
178
- if (isRegexpStringMatch(externalUrlRegex, item.url)) {
179
- return item;
180
- }
181
-
182
- // We transform the absolute URL into a relative URL.
183
- const url = new URL(item.url);
184
- return {
185
- ...item,
186
- url: withBaseUrl(`${url.pathname}${url.hash}`),
187
- };
188
- }),
177
+ props.transformItems
178
+ ? // Custom transformItems
179
+ props.transformItems(items)
180
+ : // Default transformItems
181
+ items.map((item) => ({
182
+ ...item,
183
+ url: processSearchResultUrl(item.url),
184
+ })),
189
185
  ).current;
190
186
 
191
187
  const resultsFooterComponent: DocSearchProps['resultsFooterComponent'] =
@@ -7,32 +7,34 @@
7
7
 
8
8
  /* eslint-disable jsx-a11y/no-autofocus */
9
9
 
10
- import React, {useEffect, useState, useReducer, useRef} from 'react';
10
+ import React, {useEffect, useReducer, useRef, useState} from 'react';
11
11
  import clsx from 'clsx';
12
12
 
13
- import algoliaSearch from 'algoliasearch/lite';
14
13
  import algoliaSearchHelper from 'algoliasearch-helper';
14
+ import algoliaSearch from 'algoliasearch/lite';
15
15
 
16
+ import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
16
17
  import Head from '@docusaurus/Head';
17
18
  import Link from '@docusaurus/Link';
18
- import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
19
+ import {useAllDocsData} from '@docusaurus/plugin-content-docs/client';
19
20
  import {
20
21
  HtmlClassNameProvider,
21
- usePluralForm,
22
- isRegexpStringMatch,
23
22
  useEvent,
23
+ usePluralForm,
24
24
  } from '@docusaurus/theme-common';
25
25
  import {
26
- useTitleFormatter,
27
26
  useSearchPage,
27
+ useTitleFormatter,
28
28
  } from '@docusaurus/theme-common/internal';
29
- import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
30
- import {useAllDocsData} from '@docusaurus/plugin-content-docs/client';
31
29
  import Translate, {translate} from '@docusaurus/Translate';
30
+ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
31
+ import {
32
+ useAlgoliaThemeConfig,
33
+ useSearchResultUrlProcessor,
34
+ } from '@docusaurus/theme-search-algolia/client';
32
35
  import Layout from '@theme/Layout';
33
36
 
34
37
  import styles from './styles.module.css';
35
- import type {ThemeConfig} from '@docusaurus/theme-search-algolia';
36
38
 
37
39
  // Very simple pluralization: probably good enough for now
38
40
  function useDocumentsFoundPlural() {
@@ -156,12 +158,12 @@ type ResultDispatcher =
156
158
 
157
159
  function SearchPageContent(): JSX.Element {
158
160
  const {
159
- siteConfig: {themeConfig},
160
161
  i18n: {currentLocale},
161
162
  } = useDocusaurusContext();
162
163
  const {
163
- algolia: {appId, apiKey, indexName, externalUrlRegex},
164
- } = themeConfig as ThemeConfig;
164
+ algolia: {appId, apiKey, indexName},
165
+ } = useAlgoliaThemeConfig();
166
+ const processSearchResultUrl = useSearchResultUrlProcessor();
165
167
  const documentsFoundPlural = useDocumentsFoundPlural();
166
168
 
167
169
  const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers();
@@ -244,16 +246,12 @@ function SearchPageContent(): JSX.Element {
244
246
  _highlightResult: {hierarchy: {[key: string]: {value: string}}};
245
247
  _snippetResult: {content?: {value: string}};
246
248
  }) => {
247
- const parsedURL = new URL(url);
248
249
  const titles = Object.keys(hierarchy).map((key) =>
249
250
  sanitizeValue(hierarchy[key]!.value),
250
251
  );
251
-
252
252
  return {
253
253
  title: titles.pop()!,
254
- url: isRegexpStringMatch(externalUrlRegex, parsedURL.href)
255
- ? parsedURL.href
256
- : parsedURL.pathname + parsedURL.hash,
254
+ url: processSearchResultUrl(url),
257
255
  summary: snippet.content
258
256
  ? `${sanitizeValue(snippet.content.value)}...`
259
257
  : '',
@@ -17,13 +17,23 @@ declare module '@docusaurus/theme-search-algolia' {
17
17
  indexName: string;
18
18
  searchParameters: {[key: string]: unknown};
19
19
  searchPagePath: string | false | null;
20
+ replaceSearchResultPathname?: {
21
+ from: string;
22
+ to: string;
23
+ };
20
24
  };
21
25
  };
22
26
  export type UserThemeConfig = DeepPartial<ThemeConfig>;
23
27
  }
24
28
 
25
29
  declare module '@docusaurus/theme-search-algolia/client' {
30
+ import type {ThemeConfig} from '@docusaurus/theme-search-algolia';
31
+
32
+ export function useAlgoliaThemeConfig(): ThemeConfig;
33
+
26
34
  export function useAlgoliaContextualFacetFilters(): [string, string[]];
35
+
36
+ export function useSearchResultUrlProcessor(): (url: string) => string;
27
37
  }
28
38
 
29
39
  declare module '@theme/SearchPage' {
@@ -5,6 +5,7 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
+ import {escapeRegexp} from '@docusaurus/utils';
8
9
  import {Joi} from '@docusaurus/utils-validation';
9
10
  import type {
10
11
  ThemeConfig,
@@ -39,6 +40,19 @@ export const Schema = Joi.object<ThemeConfig>({
39
40
  .try(Joi.boolean().invalid(true), Joi.string())
40
41
  .allow(null)
41
42
  .default(DEFAULT_CONFIG.searchPagePath),
43
+ replaceSearchResultPathname: Joi.object({
44
+ from: Joi.custom((from) => {
45
+ if (typeof from === 'string') {
46
+ return escapeRegexp(from);
47
+ } else if (from instanceof RegExp) {
48
+ return from.source;
49
+ }
50
+ throw new Error(
51
+ `it should be a RegExp or a string, but received ${from}`,
52
+ );
53
+ }).required(),
54
+ to: Joi.string().required(),
55
+ }).optional(),
42
56
  })
43
57
  .label('themeConfig.algolia')
44
58
  .required()