@docusaurus/theme-search-algolia 3.3.2 → 3.5.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,10 +4,18 @@
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 { useContextualSearchFilters } from '@docusaurus/theme-common';
8
- // Translate search-engine agnostic search filters to Algolia search filters
7
+ import { DEFAULT_SEARCH_TAG } from '@docusaurus/theme-common/internal';
8
+ import { useDocsContextualSearchTags } from '@docusaurus/plugin-content-docs/client';
9
+ import useDocusaurusContext from '@docusaurus/core/src/client/exports/useDocusaurusContext';
10
+ function useSearchTags() {
11
+ // only docs have custom search tags per version
12
+ const docsTags = useDocsContextualSearchTags();
13
+ return [DEFAULT_SEARCH_TAG, ...docsTags];
14
+ }
15
+ // Translate search-engine agnostic search tags to Algolia search filters
9
16
  export function useAlgoliaContextualFacetFilters() {
10
- const { locale, tags } = useContextualSearchFilters();
17
+ const locale = useDocusaurusContext().i18n.currentLocale;
18
+ const tags = useSearchTags();
11
19
  // Seems safe to convert locale->language, see AlgoliaSearchMetadata comment
12
20
  const languageFilter = `language:${locale}`;
13
21
  const tagsFilter = tags.map((tag) => `docusaurus_tag:${tag}`);
@@ -1,3 +1,2 @@
1
- /// <reference path="../../src/theme-search-algolia.d.ts" />
2
1
  import type { ThemeConfig } from '@docusaurus/theme-search-algolia';
3
2
  export declare function useAlgoliaThemeConfig(): ThemeConfig;
package/lib/index.js CHANGED
@@ -7,23 +7,12 @@
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.validateThemeConfig = void 0;
10
- const tslib_1 = require("tslib");
11
- const path_1 = tslib_1.__importDefault(require("path"));
12
- const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
13
- const lodash_1 = tslib_1.__importDefault(require("lodash"));
14
- const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
15
- const eta_1 = require("eta");
10
+ exports.default = themeSearchAlgolia;
16
11
  const utils_1 = require("@docusaurus/utils");
17
12
  const theme_translations_1 = require("@docusaurus/theme-translations");
18
- const opensearch_1 = tslib_1.__importDefault(require("./templates/opensearch"));
19
- const getCompiledOpenSearchTemplate = lodash_1.default.memoize(() => (0, eta_1.compile)(opensearch_1.default.trim()));
20
- function renderOpenSearchTemplate(data) {
21
- const compiled = getCompiledOpenSearchTemplate();
22
- return compiled(data, eta_1.defaultConfig);
23
- }
24
- const OPEN_SEARCH_FILENAME = 'opensearch.xml';
13
+ const opensearch_1 = require("./opensearch");
25
14
  function themeSearchAlgolia(context) {
26
- const { baseUrl, siteConfig: { title, url, favicon, themeConfig }, i18n: { currentLocale }, } = context;
15
+ const { baseUrl, siteConfig: { themeConfig }, i18n: { currentLocale }, } = context;
27
16
  const { algolia: { searchPagePath }, } = themeConfig;
28
17
  return {
29
18
  name: 'docusaurus-theme-search-algolia',
@@ -48,43 +37,18 @@ function themeSearchAlgolia(context) {
48
37
  });
49
38
  }
50
39
  },
