@docusaurus/theme-search-algolia 3.8.1 → 3.9.0-canary-6403
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/index.d.ts +3 -1
- package/lib/client/index.js +3 -1
- package/lib/client/useAlgoliaAskAi.d.ts +32 -0
- package/lib/client/useAlgoliaAskAi.js +59 -0
- package/lib/client/useAlgoliaContextualFacetFilters.d.ts +3 -1
- package/lib/client/useAlgoliaContextualFacetFilters.js +11 -0
- package/lib/client/utils.d.ts +9 -0
- package/lib/client/utils.js +21 -0
- package/lib/docSearchVersion.d.ts +7 -0
- package/lib/docSearchVersion.js +13 -0
- package/lib/index.js +20 -0
- package/lib/theme/SearchBar/index.js +12 -12
- package/lib/theme/SearchBar/styles.css +4 -0
- package/lib/theme/SearchPage/index.js +70 -25
- package/lib/theme/SearchPage/styles.module.css +12 -4
- package/lib/theme/SearchTranslations/index.d.ts +47 -0
- package/lib/theme/SearchTranslations/index.js +186 -31
- package/lib/validateThemeConfig.d.ts +4 -3
- package/lib/validateThemeConfig.js +66 -4
- package/package.json +14 -14
- package/src/client/index.ts +6 -1
- package/src/client/useAlgoliaAskAi.ts +108 -0
- package/src/client/useAlgoliaContextualFacetFilters.ts +17 -1
- package/src/client/utils.ts +40 -0
- package/src/docSearchVersion.ts +12 -0
- package/src/index.ts +21 -0
- package/src/theme/SearchBar/index.tsx +41 -24
- package/src/theme/SearchBar/styles.css +4 -0
- package/src/theme/SearchPage/index.tsx +71 -25
- package/src/theme/SearchPage/styles.module.css +12 -4
- package/src/theme/SearchTranslations/index.ts +237 -32
- package/src/theme-search-algolia.d.ts +51 -16
- package/src/validateThemeConfig.ts +87 -9
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
import {translate} from '@docusaurus/Translate';
|
|
8
|
+
// TODO Docusaurus v4: require DocSearch v4
|
|
9
|
+
// This needs to be cleaned after the upgrade
|
|
10
|
+
// Docusaurus v3 was made compatible with both DocSearch v3 and v4
|
|
11
|
+
// This implies that labels have been kept retro-compatible with v3
|
|
12
|
+
// Once we upgrade, we should be able to rely on v4 types only
|
|
13
|
+
// and remove v3 retro-compatibility labels that do not exist anymore in v4
|
|
8
14
|
const translations = {
|
|
9
15
|
button: {
|
|
10
16
|
buttonText: translate({
|
|
@@ -40,6 +46,68 @@ const translations = {
|
|
|
40
46
|
message: 'Cancel',
|
|
41
47
|
description: 'The label and ARIA label for search box cancel button',
|
|
42
48
|
}),
|
|
49
|
+
// v4
|
|
50
|
+
clearButtonTitle: translate({
|
|
51
|
+
id: 'theme.SearchModal.searchBox.resetButtonTitle',
|
|
52
|
+
message: 'Clear the query',
|
|
53
|
+
description: 'The label and ARIA label for search box reset button',
|
|
54
|
+
}),
|
|
55
|
+
clearButtonAriaLabel: translate({
|
|
56
|
+
id: 'theme.SearchModal.searchBox.resetButtonTitle',
|
|
57
|
+
message: 'Clear the query',
|
|
58
|
+
description: 'The label and ARIA label for search box reset button',
|
|
59
|
+
}),
|
|
60
|
+
closeButtonText: translate({
|
|
61
|
+
id: 'theme.SearchModal.searchBox.cancelButtonText',
|
|
62
|
+
message: 'Cancel',
|
|
63
|
+
description: 'The label and ARIA label for search box cancel button',
|
|
64
|
+
}),
|
|
65
|
+
closeButtonAriaLabel: translate({
|
|
66
|
+
id: 'theme.SearchModal.searchBox.cancelButtonText',
|
|
67
|
+
message: 'Cancel',
|
|
68
|
+
description: 'The label and ARIA label for search box cancel button',
|
|
69
|
+
}),
|
|
70
|
+
placeholderText: translate({
|
|
71
|
+
id: 'theme.SearchModal.searchBox.placeholderText',
|
|
72
|
+
message: 'Search docs',
|
|
73
|
+
description: 'The placeholder text for the main search input field',
|
|
74
|
+
}),
|
|
75
|
+
placeholderTextAskAi: translate({
|
|
76
|
+
id: 'theme.SearchModal.searchBox.placeholderTextAskAi',
|
|
77
|
+
message: 'Ask another question...',
|
|
78
|
+
description: 'The placeholder text when in AI question mode',
|
|
79
|
+
}),
|
|
80
|
+
placeholderTextAskAiStreaming: translate({
|
|
81
|
+
id: 'theme.SearchModal.searchBox.placeholderTextAskAiStreaming',
|
|
82
|
+
message: 'Answering...',
|
|
83
|
+
description:
|
|
84
|
+
'The placeholder text for search box when AI is streaming an answer',
|
|
85
|
+
}),
|
|
86
|
+
enterKeyHint: translate({
|
|
87
|
+
id: 'theme.SearchModal.searchBox.enterKeyHint',
|
|
88
|
+
message: 'search',
|
|
89
|
+
description: 'The hint for the search box enter key text',
|
|
90
|
+
}),
|
|
91
|
+
enterKeyHintAskAi: translate({
|
|
92
|
+
id: 'theme.SearchModal.searchBox.enterKeyHintAskAi',
|
|
93
|
+
message: 'enter',
|
|
94
|
+
description: 'The hint for the Ask AI search box enter key text',
|
|
95
|
+
}),
|
|
96
|
+
searchInputLabel: translate({
|
|
97
|
+
id: 'theme.SearchModal.searchBox.searchInputLabel',
|
|
98
|
+
message: 'Search',
|
|
99
|
+
description: 'The ARIA label for search input',
|
|
100
|
+
}),
|
|
101
|
+
backToKeywordSearchButtonText: translate({
|
|
102
|
+
id: 'theme.SearchModal.searchBox.backToKeywordSearchButtonText',
|
|
103
|
+
message: 'Back to keyword search',
|
|
104
|
+
description: 'The text for back to keyword search button',
|
|
105
|
+
}),
|
|
106
|
+
backToKeywordSearchButtonAriaLabel: translate({
|
|
107
|
+
id: 'theme.SearchModal.searchBox.backToKeywordSearchButtonAriaLabel',
|
|
108
|
+
message: 'Back to keyword search',
|
|
109
|
+
description: 'The ARIA label for back to keyword search button',
|
|
110
|
+
}),
|
|
43
111
|
},
|
|
44
112
|
startScreen: {
|
|
45
113
|
recentSearchesTitle: translate({
|
|
@@ -50,17 +118,17 @@ const translations = {
|
|
|
50
118
|
noRecentSearchesText: translate({
|
|
51
119
|
id: 'theme.SearchModal.startScreen.noRecentSearchesText',
|
|
52
120
|
message: 'No recent searches',
|
|
53
|
-
description: 'The text when no recent searches',
|
|
121
|
+
description: 'The text when there are no recent searches',
|
|
54
122
|
}),
|
|
55
123
|
saveRecentSearchButtonTitle: translate({
|
|
56
124
|
id: 'theme.SearchModal.startScreen.saveRecentSearchButtonTitle',
|
|
57
125
|
message: 'Save this search',
|
|
58
|
-
description: 'The
|
|
126
|
+
description: 'The title for save recent search button',
|
|
59
127
|
}),
|
|
60
128
|
removeRecentSearchButtonTitle: translate({
|
|
61
129
|
id: 'theme.SearchModal.startScreen.removeRecentSearchButtonTitle',
|
|
62
130
|
message: 'Remove this search from history',
|
|
63
|
-
description: 'The
|
|
131
|
+
description: 'The title for remove recent search button',
|
|
64
132
|
}),
|
|
65
133
|
favoriteSearchesTitle: translate({
|
|
66
134
|
id: 'theme.SearchModal.startScreen.favoriteSearchesTitle',
|
|
@@ -70,91 +138,178 @@ const translations = {
|
|
|
70
138
|
removeFavoriteSearchButtonTitle: translate({
|
|
71
139
|
id: 'theme.SearchModal.startScreen.removeFavoriteSearchButtonTitle',
|
|
72
140
|
message: 'Remove this search from favorites',
|
|
73
|
-
description: 'The
|
|
141
|
+
description: 'The title for remove favorite search button',
|
|
142
|
+
}),
|
|
143
|
+
recentConversationsTitle: translate({
|
|
144
|
+
id: 'theme.SearchModal.startScreen.recentConversationsTitle',
|
|
145
|
+
message: 'Recent conversations',
|
|
146
|
+
description: 'The title for recent conversations',
|
|
147
|
+
}),
|
|
148
|
+
removeRecentConversationButtonTitle: translate({
|
|
149
|
+
id: 'theme.SearchModal.startScreen.removeRecentConversationButtonTitle',
|
|
150
|
+
message: 'Remove this conversation from history',
|
|
151
|
+
description: 'The title for remove recent conversation button',
|
|
74
152
|
}),
|
|
75
153
|
},
|
|
76
154
|
errorScreen: {
|
|
77
155
|
titleText: translate({
|
|
78
156
|
id: 'theme.SearchModal.errorScreen.titleText',
|
|
79
157
|
message: 'Unable to fetch results',
|
|
80
|
-
description: 'The title for error screen
|
|
158
|
+
description: 'The title for error screen',
|
|
81
159
|
}),
|
|
82
160
|
helpText: translate({
|
|
83
161
|
id: 'theme.SearchModal.errorScreen.helpText',
|
|
84
162
|
message: 'You might want to check your network connection.',
|
|
85
|
-
description: 'The help text for error screen
|
|
163
|
+
description: 'The help text for error screen',
|
|
164
|
+
}),
|
|
165
|
+
},
|
|
166
|
+
resultsScreen: {
|
|
167
|
+
askAiPlaceholder: translate({
|
|
168
|
+
id: 'theme.SearchModal.resultsScreen.askAiPlaceholder',
|
|
169
|
+
message: 'Ask AI: ',
|
|
170
|
+
description: 'The placeholder text for Ask AI input',
|
|
171
|
+
}),
|
|
172
|
+
},
|
|
173
|
+
askAiScreen: {
|
|
174
|
+
disclaimerText: translate({
|
|
175
|
+
id: 'theme.SearchModal.askAiScreen.disclaimerText',
|
|
176
|
+
message:
|
|
177
|
+
'Answers are generated with AI which can make mistakes. Verify responses.',
|
|
178
|
+
description: 'The disclaimer text for AI answers',
|
|
179
|
+
}),
|
|
180
|
+
relatedSourcesText: translate({
|
|
181
|
+
id: 'theme.SearchModal.askAiScreen.relatedSourcesText',
|
|
182
|
+
message: 'Related sources',
|
|
183
|
+
description: 'The text for related sources',
|
|
184
|
+
}),
|
|
185
|
+
thinkingText: translate({
|
|
186
|
+
id: 'theme.SearchModal.askAiScreen.thinkingText',
|
|
187
|
+
message: 'Thinking...',
|
|
188
|
+
description: 'The text when AI is thinking',
|
|
189
|
+
}),
|
|
190
|
+
copyButtonText: translate({
|
|
191
|
+
id: 'theme.SearchModal.askAiScreen.copyButtonText',
|
|
192
|
+
message: 'Copy',
|
|
193
|
+
description: 'The text for copy button',
|
|
194
|
+
}),
|
|
195
|
+
copyButtonCopiedText: translate({
|
|
196
|
+
id: 'theme.SearchModal.askAiScreen.copyButtonCopiedText',
|
|
197
|
+
message: 'Copied!',
|
|
198
|
+
description: 'The text for copy button when copied',
|
|
199
|
+
}),
|
|
200
|
+
copyButtonTitle: translate({
|
|
201
|
+
id: 'theme.SearchModal.askAiScreen.copyButtonTitle',
|
|
202
|
+
message: 'Copy',
|
|
203
|
+
description: 'The title for copy button',
|
|
204
|
+
}),
|
|
205
|
+
likeButtonTitle: translate({
|
|
206
|
+
id: 'theme.SearchModal.askAiScreen.likeButtonTitle',
|
|
207
|
+
message: 'Like',
|
|
208
|
+
description: 'The title for like button',
|
|
209
|
+
}),
|
|
210
|
+
dislikeButtonTitle: translate({
|
|
211
|
+
id: 'theme.SearchModal.askAiScreen.dislikeButtonTitle',
|
|
212
|
+
message: 'Dislike',
|
|
213
|
+
description: 'The title for dislike button',
|
|
214
|
+
}),
|
|
215
|
+
thanksForFeedbackText: translate({
|
|
216
|
+
id: 'theme.SearchModal.askAiScreen.thanksForFeedbackText',
|
|
217
|
+
message: 'Thanks for your feedback!',
|
|
218
|
+
description: 'The text for thanks for feedback',
|
|
219
|
+
}),
|
|
220
|
+
preToolCallText: translate({
|
|
221
|
+
id: 'theme.SearchModal.askAiScreen.preToolCallText',
|
|
222
|
+
message: 'Searching...',
|
|
223
|
+
description: 'The text before tool call',
|
|
224
|
+
}),
|
|
225
|
+
duringToolCallText: translate({
|
|
226
|
+
id: 'theme.SearchModal.askAiScreen.duringToolCallText',
|
|
227
|
+
message: 'Searching for ',
|
|
228
|
+
description: 'The text during tool call',
|
|
229
|
+
}),
|
|
230
|
+
afterToolCallText: translate({
|
|
231
|
+
id: 'theme.SearchModal.askAiScreen.afterToolCallText',
|
|
232
|
+
message: 'Searched for',
|
|
233
|
+
description: 'The text after tool call',
|
|
86
234
|
}),
|
|
87
235
|
},
|
|
88
236
|
footer: {
|
|
89
237
|
selectText: translate({
|
|
90
238
|
id: 'theme.SearchModal.footer.selectText',
|
|
91
|
-
message: '
|
|
92
|
-
description: 'The
|
|
239
|
+
message: 'Select',
|
|
240
|
+
description: 'The select text for footer',
|
|
241
|
+
}),
|
|
242
|
+
submitQuestionText: translate({
|
|
243
|
+
id: 'theme.SearchModal.footer.submitQuestionText',
|
|
244
|
+
message: 'Submit question',
|
|
245
|
+
description: 'The submit question text for footer',
|
|
93
246
|
}),
|
|
94
247
|
selectKeyAriaLabel: translate({
|
|
95
248
|
id: 'theme.SearchModal.footer.selectKeyAriaLabel',
|
|
96
249
|
message: 'Enter key',
|
|
97
|
-
description:
|
|
98
|
-
'The ARIA label for the Enter key button that makes the selection',
|
|
250
|
+
description: 'The ARIA label for select key in footer',
|
|
99
251
|
}),
|
|
100
252
|
navigateText: translate({
|
|
101
253
|
id: 'theme.SearchModal.footer.navigateText',
|
|
102
|
-
message: '
|
|
103
|
-
description:
|
|
104
|
-
'The explanatory text of the action for the Arrow up and Arrow down key',
|
|
254
|
+
message: 'Navigate',
|
|
255
|
+
description: 'The navigate text for footer',
|
|
105
256
|
}),
|
|
106
257
|
navigateUpKeyAriaLabel: translate({
|
|
107
258
|
id: 'theme.SearchModal.footer.navigateUpKeyAriaLabel',
|
|
108
259
|
message: 'Arrow up',
|
|
109
|
-
description:
|
|
110
|
-
'The ARIA label for the Arrow up key button that makes the navigation',
|
|
260
|
+
description: 'The ARIA label for navigate up key in footer',
|
|
111
261
|
}),
|
|
112
262
|
navigateDownKeyAriaLabel: translate({
|
|
113
263
|
id: 'theme.SearchModal.footer.navigateDownKeyAriaLabel',
|
|
114
264
|
message: 'Arrow down',
|
|
115
|
-
description:
|
|
116
|
-
'The ARIA label for the Arrow down key button that makes the navigation',
|
|
265
|
+
description: 'The ARIA label for navigate down key in footer',
|
|
117
266
|
}),
|
|
118
267
|
closeText: translate({
|
|
119
268
|
id: 'theme.SearchModal.footer.closeText',
|
|
120
|
-
message: '
|
|
121
|
-
description: 'The
|
|
269
|
+
message: 'Close',
|
|
270
|
+
description: 'The close text for footer',
|
|
122
271
|
}),
|
|
123
272
|
closeKeyAriaLabel: translate({
|
|
124
273
|
id: 'theme.SearchModal.footer.closeKeyAriaLabel',
|
|
125
274
|
message: 'Escape key',
|
|
126
|
-
description:
|
|
127
|
-
|
|
275
|
+
description: 'The ARIA label for close key in footer',
|
|
276
|
+
}),
|
|
277
|
+
poweredByText: translate({
|
|
278
|
+
id: 'theme.SearchModal.footer.searchByText',
|
|
279
|
+
message: 'Powered by',
|
|
280
|
+
description: "The 'Powered by' text for footer",
|
|
128
281
|
}),
|
|
129
282
|
searchByText: translate({
|
|
130
283
|
id: 'theme.SearchModal.footer.searchByText',
|
|
131
|
-
message: '
|
|
132
|
-
description:
|
|
284
|
+
message: 'Powered by',
|
|
285
|
+
description: "The 'Powered by' text for footer",
|
|
286
|
+
}),
|
|
287
|
+
backToSearchText: translate({
|
|
288
|
+
id: 'theme.SearchModal.footer.backToSearchText',
|
|
289
|
+
message: 'Back to search',
|
|
290
|
+
description: 'The back to search text for footer',
|
|
133
291
|
}),
|
|
134
292
|
},
|
|
135
293
|
noResultsScreen: {
|
|
136
294
|
noResultsText: translate({
|
|
137
295
|
id: 'theme.SearchModal.noResultsScreen.noResultsText',
|
|
138
|
-
message: 'No results for',
|
|
139
|
-
description:
|
|
140
|
-
'The text explains that there are no results for the following search',
|
|
296
|
+
message: 'No results found for',
|
|
297
|
+
description: 'The text when there are no results',
|
|
141
298
|
}),
|
|
142
299
|
suggestedQueryText: translate({
|
|
143
300
|
id: 'theme.SearchModal.noResultsScreen.suggestedQueryText',
|
|
144
301
|
message: 'Try searching for',
|
|
145
|
-
description:
|
|
146
|
-
'The text for the suggested query when no results are found for the following search',
|
|
302
|
+
description: 'The text for suggested query',
|
|
147
303
|
}),
|
|
148
304
|
reportMissingResultsText: translate({
|
|
149
305
|
id: 'theme.SearchModal.noResultsScreen.reportMissingResultsText',
|
|
150
306
|
message: 'Believe this query should return results?',
|
|
151
|
-
description:
|
|
152
|
-
'The text for the question where the user thinks there are missing results',
|
|
307
|
+
description: 'The text for reporting missing results',
|
|
153
308
|
}),
|
|
154
309
|
reportMissingResultsLinkText: translate({
|
|
155
310
|
id: 'theme.SearchModal.noResultsScreen.reportMissingResultsLinkText',
|
|
156
311
|
message: 'Let us know.',
|
|
157
|
-
description: 'The text for
|
|
312
|
+
description: 'The link text for reporting missing results',
|
|
158
313
|
}),
|
|
159
314
|
},
|
|
160
315
|
},
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
import { Joi } from '@docusaurus/utils-validation';
|
|
8
|
-
import type {
|
|
8
|
+
import type { ThemeConfigValidationContext } from '@docusaurus/types';
|
|
9
|
+
import type { ThemeConfig } from '@docusaurus/theme-search-algolia';
|
|
9
10
|
export declare const DEFAULT_CONFIG: {
|
|
10
|
-
contextualSearch:
|
|
11
|
+
contextualSearch: true;
|
|
11
12
|
searchParameters: {};
|
|
12
13
|
searchPagePath: string;
|
|
13
14
|
};
|
|
14
15
|
export declare const Schema: Joi.ObjectSchema<ThemeConfig>;
|
|
15
|
-
export declare function validateThemeConfig({ validate, themeConfig, }: ThemeConfigValidationContext<ThemeConfig>): ThemeConfig;
|
|
16
|
+
export declare function validateThemeConfig({ validate, themeConfig: themeConfigInput, }: ThemeConfigValidationContext<ThemeConfig>): ThemeConfig;
|
|
@@ -10,6 +10,7 @@ exports.Schema = exports.DEFAULT_CONFIG = void 0;
|
|
|
10
10
|
exports.validateThemeConfig = validateThemeConfig;
|
|
11
11
|
const utils_1 = require("@docusaurus/utils");
|
|
12
12
|
const utils_validation_1 = require("@docusaurus/utils-validation");
|
|
13
|
+
const docSearchVersion_1 = require("./docSearchVersion");
|
|
13
14
|
exports.DEFAULT_CONFIG = {
|
|
14
15
|
// Enabled by default, as it makes sense in most cases
|
|
15
16
|
// see also https://github.com/facebook/docusaurus/issues/5880
|
|
@@ -17,6 +18,7 @@ exports.DEFAULT_CONFIG = {
|
|
|
17
18
|
searchParameters: {},
|
|
18
19
|
searchPagePath: 'search',
|
|
19
20
|
};
|
|
21
|
+
const FacetFiltersSchema = utils_validation_1.Joi.array().items(utils_validation_1.Joi.alternatives().try(utils_validation_1.Joi.string(), utils_validation_1.Joi.array().items(utils_validation_1.Joi.string())));
|
|
20
22
|
exports.Schema = utils_validation_1.Joi.object({
|
|
21
23
|
algolia: utils_validation_1.Joi.object({
|
|
22
24
|
// Docusaurus attributes
|
|
@@ -28,7 +30,9 @@ exports.Schema = utils_validation_1.Joi.object({
|
|
|
28
30
|
}),
|
|
29
31
|
apiKey: utils_validation_1.Joi.string().required(),
|
|
30
32
|
indexName: utils_validation_1.Joi.string().required(),
|
|
31
|
-
searchParameters: utils_validation_1.Joi.object(
|
|
33
|
+
searchParameters: utils_validation_1.Joi.object({
|
|
34
|
+
facetFilters: FacetFiltersSchema.optional(),
|
|
35
|
+
})
|
|
32
36
|
.default(exports.DEFAULT_CONFIG.searchParameters)
|
|
33
37
|
.unknown(),
|
|
34
38
|
searchPagePath: utils_validation_1.Joi.alternatives()
|
|
@@ -47,11 +51,69 @@ exports.Schema = utils_validation_1.Joi.object({
|
|
|
47
51
|
}).required(),
|
|
48
52
|
to: utils_validation_1.Joi.string().required(),
|
|
49
53
|
}).optional(),
|
|
54
|
+
// Ask AI configuration (DocSearch v4 only)
|
|
55
|
+
askAi: utils_validation_1.Joi.alternatives()
|
|
56
|
+
.try(
|
|
57
|
+
// Simple string format (assistantId only)
|
|
58
|
+
utils_validation_1.Joi.string(),
|
|
59
|
+
// Full configuration object
|
|
60
|
+
utils_validation_1.Joi.object({
|
|
61
|
+
indexName: utils_validation_1.Joi.string().required(),
|
|
62
|
+
apiKey: utils_validation_1.Joi.string().required(),
|
|
63
|
+
appId: utils_validation_1.Joi.string().required(),
|
|
64
|
+
assistantId: utils_validation_1.Joi.string().required(),
|
|
65
|
+
searchParameters: utils_validation_1.Joi.object({
|
|
66
|
+
facetFilters: FacetFiltersSchema.optional(),
|
|
67
|
+
}).optional(),
|
|
68
|
+
}))
|
|
69
|
+
.custom((askAiInput, helpers) => {
|
|
70
|
+
if (!askAiInput) {
|
|
71
|
+
return askAiInput;
|
|
72
|
+
}
|
|
73
|
+
const algolia = helpers.state.ancestors[0];
|
|
74
|
+
const algoliaFacetFilters = algolia.searchParameters?.facetFilters;
|
|
75
|
+
if (typeof askAiInput === 'string') {
|
|
76
|
+
return {
|
|
77
|
+
assistantId: askAiInput,
|
|
78
|
+
indexName: algolia.indexName,
|
|
79
|
+
apiKey: algolia.apiKey,
|
|
80
|
+
appId: algolia.appId,
|
|
81
|
+
...(algoliaFacetFilters
|
|
82
|
+
? {
|
|
83
|
+
searchParameters: {
|
|
84
|
+
facetFilters: algoliaFacetFilters,
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
: {}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (askAiInput.searchParameters?.facetFilters === undefined &&
|
|
91
|
+
algoliaFacetFilters) {
|
|
92
|
+
askAiInput.searchParameters = askAiInput.searchParameters ?? {};
|
|
93
|
+
askAiInput.searchParameters.facetFilters = algoliaFacetFilters;
|
|
94
|
+
}
|
|
95
|
+
return askAiInput;
|
|
96
|
+
})
|
|
97
|
+
.optional()
|
|
98
|
+
.messages({
|
|
99
|
+
'alternatives.types': 'askAi must be either a string (assistantId) or an object with indexName, apiKey, appId, and assistantId',
|
|
100
|
+
}),
|
|
50
101
|
})
|
|
51
102
|
.label('themeConfig.algolia')
|
|
52
103
|
.required()
|
|
53
|
-
.unknown(),
|
|
104
|
+
.unknown(),
|
|
54
105
|
});
|
|
55
|
-
|
|
56
|
-
|
|
106
|
+
// TODO Docusaurus v4: remove this check when we drop DocSearch v3
|
|
107
|
+
function ensureAskAISupported(themeConfig) {
|
|
108
|
+
// enforce DocsSearch v4 requirement when AskAI is configured
|
|
109
|
+
if (themeConfig.algolia.askAi && docSearchVersion_1.docSearchV3) {
|
|
110
|
+
throw new Error('The askAi feature is only supported in DocSearch v4. ' +
|
|
111
|
+
'Please upgrade to DocSearch v4 by installing "@docsearch/react": "^4.0.0" ' +
|
|
112
|
+
'or remove the askAi configuration from your theme config.');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function validateThemeConfig({ validate, themeConfig: themeConfigInput, }) {
|
|
116
|
+
const themeConfig = validate(exports.Schema, themeConfigInput);
|
|
117
|
+
ensureAskAISupported(themeConfig);
|
|
118
|
+
return themeConfig;
|
|
57
119
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docusaurus/theme-search-algolia",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.0-canary-6403",
|
|
4
4
|
"description": "Algolia search component for Docusaurus.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"sideEffects": [
|
|
@@ -33,16 +33,16 @@
|
|
|
33
33
|
"copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@docsearch/react": "^3.9.0",
|
|
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.
|
|
44
|
-
"algoliasearch": "^5.
|
|
45
|
-
"algoliasearch-helper": "^3.
|
|
36
|
+
"@docsearch/react": "^3.9.0 || ^4.1.0",
|
|
37
|
+
"@docusaurus/core": "3.9.0-canary-6403",
|
|
38
|
+
"@docusaurus/logger": "3.9.0-canary-6403",
|
|
39
|
+
"@docusaurus/plugin-content-docs": "3.9.0-canary-6403",
|
|
40
|
+
"@docusaurus/theme-common": "3.9.0-canary-6403",
|
|
41
|
+
"@docusaurus/theme-translations": "3.9.0-canary-6403",
|
|
42
|
+
"@docusaurus/utils": "3.9.0-canary-6403",
|
|
43
|
+
"@docusaurus/utils-validation": "3.9.0-canary-6403",
|
|
44
|
+
"algoliasearch": "^5.37.0",
|
|
45
|
+
"algoliasearch-helper": "^3.26.0",
|
|
46
46
|
"clsx": "^2.0.0",
|
|
47
47
|
"eta": "^2.2.0",
|
|
48
48
|
"fs-extra": "^11.1.1",
|
|
@@ -51,14 +51,14 @@
|
|
|
51
51
|
"utility-types": "^3.10.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@docusaurus/module-type-aliases": "3.
|
|
54
|
+
"@docusaurus/module-type-aliases": "3.9.0-canary-6403"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"react": "^18.0.0 || ^19.0.0",
|
|
58
58
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
59
59
|
},
|
|
60
60
|
"engines": {
|
|
61
|
-
"node": ">=
|
|
61
|
+
"node": ">=20.0"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "22c95f8cb5c070ce47e115be376fa9b828dd54df"
|
|
64
64
|
}
|
package/src/client/index.ts
CHANGED
|
@@ -6,5 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export {useAlgoliaThemeConfig} from './useAlgoliaThemeConfig';
|
|
9
|
-
export {
|
|
9
|
+
export {
|
|
10
|
+
useAlgoliaContextualFacetFilters,
|
|
11
|
+
useAlgoliaContextualFacetFiltersIfEnabled,
|
|
12
|
+
} from './useAlgoliaContextualFacetFilters';
|
|
10
13
|
export {useSearchResultUrlProcessor} from './useSearchResultUrlProcessor';
|
|
14
|
+
export {useAlgoliaAskAi} from './useAlgoliaAskAi';
|
|
15
|
+
export {mergeFacetFilters} from './utils';
|
|
@@ -0,0 +1,108 @@
|
|
|
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, useMemo, useState} from 'react';
|
|
9
|
+
import {version as docsearchVersion} from '@docsearch/react/version';
|
|
10
|
+
import translations from '@theme/SearchTranslations';
|
|
11
|
+
import {useAlgoliaContextualFacetFiltersIfEnabled} from './useAlgoliaContextualFacetFilters';
|
|
12
|
+
import {mergeFacetFilters} from './utils';
|
|
13
|
+
import type {AskAiConfig} from '@docusaurus/theme-search-algolia';
|
|
14
|
+
import type {
|
|
15
|
+
DocSearchModalProps,
|
|
16
|
+
DocSearchTranslations,
|
|
17
|
+
} from '@docsearch/react';
|
|
18
|
+
import type {FacetFilters} from 'algoliasearch/lite';
|
|
19
|
+
|
|
20
|
+
// The minimal props the hook needs from DocSearch v4 props
|
|
21
|
+
// TODO Docusaurus v4: cleanup after we drop support for DocSearch v3
|
|
22
|
+
interface DocSearchV4PropsLite {
|
|
23
|
+
indexName: string;
|
|
24
|
+
apiKey: string;
|
|
25
|
+
appId: string;
|
|
26
|
+
placeholder?: string;
|
|
27
|
+
translations?: DocSearchTranslations;
|
|
28
|
+
searchParameters?: DocSearchModalProps['searchParameters'];
|
|
29
|
+
askAi?: AskAiConfig;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const isV4 = docsearchVersion.startsWith('4.');
|
|
33
|
+
|
|
34
|
+
type UseAskAiResult = {
|
|
35
|
+
canHandleAskAi: boolean;
|
|
36
|
+
isAskAiActive: boolean;
|
|
37
|
+
currentPlaceholder: string | undefined;
|
|
38
|
+
onAskAiToggle: (active: boolean) => void;
|
|
39
|
+
askAi?: AskAiConfig;
|
|
40
|
+
extraAskAiProps: Partial<DocSearchModalProps> & {
|
|
41
|
+
askAi?: AskAiConfig;
|
|
42
|
+
canHandleAskAi?: boolean;
|
|
43
|
+
isAskAiActive?: boolean;
|
|
44
|
+
onAskAiToggle?: (active: boolean) => void;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// We need to apply contextualSearch facetFilters to AskAI filters
|
|
49
|
+
// This can't be done at config normalization time because contextual filters
|
|
50
|
+
// can only be determined at runtime
|
|
51
|
+
function applyAskAiContextualSearch(
|
|
52
|
+
askAi: AskAiConfig | undefined,
|
|
53
|
+
contextualSearchFilters: FacetFilters | undefined,
|
|
54
|
+
): AskAiConfig | undefined {
|
|
55
|
+
if (!askAi) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
if (!contextualSearchFilters) {
|
|
59
|
+
return askAi;
|
|
60
|
+
}
|
|
61
|
+
const askAiFacetFilters = askAi.searchParameters?.facetFilters;
|
|
62
|
+
return {
|
|
63
|
+
...askAi,
|
|
64
|
+
searchParameters: {
|
|
65
|
+
...askAi.searchParameters,
|
|
66
|
+
facetFilters: mergeFacetFilters(
|
|
67
|
+
askAiFacetFilters,
|
|
68
|
+
contextualSearchFilters,
|
|
69
|
+
),
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function useAlgoliaAskAi(props: DocSearchV4PropsLite): UseAskAiResult {
|
|
75
|
+
const [isAskAiActive, setIsAskAiActive] = useState(false);
|
|
76
|
+
const contextualSearchFilters = useAlgoliaContextualFacetFiltersIfEnabled();
|
|
77
|
+
|
|
78
|
+
const askAi = useMemo(() => {
|
|
79
|
+
return applyAskAiContextualSearch(props.askAi, contextualSearchFilters);
|
|
80
|
+
}, [props.askAi, contextualSearchFilters]);
|
|
81
|
+
|
|
82
|
+
const canHandleAskAi = Boolean(askAi);
|
|
83
|
+
|
|
84
|
+
const currentPlaceholder =
|
|
85
|
+
isAskAiActive && isV4
|
|
86
|
+
? translations.modal?.searchBox?.placeholderTextAskAi
|
|
87
|
+
: translations.modal?.searchBox?.placeholderText || props?.placeholder;
|
|
88
|
+
|
|
89
|
+
const onAskAiToggle = useCallback((askAiToggle: boolean) => {
|
|
90
|
+
setIsAskAiActive(askAiToggle);
|
|
91
|
+
}, []);
|
|
92
|
+
|
|
93
|
+
const extraAskAiProps: UseAskAiResult['extraAskAiProps'] = {
|
|
94
|
+
askAi,
|
|
95
|
+
canHandleAskAi,
|
|
96
|
+
isAskAiActive,
|
|
97
|
+
onAskAiToggle,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
canHandleAskAi,
|
|
102
|
+
isAskAiActive,
|
|
103
|
+
currentPlaceholder,
|
|
104
|
+
onAskAiToggle,
|
|
105
|
+
askAi,
|
|
106
|
+
extraAskAiProps,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import {DEFAULT_SEARCH_TAG} from '@docusaurus/theme-common/internal';
|
|
9
9
|
import {useDocsContextualSearchTags} from '@docusaurus/plugin-content-docs/client';
|
|
10
10
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
11
|
+
import {useAlgoliaThemeConfig} from './useAlgoliaThemeConfig';
|
|
12
|
+
import type {FacetFilters} from 'algoliasearch/lite';
|
|
11
13
|
|
|
12
14
|
function useSearchTags() {
|
|
13
15
|
// only docs have custom search tags per version
|
|
@@ -16,7 +18,7 @@ function useSearchTags() {
|
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
// Translate search-engine agnostic search tags to Algolia search filters
|
|
19
|
-
export function useAlgoliaContextualFacetFilters():
|
|
21
|
+
export function useAlgoliaContextualFacetFilters(): FacetFilters {
|
|
20
22
|
const locale = useDocusaurusContext().i18n.currentLocale;
|
|
21
23
|
const tags = useSearchTags();
|
|
22
24
|
|
|
@@ -27,3 +29,17 @@ export function useAlgoliaContextualFacetFilters(): [string, string[]] {
|
|
|
27
29
|
|
|
28
30
|
return [languageFilter, tagsFilter];
|
|
29
31
|
}
|
|
32
|
+
|
|
33
|
+
export function useAlgoliaContextualFacetFiltersIfEnabled():
|
|
34
|
+
| FacetFilters
|
|
35
|
+
| undefined {
|
|
36
|
+
const {
|
|
37
|
+
algolia: {contextualSearch},
|
|
38
|
+
} = useAlgoliaThemeConfig();
|
|
39
|
+
const facetFilters = useAlgoliaContextualFacetFilters();
|
|
40
|
+
if (contextualSearch) {
|
|
41
|
+
return facetFilters;
|
|
42
|
+
} else {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
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 type {FacetFilters} from 'algoliasearch/lite';
|
|
9
|
+
|
|
10
|
+
export function mergeFacetFilters(
|
|
11
|
+
f1: FacetFilters,
|
|
12
|
+
f2: FacetFilters,
|
|
13
|
+
): FacetFilters;
|
|
14
|
+
|
|
15
|
+
export function mergeFacetFilters(
|
|
16
|
+
f1: FacetFilters | undefined,
|
|
17
|
+
f2: FacetFilters | undefined,
|
|
18
|
+
): FacetFilters | undefined;
|
|
19
|
+
|
|
20
|
+
export function mergeFacetFilters(
|
|
21
|
+
f1: FacetFilters | undefined,
|
|
22
|
+
f2: FacetFilters | undefined,
|
|
23
|
+
): FacetFilters | undefined {
|
|
24
|
+
if (f1 === undefined) {
|
|
25
|
+
return f2;
|
|
26
|
+
}
|
|
27
|
+
if (f2 === undefined) {
|
|
28
|
+
return f1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const normalize = (f: FacetFilters): FacetFilters =>
|
|
32
|
+
typeof f === 'string' ? [f] : f;
|
|
33
|
+
|
|
34
|
+
// Historical behavior: we flatten everything
|
|
35
|
+
// TODO I'm pretty sure this is incorrect
|
|
36
|
+
// see https://www.algolia.com/doc/api-reference/api-parameters/facetFilters/?client=javascript
|
|
37
|
+
// Note: Algolia is working to provide a reliable facet merging strategy
|
|
38
|
+
// see https://github.com/facebook/docusaurus/pull/11327#issuecomment-3284742923
|
|
39
|
+
return [...normalize(f1), ...normalize(f2)];
|
|
40
|
+
}
|