@seqera/docusaurus-theme-seqera 1.0.25 → 1.0.26-next.85
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/getSwizzleConfig.js +0 -9
- package/lib/theme/SearchPage/generalUtils.d.ts +4 -0
- package/lib/theme/SearchPage/generalUtils.js +19 -0
- package/lib/theme/SearchPage/index.d.ts +1 -0
- package/lib/theme/SearchPage/index.js +836 -0
- package/lib/theme/SearchPage/styles.module.css +325 -0
- package/lib/theme/SearchPage/useSearchPage.d.ts +7 -0
- package/lib/theme/SearchPage/useSearchPage.js +68 -0
- package/package.json +3 -1
- package/src/getSwizzleConfig.ts +0 -10
- package/src/theme/SearchPage/generalUtils.ts +20 -0
- package/src/theme/SearchPage/index.tsx +943 -0
- package/src/theme/SearchPage/styles.module.css +325 -0
- package/src/theme/SearchPage/useSearchPage.ts +67 -0
- package/src/theme-seqera.d.ts +3 -0
- package/lib/theme/SearchBar.d.ts +0 -1
- package/lib/theme/SearchBar.js +0 -5
- package/src/theme/SearchBar.tsx +0 -8
|
@@ -0,0 +1,836 @@
|
|
|
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
|
+
* Swizzled from docusaurus-theme-search-typesense to remove hardcoded
|
|
8
|
+
* group_by: 'url' so search page results match the search bar.
|
|
9
|
+
* Product routes are configured via themeConfig.typesense.productRoutes.
|
|
10
|
+
*/
|
|
11
|
+
/* eslint-disable jsx-a11y/no-autofocus */
|
|
12
|
+
import React, {useEffect, useMemo, useState, useReducer, useRef} from 'react';
|
|
13
|
+
import clsx from 'clsx';
|
|
14
|
+
import algoliaSearchHelper from 'algoliasearch-helper';
|
|
15
|
+
import Head from '@docusaurus/Head';
|
|
16
|
+
import Link from '@docusaurus/Link';
|
|
17
|
+
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
|
18
|
+
import {
|
|
19
|
+
HtmlClassNameProvider,
|
|
20
|
+
usePluralForm,
|
|
21
|
+
isRegexpStringMatch,
|
|
22
|
+
useEvent,
|
|
23
|
+
// @ts-ignore
|
|
24
|
+
} from '@docusaurus/theme-common';
|
|
25
|
+
import {useSearchPage} from './useSearchPage';
|
|
26
|
+
import {useTitleFormatter} from './generalUtils';
|
|
27
|
+
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
28
|
+
import {useAllDocsData} from '@docusaurus/plugin-content-docs/client';
|
|
29
|
+
import Translate, {translate} from '@docusaurus/Translate';
|
|
30
|
+
import Layout from '@theme/Layout';
|
|
31
|
+
import styles from './styles.module.css';
|
|
32
|
+
import TypesenseInstantSearchAdapter from 'typesense-instantsearch-adapter';
|
|
33
|
+
// Non-content docusaurus_tag values to always exclude from search results.
|
|
34
|
+
// Verified against live Typesense facets — blog/doc-list tags have zero documents
|
|
35
|
+
// and are kept here defensively in case content is re-indexed with those tags.
|
|
36
|
+
const NON_CONTENT_TAGS = [
|
|
37
|
+
'default',
|
|
38
|
+
'doc_tag_doc_list',
|
|
39
|
+
'blog_posts_list',
|
|
40
|
+
'blog_tags_posts',
|
|
41
|
+
'doc_tags_list',
|
|
42
|
+
'blog_tags_list',
|
|
43
|
+
];
|
|
44
|
+
// Custom dropdown for product/version filtering.
|
|
45
|
+
// A native <select> always closes and commits a value on click, making it impossible
|
|
46
|
+
// to let Platform Enterprise "expand" sub-options without immediately selecting it.
|
|
47
|
+
function FilterSelect({value, onChange, options}) {
|
|
48
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
49
|
+
const [expandedId, setExpandedId] = useState(null);
|
|
50
|
+
const containerRef = useRef(null);
|
|
51
|
+
// Close when clicking outside
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!isOpen) return undefined;
|
|
54
|
+
function handleClickOutside(e) {
|
|
55
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
56
|
+
setIsOpen(false);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
60
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
61
|
+
}, [isOpen]);
|
|
62
|
+
// Auto-expand the selected product's group when the dropdown opens
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!isOpen || !value) return;
|
|
65
|
+
const productId = value.includes('@') ? value.split('@')[0] : value;
|
|
66
|
+
const product = options.find((o) => o.id === productId);
|
|
67
|
+
if (product && product.versions.length > 1) {
|
|
68
|
+
setExpandedId(productId);
|
|
69
|
+
}
|
|
70
|
+
}, [isOpen]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
71
|
+
function handleSelect(newValue) {
|
|
72
|
+
onChange(newValue);
|
|
73
|
+
setIsOpen(false);
|
|
74
|
+
setExpandedId(null);
|
|
75
|
+
}
|
|
76
|
+
// Build the trigger label from the current value
|
|
77
|
+
const triggerLabel = (() => {
|
|
78
|
+
if (!value)
|
|
79
|
+
return translate({
|
|
80
|
+
id: 'theme.SearchPage.allProductsOption',
|
|
81
|
+
message: 'All products',
|
|
82
|
+
});
|
|
83
|
+
const [productId, versionName] = value.includes('@')
|
|
84
|
+
? value.split('@')
|
|
85
|
+
: [value, null];
|
|
86
|
+
const product = options.find((o) => o.id === productId);
|
|
87
|
+
if (!product)
|
|
88
|
+
return translate({
|
|
89
|
+
id: 'theme.SearchPage.allProductsOption',
|
|
90
|
+
message: 'All products',
|
|
91
|
+
});
|
|
92
|
+
if (versionName) {
|
|
93
|
+
const version = product.versions.find((v) => v.name === versionName);
|
|
94
|
+
return `${product.label} \u2013 ${version?.label || versionName}`;
|
|
95
|
+
}
|
|
96
|
+
if (product.versions.length > 1) {
|
|
97
|
+
const current =
|
|
98
|
+
product.versions.find((v) => v.isLast) || product.versions[0];
|
|
99
|
+
return `${product.label} \u2013 Current (${current.label})`;
|
|
100
|
+
}
|
|
101
|
+
return product.label;
|
|
102
|
+
})();
|
|
103
|
+
return (
|
|
104
|
+
<div className={styles.filterSelectWrapper} ref={containerRef}>
|
|
105
|
+
<div className={clsx(styles.filterBox, isOpen && styles.filterBoxOpen)}>
|
|
106
|
+
<button
|
|
107
|
+
type="button"
|
|
108
|
+
className={styles.filterTrigger}
|
|
109
|
+
onClick={() => setIsOpen((o) => !o)}
|
|
110
|
+
aria-haspopup="listbox"
|
|
111
|
+
aria-expanded={isOpen}>
|
|
112
|
+
<span>{triggerLabel}</span>
|
|
113
|
+
<svg
|
|
114
|
+
aria-hidden="true"
|
|
115
|
+
className={clsx(
|
|
116
|
+
styles.filterTriggerChevron,
|
|
117
|
+
isOpen
|
|
118
|
+
? styles.filterTriggerChevronOpen
|
|
119
|
+
: styles.filterTriggerChevronClosed,
|
|
120
|
+
)}
|
|
121
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
122
|
+
viewBox="4 4 16 16"
|
|
123
|
+
fill="currentColor">
|
|
124
|
+
<path d="M11.8152 13.1989L10.0167 11.1432C9.80447 10.9013 9.97697 10.5214 10.2991 10.5214H13.8961C13.9682 10.5214 14.0388 10.5421 14.0994 10.5811C14.16 10.6201 14.2081 10.6758 14.2379 10.7414C14.2677 10.8071 14.2779 10.8799 14.2674 10.9512C14.2569 11.0226 14.226 11.0893 14.1785 11.1435L12.38 13.1985C12.3448 13.2388 12.3014 13.2711 12.2527 13.2932C12.204 13.3153 12.1511 13.3268 12.0976 13.3268C12.0441 13.3268 11.9912 13.3153 11.9425 13.2932C11.8938 13.2711 11.8504 13.2388 11.8152 13.1985V13.1989Z" />
|
|
125
|
+
</svg>
|
|
126
|
+
</button>
|
|
127
|
+
</div>
|
|
128
|
+
{isOpen && (
|
|
129
|
+
<ul className={styles.filterDropdown} role="listbox">
|
|
130
|
+
<li
|
|
131
|
+
role="option"
|
|
132
|
+
aria-selected={!value}
|
|
133
|
+
className={clsx(
|
|
134
|
+
styles.filterOption,
|
|
135
|
+
!value && styles.filterOptionActive,
|
|
136
|
+
)}
|
|
137
|
+
onClick={() => handleSelect('')}>
|
|
138
|
+
{translate({
|
|
139
|
+
id: 'theme.SearchPage.allProductsOption',
|
|
140
|
+
message: 'All products',
|
|
141
|
+
})}
|
|
142
|
+
</li>
|
|
143
|
+
{options.map((option) => {
|
|
144
|
+
if (option.versions.length > 1) {
|
|
145
|
+
const isExpanded = expandedId === option.id;
|
|
146
|
+
const isActive =
|
|
147
|
+
value === option.id || value.startsWith(`${option.id}@`);
|
|
148
|
+
const currentVersion =
|
|
149
|
+
option.versions.find((v) => v.isLast) || option.versions[0];
|
|
150
|
+
const olderVersions = option.versions.filter((v) => !v.isLast);
|
|
151
|
+
return (
|
|
152
|
+
<React.Fragment key={option.id}>
|
|
153
|
+
<li
|
|
154
|
+
role="option"
|
|
155
|
+
aria-selected={false}
|
|
156
|
+
aria-expanded={isExpanded}
|
|
157
|
+
className={clsx(
|
|
158
|
+
styles.filterOption,
|
|
159
|
+
styles.filterOptionExpandable,
|
|
160
|
+
isActive && styles.filterOptionActive,
|
|
161
|
+
)}
|
|
162
|
+
onClick={() =>
|
|
163
|
+
setExpandedId(isExpanded ? null : option.id)
|
|
164
|
+
}>
|
|
165
|
+
<span>{option.label}</span>
|
|
166
|
+
<svg
|
|
167
|
+
aria-hidden="true"
|
|
168
|
+
className={clsx(
|
|
169
|
+
styles.filterExpandChevron,
|
|
170
|
+
isExpanded
|
|
171
|
+
? styles.filterTriggerChevronOpen
|
|
172
|
+
: styles.filterTriggerChevronClosed,
|
|
173
|
+
)}
|
|
174
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
175
|
+
viewBox="4 4 16 16"
|
|
176
|
+
fill="currentColor">
|
|
177
|
+
<path d="M11.8152 13.1989L10.0167 11.1432C9.80447 10.9013 9.97697 10.5214 10.2991 10.5214H13.8961C13.9682 10.5214 14.0388 10.5421 14.0994 10.5811C14.16 10.6201 14.2081 10.6758 14.2379 10.7414C14.2677 10.8071 14.2779 10.8799 14.2674 10.9512C14.2569 11.0226 14.226 11.0893 14.1785 11.1435L12.38 13.1985C12.3448 13.2388 12.3014 13.2711 12.2527 13.2932C12.204 13.3153 12.1511 13.3268 12.0976 13.3268C12.0441 13.3268 11.9912 13.3153 11.9425 13.2932C11.8938 13.2711 11.8504 13.2388 11.8152 13.1985V13.1989Z" />
|
|
178
|
+
</svg>
|
|
179
|
+
</li>
|
|
180
|
+
{isExpanded && (
|
|
181
|
+
<>
|
|
182
|
+
<li
|
|
183
|
+
role="option"
|
|
184
|
+
aria-selected={value === option.id}
|
|
185
|
+
className={clsx(
|
|
186
|
+
styles.filterOption,
|
|
187
|
+
styles.filterSubOption,
|
|
188
|
+
value === option.id && styles.filterOptionActive,
|
|
189
|
+
)}
|
|
190
|
+
onClick={() => handleSelect(option.id)}>
|
|
191
|
+
Current ({currentVersion.label})
|
|
192
|
+
</li>
|
|
193
|
+
{olderVersions.map((v, i) => (
|
|
194
|
+
<li
|
|
195
|
+
key={i}
|
|
196
|
+
role="option"
|
|
197
|
+
aria-selected={value === `${option.id}@${v.name}`}
|
|
198
|
+
className={clsx(
|
|
199
|
+
styles.filterOption,
|
|
200
|
+
styles.filterSubOption,
|
|
201
|
+
value === `${option.id}@${v.name}` &&
|
|
202
|
+
styles.filterOptionActive,
|
|
203
|
+
)}
|
|
204
|
+
onClick={() =>
|
|
205
|
+
handleSelect(`${option.id}@${v.name}`)
|
|
206
|
+
}>
|
|
207
|
+
{v.label}
|
|
208
|
+
</li>
|
|
209
|
+
))}
|
|
210
|
+
</>
|
|
211
|
+
)}
|
|
212
|
+
</React.Fragment>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
return (
|
|
216
|
+
<li
|
|
217
|
+
key={option.id}
|
|
218
|
+
role="option"
|
|
219
|
+
aria-selected={value === option.id}
|
|
220
|
+
className={clsx(
|
|
221
|
+
styles.filterOption,
|
|
222
|
+
value === option.id && styles.filterOptionActive,
|
|
223
|
+
)}
|
|
224
|
+
onClick={() => handleSelect(option.id)}>
|
|
225
|
+
{option.label}
|
|
226
|
+
</li>
|
|
227
|
+
);
|
|
228
|
+
})}
|
|
229
|
+
</ul>
|
|
230
|
+
)}
|
|
231
|
+
</div>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
// Very simple pluralization: probably good enough for now
|
|
235
|
+
function useDocumentsFoundPlural() {
|
|
236
|
+
const {selectMessage} = usePluralForm();
|
|
237
|
+
return (count) =>
|
|
238
|
+
selectMessage(
|
|
239
|
+
count,
|
|
240
|
+
translate(
|
|
241
|
+
{
|
|
242
|
+
id: 'theme.SearchPage.documentsFound.plurals',
|
|
243
|
+
description:
|
|
244
|
+
'Pluralized label for "{count} documents found". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',
|
|
245
|
+
message: 'One document found|{count} documents found',
|
|
246
|
+
},
|
|
247
|
+
{count},
|
|
248
|
+
),
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
function useDocsSearchVersionsHelpers() {
|
|
252
|
+
const allDocsData = useAllDocsData();
|
|
253
|
+
// State of the version select menus / algolia facet filters
|
|
254
|
+
// docsPluginId -> versionName map
|
|
255
|
+
const [searchVersions, setSearchVersions] = useState(() =>
|
|
256
|
+
Object.entries(allDocsData).reduce(
|
|
257
|
+
(acc, [pluginId, pluginData]) => ({
|
|
258
|
+
...acc,
|
|
259
|
+
[pluginId]: pluginData.versions[0].name,
|
|
260
|
+
}),
|
|
261
|
+
{},
|
|
262
|
+
),
|
|
263
|
+
);
|
|
264
|
+
// Set the value of a single select menu
|
|
265
|
+
const setSearchVersion = (pluginId, searchVersion) =>
|
|
266
|
+
setSearchVersions((s) => ({...s, [pluginId]: searchVersion}));
|
|
267
|
+
const versioningEnabled = Object.values(allDocsData).some(
|
|
268
|
+
(docsData) => docsData.versions.length > 1,
|
|
269
|
+
);
|
|
270
|
+
return {
|
|
271
|
+
allDocsData,
|
|
272
|
+
versioningEnabled,
|
|
273
|
+
searchVersions,
|
|
274
|
+
setSearchVersion,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function SearchPageContent() {
|
|
278
|
+
const {
|
|
279
|
+
siteConfig: {themeConfig},
|
|
280
|
+
i18n: {currentLocale},
|
|
281
|
+
} = useDocusaurusContext();
|
|
282
|
+
const {
|
|
283
|
+
typesense: {
|
|
284
|
+
typesenseCollectionName,
|
|
285
|
+
typesenseServerConfig,
|
|
286
|
+
typesenseSearchParameters,
|
|
287
|
+
contextualSearch,
|
|
288
|
+
externalUrlRegex,
|
|
289
|
+
productRoutes = [],
|
|
290
|
+
},
|
|
291
|
+
} = themeConfig;
|
|
292
|
+
const documentsFoundPlural = useDocumentsFoundPlural();
|
|
293
|
+
const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers();
|
|
294
|
+
// Maps URL path prefixes to product labels using the configured productRoutes.
|
|
295
|
+
function getProductLabel(pathname) {
|
|
296
|
+
const match = productRoutes.find(([prefix]) => pathname.startsWith(prefix));
|
|
297
|
+
return match ? match[1] : null;
|
|
298
|
+
}
|
|
299
|
+
// Compute tags for old versions of versioned plugins to exclude from results,
|
|
300
|
+
// so only the latest version of each plugin appears by default.
|
|
301
|
+
// The exclusion approach (rather than a whitelist) is used deliberately so that
|
|
302
|
+
// content accessible via URL rewrites or non-standard tags is not accidentally dropped.
|
|
303
|
+
const oldVersionTags = useMemo(() => {
|
|
304
|
+
const tags = [];
|
|
305
|
+
Object.entries(docsSearchVersionsHelpers.allDocsData).forEach(
|
|
306
|
+
([pluginId, pluginData]) => {
|
|
307
|
+
if (pluginData.versions.length > 1) {
|
|
308
|
+
const latest =
|
|
309
|
+
pluginData.versions.find((v) => v.isLast) || pluginData.versions[0];
|
|
310
|
+
pluginData.versions
|
|
311
|
+
.filter((v) => v.name !== latest.name)
|
|
312
|
+
.forEach((v) => tags.push(`docs-${pluginId}-${v.name}`));
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
);
|
|
316
|
+
return tags;
|
|
317
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
318
|
+
const {searchQuery, setSearchQuery, selectedFilter, setSelectedFilter} =
|
|
319
|
+
useSearchPage();
|
|
320
|
+
// inputValue tracks the live input; searchQuery only updates on submit
|
|
321
|
+
const [inputValue, setInputValue] = useState(searchQuery);
|
|
322
|
+
// Sync inputValue when searchQuery is populated from the URL on hydration
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
setInputValue(searchQuery);
|
|
325
|
+
}, [searchQuery]);
|
|
326
|
+
// Products available for filtering — plugin-based products (from allDocsData) and
|
|
327
|
+
// rewrite-based products with a known customTag (e.g. Nextflow → docs-default-current).
|
|
328
|
+
// Each entry has a stable `id` used as the select option value:
|
|
329
|
+
// - plugin-based: id = pluginId
|
|
330
|
+
// - rewrite-based: id = customTag (pluginId is null)
|
|
331
|
+
const productOptions = useMemo(
|
|
332
|
+
() =>
|
|
333
|
+
productRoutes
|
|
334
|
+
.filter(
|
|
335
|
+
([, , pluginId, customTag]) =>
|
|
336
|
+
(pluginId && docsSearchVersionsHelpers.allDocsData[pluginId]) ||
|
|
337
|
+
customTag,
|
|
338
|
+
)
|
|
339
|
+
.map(([, label, pluginId, customTag]) => ({
|
|
340
|
+
id: pluginId || customTag,
|
|
341
|
+
label,
|
|
342
|
+
pluginId,
|
|
343
|
+
customTag,
|
|
344
|
+
versions:
|
|
345
|
+
pluginId && docsSearchVersionsHelpers.allDocsData[pluginId]
|
|
346
|
+
? docsSearchVersionsHelpers.allDocsData[pluginId].versions
|
|
347
|
+
: [],
|
|
348
|
+
})),
|
|
349
|
+
[],
|
|
350
|
+
);
|
|
351
|
+
const initialSearchResultState = {
|
|
352
|
+
items: [],
|
|
353
|
+
query: null,
|
|
354
|
+
totalResults: null,
|
|
355
|
+
totalPages: null,
|
|
356
|
+
lastPage: null,
|
|
357
|
+
hasMore: null,
|
|
358
|
+
loading: null,
|
|
359
|
+
};
|
|
360
|
+
const [searchResultState, searchResultStateDispatcher] = useReducer(
|
|
361
|
+
(prevState, data) => {
|
|
362
|
+
switch (data.type) {
|
|
363
|
+
case 'reset': {
|
|
364
|
+
return initialSearchResultState;
|
|
365
|
+
}
|
|
366
|
+
case 'loading': {
|
|
367
|
+
return {...prevState, loading: true};
|
|
368
|
+
}
|
|
369
|
+
case 'update': {
|
|
370
|
+
if (searchQuery !== data.value.query) {
|
|
371
|
+
return prevState;
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
...data.value,
|
|
375
|
+
items:
|
|
376
|
+
data.value.lastPage === 0
|
|
377
|
+
? data.value.items
|
|
378
|
+
: prevState.items.concat(data.value.items),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
case 'advance': {
|
|
382
|
+
const hasMore =
|
|
383
|
+
(prevState.totalPages ?? 0) > (prevState.lastPage ?? 0) + 1;
|
|
384
|
+
return {
|
|
385
|
+
...prevState,
|
|
386
|
+
lastPage: hasMore
|
|
387
|
+
? (prevState.lastPage ?? 0) + 1
|
|
388
|
+
: prevState.lastPage,
|
|
389
|
+
hasMore,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
default:
|
|
393
|
+
return prevState;
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
initialSearchResultState,
|
|
397
|
+
);
|
|
398
|
+
// Memoize the adapter and helper so they're only created once, not on every render.
|
|
399
|
+
// Creating a new TypesenseInstantSearchAdapter on every render causes repeated
|
|
400
|
+
// network activity and accumulates stale event listeners.
|
|
401
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
402
|
+
const typesenseInstantSearchAdapter = useMemo(() => {
|
|
403
|
+
// Parse selectedFilter: '' | 'productId' | 'productId@versionName'
|
|
404
|
+
const atIdx = selectedFilter.indexOf('@');
|
|
405
|
+
const filterId =
|
|
406
|
+
atIdx >= 0 ? selectedFilter.slice(0, atIdx) : selectedFilter;
|
|
407
|
+
const filterVersion = atIdx >= 0 ? selectedFilter.slice(atIdx + 1) : null;
|
|
408
|
+
const filterProduct = filterId
|
|
409
|
+
? productOptions.find((p) => p.id === filterId)
|
|
410
|
+
: null;
|
|
411
|
+
let filterBy;
|
|
412
|
+
if (filterProduct) {
|
|
413
|
+
// Specific product selected: use an explicit inclusion filter so old versions
|
|
414
|
+
// are not accidentally blocked by the config-level exclusion list.
|
|
415
|
+
// We rebuild from NON_CONTENT_TAGS rather than typesenseSearchParameters.filter_by
|
|
416
|
+
// because the config filter already excludes old version tags, which would
|
|
417
|
+
// conflict when the user intentionally selects an older version.
|
|
418
|
+
let inclusionTag;
|
|
419
|
+
if (filterProduct.customTag) {
|
|
420
|
+
// Rewrite-based product (e.g. Nextflow): use its known docusaurus_tag directly
|
|
421
|
+
inclusionTag = filterProduct.customTag;
|
|
422
|
+
} else {
|
|
423
|
+
const targetVersionName =
|
|
424
|
+
filterVersion ||
|
|
425
|
+
(
|
|
426
|
+
filterProduct.versions.find((v) => v.isLast) ||
|
|
427
|
+
filterProduct.versions[0]
|
|
428
|
+
).name;
|
|
429
|
+
inclusionTag = `docs-${filterProduct.pluginId}-${targetVersionName}`;
|
|
430
|
+
}
|
|
431
|
+
filterBy = [
|
|
432
|
+
`docusaurus_tag:!=[${NON_CONTENT_TAGS.join(',')}]`,
|
|
433
|
+
`docusaurus_tag:=[${inclusionTag}]`,
|
|
434
|
+
].join(' && ');
|
|
435
|
+
} else {
|
|
436
|
+
// All products: use config filter + exclude remaining old version tags.
|
|
437
|
+
// Exclusion (rather than a whitelist) ensures content accessible via URL
|
|
438
|
+
// rewrites or non-standard tags is not accidentally dropped.
|
|
439
|
+
const versionExclusion =
|
|
440
|
+
oldVersionTags.length > 0
|
|
441
|
+
? `docusaurus_tag:!=[${oldVersionTags.join(',')}]`
|
|
442
|
+
: null;
|
|
443
|
+
filterBy = [typesenseSearchParameters.filter_by, versionExclusion]
|
|
444
|
+
.filter(Boolean)
|
|
445
|
+
.join(' && ');
|
|
446
|
+
}
|
|
447
|
+
return new TypesenseInstantSearchAdapter({
|
|
448
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
449
|
+
server: typesenseServerConfig,
|
|
450
|
+
additionalSearchParameters: {
|
|
451
|
+
// Defaults matching typesense-docsearch-react (SearchBar) behaviour
|
|
452
|
+
query_by:
|
|
453
|
+
'hierarchy.lvl0,hierarchy.lvl1,hierarchy.lvl2,hierarchy.lvl3,hierarchy.lvl4,hierarchy.lvl5,hierarchy.lvl6,content',
|
|
454
|
+
include_fields:
|
|
455
|
+
'hierarchy.lvl0,hierarchy.lvl1,hierarchy.lvl2,hierarchy.lvl3,hierarchy.lvl4,hierarchy.lvl5,hierarchy.lvl6,content,anchor,url,type,id',
|
|
456
|
+
highlight_full_fields:
|
|
457
|
+
'hierarchy.lvl0,hierarchy.lvl1,hierarchy.lvl2,hierarchy.lvl3,hierarchy.lvl4,hierarchy.lvl5,hierarchy.lvl6,content',
|
|
458
|
+
group_by: 'url',
|
|
459
|
+
group_limit: 1,
|
|
460
|
+
sort_by: 'item_priority:desc',
|
|
461
|
+
snippet_threshold: 8,
|
|
462
|
+
highlight_affix_num_tokens: 4,
|
|
463
|
+
...typesenseSearchParameters,
|
|
464
|
+
filter_by: filterBy,
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
}, [selectedFilter]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
468
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
469
|
+
const algoliaHelper = useMemo(
|
|
470
|
+
() =>
|
|
471
|
+
algoliaSearchHelper(
|
|
472
|
+
typesenseInstantSearchAdapter.searchClient,
|
|
473
|
+
typesenseCollectionName,
|
|
474
|
+
{
|
|
475
|
+
hitsPerPage: typesenseSearchParameters.per_page ?? 20,
|
|
476
|
+
advancedSyntax: true,
|
|
477
|
+
...(contextualSearch && {
|
|
478
|
+
disjunctiveFacets: ['language', 'docusaurus_tag'],
|
|
479
|
+
}),
|
|
480
|
+
},
|
|
481
|
+
),
|
|
482
|
+
[typesenseInstantSearchAdapter],
|
|
483
|
+
);
|
|
484
|
+
useEffect(() => {
|
|
485
|
+
const sanitizeValue = (value) =>
|
|
486
|
+
value.replace(
|
|
487
|
+
/algolia-docsearch-suggestion--highlight/g,
|
|
488
|
+
'search-result-match',
|
|
489
|
+
);
|
|
490
|
+
function handleResult({results: {query, hits, page, nbHits, nbPages}}) {
|
|
491
|
+
if (query === '' || !Array.isArray(hits)) {
|
|
492
|
+
searchResultStateDispatcher({type: 'reset'});
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
const items = hits.map((hit) => {
|
|
496
|
+
const {url, _highlightResult, _snippetResult: snippet = {}} = hit;
|
|
497
|
+
const parsedURL = new URL(url);
|
|
498
|
+
// Build levels using both raw and highlighted values.
|
|
499
|
+
// Raw values are plain text matching what the page breadcrumbs show.
|
|
500
|
+
// Highlighted values show which part of the hierarchy matched the query.
|
|
501
|
+
const levels = [0, 1, 2, 3, 4, 5, 6]
|
|
502
|
+
.map((lvl) => {
|
|
503
|
+
// Raw value: try dot-notation key first, then nested object
|
|
504
|
+
const raw =
|
|
505
|
+
hit[`hierarchy.lvl${lvl}`] || hit.hierarchy?.[`lvl${lvl}`] || '';
|
|
506
|
+
const h = _highlightResult[`hierarchy.lvl${lvl}`];
|
|
507
|
+
const highlighted = h ? sanitizeValue(h.value) : raw;
|
|
508
|
+
return {raw, highlighted};
|
|
509
|
+
})
|
|
510
|
+
.filter((l) => l.raw);
|
|
511
|
+
// Last level is the page/section title; remainder are breadcrumbs
|
|
512
|
+
const titleLevel = levels.pop();
|
|
513
|
+
const product = getProductLabel(parsedURL.pathname);
|
|
514
|
+
// Replace lvl0 ("Documentation") with the product label
|
|
515
|
+
if (product && levels.length > 0) {
|
|
516
|
+
levels[0] = {raw: product, highlighted: product};
|
|
517
|
+
} else if (product) {
|
|
518
|
+
levels.unshift({raw: product, highlighted: product});
|
|
519
|
+
}
|
|
520
|
+
const resultUrl = isRegexpStringMatch(externalUrlRegex, parsedURL.href)
|
|
521
|
+
? parsedURL.href
|
|
522
|
+
: parsedURL.pathname + parsedURL.hash;
|
|
523
|
+
return {
|
|
524
|
+
title: titleLevel?.highlighted || '',
|
|
525
|
+
url: resultUrl,
|
|
526
|
+
summary: snippet.content?.value
|
|
527
|
+
? `${sanitizeValue(snippet.content.value)}...`
|
|
528
|
+
: '',
|
|
529
|
+
// Include all levels (parent categories + current page/section)
|
|
530
|
+
// so the breadcrumb matches the full path shown on the page.
|
|
531
|
+
breadcrumbs: [...levels, ...(titleLevel ? [titleLevel] : [])].map(
|
|
532
|
+
(l) => l.raw,
|
|
533
|
+
),
|
|
534
|
+
};
|
|
535
|
+
});
|
|
536
|
+
searchResultStateDispatcher({
|
|
537
|
+
type: 'update',
|
|
538
|
+
value: {
|
|
539
|
+
items,
|
|
540
|
+
query,
|
|
541
|
+
totalResults: nbHits,
|
|
542
|
+
totalPages: nbPages,
|
|
543
|
+
lastPage: page,
|
|
544
|
+
hasMore: nbPages > page + 1,
|
|
545
|
+
loading: false,
|
|
546
|
+
},
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
function handleError(e) {
|
|
550
|
+
console.error(e);
|
|
551
|
+
}
|
|
552
|
+
algoliaHelper.on('result', handleResult);
|
|
553
|
+
// @ts-ignore — 'error' is a valid event but missing from type definitions
|
|
554
|
+
algoliaHelper.on('error', handleError);
|
|
555
|
+
return () => {
|
|
556
|
+
algoliaHelper.removeAllListeners('result');
|
|
557
|
+
algoliaHelper.removeAllListeners('error');
|
|
558
|
+
};
|
|
559
|
+
}, [algoliaHelper]); // algoliaHelper is stable (useMemo with []), so this runs once
|
|
560
|
+
const [loaderRef, setLoaderRef] = useState(null);
|
|
561
|
+
const prevY = useRef(0);
|
|
562
|
+
const observer = useRef(
|
|
563
|
+
ExecutionEnvironment.canUseIntersectionObserver &&
|
|
564
|
+
new IntersectionObserver(
|
|
565
|
+
(entries) => {
|
|
566
|
+
const entry = entries[0];
|
|
567
|
+
if (!entry) return;
|
|
568
|
+
const {
|
|
569
|
+
isIntersecting,
|
|
570
|
+
boundingClientRect: {y: currentY},
|
|
571
|
+
} = entry;
|
|
572
|
+
if (isIntersecting && prevY.current > currentY) {
|
|
573
|
+
searchResultStateDispatcher({type: 'advance'});
|
|
574
|
+
}
|
|
575
|
+
prevY.current = currentY;
|
|
576
|
+
},
|
|
577
|
+
{threshold: 1},
|
|
578
|
+
),
|
|
579
|
+
);
|
|
580
|
+
const getTitle = () =>
|
|
581
|
+
searchQuery
|
|
582
|
+
? translate(
|
|
583
|
+
{
|
|
584
|
+
id: 'theme.SearchPage.existingResultsTitle',
|
|
585
|
+
message: 'Search results for "{query}"',
|
|
586
|
+
description: 'The search page title for non-empty query',
|
|
587
|
+
},
|
|
588
|
+
{query: searchQuery},
|
|
589
|
+
)
|
|
590
|
+
: translate({
|
|
591
|
+
id: 'theme.SearchPage.emptyResultsTitle',
|
|
592
|
+
message: 'Search the documentation',
|
|
593
|
+
description: 'The search page title for empty query',
|
|
594
|
+
});
|
|
595
|
+
const makeSearch = useEvent((page = 0) => {
|
|
596
|
+
if (contextualSearch) {
|
|
597
|
+
algoliaHelper.addDisjunctiveFacetRefinement('docusaurus_tag', 'default');
|
|
598
|
+
algoliaHelper.addDisjunctiveFacetRefinement('language', currentLocale);
|
|
599
|
+
Object.entries(docsSearchVersionsHelpers.searchVersions).forEach(
|
|
600
|
+
([pluginId, searchVersion]) => {
|
|
601
|
+
algoliaHelper.addDisjunctiveFacetRefinement(
|
|
602
|
+
'docusaurus_tag',
|
|
603
|
+
`docs-${pluginId}-${searchVersion}`,
|
|
604
|
+
);
|
|
605
|
+
},
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
algoliaHelper.setQuery(searchQuery).setPage(page).search();
|
|
609
|
+
});
|
|
610
|
+
useEffect(() => {
|
|
611
|
+
if (!loaderRef) {
|
|
612
|
+
return undefined;
|
|
613
|
+
}
|
|
614
|
+
const currentObserver = observer.current;
|
|
615
|
+
if (currentObserver) {
|
|
616
|
+
currentObserver.observe(loaderRef);
|
|
617
|
+
return () => currentObserver.unobserve(loaderRef);
|
|
618
|
+
}
|
|
619
|
+
return () => true;
|
|
620
|
+
}, [loaderRef]);
|
|
621
|
+
useEffect(() => {
|
|
622
|
+
searchResultStateDispatcher({type: 'reset'});
|
|
623
|
+
if (searchQuery) {
|
|
624
|
+
searchResultStateDispatcher({type: 'loading'});
|
|
625
|
+
makeSearch();
|
|
626
|
+
}
|
|
627
|
+
}, [
|
|
628
|
+
searchQuery,
|
|
629
|
+
docsSearchVersionsHelpers.searchVersions,
|
|
630
|
+
makeSearch,
|
|
631
|
+
selectedFilter,
|
|
632
|
+
]);
|
|
633
|
+
useEffect(() => {
|
|
634
|
+
if (!searchResultState.lastPage || searchResultState.lastPage === 0) {
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
makeSearch(searchResultState.lastPage);
|
|
638
|
+
}, [makeSearch, searchResultState.lastPage]);
|
|
639
|
+
return (
|
|
640
|
+
<Layout>
|
|
641
|
+
<Head>
|
|
642
|
+
<title>{useTitleFormatter(getTitle())}</title>
|
|
643
|
+
{/*
|
|
644
|
+
We should not index search pages
|
|
645
|
+
See https://github.com/facebook/docusaurus/pull/3233
|
|
646
|
+
*/}
|
|
647
|
+
<meta property="robots" content="noindex, follow" />
|
|
648
|
+
</Head>
|
|
649
|
+
|
|
650
|
+
<div className="container margin-vert--lg">
|
|
651
|
+
<h1>{getTitle()}</h1>
|
|
652
|
+
|
|
653
|
+
<form
|
|
654
|
+
onSubmit={(e) => {
|
|
655
|
+
e.preventDefault();
|
|
656
|
+
setSearchQuery(inputValue);
|
|
657
|
+
}}>
|
|
658
|
+
<div className={styles.searchQueryColumn}>
|
|
659
|
+
<div className={styles.searchInputRow}>
|
|
660
|
+
<input
|
|
661
|
+
type="search"
|
|
662
|
+
name="q"
|
|
663
|
+
className={styles.searchQueryInput}
|
|
664
|
+
placeholder={translate({
|
|
665
|
+
id: 'theme.SearchPage.inputPlaceholder',
|
|
666
|
+
message: 'Type your search here',
|
|
667
|
+
description: 'The placeholder for search page input',
|
|
668
|
+
})}
|
|
669
|
+
aria-label={translate({
|
|
670
|
+
id: 'theme.SearchPage.inputLabel',
|
|
671
|
+
message: 'Search',
|
|
672
|
+
description: 'The ARIA label for search page input',
|
|
673
|
+
})}
|
|
674
|
+
onChange={(e) => setInputValue(e.target.value)}
|
|
675
|
+
value={inputValue}
|
|
676
|
+
autoComplete="off"
|
|
677
|
+
autoFocus
|
|
678
|
+
/>
|
|
679
|
+
{productOptions.length > 0 && (
|
|
680
|
+
<FilterSelect
|
|
681
|
+
value={selectedFilter}
|
|
682
|
+
onChange={setSelectedFilter}
|
|
683
|
+
options={productOptions}
|
|
684
|
+
/>
|
|
685
|
+
)}
|
|
686
|
+
<button
|
|
687
|
+
type="submit"
|
|
688
|
+
className={styles.searchQueryButton}
|
|
689
|
+
aria-label={translate({
|
|
690
|
+
id: 'theme.SearchPage.searchButtonLabel',
|
|
691
|
+
message: 'Search',
|
|
692
|
+
description: 'The ARIA label for the search button',
|
|
693
|
+
})}>
|
|
694
|
+
<Translate
|
|
695
|
+
id="theme.SearchPage.searchButton"
|
|
696
|
+
description="The label for the search button">
|
|
697
|
+
Search
|
|
698
|
+
</Translate>
|
|
699
|
+
</button>
|
|
700
|
+
</div>
|
|
701
|
+
</div>
|
|
702
|
+
</form>
|
|
703
|
+
|
|
704
|
+
<div className="row">
|
|
705
|
+
<div className={clsx('col', 'col--8', styles.searchResultsColumn)}>
|
|
706
|
+
{!!searchResultState.totalResults &&
|
|
707
|
+
documentsFoundPlural(searchResultState.totalResults)}
|
|
708
|
+
</div>
|
|
709
|
+
|
|
710
|
+
<div
|
|
711
|
+
className={clsx(
|
|
712
|
+
'col',
|
|
713
|
+
'col--4',
|
|
714
|
+
'text--right',
|
|
715
|
+
styles.searchLogoColumn,
|
|
716
|
+
)}>
|
|
717
|
+
<a
|
|
718
|
+
target="_blank"
|
|
719
|
+
rel="noopener noreferrer"
|
|
720
|
+
href={`https://typesense.org/?utm_medium=referral&utm_content=powered_by&utm_campaign=docsearch`}
|
|
721
|
+
aria-label={translate({
|
|
722
|
+
id: 'theme.SearchPage.typesenseLabel',
|
|
723
|
+
message: 'Search by Typesense',
|
|
724
|
+
description: 'The ARIA label for Typesense mention',
|
|
725
|
+
})}>
|
|
726
|
+
<svg
|
|
727
|
+
fill="none"
|
|
728
|
+
height="21"
|
|
729
|
+
viewBox="0 0 141 21"
|
|
730
|
+
width="141"
|
|
731
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
732
|
+
<clipPath id="a">
|
|
733
|
+
<path d="m0 0h141v21h-141z" />
|
|
734
|
+
</clipPath>
|
|
735
|
+
<g clipPath="url(#a)">
|
|
736
|
+
<g fill="#1035bc">
|
|
737
|
+
<path d="m62.0647 6.453c.0371.19.0557.37367.0557.551 0 .16467-.0186.342-.0557.532l-2.3561-.019v6.384c0 .532.2412.798.7235.798h1.41c.0866.2153.1299.4307.1299.646s-.0124.3483-.0371.399c-.569.076-1.1564.114-1.7625.114-1.1997 0-1.7995-.5257-1.7995-1.577v-6.764l-1.3172.019c-.0371-.19-.0557-.36733-.0557-.532 0-.17733.0186-.361.0557-.551l1.3172.019v-1.995c0-.342.0494-.58267.1484-.722.0989-.152.2906-.228.5751-.228h.5009l.1113.114v2.85z" />
|
|
738
|
+
<path d="m71.0419 6.548-2.5416 8.911c-.47 1.634-.9709 2.7867-1.5027 3.458s-1.3296 1.007-2.3932 1.007c-.5442 0-1.0452-.0823-1.5028-.247-.0371-.3547.0619-.6967.2969-1.026.3834.1393.7915.209 1.2244.209.6555 0 1.1564-.228 1.5027-.684s.6617-1.1653.9462-2.128l.0556-.19c-.3215-.0253-.5689-.1013-.742-.228-.1608-.1267-.2969-.361-.4082-.703l-2.5973-8.36c.3834-.16467.6555-.247.8163-.247.3587 0 .5999.22167.7235.665l1.4657 4.769c.0494.152.3339 1.14.8534 2.964.0247.0887.0865.133.1855.133l2.2633-8.398c.1608-.05067.3711-.076.6308-.076.2721 0 .5009.038.6864.114z" />
|
|
739
|
+
<path d="m74.6067 15.155v3.762c0 .342-.0495.5827-.1484.722-.0989.152-.2968.228-.5937.228h-.5009l-.1113-.114v-13.243l.1113-.114h.4824c.2968 0 .4947.08233.5936.247.1114.152.167.40533.167.76v.095c.7421-.84867 1.6264-1.273 2.653-1.273 1.0513 0 1.8428.437 2.3746 1.311.5319.86133.7978 2.05834.7978 3.591 0 .7473-.099 1.4187-.2968 2.014-.1856.5953-.4391 1.102-.7607 1.52-.3092.4053-.6679.722-1.076.95-.4082.2153-.8287.323-1.2616.323-.8534 0-1.6635-.2597-2.4303-.779zm0-6.175v4.883c.7545.57 1.4656.855 2.1335.855s1.2183-.304 1.6512-.912c.4328-.608.6493-1.5263.6493-2.755 0-.608-.0557-1.13366-.167-1.577-.0989-.456-.235-.82967-.4081-1.121-.1732-.304-.3773-.52567-.6123-.665-.2226-.152-.4638-.228-.7235-.228-.4947 0-.9647.133-1.41.399-.4452.266-.8163.63967-1.1131 1.121z" />
|
|
740
|
+
<path d="m89.8263 11.545h-5.7512c.0619 2.1533.8596 3.23 2.3932 3.23.8411 0 1.7378-.266 2.6901-.798.2721.2533.4391.5763.5009.969-1.0142.7093-2.152 1.064-3.4136 1.064-.6431 0-1.1935-.1203-1.6511-.361-.4576-.2533-.8349-.5953-1.1317-1.026-.2845-.4433-.4947-.9627-.6308-1.558-.136-.5953-.204-1.2477-.204-1.957 0-.722.0803-1.38067.2411-1.976.1732-.59533.4205-1.10833.7421-1.539s.705-.76633 1.1503-1.007c.4576-.24067.977-.361 1.5583-.361.569 0 1.0761.10767 1.5213.323.4576.20267.8349.48767 1.1317.855.3092.35467.5442.78533.705 1.292.1608.494.2411 1.026.2411 1.596 0 .228-.0123.4497-.0371.665-.0123.2027-.0309.399-.0556.589zm-5.7512-1.083h4.4525v-.247c0-.874-.1793-1.577-.538-2.109s-.8967-.798-1.614-.798c-.705 0-1.2554.285-1.6512.855-.3834.57-.5998 1.33633-.6493 2.299z" />
|
|
741
|
+
<path d="m91.7359 15.117c.0123-.2787.0865-.5827.2226-.912.1484-.342.3154-.608.5009-.798.9771.5447 1.8367.817 2.5787.817.4082 0 .7359-.0823.9833-.247.2597-.1647.3896-.3863.3896-.665 0-.4433-.3339-.798-1.0018-1.064l-1.0389-.399c-1.5584-.5827-2.3376-1.5137-2.3376-2.793 0-.456.0804-.86133.2412-1.216.1731-.36733.4081-.67767.705-.931.3092-.266.674-.46867 1.0945-.608s.8905-.209 1.41-.209c.235 0 .4947.019.7792.057.2968.038.5937.095.8905.171.2968.06333.5813.13933.8534.228s.5071.18367.705.285c0 .31667-.0619.646-.1856.988-.1236.342-.2906.59533-.5009.76-.977-.44333-1.8243-.665-2.5416-.665-.3216 0-.5751.08233-.7606.247-.1856.152-.2783.35467-.2783.608 0 .39267.3092.703.9276.931l1.1317.418c.8163.2913 1.4223.6903 1.8181 1.197.3957.5067.5936 1.0957.5936 1.767 0 .8993-.3277 1.6213-.9832 2.166-.6555.532-1.5955.798-2.8199.798-1.1998 0-2.3253-.3103-3.3765-.931z" />
|
|
742
|
+
<path d="m107.996 11.868h-5.121c.037.6967.192 1.2477.464 1.653.284.3927.773.589 1.466.589.717 0 1.539-.2153 2.467-.646.359.38.587.8803.686 1.501-.989.722-2.176 1.083-3.562 1.083-1.311 0-2.306-.4117-2.987-1.235-.667-.836-1.001-2.071-1.001-3.705 0-.76.086-1.444.259-2.052.174-.62067.427-1.14633.761-1.577.334-.44333.742-.78533 1.224-1.026.483-.24067 1.033-.361 1.652-.361.63 0 1.187.10133 1.669.304.483.19.891.46867 1.225.836.334.35467.581.779.742 1.273.173.494.26 1.03233.26 1.615 0 .3167-.019.6207-.056.912-.037.2787-.087.5573-.148.836zm-3.581-3.99c-.965 0-1.484.74733-1.558 2.242h3.079v-.228c0-.608-.123-1.09567-.371-1.463-.247-.36733-.631-.551-1.15-.551z" />
|
|
743
|
+
<path d="m118.163 9.436v4.142c0 .8107.13 1.4123.39 1.805-.396.3547-.872.532-1.429.532-.532 0-.897-.1203-1.095-.361-.197-.2533-.296-.646-.296-1.178v-4.427c0-.57-.068-.969-.204-1.197-.137-.228-.39-.342-.761-.342-.656 0-1.268.304-1.837.912v6.46c-.185.038-.383.0633-.593.076-.198.0127-.402.019-.613.019-.21 0-.42-.0063-.63-.019-.198-.0127-.39-.038-.576-.076v-9.405l.112-.133h.927c.693 0 1.126.38 1.299 1.14.903-.798 1.8-1.197 2.69-1.197.891 0 1.546.29767 1.967.893.433.58267.649 1.368.649 2.356z" />
|
|
744
|
+
<path d="m120.109 15.117c.012-.2787.087-.5827.223-.912.148-.342.315-.608.501-.798.977.5447 1.836.817 2.578.817.408 0 .736-.0823.984-.247.259-.1647.389-.3863.389-.665 0-.4433-.334-.798-1.002-1.064l-1.039-.399c-1.558-.5827-2.337-1.5137-2.337-2.793 0-.456.08-.86133.241-1.216.173-.36733.408-.67767.705-.931.309-.266.674-.46867 1.095-.608.42-.13933.89-.209 1.41-.209.235 0 .494.019.779.057.297.038.593.095.89.171.297.06333.582.13933.854.228s.507.18367.705.285c0 .31667-.062.646-.186.988s-.291.59533-.501.76c-.977-.44333-1.824-.665-2.541-.665-.322 0-.576.08233-.761.247-.186.152-.278.35467-.278.608 0 .39267.309.703.927.931l1.132.418c.816.2913 1.422.6903 1.818 1.197s.594 1.0957.594 1.767c0 .8993-.328 1.6213-.984 2.166-.655.532-1.595.798-2.819.798-1.2 0-2.326-.3103-3.377-.931z" />
|
|
745
|
+
<path d="m136.369 11.868h-5.121c.037.6967.192 1.2477.464 1.653.285.3927.773.589 1.466.589.717 0 1.54-.2153 2.467-.646.359.38.588.8803.687 1.501-.99.722-2.177 1.083-3.562 1.083-1.311 0-2.307-.4117-2.987-1.235-.668-.836-1.002-2.071-1.002-3.705 0-.76.086-1.444.26-2.052.173-.62067.426-1.14633.76-1.577.334-.44333.742-.78533 1.225-1.026.482-.24067 1.032-.361 1.651-.361.631 0 1.187.10133 1.67.304.482.19.89.46867 1.224.836.334.35467.581.779.742 1.273.173.494.26 1.03233.26 1.615 0 .3167-.019.6207-.056.912-.037.2787-.086.5573-.148.836zm-3.581-3.99c-.965 0-1.484.74733-1.558 2.242h3.079v-.228c0-.608-.123-1.09567-.371-1.463-.247-.36733-.63-.551-1.15-.551z" />
|
|
746
|
+
<path d="m139.245 18.442v-17.385c.186-.038.396-.057.631-.057.247 0 .476.019.686.057v17.385c-.21.038-.439.057-.686.057-.235 0-.445-.019-.631-.057z" />
|
|
747
|
+
</g>
|
|
748
|
+
<path
|
|
749
|
+
d="m2.648 14.604c.216.144.556.272 1.02.384s.872.168 1.224.168c.592 0 1.104-.092 1.536-.276.44-.184.772-.436.996-.756.232-.32.348-.688.348-1.104 0-.384-.08-.712-.24-.984-.16-.28-.396-.528-.708-.744-.304-.216-.708-.44-1.212-.672-.56-.256-.984-.468-1.272-.636s-.512-.352-.672-.552c-.152-.208-.228-.456-.228-.744 0-.384.156-.684.468-.9.32-.216.744-.324 1.272-.324.352 0 .648.036.888.108.248.072.52.176.816.312l.324-.732c-.28-.144-.604-.264-.972-.36-.36-.096-.732-.144-1.116-.144-.52 0-.98.092-1.38.276-.392.176-.696.42-.912.732-.208.312-.312.66-.312 1.044 0 .544.172 1.004.516 1.38.352.376.9.724 1.644 1.044.52.224.928.424 1.224.6.304.168.536.36.696.576.16.208.24.452.24.732 0 .392-.172.712-.516.96-.336.24-.816.36-1.44.36-.352 0-.712-.048-1.08-.144-.36-.104-.668-.22-.924-.348zm11.0963-2.364c0-.96-.204-1.736-.612-2.328-.4-.592-1.024-.888-1.872-.888-.56 0-1.048.132-1.46396.396-.408.256-.72.616-.936 1.08-.208.456-.312.98-.312 1.572 0 .936.26 1.684.78 2.244s1.27596.84 2.26796.84c.4 0 .764-.052 1.092-.156.328-.112.656-.26.984-.444l-.3-.696c-.36.176-.672.304-.936.384-.256.08-.54.12-.852.12-.688 0-1.2-.188-1.536-.564-.32796-.384-.50396-.904-.52796-1.56zm-4.19996-.648c.056-.544.224-.972.50396-1.284.288-.32.68-.48 1.176-.48.92 0 1.448.588 1.584 1.764zm5.84426-1.344c.288-.128.552-.224.792-.288.248-.072.544-.108.888-.108.44 0 .76.124.96.372.208.248.312.588.312 1.02v.324h-1.5c-.632 0-1.14.156-1.524.468s-.576.748-.576 1.308c0 .536.168.972.504 1.308.344.336.84.504 1.488.504.304 0 .616-.072.936-.216.32-.152.588-.328.804-.528l.12.588h.708v-4.02c0-.376-.096-.712-.288-1.008s-.46-.528-.804-.696-.736-.252-1.176-.252c-.264 0-.6.048-1.008.144-.4.096-.704.216-.912.36zm.228 3.096c0-.32.104-.588.312-.804.216-.224.512-.336.888-.336h1.536v1.32c-.216.256-.468.464-.756.624-.28.16-.576.24-.888.24-.36 0-.632-.104-.816-.312s-.276-.452-.276-.732zm6.0874-2.352c.272-.28.604-.524.996-.732.392-.216.748-.324 1.068-.324l-.228-.756c-.28 0-.604.1-.972.3s-.684.412-.948.636l-.3-.936h-.564v5.82h.948zm6.9986 2.952c-.28.144-.532.248-.756.312s-.512.096-.864.096c-.584 0-1.036-.212-1.356-.636s-.48-.976-.48-1.656c.008-.648.18-1.18.516-1.596.336-.424.792-.636 1.368-.636.336 0 .608.032.816.096.216.064.484.164.804.3l.288-.672c-.232-.152-.54-.276-.924-.372-.376-.104-.696-.156-.96-.156-.576 0-1.08.132-1.512.396-.432.256-.768.616-1.008 1.08-.232.456-.348.98-.348 1.572 0 .6.112 1.136.336 1.608.224.464.548.828.972 1.092.424.256.924.384 1.5.384.264 0 .588-.052.972-.156.384-.096.696-.22.936-.372zm4.6201-4.944c.616 0 1.072.188 1.368.564.304.368.456.864.456 1.488v3.936h-.948v-3.804c0-.432-.08-.768-.24-1.008s-.428-.36-.804-.36c-.288 0-.616.1-.984.3-.36.2-.68.452-.96.756v4.128h-.948v-8.352l.948-.12v3.396c.28-.272.612-.492.996-.66.392-.176.764-.264 1.116-.264zm8.5136.024c.864 0 1.54.284 2.028.852.496.56.744 1.304.744 2.232 0 .592-.116 1.12-.348 1.584-.224.456-.548.816-.972 1.08-.416.256-.908.384-1.476.384-.24 0-.496-.052-.768-.156-.264-.096-.512-.236-.744-.42l-.204.42h-.564v-8.352l.948-.12v2.94c.216-.144.444-.252.684-.324.24-.08.464-.12.672-.12zm0 5.328c.576 0 1.02-.208 1.332-.624s.472-.952.48-1.608c0-.688-.156-1.24-.468-1.656-.304-.424-.748-.636-1.332-.636-.288 0-.54.044-.756.132s-.42.224-.612.408v3.468c.192.168.396.296.612.384s.464.132.744.132zm5.0915 1.608c-.088.24-.224.42-.408.54-.176.128-.452.28-.828.456l.288.684c.424-.088.796-.26 1.116-.516.328-.256.56-.564.696-.924l2.568-7.02h-.96l-1.668 4.5-1.764-4.68-.84.36 2.16 5.604z"
|
|
750
|
+
fill="#000"
|
|
751
|
+
fillOpacity=".25"
|
|
752
|
+
/>
|
|
753
|
+
</g>
|
|
754
|
+
</svg>
|
|
755
|
+
</a>
|
|
756
|
+
</div>
|
|
757
|
+
</div>
|
|
758
|
+
|
|
759
|
+
{searchResultState.items.length > 0 ? (
|
|
760
|
+
<main>
|
|
761
|
+
{searchResultState.items.map(
|
|
762
|
+
({title, url, summary, breadcrumbs}, i) => (
|
|
763
|
+
<article key={i} className={styles.searchResultItem}>
|
|
764
|
+
<h2 className={styles.searchResultItemHeading}>
|
|
765
|
+
<Link to={url} dangerouslySetInnerHTML={{__html: title}} />
|
|
766
|
+
</h2>
|
|
767
|
+
|
|
768
|
+
{breadcrumbs.length > 0 && (
|
|
769
|
+
<nav aria-label="breadcrumbs">
|
|
770
|
+
<ul
|
|
771
|
+
className={clsx(
|
|
772
|
+
'breadcrumbs',
|
|
773
|
+
styles.searchResultItemPath,
|
|
774
|
+
)}>
|
|
775
|
+
{breadcrumbs.map((html, index) => (
|
|
776
|
+
<li
|
|
777
|
+
key={index}
|
|
778
|
+
className="breadcrumbs__item"
|
|
779
|
+
// Developer provided the HTML, so assume it's safe.
|
|
780
|
+
// eslint-disable-next-line react/no-danger
|
|
781
|
+
dangerouslySetInnerHTML={{__html: html}}
|
|
782
|
+
/>
|
|
783
|
+
))}
|
|
784
|
+
</ul>
|
|
785
|
+
</nav>
|
|
786
|
+
)}
|
|
787
|
+
|
|
788
|
+
{summary && (
|
|
789
|
+
<p
|
|
790
|
+
className={styles.searchResultItemSummary}
|
|
791
|
+
// Developer provided the HTML, so assume it's safe.
|
|
792
|
+
// eslint-disable-next-line react/no-danger
|
|
793
|
+
dangerouslySetInnerHTML={{__html: summary}}
|
|
794
|
+
/>
|
|
795
|
+
)}
|
|
796
|
+
</article>
|
|
797
|
+
),
|
|
798
|
+
)}
|
|
799
|
+
</main>
|
|
800
|
+
) : (
|
|
801
|
+
[
|
|
802
|
+
searchQuery && !searchResultState.loading && (
|
|
803
|
+
<p key="no-results">
|
|
804
|
+
<Translate
|
|
805
|
+
id="theme.SearchPage.noResultsText"
|
|
806
|
+
description="The paragraph for empty search result">
|
|
807
|
+
No results were found
|
|
808
|
+
</Translate>
|
|
809
|
+
</p>
|
|
810
|
+
),
|
|
811
|
+
!!searchResultState.loading && (
|
|
812
|
+
<div key="spinner" className={styles.loadingSpinner} />
|
|
813
|
+
),
|
|
814
|
+
]
|
|
815
|
+
)}
|
|
816
|
+
|
|
817
|
+
{searchResultState.hasMore && (
|
|
818
|
+
<div className={styles.loader} ref={setLoaderRef}>
|
|
819
|
+
<Translate
|
|
820
|
+
id="theme.SearchPage.fetchingNewResults"
|
|
821
|
+
description="The paragraph for fetching new search results">
|
|
822
|
+
Fetching new results...
|
|
823
|
+
</Translate>
|
|
824
|
+
</div>
|
|
825
|
+
)}
|
|
826
|
+
</div>
|
|
827
|
+
</Layout>
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
export default function SearchPage() {
|
|
831
|
+
return (
|
|
832
|
+
<HtmlClassNameProvider className="search-page-wrapper">
|
|
833
|
+
<SearchPageContent />
|
|
834
|
+
</HtmlClassNameProvider>
|
|
835
|
+
);
|
|
836
|
+
}
|