51
- async postBuild({ outDir }) {
52
- if (searchPagePath) {
53
- const siteUrl = (0, utils_1.normalizeUrl)([url, baseUrl]);
54
- try {
55
- await fs_extra_1.default.writeFile(path_1.default.join(outDir, OPEN_SEARCH_FILENAME), renderOpenSearchTemplate({
56
- title,
57
- siteUrl,
58
- searchUrl: (0, utils_1.normalizeUrl)([siteUrl, searchPagePath]),
59
- faviconUrl: favicon ? (0, utils_1.normalizeUrl)([siteUrl, favicon]) : null,
60
- }));
61
- }
62
- catch (err) {
63
- logger_1.default.error('Generating OpenSearch file failed.');
64
- throw err;
65
- }
40
+ async postBuild() {
41
+ if ((0, opensearch_1.shouldCreateOpenSearchFile)({ context })) {
42
+ await (0, opensearch_1.createOpenSearchFile)({ context });
66
43
  }
67
44
  },
68
45
  injectHtmlTags() {
69
- if (!searchPagePath) {
70
- return {};
46
+ if ((0, opensearch_1.shouldCreateOpenSearchFile)({ context })) {
47
+ return { headTags: (0, opensearch_1.createOpenSearchHeadTags)({ context }) };
71
48
  }
72
- return {
73
- headTags: [
74
- {
75
- tagName: 'link',
76
- attributes: {
77
- rel: 'search',
78
- type: 'application/opensearchdescription+xml',
79
- title,
80
- href: (0, utils_1.normalizeUrl)([baseUrl, OPEN_SEARCH_FILENAME]),
81
- },
82
- },
83
- ],
84
- };
49
+ return {};
85
50
  },
86
51
  };
87
52
  }
88
- exports.default = themeSearchAlgolia;
89
53
  var validateThemeConfig_1 = require("./validateThemeConfig");
90
54
  Object.defineProperty(exports, "validateThemeConfig", { enumerable: true, get: function () { return validateThemeConfig_1.validateThemeConfig; } });
@@ -0,0 +1,16 @@
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 type { HtmlTags, LoadContext } from '@docusaurus/types';
8
+ export declare function shouldCreateOpenSearchFile({ context, }: {
9
+ context: LoadContext;
10
+ }): boolean;
11
+ export declare function createOpenSearchFile({ context, }: {
12
+ context: LoadContext;
13
+ }): Promise<void>;
14
+ export declare function createOpenSearchHeadTags({ context, }: {
15
+ context: LoadContext;
16
+ }): HtmlTags;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.shouldCreateOpenSearchFile = shouldCreateOpenSearchFile;
10
+ exports.createOpenSearchFile = createOpenSearchFile;
11
+ exports.createOpenSearchHeadTags = createOpenSearchHeadTags;
12
+ const tslib_1 = require("tslib");
13
+ const path_1 = tslib_1.__importDefault(require("path"));
14
+ const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
15
+ const lodash_1 = tslib_1.__importDefault(require("lodash"));
16
+ const eta_1 = require("eta");
17
+ const utils_1 = require("@docusaurus/utils");
18
+ const opensearch_1 = tslib_1.__importDefault(require("./templates/opensearch"));
19
+ const getCompiledOpenSearchTemplate = lodash_1.default.memoize(() => (0, eta_1.compile)(opensearch_1.default.trim()));
20
+ function renderOpenSearchTemplate(data) {
21
+ const compiled = getCompiledOpenSearchTemplate();
22
+ return compiled(data, eta_1.defaultConfig);
23
+ }
24
+ const OPEN_SEARCH_FILENAME = 'opensearch.xml';
25
+ function shouldCreateOpenSearchFile({ context, }) {
26
+ const { siteConfig: { themeConfig, future: { experimental_router: router }, }, } = context;
27
+ const { algolia: { searchPagePath }, } = themeConfig;
28
+ return !!searchPagePath && router !== 'hash';
29
+ }
30
+ function createOpenSearchFileContent({ context, searchPagePath, }) {
31
+ const { baseUrl, siteConfig: { title, url, favicon }, } = context;
32
+ const siteUrl = (0, utils_1.normalizeUrl)([url, baseUrl]);
33
+ return renderOpenSearchTemplate({
34
+ title,
35
+ siteUrl,
36
+ searchUrl: (0, utils_1.normalizeUrl)([siteUrl, searchPagePath]),
37
+ faviconUrl: favicon ? (0, utils_1.normalizeUrl)([siteUrl, favicon]) : null,
38
+ });
39
+ }
40
+ async function createOpenSearchFile({ context, }) {
41
+ const { outDir, siteConfig: { themeConfig }, } = context;
42
+ const { algolia: { searchPagePath }, } = themeConfig;
43
+ if (!searchPagePath) {
44
+ throw new Error('no searchPagePath provided in themeConfig.algolia');
45
+ }
46
+ const fileContent = createOpenSearchFileContent({ context, searchPagePath });
47
+ try {
48
+ await fs_extra_1.default.writeFile(path_1.default.join(outDir, OPEN_SEARCH_FILENAME), fileContent);
49
+ }
50
+ catch (err) {
51
+ throw new Error('Generating OpenSearch file failed.', { cause: err });
52
+ }
53
+ }
54
+ function createOpenSearchHeadTags({ context, }) {
55
+ const { baseUrl, siteConfig: { title }, } = context;
56
+ return {
57
+ tagName: 'link',
58
+ attributes: {
59
+ rel: 'search',
60
+ type: 'application/opensearchdescription+xml',
61
+ title,
62
+ href: (0, utils_1.normalizeUrl)([baseUrl, OPEN_SEARCH_FILENAME]),
63
+ },
64
+ };
65
+ }
@@ -4,5 +4,4 @@
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
- /// <reference types="react" />
8
7
  export default function SearchBar(): JSX.Element;
@@ -73,29 +73,33 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
73
73
  DocSearchModal = Modal;
74
74
  });
