@docusaurus/theme-search-algolia 0.0.0-4198 → 0.0.0-4202
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/copyUntypedFiles.js +20 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.js +80 -0
- package/lib/templates/opensearch.d.ts +8 -0
- package/lib/templates/opensearch.js +23 -0
- package/lib/theme/SearchBar/index.d.ts +9 -0
- package/lib/theme/SearchBar/index.js +153 -0
- package/lib/theme/SearchBar/styles.css +21 -0
- package/lib/theme/SearchBar/styles.module.css +20 -0
- package/lib/theme/SearchMetadata/index.d.ts +10 -0
- package/lib/theme/SearchMetadata/index.js +20 -0
- package/lib/theme/SearchPage/index.d.ts +9 -0
- package/lib/theme/SearchPage/index.js +304 -0
- package/lib/theme/SearchPage/styles.module.css +119 -0
- package/lib/theme/hooks/useAlgoliaContextualFacetFilters.d.ts +8 -0
- package/lib/theme/hooks/useAlgoliaContextualFacetFilters.js +15 -0
- package/lib/theme/hooks/useSearchQuery.d.ts +9 -0
- package/lib/theme/hooks/useSearchQuery.js +44 -0
- package/lib/validateThemeConfig.d.ts +13 -0
- package/lib/validateThemeConfig.js +39 -0
- package/package.json +18 -7
- package/src/{index.js → index.ts} +25 -16
- package/src/templates/{opensearch.js → opensearch.ts} +1 -1
- package/src/theme/SearchBar/{index.js → index.tsx} +70 -26
- package/src/theme/SearchMetadata/{index.js → index.tsx} +8 -1
- package/src/theme/SearchPage/{index.js → index.tsx} +60 -26
- package/src/theme/hooks/{useAlgoliaContextualFacetFilters.js → useAlgoliaContextualFacetFilters.ts} +3 -2
- package/src/theme/hooks/{useSearchQuery.js → useSearchQuery.ts} +4 -3
- package/src/theme-search-algolia.d.ts +49 -0
- package/src/types.d.ts +10 -0
- package/src/{validateThemeConfig.js → validateThemeConfig.ts} +9 -6
- package/tsconfig.browser.json +8 -0
- package/tsconfig.json +9 -0
- package/tsconfig.server.json +4 -0
|
@@ -5,26 +5,33 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import {defaultConfig, compile} from 'eta';
|
|
11
|
+
import {normalizeUrl, getSwizzledComponent} from '@docusaurus/utils';
|
|
12
|
+
import openSearchTemplate from './templates/opensearch';
|
|
13
|
+
import {memoize} from 'lodash';
|
|
14
|
+
|
|
15
|
+
import type {DocusaurusContext, Plugin} from '@docusaurus/types';
|
|
15
16
|
|
|
16
17
|
const getCompiledOpenSearchTemplate = memoize(() => {
|
|
17
|
-
return
|
|
18
|
+
return compile(openSearchTemplate.trim());
|
|
18
19
|
});
|
|
19
20
|
|
|
20
|
-
function renderOpenSearchTemplate(data
|
|
21
|
+
function renderOpenSearchTemplate(data: {
|
|
22
|
+
title: string;
|
|
23
|
+
url: string;
|
|
24
|
+
favicon: string | null;
|
|
25
|
+
}) {
|
|
21
26
|
const compiled = getCompiledOpenSearchTemplate();
|
|
22
|
-
return compiled(data,
|
|
27
|
+
return compiled(data, defaultConfig);
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
const OPEN_SEARCH_FILENAME = 'opensearch.xml';
|
|
26
31
|
|
|
27
|
-
function theme(
|
|
32
|
+
export default function theme(
|
|
33
|
+
context: DocusaurusContext & {baseUrl: string},
|
|
34
|
+
): Plugin<void> {
|
|
28
35
|
const {
|
|
29
36
|
baseUrl,
|
|
30
37
|
siteConfig: {title, url, favicon},
|
|
@@ -37,12 +44,16 @@ function theme(context) {
|
|
|
37
44
|
return {
|
|
38
45
|
name: 'docusaurus-theme-search-algolia',
|
|
39
46
|
|
|
47
|
+
getPathsToWatch() {
|
|
48
|
+
return [pagePath];
|
|
49
|
+
},
|
|
50
|
+
|
|
40
51
|
getThemePath() {
|
|
41
52
|
return path.resolve(__dirname, './theme');
|
|
42
53
|
},
|
|
43
54
|
|
|
44
|
-
|
|
45
|
-
return
|
|
55
|
+
getTypeScriptThemePath() {
|
|
56
|
+
return path.resolve(__dirname, '..', 'src', 'theme');
|
|
46
57
|
},
|
|
47
58
|
|
|
48
59
|
async contentLoaded({actions: {addRoute}}) {
|
|
@@ -87,6 +98,4 @@ function theme(context) {
|
|
|
87
98
|
};
|
|
88
99
|
}
|
|
89
100
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
theme.validateThemeConfig = validateThemeConfig;
|
|
101
|
+
export {validateThemeConfig} from './validateThemeConfig';
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
export default `
|
|
9
9
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
10
10
|
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
|
|
11
11
|
xmlns:moz="http://www.mozilla.org/2006/browser/search/">
|
|
@@ -4,6 +4,7 @@
|
|
|
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
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
7
8
|
|
|
8
9
|
import React, {useState, useRef, useCallback, useMemo} from 'react';
|
|
9
10
|
import {createPortal} from 'react-dom';
|
|
@@ -19,13 +20,42 @@ import useAlgoliaContextualFacetFilters from '@theme/hooks/useAlgoliaContextualF
|
|
|
19
20
|
import {translate} from '@docusaurus/Translate';
|
|
20
21
|
import styles from './styles.module.css';
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
import type {
|
|
24
|
+
DocSearchModal as DocSearchModalType,
|
|
25
|
+
DocSearchModalProps,
|
|
26
|
+
} from '@docsearch/react';
|
|
27
|
+
import type {
|
|
28
|
+
InternalDocSearchHit,
|
|
29
|
+
StoredDocSearchHit,
|
|
30
|
+
} from '@docsearch/react/dist/esm/types';
|
|
31
|
+
import type {AutocompleteState} from '@algolia/autocomplete-core';
|
|
23
32
|
|
|
24
|
-
|
|
33
|
+
type DocSearchProps = Omit<
|
|
34
|
+
DocSearchModalProps,
|
|
35
|
+
'onClose' | 'initialScrollY'
|
|
36
|
+
> & {
|
|
37
|
+
contextualSearch?: string;
|
|
38
|
+
externalUrlRegex?: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
let DocSearchModal: typeof DocSearchModalType | null = null;
|
|
42
|
+
|
|
43
|
+
function Hit({
|
|
44
|
+
hit,
|
|
45
|
+
children,
|
|
46
|
+
}: {
|
|
47
|
+
hit: InternalDocSearchHit | StoredDocSearchHit;
|
|
48
|
+
children: React.ReactNode;
|
|
49
|
+
}) {
|
|
25
50
|
return <Link to={hit.url}>{children}</Link>;
|
|
26
51
|
}
|
|
27
52
|
|
|
28
|
-
|
|
53
|
+
type ResultsFooterProps = {
|
|
54
|
+
state: AutocompleteState<InternalDocSearchHit>;
|
|
55
|
+
onClose: () => void;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function ResultsFooter({state, onClose}: ResultsFooterProps) {
|
|
29
59
|
const {generateSearchPageLink} = useSearchQuery();
|
|
30
60
|
|
|
31
61
|
return (
|
|
@@ -35,7 +65,11 @@ function ResultsFooter({state, onClose}) {
|
|
|
35
65
|
);
|
|
36
66
|
}
|
|
37
67
|
|
|
38
|
-
function DocSearch({
|
|
68
|
+
function DocSearch({
|
|
69
|
+
contextualSearch,
|
|
70
|
+
externalUrlRegex,
|
|
71
|
+
...props
|
|
72
|
+
}: DocSearchProps) {
|
|
39
73
|
const {siteMetadata} = useDocusaurusContext();
|
|
40
74
|
|
|
41
75
|
const contextualSearchFacetFilters = useAlgoliaContextualFacetFilters();
|
|
@@ -56,10 +90,12 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
|
|
|
56
90
|
|
|
57
91
|
const {withBaseUrl} = useBaseUrlUtils();
|
|
58
92
|
const history = useHistory();
|
|
59
|
-
const searchContainer = useRef(null);
|
|
60
|
-
const searchButtonRef = useRef(null);
|
|
93
|
+
const searchContainer = useRef<HTMLDivElement | null>(null);
|
|
94
|
+
const searchButtonRef = useRef<HTMLButtonElement>(null);
|
|
61
95
|
const [isOpen, setIsOpen] = useState(false);
|
|
62
|
-
const [initialQuery, setInitialQuery] = useState(
|
|
96
|
+
const [initialQuery, setInitialQuery] = useState<string | undefined>(
|
|
97
|
+
undefined,
|
|
98
|
+
);
|
|
63
99
|
|
|
64
100
|
const importDocSearchModalIfNeeded = useCallback(() => {
|
|
65
101
|
if (DocSearchModal) {
|
|
@@ -67,7 +103,9 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
|
|
|
67
103
|
}
|
|
68
104
|
|
|
69
105
|
return Promise.all([
|
|
106
|
+
// @ts-ignore
|
|
70
107
|
import('@docsearch/react/modal'),
|
|
108
|
+
// @ts-ignore
|
|
71
109
|
import('@docsearch/react/style'),
|
|
72
110
|
import('./styles.css'),
|
|
73
111
|
]).then(([{DocSearchModal: Modal}]) => {
|
|
@@ -88,7 +126,7 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
|
|
|
88
126
|
|
|
89
127
|
const onClose = useCallback(() => {
|
|
90
128
|
setIsOpen(false);
|
|
91
|
-
searchContainer.current
|
|
129
|
+
searchContainer.current?.remove();
|
|
92
130
|
}, [setIsOpen]);
|
|
93
131
|
|
|
94
132
|
const onInput = useCallback(
|
|
@@ -102,35 +140,38 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
|
|
|
102
140
|
);
|
|
103
141
|
|
|
104
142
|
const navigator = useRef({
|
|
105
|
-
navigate({itemUrl}) {
|
|
143
|
+
navigate({itemUrl}: {itemUrl?: string}) {
|
|
106
144
|
// Algolia results could contain URL's from other domains which cannot
|
|
107
145
|
// be served through history and should navigate with window.location
|
|
108
146
|
if (isRegexpStringMatch(externalUrlRegex, itemUrl)) {
|
|
109
|
-
window.location.href = itemUrl
|
|
147
|
+
window.location.href = itemUrl!;
|
|
110
148
|
} else {
|
|
111
|
-
history.push(itemUrl);
|
|
149
|
+
history.push(itemUrl!);
|
|
112
150
|
}
|
|
113
151
|
},
|
|
114
152
|
}).current;
|
|
115
153
|
|
|
116
|
-
const transformItems = useRef(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
154
|
+
const transformItems = useRef<DocSearchModalProps['transformItems']>(
|
|
155
|
+
(items) => {
|
|
156
|
+
return items.map((item) => {
|
|
157
|
+
// If Algolia contains a external domain, we should navigate without relative URL
|
|
158
|
+
if (isRegexpStringMatch(externalUrlRegex, item.url)) {
|
|
159
|
+
return item;
|
|
160
|
+
}
|
|
122
161
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
162
|
+
// We transform the absolute URL into a relative URL.
|
|
163
|
+
const url = new URL(item.url);
|
|
164
|
+
return {
|
|
165
|
+
...item,
|
|
166
|
+
url: withBaseUrl(`${url.pathname}${url.hash}`),
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
).current;
|
|
131
171
|
|
|
132
172
|
const resultsFooterComponent = useMemo(
|
|
133
|
-
() => (footerProps) =>
|
|
173
|
+
() => (footerProps: ResultsFooterProps) =>
|
|
174
|
+
<ResultsFooter {...footerProps} onClose={onClose} />,
|
|
134
175
|
[onClose],
|
|
135
176
|
);
|
|
136
177
|
|
|
@@ -188,6 +229,8 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
|
|
|
188
229
|
</div>
|
|
189
230
|
|
|
190
231
|
{isOpen &&
|
|
232
|
+
DocSearchModal &&
|
|
233
|
+
searchContainer.current &&
|
|
191
234
|
createPortal(
|
|
192
235
|
<DocSearchModal
|
|
193
236
|
onClose={onClose}
|
|
@@ -209,6 +252,7 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
|
|
|
209
252
|
|
|
210
253
|
function SearchBar() {
|
|
211
254
|
const {siteConfig} = useDocusaurusContext();
|
|
255
|
+
// @ts-ignore
|
|
212
256
|
return <DocSearch {...siteConfig.themeConfig.algolia} />;
|
|
213
257
|
}
|
|
214
258
|
|
|
@@ -8,9 +8,14 @@
|
|
|
8
8
|
import React from 'react';
|
|
9
9
|
|
|
10
10
|
import Head from '@docusaurus/Head';
|
|
11
|
+
import type {SearchMetadataProps} from '@theme/SearchMetadata';
|
|
11
12
|
|
|
12
13
|
// Override default/agnostic SearchMetas to use Algolia-specific metadata
|
|
13
|
-
|
|
14
|
+
function SearchMetadata({
|
|
15
|
+
locale,
|
|
16
|
+
version,
|
|
17
|
+
tag,
|
|
18
|
+
}: SearchMetadataProps): JSX.Element {
|
|
14
19
|
// Seems safe to consider here the locale is the language,
|
|
15
20
|
// as the existing docsearch:language filter is afaik a regular string-based filter
|
|
16
21
|
const language = locale;
|
|
@@ -23,3 +28,5 @@ export default function AlgoliaSearchMetadata({locale, version, tag}) {
|
|
|
23
28
|
</Head>
|
|
24
29
|
);
|
|
25
30
|
}
|
|
31
|
+
|
|
32
|
+
export default SearchMetadata;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
/* eslint-disable jsx-a11y/no-autofocus */
|
|
9
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
9
10
|
|
|
10
11
|
import React, {useEffect, useState, useReducer, useRef} from 'react';
|
|
11
12
|
|
|
@@ -32,7 +33,7 @@ import styles from './styles.module.css';
|
|
|
32
33
|
// Very simple pluralization: probably good enough for now
|
|
33
34
|
function useDocumentsFoundPlural() {
|
|
34
35
|
const {selectMessage} = usePluralForm();
|
|
35
|
-
return (count) =>
|
|
36
|
+
return (count: number) =>
|
|
36
37
|
selectMessage(
|
|
37
38
|
count,
|
|
38
39
|
translate(
|
|
@@ -52,14 +53,19 @@ function useDocsSearchVersionsHelpers() {
|
|
|
52
53
|
|
|
53
54
|
// State of the version select menus / algolia facet filters
|
|
54
55
|
// docsPluginId -> versionName map
|
|
55
|
-
const [searchVersions, setSearchVersions] = useState(
|
|
56
|
-
|
|
57
|
-
return
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
const [searchVersions, setSearchVersions] = useState<Record<string, string>>(
|
|
57
|
+
() => {
|
|
58
|
+
return Object.entries(allDocsData).reduce(
|
|
59
|
+
(acc, [pluginId, pluginData]) => {
|
|
60
|
+
return {...acc, [pluginId]: pluginData.versions[0].name};
|
|
61
|
+
},
|
|
62
|
+
{},
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
);
|
|
60
66
|
|
|
61
67
|
// Set the value of a single select menu
|
|
62
|
-
const setSearchVersion = (pluginId, searchVersion) =>
|
|
68
|
+
const setSearchVersion = (pluginId: string, searchVersion: string) =>
|
|
63
69
|
setSearchVersions((s) => ({...s, [pluginId]: searchVersion}));
|
|
64
70
|
|
|
65
71
|
const versioningEnabled = Object.values(allDocsData).some(
|
|
@@ -75,7 +81,11 @@ function useDocsSearchVersionsHelpers() {
|
|
|
75
81
|
}
|
|
76
82
|
|
|
77
83
|
// We want to display one select per versioned docs plugin instance
|
|
78
|
-
const SearchVersionSelectList = ({
|
|
84
|
+
const SearchVersionSelectList = ({
|
|
85
|
+
docsSearchVersionsHelpers,
|
|
86
|
+
}: {
|
|
87
|
+
docsSearchVersionsHelpers: ReturnType<typeof useDocsSearchVersionsHelpers>;
|
|
88
|
+
}) => {
|
|
79
89
|
const versionedPluginEntries = Object.entries(
|
|
80
90
|
docsSearchVersionsHelpers.allDocsData,
|
|
81
91
|
)
|
|
@@ -118,10 +128,32 @@ const SearchVersionSelectList = ({docsSearchVersionsHelpers}) => {
|
|
|
118
128
|
);
|
|
119
129
|
};
|
|
120
130
|
|
|
121
|
-
|
|
131
|
+
type ResultDispatcherState = {
|
|
132
|
+
items: {
|
|
133
|
+
title: string;
|
|
134
|
+
url: string;
|
|
135
|
+
summary: string;
|
|
136
|
+
breadcrumbs: string[];
|
|
137
|
+
}[];
|
|
138
|
+
query: string | null;
|
|
139
|
+
totalResults: number | null;
|
|
140
|
+
totalPages: number | null;
|
|
141
|
+
lastPage: number | null;
|
|
142
|
+
hasMore: boolean | null;
|
|
143
|
+
loading: boolean | null;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
type ResultDispatcher =
|
|
147
|
+
| {type: 'reset'; value?: undefined}
|
|
148
|
+
| {type: 'loading'; value?: undefined}
|
|
149
|
+
| {type: 'update'; value: ResultDispatcherState}
|
|
150
|
+
| {type: 'advance'; value?: undefined};
|
|
151
|
+
|
|
152
|
+
function SearchPage(): JSX.Element {
|
|
122
153
|
const {
|
|
123
154
|
siteConfig: {
|
|
124
155
|
themeConfig: {
|
|
156
|
+
// @ts-ignore
|
|
125
157
|
algolia: {appId, apiKey, indexName, externalUrlRegex},
|
|
126
158
|
},
|
|
127
159
|
},
|
|
@@ -131,7 +163,7 @@ function SearchPage() {
|
|
|
131
163
|
|
|
132
164
|
const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers();
|
|
133
165
|
const {searchQuery, setSearchQuery} = useSearchQuery();
|
|
134
|
-
const initialSearchResultState = {
|
|
166
|
+
const initialSearchResultState: ResultDispatcherState = {
|
|
135
167
|
items: [],
|
|
136
168
|
query: null,
|
|
137
169
|
totalResults: null,
|
|
@@ -141,8 +173,8 @@ function SearchPage() {
|
|
|
141
173
|
loading: null,
|
|
142
174
|
};
|
|
143
175
|
const [searchResultState, searchResultStateDispatcher] = useReducer(
|
|
144
|
-
(prevState
|
|
145
|
-
switch (type) {
|
|
176
|
+
(prevState: ResultDispatcherState, data: ResultDispatcher) => {
|
|
177
|
+
switch (data.type) {
|
|
146
178
|
case 'reset': {
|
|
147
179
|
return initialSearchResultState;
|
|
148
180
|
}
|
|
@@ -150,24 +182,24 @@ function SearchPage() {
|
|
|
150
182
|
return {...prevState, loading: true};
|
|
151
183
|
}
|
|
152
184
|
case 'update': {
|
|
153
|
-
if (searchQuery !==
|
|
185
|
+
if (searchQuery !== data.value.query) {
|
|
154
186
|
return prevState;
|
|
155
187
|
}
|
|
156
188
|
|
|
157
189
|
return {
|
|
158
|
-
...
|
|
190
|
+
...data.value,
|
|
159
191
|
items:
|
|
160
|
-
|
|
161
|
-
?
|
|
162
|
-
: prevState.items.concat(
|
|
192
|
+
data.value.lastPage === 0
|
|
193
|
+
? data.value.items
|
|
194
|
+
: prevState.items.concat(data.value.items),
|
|
163
195
|
};
|
|
164
196
|
}
|
|
165
197
|
case 'advance': {
|
|
166
|
-
const hasMore = prevState.totalPages > prevState.lastPage + 1;
|
|
198
|
+
const hasMore = prevState.totalPages! > prevState.lastPage! + 1;
|
|
167
199
|
|
|
168
200
|
return {
|
|
169
201
|
...prevState,
|
|
170
|
-
lastPage: hasMore ? prevState.lastPage + 1 : prevState.lastPage,
|
|
202
|
+
lastPage: hasMore ? prevState.lastPage! + 1 : prevState.lastPage,
|
|
171
203
|
hasMore,
|
|
172
204
|
};
|
|
173
205
|
}
|
|
@@ -193,7 +225,7 @@ function SearchPage() {
|
|
|
193
225
|
return;
|
|
194
226
|
}
|
|
195
227
|
|
|
196
|
-
const sanitizeValue = (value) => {
|
|
228
|
+
const sanitizeValue = (value: string) => {
|
|
197
229
|
return value.replace(
|
|
198
230
|
/algolia-docsearch-suggestion--highlight/g,
|
|
199
231
|
'search-result-match',
|
|
@@ -212,7 +244,7 @@ function SearchPage() {
|
|
|
212
244
|
});
|
|
213
245
|
|
|
214
246
|
return {
|
|
215
|
-
title: titles.pop()
|
|
247
|
+
title: titles.pop()!,
|
|
216
248
|
url: isRegexpStringMatch(externalUrlRegex, parsedURL.href)
|
|
217
249
|
? parsedURL.href
|
|
218
250
|
: parsedURL.pathname + parsedURL.hash,
|
|
@@ -239,7 +271,7 @@ function SearchPage() {
|
|
|
239
271
|
},
|
|
240
272
|
);
|
|
241
273
|
|
|
242
|
-
const [loaderRef, setLoaderRef] = useState(null);
|
|
274
|
+
const [loaderRef, setLoaderRef] = useState<HTMLDivElement | null>(null);
|
|
243
275
|
const prevY = useRef(0);
|
|
244
276
|
const observer = useRef(
|
|
245
277
|
ExecutionEnvironment.canUseDOM &&
|
|
@@ -278,7 +310,7 @@ function SearchPage() {
|
|
|
278
310
|
description: 'The search page title for empty query',
|
|
279
311
|
});
|
|
280
312
|
|
|
281
|
-
const makeSearch = useDynamicCallback((page = 0) => {
|
|
313
|
+
const makeSearch = useDynamicCallback((page: number = 0) => {
|
|
282
314
|
algoliaHelper.addDisjunctiveFacetRefinement('docusaurus_tag', 'default');
|
|
283
315
|
algoliaHelper.addDisjunctiveFacetRefinement('language', currentLocale);
|
|
284
316
|
|
|
@@ -299,9 +331,11 @@ function SearchPage() {
|
|
|
299
331
|
return undefined;
|
|
300
332
|
}
|
|
301
333
|
const currentObserver = observer.current;
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
334
|
+
if (currentObserver) {
|
|
335
|
+
currentObserver.observe(loaderRef);
|
|
336
|
+
return () => currentObserver.unobserve(loaderRef);
|
|
337
|
+
}
|
|
338
|
+
return () => true;
|
|
305
339
|
}, [loaderRef]);
|
|
306
340
|
|
|
307
341
|
useEffect(() => {
|
package/src/theme/hooks/{useAlgoliaContextualFacetFilters.js → useAlgoliaContextualFacetFilters.ts}
RENAMED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import type {useAlgoliaContextualFacetFiltersReturns} from '@theme/hooks/useAlgoliaContextualFacetFilters';
|
|
9
|
+
import {useContextualSearchFilters} from '@docusaurus/theme-common';
|
|
9
10
|
|
|
10
11
|
// Translate search-engine agnostic search filters to Algolia search filters
|
|
11
|
-
export default function useAlgoliaContextualFacetFilters() {
|
|
12
|
+
export default function useAlgoliaContextualFacetFilters(): useAlgoliaContextualFacetFiltersReturns {
|
|
12
13
|
const {locale, tags} = useContextualSearchFilters();
|
|
13
14
|
|
|
14
15
|
// seems safe to convert locale->language, see AlgoliaSearchMetadata comment
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
import {useHistory} from '@docusaurus/router';
|
|
9
9
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
10
10
|
import {useCallback, useEffect, useState} from 'react';
|
|
11
|
+
import type {SearchQuery} from '@theme/hooks/useSearchQuery';
|
|
11
12
|
|
|
12
13
|
const SEARCH_PARAM_QUERY = 'q';
|
|
13
14
|
|
|
14
|
-
function useSearchQuery() {
|
|
15
|
+
function useSearchQuery(): SearchQuery {
|
|
15
16
|
const history = useHistory();
|
|
16
17
|
const {
|
|
17
18
|
siteConfig: {baseUrl},
|
|
@@ -28,7 +29,7 @@ function useSearchQuery() {
|
|
|
28
29
|
}, []);
|
|
29
30
|
|
|
30
31
|
const setSearchQuery = useCallback(
|
|
31
|
-
(newSearchQuery) => {
|
|
32
|
+
(newSearchQuery: string) => {
|
|
32
33
|
const searchParams = new URLSearchParams(window.location.search);
|
|
33
34
|
|
|
34
35
|
if (newSearchQuery) {
|
|
@@ -46,7 +47,7 @@ function useSearchQuery() {
|
|
|
46
47
|
);
|
|
47
48
|
|
|
48
49
|
const generateSearchPageLink = useCallback(
|
|
49
|
-
(targetSearchQuery) => {
|
|
50
|
+
(targetSearchQuery: string) => {
|
|
50
51
|
// Refer to https://github.com/facebook/docusaurus/pull/2838
|
|
51
52
|
return `${baseUrl}search?q=${encodeURIComponent(targetSearchQuery)}`;
|
|
52
53
|
},
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
declare module '@docusaurus/theme-search-algolia' {
|
|
9
|
+
export type Options = never;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
declare module '@theme/hooks/useSearchQuery' {
|
|
13
|
+
export interface SearchQuery {
|
|
14
|
+
searchQuery: string;
|
|
15
|
+
|
|
16
|
+
setSearchQuery(newSearchQuery: string): void;
|
|
17
|
+
|
|
18
|
+
generateSearchPageLink(targetSearchQuery: string): string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default function useSearchQuery(): SearchQuery;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
declare module '@theme/hooks/useAlgoliaContextualFacetFilters' {
|
|
25
|
+
export type useAlgoliaContextualFacetFiltersReturns = [string, string[]];
|
|
26
|
+
|
|
27
|
+
export default function useAlgoliaContextualFacetFilters(): useAlgoliaContextualFacetFiltersReturns;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
declare module '@theme/SearchPage' {
|
|
31
|
+
const SearchPage: () => JSX.Element;
|
|
32
|
+
export default SearchPage;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
declare module '@theme/SearchMetadata' {
|
|
36
|
+
export type SearchMetadataProps = {
|
|
37
|
+
readonly locale?: string;
|
|
38
|
+
readonly version?: string;
|
|
39
|
+
readonly tag?: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const SearchMetadata: (props: SearchMetadataProps) => JSX.Element;
|
|
43
|
+
export default SearchMetadata;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
declare module '@theme/SearchBar' {
|
|
47
|
+
const SearchBar: () => JSX.Element;
|
|
48
|
+
export default SearchBar;
|
|
49
|
+
}
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
/// <reference types="@docusaurus/module-type-aliases" />
|
|
9
|
+
/// <reference types="@docusaurus/theme-common" />
|
|
10
|
+
/// <reference types="@docusaurus/theme-classic" />
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import {Joi} from '@docusaurus/utils-validation';
|
|
9
|
+
import type {ThemeConfig, Validate, ValidationResult} from '@docusaurus/types';
|
|
9
10
|
|
|
10
11
|
const DEFAULT_CONFIG = {
|
|
11
12
|
contextualSearch: false, // future: maybe we want to enable this by default
|
|
@@ -18,7 +19,7 @@ const DEFAULT_CONFIG = {
|
|
|
18
19
|
};
|
|
19
20
|
exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
|
|
20
21
|
|
|
21
|
-
const Schema = Joi.object({
|
|
22
|
+
export const Schema = Joi.object({
|
|
22
23
|
algolia: Joi.object({
|
|
23
24
|
// Docusaurus attributes
|
|
24
25
|
contextualSearch: Joi.boolean().default(DEFAULT_CONFIG.contextualSearch),
|
|
@@ -35,11 +36,13 @@ const Schema = Joi.object({
|
|
|
35
36
|
.required()
|
|
36
37
|
.unknown(), // DocSearch 3 is still alpha: don't validate the rest for now
|
|
37
38
|
});
|
|
38
|
-
exports.Schema = Schema;
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
export function validateThemeConfig({
|
|
41
41
|
validate,
|
|
42
42
|
themeConfig,
|
|
43
|
-
}
|
|
43
|
+
}: {
|
|
44
|
+
validate: Validate<ThemeConfig>;
|
|
45
|
+
themeConfig: ThemeConfig;
|
|
46
|
+
}): ValidationResult<ThemeConfig> {
|
|
44
47
|
return validate(Schema, themeConfig);
|
|
45
|
-
}
|
|
48
|
+
}
|
package/tsconfig.json
ADDED