@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.
- package/lib/client/useAlgoliaContextualFacetFilters.js +11 -3
- package/lib/client/useAlgoliaThemeConfig.d.ts +0 -1
- package/lib/index.js +9 -45
- package/lib/opensearch.d.ts +16 -0
- package/lib/opensearch.js +65 -0
- package/lib/theme/SearchBar/index.d.ts +0 -1
- package/lib/theme/SearchBar/index.js +30 -26
- package/lib/theme/SearchPage/index.d.ts +0 -1
- package/lib/theme/SearchPage/index.js +21 -13
- package/lib/validateThemeConfig.js +2 -2
- package/package.json +10 -10
- package/src/client/useAlgoliaContextualFacetFilters.ts +12 -3
- package/src/index.ts +12 -57
- package/src/opensearch.ts +115 -0
- package/src/theme/SearchBar/index.tsx +31 -26
- package/src/theme/SearchPage/index.tsx +24 -14
|
@@ -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 {
|
|
8
|
-
|
|
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
|
|
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}`);
|
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
|
-
|
|
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 =
|
|
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: {
|
|
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(
|
|
52
|
-
if (
|
|
53
|
-
|
|
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 (
|
|
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
|
+
}
|
|
@@ -73,29 +73,33 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
|
|
|
73
73
|
DocSearchModal = Modal;
|
|
74
74
|
});
|
|
75
75
|
}, []);
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
}, [
|
|
91
|
-
const
|
|
90
|
+
}, []);
|
|
91
|
+
const handleInput = useCallback(
|
|
92
92
|
(event) => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
[
|
|
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={
|
|
126
|
-
[
|
|
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={
|
|
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={
|
|
176
|
+
onClose={closeModal}
|
|
173
177
|
initialScrollY={window.scrollY}
|
|
174
178
|
initialQuery={initialQuery}
|
|
175
179
|
navigator={navigator}
|
|
@@ -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
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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.
|
|
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
|
+
"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.
|
|
38
|
-
"@docusaurus/logger": "3.
|
|
39
|
-
"@docusaurus/plugin-content-docs": "3.
|
|
40
|
-
"@docusaurus/theme-common": "3.
|
|
41
|
-
"@docusaurus/theme-translations": "3.
|
|
42
|
-
"@docusaurus/utils": "3.
|
|
43
|
-
"@docusaurus/utils-validation": "3.
|
|
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.
|
|
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": "
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
|
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: {
|
|
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(
|
|
74
|
-
if (
|
|
75
|
-
|
|
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 (
|
|
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
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
|
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
|
-
}, [
|
|
155
|
+
}, []);
|
|
155
156
|
|
|
156
|
-
const
|
|
157
|
+
const handleInput = useCallback(
|
|
157
158
|
(event: KeyboardEvent) => {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
[
|
|
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={
|
|
196
|
-
[
|
|
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={
|
|
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={
|
|
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
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
(
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
/>
|