75
75
  }, []);
76
- const onOpen = useCallback(() => {
77
- importDocSearchModalIfNeeded().then(() => {
78
- searchContainer.current = document.createElement('div');
79
- document.body.insertBefore(
80
- searchContainer.current,
81
- document.body.firstChild,
82
- );
83
- setIsOpen(true);
84
- });
85
- }, [importDocSearchModalIfNeeded, setIsOpen]);
86
- const onClose = useCallback(() => {
76
+ const prepareSearchContainer = useCallback(() => {
77
+ if (!searchContainer.current) {
78
+ const divElement = document.createElement('div');
79
+ searchContainer.current = divElement;
80
+ document.body.insertBefore(divElement, document.body.firstChild);
81
+ }
82
+ }, []);
83
+ const openModal = useCallback(() => {
84
+ prepareSearchContainer();
85
+ importDocSearchModalIfNeeded().then(() => setIsOpen(true));
86
+ }, [importDocSearchModalIfNeeded, prepareSearchContainer]);
87
+ const closeModal = useCallback(() => {
87
88
  setIsOpen(false);
88
- searchContainer.current?.remove();
89
89
  searchButtonRef.current?.focus();
90
- }, [setIsOpen]);
91
- const onInput = useCallback(
90
+ }, []);
91
+ const handleInput = useCallback(
92
92
  (event) => {
93
- importDocSearchModalIfNeeded().then(() => {
94
- setIsOpen(true);
95
- setInitialQuery(event.key);
96
- });
93
+ if (event.key === 'f' && (event.metaKey || event.ctrlKey)) {
94
+ // ignore browser's ctrl+f
95
+ return;
96
+ }
97
+ // prevents duplicate key insertion in the modal input
98
+ event.preventDefault();
99
+ setInitialQuery(event.key);
100
+ openModal();
97
101
  },
98
- [importDocSearchModalIfNeeded, setIsOpen, setInitialQuery],
102
+ [openModal],
99
103
  );
100
104
  const navigator = useRef({
101
105
  navigate({itemUrl}) {
@@ -122,8 +126,8 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
122
126
  () =>
123
127
  // eslint-disable-next-line react/no-unstable-nested-components
124
128
  (footerProps) =>
125
- <ResultsFooter {...footerProps} onClose={onClose} />,
126
- [onClose],
129
+ <ResultsFooter {...footerProps} onClose={closeModal} />,
130
+ [closeModal],
127
131
  );
128
132
  const transformSearchClient = useCallback(
129
133
  (searchClient) => {
@@ -137,9 +141,9 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
137
141
  );
138
142
  useDocSearchKeyboardEvents({
139
143
  isOpen,
140
- onOpen,
141
- onClose,
142
- onInput,
144
+ onOpen: openModal,
145
+ onClose: closeModal,
146
+ onInput: handleInput,
143
147
  searchButtonRef,
144
148
  });
145
149
  return (
@@ -159,7 +163,7 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
159
163
  onTouchStart={importDocSearchModalIfNeeded}
160
164
  onFocus={importDocSearchModalIfNeeded}
161
165
  onMouseOver={importDocSearchModalIfNeeded}
162
- onClick={onOpen}
166
+ onClick={openModal}
163
167
  ref={searchButtonRef}
164
168
  translations={translations.button}
165
169
  />
@@ -169,7 +173,7 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
169
173
  searchContainer.current &&
170
174
  createPortal(
171
175
  <DocSearchModal
172
- onClose={onClose}
176
+ onClose={closeModal}
173
177
  initialScrollY={window.scrollY}
174
178
  initialQuery={initialQuery}
175
179
  navigator={navigator}
@@ -4,5 +4,4 @@
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
- /// <reference types="react" />
8
7
  export default function SearchPage(): JSX.Element;
@@ -119,7 +119,7 @@ function SearchPageContent() {
119
119
  i18n: {currentLocale},
120
120
  } = useDocusaurusContext();
121
121
  const {
122
- algolia: {appId, apiKey, indexName},
122
+ algolia: {appId, apiKey, indexName, contextualSearch},
123
123
  } = useAlgoliaThemeConfig();
124
124
  const processSearchResultUrl = useSearchResultUrlProcessor();
125
125
  const documentsFoundPlural = useDocumentsFoundPlural();
@@ -169,11 +169,17 @@ function SearchPageContent() {
169
169
  },
170
170
  initialSearchResultState,
171
171
  );
172
+ // respect settings from the theme config for facets
173
+ const disjunctiveFacets = contextualSearch
174
+ ? ['language', 'docusaurus_tag']
175
+ : [];
172
176
  const algoliaClient = algoliaSearch(appId, apiKey);
173
177
  const algoliaHelper = algoliaSearchHelper(algoliaClient, indexName, {
178
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
179
+ // @ts-ignore: why errors happens after upgrading to TS 5.5 ?
174
180
  hitsPerPage: 15,
175
181
  advancedSyntax: true,
176
- disjunctiveFacets: ['language', 'docusaurus_tag'],
182
+ disjunctiveFacets,
177
183
  });
178
184
  algoliaHelper.on(
179
185
  'result',
@@ -256,16 +262,18 @@ function SearchPageContent() {
256
262
  description: 'The search page title for empty query',
257
263
  });
258
264
  const makeSearch = useEvent((page = 0) => {
259
- algoliaHelper.addDisjunctiveFacetRefinement('docusaurus_tag', 'default');
260
- algoliaHelper.addDisjunctiveFacetRefinement('language', currentLocale);
261
- Object.entries(docsSearchVersionsHelpers.searchVersions).forEach(
262
- ([pluginId, searchVersion]) => {
263
- algoliaHelper.addDisjunctiveFacetRefinement(
264
- 'docusaurus_tag',
265
- `docs-${pluginId}-${searchVersion}`,
266
- );
267
- },
268
- );
265
+ if (contextualSearch) {
266
+ algoliaHelper.addDisjunctiveFacetRefinement('docusaurus_tag', 'default');
267
+ algoliaHelper.addDisjunctiveFacetRefinement('language', currentLocale);
268
+ Object.entries(docsSearchVersionsHelpers.searchVersions).forEach(
269
+ ([pluginId, searchVersion]) => {
270
+ algoliaHelper.addDisjunctiveFacetRefinement(
271
+ 'docusaurus_tag',
272
+ `docs-${pluginId}-${searchVersion}`,
273
+ );
274
+ },
275
+ );
276
+ }
269
277
  algoliaHelper.setQuery(searchQuery).setPage(page).search();
270
278
  });
271
279
  useEffect(() => {
@@ -335,7 +343,7 @@ function SearchPageContent() {
335
343
  />
336
344
  </div>
337
345
 
338
- {docsSearchVersionsHelpers.versioningEnabled && (
346
+ {contextualSearch && docsSearchVersionsHelpers.versioningEnabled && (
339
347
  <SearchVersionSelectList
340
348
  docsSearchVersionsHelpers={docsSearchVersionsHelpers}
341
349
  />
@@ -6,7 +6,8 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.validateThemeConfig = exports.Schema = exports.DEFAULT_CONFIG = void 0;
9
+ exports.Schema = exports.DEFAULT_CONFIG = void 0;
10
+ exports.validateThemeConfig = validateThemeConfig;
10
11
  const utils_1 = require("@docusaurus/utils");
11
12
  const utils_validation_1 = require("@docusaurus/utils-validation");
12
13
  exports.DEFAULT_CONFIG = {
@@ -54,4 +55,3 @@ exports.Schema = utils_validation_1.Joi.object({
54
55
  function validateThemeConfig({ validate, themeConfig, }) {
55
56
  return validate(exports.Schema, themeConfig);
56
57
  }
57
- exports.validateThemeConfig = validateThemeConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docusaurus/theme-search-algolia",
3
- "version": "3.3.2",
3
+ "version": "3.5.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.5.2",
37
- "@docusaurus/core": "3.3.2",
38
- "@docusaurus/logger": "3.3.2",
39
- "@docusaurus/plugin-content-docs": "3.3.2",
40
- "@docusaurus/theme-common": "3.3.2",
41
- "@docusaurus/theme-translations": "3.3.2",
42
- "@docusaurus/utils": "3.3.2",
43
- "@docusaurus/utils-validation": "3.3.2",
37
+ "@docusaurus/core": "3.5.0",
38
+ "@docusaurus/logger": "3.5.0",
39
+ "@docusaurus/plugin-content-docs": "3.5.0",
40
+ "@docusaurus/theme-common": "3.5.0",
41
+ "@docusaurus/theme-translations": "3.5.0",
42
+ "@docusaurus/utils": "3.5.0",
43
+ "@docusaurus/utils-validation": "3.5.0",
44
44
  "algoliasearch": "^4.18.0",
45
45
  "algoliasearch-helper": "^3.13.3",
46
46
  "clsx": "^2.0.0",
@@ -51,7 +51,7 @@
51
51
  "utility-types": "^3.10.0"
52
52
  },
53
53
  "devDependencies": {
54
- "@docusaurus/module-type-aliases": "3.3.2"
54
+ "@docusaurus/module-type-aliases": "3.5.0"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "react": "^18.0.0",
@@ -60,5 +60,5 @@
60
60
  "engines": {
61
61
  "node": ">=18.0"
62
62
  },
63
- "gitHead": "bc638d674bfbde1e254ef306697f47e764b5e107"
63
+ "gitHead": "cb5829f3c34b26d798b869e38ee25073488140bd"
64
64
  }
@@ -5,11 +5,20 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import {useContextualSearchFilters} from '@docusaurus/theme-common';
8
+ import {DEFAULT_SEARCH_TAG} from '@docusaurus/theme-common/internal';
9
+ import {useDocsContextualSearchTags} from '@docusaurus/plugin-content-docs/client';
10
+ import useDocusaurusContext from '@docusaurus/core/src/client/exports/useDocusaurusContext';
9
11
 
10
- // Translate search-engine agnostic search filters to Algolia search filters
12
+ function useSearchTags() {
13
+ // only docs have custom search tags per version
14
+ const docsTags = useDocsContextualSearchTags();
15
+ return [DEFAULT_SEARCH_TAG, ...docsTags];
16
+ }
17
+
18
+ // Translate search-engine agnostic search tags to Algolia search filters
11
19
  export function useAlgoliaContextualFacetFilters(): [string, string[]] {
12
- const {locale, tags} = useContextualSearchFilters();
20
+ const locale = useDocusaurusContext().i18n.currentLocale;
21
+ const tags = useSearchTags();
13
22
 
14
23
  // Seems safe to convert locale->language, see AlgoliaSearchMetadata comment
15
24
  const languageFilter = `language:${locale}`;
package/src/index.ts CHANGED
@@ -5,38 +5,21 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import path from 'path';
9
- import fs from 'fs-extra';
10
- import _ from 'lodash';
11
- import logger from '@docusaurus/logger';
12
- import {defaultConfig, compile} from 'eta';
13
8
  import {normalizeUrl} from '@docusaurus/utils';
14
9
  import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations';
15
- import openSearchTemplate from './templates/opensearch';
10
+ import {
11
+ createOpenSearchFile,
12
+ createOpenSearchHeadTags,
13
+ shouldCreateOpenSearchFile,
14
+ } from './opensearch';
16
15
 
17
16
  import type {LoadContext, Plugin} from '@docusaurus/types';
18
17
  import type {ThemeConfig} from '@docusaurus/theme-search-algolia';
19
18
 
20
- const getCompiledOpenSearchTemplate = _.memoize(() =>
21
- compile(openSearchTemplate.trim()),
22
- );
23
-
24
- function renderOpenSearchTemplate(data: {
25
- title: string;
26
- siteUrl: string;
27
- searchUrl: string;
28
- faviconUrl: string | null;
29
- }) {
30
- const compiled = getCompiledOpenSearchTemplate();
31
- return compiled(data, defaultConfig);
32
- }
33
-
34
- const OPEN_SEARCH_FILENAME = 'opensearch.xml';
35
-
36
19
  export default function themeSearchAlgolia(context: LoadContext): Plugin<void> {
37
20
  const {
38
21
  baseUrl,
39
- siteConfig: {title, url, favicon, themeConfig},
22
+ siteConfig: {themeConfig},
40
23
  i18n: {currentLocale},
41
24
  } = context;
42
25
  const {
@@ -70,45 +53,17 @@ export default function themeSearchAlgolia(context: LoadContext): Plugin<void> {
70
53
  }
71
54
  },
72
55
 
73
- async postBuild({outDir}) {
74
- if (searchPagePath) {
75
- const siteUrl = normalizeUrl([url, baseUrl]);
76
-
77
- try {
78
- await fs.writeFile(
79
- path.join(outDir, OPEN_SEARCH_FILENAME),
80
- renderOpenSearchTemplate({
81
- title,
82
- siteUrl,
83
- searchUrl: normalizeUrl([siteUrl, searchPagePath]),
84
- faviconUrl: favicon ? normalizeUrl([siteUrl, favicon]) : null,
85
- }),
86
- );
87
- } catch (err) {
88
- logger.error('Generating OpenSearch file failed.');
89
- throw err;
90
- }
56
+ async postBuild() {
57
+ if (shouldCreateOpenSearchFile({context})) {
58
+ await createOpenSearchFile({context});
91
59
  }
92
60
  },
93
61
 
94
62
  injectHtmlTags() {
95
- if (!searchPagePath) {
96
- return {};
63
+ if (shouldCreateOpenSearchFile({context})) {
64
+ return {headTags: createOpenSearchHeadTags({context})};
97
65
  }
98
-
99
- return {
100
- headTags: [
101
- {
102
- tagName: 'link',
103
- attributes: {
104
- rel: 'search',
105
- type: 'application/opensearchdescription+xml',
106
- title,
107
- href: normalizeUrl([baseUrl, OPEN_SEARCH_FILENAME]),
108
- },
109
- },
110
- ],
111
- };
66
+ return {};
112
67
  },
113
68
  };
114
69
  }
@@ -0,0 +1,115 @@
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 path from 'path';
9
+ import fs from 'fs-extra';
10
+ import _ from 'lodash';
11
+ import {defaultConfig, compile} from 'eta';
12
+ import {normalizeUrl} from '@docusaurus/utils';
13
+ import openSearchTemplate from './templates/opensearch';
14
+
15
+ import type {HtmlTags, LoadContext} from '@docusaurus/types';
16
+ import type {ThemeConfig} from '@docusaurus/theme-search-algolia';
17
+
18
+ const getCompiledOpenSearchTemplate = _.memoize(() =>
19
+ compile(openSearchTemplate.trim()),
20
+ );
21
+
22
+ function renderOpenSearchTemplate(data: {
23
+ title: string;
24
+ siteUrl: string;
25
+ searchUrl: string;
26
+ faviconUrl: string | null;
27
+ }) {
28
+ const compiled = getCompiledOpenSearchTemplate();
29
+ return compiled(data, defaultConfig);
30
+ }
31
+
32
+ const OPEN_SEARCH_FILENAME = 'opensearch.xml';
33
+
34
+ export function shouldCreateOpenSearchFile({
35
+ context,
36
+ }: {
37
+ context: LoadContext;
38
+ }): boolean {
39
+ const {
40
+ siteConfig: {
41
+ themeConfig,
42
+ future: {experimental_router: router},
43
+ },
44
+ } = context;
45
+ const {
46
+ algolia: {searchPagePath},
47
+ } = themeConfig as ThemeConfig;
48
+
49
+ return !!searchPagePath && router !== 'hash';
50
+ }
51
+
52
+ function createOpenSearchFileContent({
53
+ context,
54
+ searchPagePath,
55
+ }: {
56
+ context: LoadContext;
57
+ searchPagePath: string;
58
+ }): string {
59
+ const {
60
+ baseUrl,
61
+ siteConfig: {title, url, favicon},
62
+ } = context;
63
+
64
+ const siteUrl = normalizeUrl([url, baseUrl]);
65
+
66
+ return renderOpenSearchTemplate({
67
+ title,
68
+ siteUrl,
69
+ searchUrl: normalizeUrl([siteUrl, searchPagePath]),
70
+ faviconUrl: favicon ? normalizeUrl([siteUrl, favicon]) : null,
71
+ });
72
+ }
73
+
74
+ export async function createOpenSearchFile({
75
+ context,
76
+ }: {
77
+ context: LoadContext;
78
+ }): Promise<void> {
79
+ const {
80
+ outDir,
81
+ siteConfig: {themeConfig},
82
+ } = context;
83
+ const {
84
+ algolia: {searchPagePath},
85
+ } = themeConfig as ThemeConfig;
86
+ if (!searchPagePath) {
87
+ throw new Error('no searchPagePath provided in themeConfig.algolia');
88
+ }
89
+ const fileContent = createOpenSearchFileContent({context, searchPagePath});
90
+ try {
91
+ await fs.writeFile(path.join(outDir, OPEN_SEARCH_FILENAME), fileContent);
92
+ } catch (err) {
93
+ throw new Error('Generating OpenSearch file failed.', {cause: err});
94
+ }
95
+ }
96
+
97
+ export function createOpenSearchHeadTags({
98
+ context,
99
+ }: {
100
+ context: LoadContext;
101
+ }): HtmlTags {
102
+ const {
103
+ baseUrl,
104
+ siteConfig: {title},
105
+ } = context;
106
+ return {
107
+ tagName: 'link',
108
+ attributes: {
109
+ rel: 'search',
110
+ type: 'application/opensearchdescription+xml',
111
+ title,
112
+ href: normalizeUrl([baseUrl, OPEN_SEARCH_FILENAME]),
113
+ },
114
+ };
115
+ }
@@ -136,31 +136,36 @@ function DocSearch({
136
136
  });
137
137
  }, []);
138
138
 
139
- const onOpen = useCallback(() => {
140
- importDocSearchModalIfNeeded().then(() => {
141
- searchContainer.current = document.createElement('div');
142
- document.body.insertBefore(
143
- searchContainer.current,
144
- document.body.firstChild,
145
- );
146
- setIsOpen(true);
147
- });
148
- }, [importDocSearchModalIfNeeded, setIsOpen]);
139
+ const prepareSearchContainer = useCallback(() => {
140
+ if (!searchContainer.current) {
141
+ const divElement = document.createElement('div');
142
+ searchContainer.current = divElement;
143
+ document.body.insertBefore(divElement, document.body.firstChild);
144
+ }
145
+ }, []);
149
146
 
150
- const onClose = useCallback(() => {
147
+ const openModal = useCallback(() => {
148
+ prepareSearchContainer();
149
+ importDocSearchModalIfNeeded().then(() => setIsOpen(true));
150
+ }, [importDocSearchModalIfNeeded, prepareSearchContainer]);
151
+
152
+ const closeModal = useCallback(() => {
151
153
  setIsOpen(false);
152
- searchContainer.current?.remove();
153
154
  searchButtonRef.current?.focus();
154
- }, [setIsOpen]);
155
+ }, []);
155
156
 
156
- const onInput = useCallback(
157
+ const handleInput = useCallback(
157
158
  (event: KeyboardEvent) => {
158
- importDocSearchModalIfNeeded().then(() => {
159
- setIsOpen(true);
160
- setInitialQuery(event.key);
161
- });
159
+ if (event.key === 'f' && (event.metaKey || event.ctrlKey)) {
160
+ // ignore browser's ctrl+f
161
+ return;
162
+ }
163
+ // prevents duplicate key insertion in the modal input
164
+ event.preventDefault();
165
+ setInitialQuery(event.key);
166
+ openModal();
162
167
  },
163
- [importDocSearchModalIfNeeded, setIsOpen, setInitialQuery],
168
+ [openModal],
164
169
  );
165
170
 
166
171
  const navigator = useRef({
@@ -192,8 +197,8 @@ function DocSearch({
192
197
  () =>
193
198
  // eslint-disable-next-line react/no-unstable-nested-components
194
199
  (footerProps: Omit<ResultsFooterProps, 'onClose'>): JSX.Element =>
195
- <ResultsFooter {...footerProps} onClose={onClose} />,
196
- [onClose],
200
+ <ResultsFooter {...footerProps} onClose={closeModal} />,
201
+ [closeModal],
197
202
  );
198
203
 
199
204
  const transformSearchClient = useCallback(
@@ -210,9 +215,9 @@ function DocSearch({
210
215
 
211
216
  useDocSearchKeyboardEvents({
212
217
  isOpen,
213
- onOpen,
214
- onClose,
215
- onInput,
218
+ onOpen: openModal,
219
+ onClose: closeModal,
220
+ onInput: handleInput,
216
221
  searchButtonRef,
217
222
  });
218
223
 
@@ -233,7 +238,7 @@ function DocSearch({
233
238
  onTouchStart={importDocSearchModalIfNeeded}
234
239
  onFocus={importDocSearchModalIfNeeded}
235
240
  onMouseOver={importDocSearchModalIfNeeded}
236
- onClick={onOpen}
241
+ onClick={openModal}
237
242
  ref={searchButtonRef}
238
243
  translations={translations.button}
239
244
  />
@@ -243,7 +248,7 @@ function DocSearch({
243
248
  searchContainer.current &&
244
249
  createPortal(
245
250
  <DocSearchModal
246
- onClose={onClose}
251
+ onClose={closeModal}
247
252
  initialScrollY={window.scrollY}
248
253
  initialQuery={initialQuery}
249
254
  navigator={navigator}
@@ -159,8 +159,9 @@ function SearchPageContent(): JSX.Element {
159
159
  i18n: {currentLocale},
160
160
  } = useDocusaurusContext();
161
161
  const {
162
- algolia: {appId, apiKey, indexName},
162
+ algolia: {appId, apiKey, indexName, contextualSearch},
163
163
  } = useAlgoliaThemeConfig();
164
+
164
165
  const processSearchResultUrl = useSearchResultUrlProcessor();
165
166
  const documentsFoundPlural = useDocumentsFoundPlural();
166
167
 
@@ -213,11 +214,18 @@ function SearchPageContent(): JSX.Element {
213
214
  initialSearchResultState,
214
215
  );
215
216
 
217
+ // respect settings from the theme config for facets
218
+ const disjunctiveFacets = contextualSearch
219
+ ? ['language', 'docusaurus_tag']
220
+ : [];
221
+
216
222
  const algoliaClient = algoliaSearch(appId, apiKey);
217
223
  const algoliaHelper = algoliaSearchHelper(algoliaClient, indexName, {
224
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
225
+ // @ts-ignore: why errors happens after upgrading to TS 5.5 ?
218
226
  hitsPerPage: 15,
219
227
  advancedSyntax: true,
220
- disjunctiveFacets: ['language', 'docusaurus_tag'],
228
+ disjunctiveFacets,
221
229
  });
222
230
 
223
231
  algoliaHelper.on(
@@ -313,17 +321,19 @@ function SearchPageContent(): JSX.Element {
313
321
  });
314
322
 
315
323
  const makeSearch = useEvent((page: number = 0) => {
316
- algoliaHelper.addDisjunctiveFacetRefinement('docusaurus_tag', 'default');
317
- algoliaHelper.addDisjunctiveFacetRefinement('language', currentLocale);
318
-
319
- Object.entries(docsSearchVersionsHelpers.searchVersions).forEach(
320
- ([pluginId, searchVersion]) => {
321
- algoliaHelper.addDisjunctiveFacetRefinement(
322
- 'docusaurus_tag',
323
- `docs-${pluginId}-${searchVersion}`,
324
- );
325
- },
326
- );
324
+ if (contextualSearch) {
325
+ algoliaHelper.addDisjunctiveFacetRefinement('docusaurus_tag', 'default');
326
+ algoliaHelper.addDisjunctiveFacetRefinement('language', currentLocale);
327
+
328
+ Object.entries(docsSearchVersionsHelpers.searchVersions).forEach(
329
+ ([pluginId, searchVersion]) => {
330
+ algoliaHelper.addDisjunctiveFacetRefinement(
331
+ 'docusaurus_tag',
332
+ `docs-${pluginId}-${searchVersion}`,
333
+ );
334
+ },
335
+ );
336
+ }
327
337
 
328
338
  algoliaHelper.setQuery(searchQuery).setPage(page).search();
329
339
  });
@@ -401,7 +411,7 @@ function SearchPageContent(): JSX.Element {
401
411
  />
402
412
  </div>
403
413
 
404
- {docsSearchVersionsHelpers.versioningEnabled && (
414
+ {contextualSearch && docsSearchVersionsHelpers.versioningEnabled && (
405
415
  <SearchVersionSelectList
406
416
  docsSearchVersionsHelpers={docsSearchVersionsHelpers}
407
417
  />