@docusaurus/theme-search-algolia 2.0.0-beta.1 → 2.0.0-beta.10

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.
Files changed (36) hide show
  1. package/copyUntypedFiles.js +20 -0
  2. package/lib/index.d.ts +11 -0
  3. package/lib/index.js +85 -0
  4. package/lib/templates/opensearch.d.ts +8 -0
  5. package/lib/templates/opensearch.js +23 -0
  6. package/lib/theme/SearchBar/index.d.ts +9 -0
  7. package/lib/theme/SearchBar/index.js +153 -0
  8. package/lib/theme/SearchBar/styles.css +21 -0
  9. package/lib/theme/SearchBar/styles.module.css +20 -0
  10. package/lib/theme/SearchMetadata/index.d.ts +10 -0
  11. package/lib/theme/SearchMetadata/index.js +20 -0
  12. package/lib/theme/SearchPage/index.d.ts +9 -0
  13. package/lib/theme/SearchPage/index.js +299 -0
  14. package/lib/theme/SearchPage/styles.module.css +119 -0
  15. package/lib/theme/hooks/useAlgoliaContextualFacetFilters.d.ts +8 -0
  16. package/lib/theme/hooks/useAlgoliaContextualFacetFilters.js +15 -0
  17. package/lib/theme/hooks/useSearchQuery.d.ts +9 -0
  18. package/lib/theme/hooks/useSearchQuery.js +43 -0
  19. package/lib/validateThemeConfig.d.ts +13 -0
  20. package/lib/validateThemeConfig.js +39 -0
  21. package/package.json +24 -12
  22. package/src/__tests__/validateThemeConfig.test.js +14 -0
  23. package/src/{index.js → index.ts} +37 -19
  24. package/src/templates/{opensearch.js → opensearch.ts} +4 -2
  25. package/src/theme/SearchBar/{index.js → index.tsx} +78 -26
  26. package/src/theme/{SearchMetadatas/index.js → SearchMetadata/index.tsx} +9 -2
  27. package/src/theme/SearchPage/{index.js → index.tsx} +81 -51
  28. package/src/theme/hooks/{useAlgoliaContextualFacetFilters.js → useAlgoliaContextualFacetFilters.ts} +4 -3
  29. package/src/theme/hooks/useSearchQuery.ts +63 -0
  30. package/src/theme-search-algolia.d.ts +47 -0
  31. package/src/types.d.ts +10 -0
  32. package/src/{validateThemeConfig.js → validateThemeConfig.ts} +10 -7
  33. package/tsconfig.browser.json +8 -0
  34. package/tsconfig.json +9 -0
  35. package/tsconfig.server.json +4 -0
  36. package/src/theme/hooks/useSearchQuery.js +0 -44
@@ -0,0 +1,299 @@
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
+ /* eslint-disable jsx-a11y/no-autofocus */
8
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
9
+ import React, { useEffect, useState, useReducer, useRef } from 'react';
10
+ import algoliaSearch from 'algoliasearch/lite';
11
+ import algoliaSearchHelper from 'algoliasearch-helper';
12
+ import clsx from 'clsx';
13
+ import Head from '@docusaurus/Head';
14
+ import Link from '@docusaurus/Link';
15
+ import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
16
+ import { useTitleFormatter, usePluralForm, isRegexpStringMatch, useDynamicCallback, } from '@docusaurus/theme-common';
17
+ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
18
+ import { useAllDocsData } from '@theme/hooks/useDocs';
19
+ import useSearchQuery from '@theme/hooks/useSearchQuery';
20
+ import Layout from '@theme/Layout';
21
+ import Translate, { translate } from '@docusaurus/Translate';
22
+ import styles from './styles.module.css';
23
+ // Very simple pluralization: probably good enough for now
24
+ function useDocumentsFoundPlural() {
25
+ const { selectMessage } = usePluralForm();
26
+ return (count) => selectMessage(count, translate({
27
+ id: 'theme.SearchPage.documentsFound.plurals',
28
+ description: '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)',
29
+ message: 'One document found|{count} documents found',
30
+ }, { count }));
31
+ }
32
+ function useDocsSearchVersionsHelpers() {
33
+ const allDocsData = useAllDocsData();
34
+ // State of the version select menus / algolia facet filters
35
+ // docsPluginId -> versionName map
36
+ const [searchVersions, setSearchVersions] = useState(() => Object.entries(allDocsData).reduce((acc, [pluginId, pluginData]) => ({
37
+ ...acc,
38
+ [pluginId]: pluginData.versions[0].name,
39
+ }), {}));
40
+ // Set the value of a single select menu
41
+ const setSearchVersion = (pluginId, searchVersion) => setSearchVersions((s) => ({ ...s, [pluginId]: searchVersion }));
42
+ const versioningEnabled = Object.values(allDocsData).some((docsData) => docsData.versions.length > 1);
43
+ return {
44
+ allDocsData,
45
+ versioningEnabled,
46
+ searchVersions,
47
+ setSearchVersion,
48
+ };
49
+ }
50
+ // We want to display one select per versioned docs plugin instance
51
+ function SearchVersionSelectList({ docsSearchVersionsHelpers, }) {
52
+ const versionedPluginEntries = Object.entries(docsSearchVersionsHelpers.allDocsData)
53
+ // Do not show a version select for unversioned docs plugin instances
54
+ .filter(([, docsData]) => docsData.versions.length > 1);
55
+ return (<div className={clsx('col', 'col--3', 'padding-left--none', styles.searchVersionColumn)}>
56
+ {versionedPluginEntries.map(([pluginId, docsData]) => {
57
+ const labelPrefix = versionedPluginEntries.length > 1 ? `${pluginId}: ` : '';
58
+ return (<select key={pluginId} onChange={(e) => docsSearchVersionsHelpers.setSearchVersion(pluginId, e.target.value)} defaultValue={docsSearchVersionsHelpers.searchVersions[pluginId]} className={styles.searchVersionInput}>
59
+ {docsData.versions.map((version, i) => (<option key={i} label={`${labelPrefix}${version.label}`} value={version.name}/>))}
60
+ </select>);
61
+ })}
62
+ </div>);
63
+ }
64
+ function SearchPage() {
65
+ const { siteConfig: { themeConfig: {
66
+ // @ts-ignore
67
+ algolia: { appId, apiKey, indexName, externalUrlRegex }, }, }, i18n: { currentLocale }, } = useDocusaurusContext();
68
+ const documentsFoundPlural = useDocumentsFoundPlural();
69
+ const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers();
70
+ const { searchQuery, setSearchQuery } = useSearchQuery();
71
+ const initialSearchResultState = {
72
+ items: [],
73
+ query: null,
74
+ totalResults: null,
75
+ totalPages: null,
76
+ lastPage: null,
77
+ hasMore: null,
78
+ loading: null,
79
+ };
80
+ const [searchResultState, searchResultStateDispatcher] = useReducer((prevState, data) => {
81
+ switch (data.type) {
82
+ case 'reset': {
83
+ return initialSearchResultState;
84
+ }
85
+ case 'loading': {
86
+ return { ...prevState, loading: true };
87
+ }
88
+ case 'update': {
89
+ if (searchQuery !== data.value.query) {
90
+ return prevState;
91
+ }
92
+ return {
93
+ ...data.value,
94
+ items: data.value.lastPage === 0
95
+ ? data.value.items
96
+ : prevState.items.concat(data.value.items),
97
+ };
98
+ }
99
+ case 'advance': {
100
+ const hasMore = prevState.totalPages > prevState.lastPage + 1;
101
+ return {
102
+ ...prevState,
103
+ lastPage: hasMore ? prevState.lastPage + 1 : prevState.lastPage,
104
+ hasMore,
105
+ };
106
+ }
107
+ default:
108
+ return prevState;
109
+ }
110
+ }, initialSearchResultState);
111
+ const algoliaClient = algoliaSearch(appId, apiKey);
112
+ const algoliaHelper = algoliaSearchHelper(algoliaClient, indexName, {
113
+ hitsPerPage: 15,
114
+ advancedSyntax: true,
115
+ disjunctiveFacets: ['language', 'docusaurus_tag'],
116
+ });
117
+ algoliaHelper.on('result', ({ results: { query, hits, page, nbHits, nbPages } }) => {
118
+ if (query === '' || !(hits instanceof Array)) {
119
+ searchResultStateDispatcher({ type: 'reset' });
120
+ return;
121
+ }
122
+ const sanitizeValue = (value) => value.replace(/algolia-docsearch-suggestion--highlight/g, 'search-result-match');
123
+ const items = hits.map(({ url, _highlightResult: { hierarchy }, _snippetResult: snippet = {}, }) => {
124
+ const parsedURL = new URL(url);
125
+ const titles = Object.keys(hierarchy).map((key) => sanitizeValue(hierarchy[key].value));
126
+ return {
127
+ title: titles.pop(),
128
+ url: isRegexpStringMatch(externalUrlRegex, parsedURL.href)
129
+ ? parsedURL.href
130
+ : parsedURL.pathname + parsedURL.hash,
131
+ summary: snippet.content
132
+ ? `${sanitizeValue(snippet.content.value)}...`
133
+ : '',
134
+ breadcrumbs: titles,
135
+ };
136
+ });
137
+ searchResultStateDispatcher({
138
+ type: 'update',
139
+ value: {
140
+ items,
141
+ query,
142
+ totalResults: nbHits,
143
+ totalPages: nbPages,
144
+ lastPage: page,
145
+ hasMore: nbPages > page + 1,
146
+ loading: false,
147
+ },
148
+ });
149
+ });
150
+ const [loaderRef, setLoaderRef] = useState(null);
151
+ const prevY = useRef(0);
152
+ const observer = useRef(ExecutionEnvironment.canUseDOM &&
153
+ new IntersectionObserver((entries) => {
154
+ const { isIntersecting, boundingClientRect: { y: currentY }, } = entries[0];
155
+ if (isIntersecting && prevY.current > currentY) {
156
+ searchResultStateDispatcher({ type: 'advance' });
157
+ }
158
+ prevY.current = currentY;
159
+ }, { threshold: 1 }));
160
+ const getTitle = () => searchQuery
161
+ ? translate({
162
+ id: 'theme.SearchPage.existingResultsTitle',
163
+ message: 'Search results for "{query}"',
164
+ description: 'The search page title for non-empty query',
165
+ }, {
166
+ query: searchQuery,
167
+ })
168
+ : translate({
169
+ id: 'theme.SearchPage.emptyResultsTitle',
170
+ message: 'Search the documentation',
171
+ description: 'The search page title for empty query',
172
+ });
173
+ const makeSearch = useDynamicCallback((page = 0) => {
174
+ algoliaHelper.addDisjunctiveFacetRefinement('docusaurus_tag', 'default');
175
+ algoliaHelper.addDisjunctiveFacetRefinement('language', currentLocale);
176
+ Object.entries(docsSearchVersionsHelpers.searchVersions).forEach(([pluginId, searchVersion]) => {
177
+ algoliaHelper.addDisjunctiveFacetRefinement('docusaurus_tag', `docs-${pluginId}-${searchVersion}`);
178
+ });
179
+ algoliaHelper.setQuery(searchQuery).setPage(page).search();
180
+ });
181
+ useEffect(() => {
182
+ if (!loaderRef) {
183
+ return undefined;
184
+ }
185
+ const currentObserver = observer.current;
186
+ if (currentObserver) {
187
+ currentObserver.observe(loaderRef);
188
+ return () => currentObserver.unobserve(loaderRef);
189
+ }
190
+ return () => true;
191
+ }, [loaderRef]);
192
+ useEffect(() => {
193
+ searchResultStateDispatcher({ type: 'reset' });
194
+ if (searchQuery) {
195
+ searchResultStateDispatcher({ type: 'loading' });
196
+ setTimeout(() => {
197
+ makeSearch();
198
+ }, 300);
199
+ }
200
+ }, [searchQuery, docsSearchVersionsHelpers.searchVersions, makeSearch]);
201
+ useEffect(() => {
202
+ if (!searchResultState.lastPage || searchResultState.lastPage === 0) {
203
+ return;
204
+ }
205
+ makeSearch(searchResultState.lastPage);
206
+ }, [makeSearch, searchResultState.lastPage]);
207
+ return (<Layout wrapperClassName="search-page-wrapper">
208
+ <Head>
209
+ <title>{useTitleFormatter(getTitle())}</title>
210
+ {/*
211
+ We should not index search pages
212
+ See https://github.com/facebook/docusaurus/pull/3233
213
+ */}
214
+ <meta property="robots" content="noindex, follow"/>
215
+ </Head>
216
+
217
+ <div className="container margin-vert--lg">
218
+ <h1>{getTitle()}</h1>
219
+
220
+ <form className="row" onSubmit={(e) => e.preventDefault()}>
221
+ <div className={clsx('col', styles.searchQueryColumn, {
222
+ 'col--9': docsSearchVersionsHelpers.versioningEnabled,
223
+ 'col--12': !docsSearchVersionsHelpers.versioningEnabled,
224
+ })}>
225
+ <input type="search" name="q" className={styles.searchQueryInput} placeholder={translate({
226
+ id: 'theme.SearchPage.inputPlaceholder',
227
+ message: 'Type your search here',
228
+ description: 'The placeholder for search page input',
229
+ })} aria-label={translate({
230
+ id: 'theme.SearchPage.inputLabel',
231
+ message: 'Search',
232
+ description: 'The ARIA label for search page input',
233
+ })} onChange={(e) => setSearchQuery(e.target.value)} value={searchQuery} autoComplete="off" autoFocus/>
234
+ </div>
235
+
236
+ {docsSearchVersionsHelpers.versioningEnabled && (<SearchVersionSelectList docsSearchVersionsHelpers={docsSearchVersionsHelpers}/>)}
237
+ </form>
238
+
239
+ <div className="row">
240
+ <div className={clsx('col', 'col--8', styles.searchResultsColumn)}>
241
+ {!!searchResultState.totalResults &&
242
+ documentsFoundPlural(searchResultState.totalResults)}
243
+ </div>
244
+
245
+ <div className={clsx('col', 'col--4', 'text--right', styles.searchLogoColumn)}>
246
+ <a target="_blank" rel="noopener noreferrer" href="https://www.algolia.com/" aria-label={translate({
247
+ id: 'theme.SearchPage.algoliaLabel',
248
+ message: 'Search by Algolia',
249
+ description: 'The ARIA label for Algolia mention',
250
+ })}>
251
+ <svg viewBox="0 0 168 24" className={styles.algoliaLogo}>
252
+ <g fill="none">
253
+ <path className={styles.algoliaLogoPathFill} d="M120.925 18.804c-4.386.02-4.386-3.54-4.386-4.106l-.007-13.336 2.675-.424v13.254c0 .322 0 2.358 1.718 2.364v2.248zm-10.846-2.18c.821 0 1.43-.047 1.855-.129v-2.719a6.334 6.334 0 0 0-1.574-.199 5.7 5.7 0 0 0-.897.069 2.699 2.699 0 0 0-.814.24c-.24.116-.439.28-.582.491-.15.212-.219.335-.219.656 0 .628.219.991.616 1.23s.938.362 1.615.362zm-.233-9.7c.883 0 1.629.109 2.231.328.602.218 1.088.525 1.444.915.363.396.609.922.76 1.483.157.56.232 1.175.232 1.85v6.874a32.5 32.5 0 0 1-1.868.314c-.834.123-1.772.185-2.813.185-.69 0-1.327-.069-1.895-.198a4.001 4.001 0 0 1-1.471-.636 3.085 3.085 0 0 1-.951-1.134c-.226-.465-.343-1.12-.343-1.803 0-.656.13-1.073.384-1.525a3.24 3.24 0 0 1 1.047-1.106c.445-.287.95-.492 1.532-.615a8.8 8.8 0 0 1 1.82-.185 8.404 8.404 0 0 1 1.972.24v-.438c0-.307-.035-.6-.11-.874a1.88 1.88 0 0 0-.384-.73 1.784 1.784 0 0 0-.724-.493 3.164 3.164 0 0 0-1.143-.205c-.616 0-1.177.075-1.69.164a7.735 7.735 0 0 0-1.26.307l-.321-2.192c.335-.117.834-.233 1.478-.349a10.98 10.98 0 0 1 2.073-.178zm52.842 9.626c.822 0 1.43-.048 1.854-.13V13.7a6.347 6.347 0 0 0-1.574-.199c-.294 0-.595.021-.896.069a2.7 2.7 0 0 0-.814.24 1.46 1.46 0 0 0-.582.491c-.15.212-.218.335-.218.656 0 .628.218.991.615 1.23.404.245.938.362 1.615.362zm-.226-9.694c.883 0 1.629.108 2.231.327.602.219 1.088.526 1.444.915.355.39.609.923.759 1.483a6.8 6.8 0 0 1 .233 1.852v6.873c-.41.088-1.034.19-1.868.314-.834.123-1.772.184-2.813.184-.69 0-1.327-.068-1.895-.198a4.001 4.001 0 0 1-1.471-.635 3.085 3.085 0 0 1-.951-1.134c-.226-.465-.343-1.12-.343-1.804 0-.656.13-1.073.384-1.524.26-.45.608-.82 1.047-1.107.445-.286.95-.491 1.532-.614a8.803 8.803 0 0 1 2.751-.13c.329.034.671.096 1.04.185v-.437a3.3 3.3 0 0 0-.109-.875 1.873 1.873 0 0 0-.384-.731 1.784 1.784 0 0 0-.724-.492 3.165 3.165 0 0 0-1.143-.205c-.616 0-1.177.075-1.69.164a7.75 7.75 0 0 0-1.26.307l-.321-2.193c.335-.116.834-.232 1.478-.348a11.633 11.633 0 0 1 2.073-.177zm-8.034-1.271a1.626 1.626 0 0 1-1.628-1.62c0-.895.725-1.62 1.628-1.62.904 0 1.63.725 1.63 1.62 0 .895-.733 1.62-1.63 1.62zm1.348 13.22h-2.689V7.27l2.69-.423v11.956zm-4.714 0c-4.386.02-4.386-3.54-4.386-4.107l-.008-13.336 2.676-.424v13.254c0 .322 0 2.358 1.718 2.364v2.248zm-8.698-5.903c0-1.156-.253-2.119-.746-2.788-.493-.677-1.183-1.01-2.067-1.01-.882 0-1.574.333-2.065 1.01-.493.676-.733 1.632-.733 2.788 0 1.168.246 1.953.74 2.63.492.683 1.183 1.018 2.066 1.018.882 0 1.574-.342 2.067-1.019.492-.683.738-1.46.738-2.63zm2.737-.007c0 .902-.13 1.584-.397 2.33a5.52 5.52 0 0 1-1.128 1.906 4.986 4.986 0 0 1-1.752 1.223c-.685.286-1.739.45-2.265.45-.528-.006-1.574-.157-2.252-.45a5.096 5.096 0 0 1-1.744-1.223c-.487-.527-.863-1.162-1.137-1.906a6.345 6.345 0 0 1-.41-2.33c0-.902.123-1.77.397-2.508a5.554 5.554 0 0 1 1.15-1.892 5.133 5.133 0 0 1 1.75-1.216c.679-.287 1.425-.423 2.232-.423.808 0 1.553.142 2.237.423a4.88 4.88 0 0 1 1.753 1.216 5.644 5.644 0 0 1 1.135 1.892c.287.738.431 1.606.431 2.508zm-20.138 0c0 1.12.246 2.363.738 2.882.493.52 1.13.78 1.91.78.424 0 .828-.062 1.204-.178.377-.116.677-.253.917-.417V9.33a10.476 10.476 0 0 0-1.766-.226c-.971-.028-1.71.37-2.23 1.004-.513.636-.773 1.75-.773 2.788zm7.438 5.274c0 1.824-.466 3.156-1.404 4.004-.936.846-2.367 1.27-4.296 1.27-.705 0-2.17-.137-3.34-.396l.431-2.118c.98.205 2.272.26 2.95.26 1.074 0 1.84-.219 2.299-.656.459-.437.684-1.086.684-1.948v-.437a8.07 8.07 0 0 1-1.047.397c-.43.13-.93.198-1.492.198-.739 0-1.41-.116-2.018-.349a4.206 4.206 0 0 1-1.567-1.025c-.431-.45-.774-1.017-1.013-1.694-.24-.677-.363-1.885-.363-2.773 0-.834.13-1.88.384-2.577.26-.696.629-1.298 1.129-1.796.493-.498 1.095-.881 1.8-1.162a6.605 6.605 0 0 1 2.428-.457c.87 0 1.67.109 2.45.24.78.129 1.444.265 1.985.415V18.17zM6.972 6.677v1.627c-.712-.446-1.52-.67-2.425-.67-.585 0-1.045.13-1.38.391a1.24 1.24 0 0 0-.502 1.03c0 .425.164.765.494 1.02.33.256.835.532 1.516.83.447.192.795.356 1.045.495.25.138.537.332.862.582.324.25.563.548.718.894.154.345.23.741.23 1.188 0 .947-.334 1.691-1.004 2.234-.67.542-1.537.814-2.601.814-1.18 0-2.16-.229-2.936-.686v-1.708c.84.628 1.814.942 2.92.942.585 0 1.048-.136 1.388-.407.34-.271.51-.646.51-1.125 0-.287-.1-.55-.302-.79-.203-.24-.42-.42-.655-.542-.234-.123-.585-.29-1.053-.503a61.27 61.27 0 0 1-.582-.271 13.67 13.67 0 0 1-.55-.287 4.275 4.275 0 0 1-.567-.351 6.92 6.92 0 0 1-.455-.4c-.18-.17-.31-.34-.39-.51-.08-.17-.155-.37-.224-.598a2.553 2.553 0 0 1-.104-.742c0-.915.333-1.638.998-2.17.664-.532 1.523-.798 2.576-.798.968 0 1.793.17 2.473.51zm7.468 5.696v-.287c-.022-.607-.187-1.088-.495-1.444-.309-.357-.75-.535-1.324-.535-.532 0-.99.194-1.373.583-.382.388-.622.949-.717 1.683h3.909zm1.005 2.792v1.404c-.596.34-1.383.51-2.362.51-1.255 0-2.255-.377-3-1.132-.744-.755-1.116-1.744-1.116-2.968 0-1.297.34-2.316 1.021-3.055.68-.74 1.548-1.11 2.6-1.11 1.033 0 1.852.323 2.458.966.606.644.91 1.572.91 2.784 0 .33-.033.676-.096 1.038h-5.314c.107.702.405 1.239.894 1.611.49.372 1.106.558 1.85.558.862 0 1.58-.202 2.155-.606zm6.605-1.77h-1.212c-.596 0-1.045.116-1.349.35-.303.234-.454.532-.454.894 0 .372.117.664.35.877.235.213.575.32 1.022.32.51 0 .912-.142 1.204-.424.293-.281.44-.651.44-1.108v-.91zm-4.068-2.554V9.325c.627-.361 1.457-.542 2.489-.542 2.116 0 3.175 1.026 3.175 3.08V17h-1.548v-.957c-.415.68-1.143 1.02-2.186 1.02-.766 0-1.38-.22-1.843-.661-.462-.442-.694-1.003-.694-1.684 0-.776.293-1.38.878-1.81.585-.431 1.404-.647 2.457-.647h1.34V11.8c0-.554-.133-.971-.399-1.253-.266-.282-.707-.423-1.324-.423a4.07 4.07 0 0 0-2.345.718zm9.333-1.93v1.42c.394-1 1.101-1.5 2.123-1.5.148 0 .313.016.494.048v1.531a1.885 1.885 0 0 0-.75-.143c-.542 0-.989.24-1.34.718-.351.479-.527 1.048-.527 1.707V17h-1.563V8.91h1.563zm5.01 4.084c.022.82.272 1.492.75 2.019.479.526 1.15.79 2.01.79.639 0 1.235-.176 1.788-.527v1.404c-.521.319-1.186.479-1.995.479-1.265 0-2.276-.4-3.031-1.197-.755-.798-1.133-1.792-1.133-2.984 0-1.16.38-2.151 1.14-2.975.761-.825 1.79-1.237 3.088-1.237.702 0 1.346.149 1.93.447v1.436a3.242 3.242 0 0 0-1.77-.495c-.84 0-1.513.266-2.019.798-.505.532-.758 1.213-.758 2.042zM40.24 5.72v4.579c.458-1 1.293-1.5 2.505-1.5.787 0 1.42.245 1.899.734.479.49.718 1.17.718 2.042V17h-1.564v-5.106c0-.553-.14-.98-.422-1.284-.282-.303-.652-.455-1.11-.455-.531 0-1.002.202-1.411.606-.41.405-.615 1.022-.615 1.851V17h-1.563V5.72h1.563zm14.966 10.02c.596 0 1.096-.253 1.5-.758.404-.506.606-1.157.606-1.955 0-.915-.202-1.62-.606-2.114-.404-.495-.92-.742-1.548-.742-.553 0-1.05.224-1.491.67-.442.447-.662 1.133-.662 2.058 0 .958.212 1.67.638 2.138.425.469.946.703 1.563.703zM53.004 5.72v4.42c.574-.894 1.388-1.341 2.44-1.341 1.022 0 1.857.383 2.506 1.149.649.766.973 1.781.973 3.047 0 1.138-.309 2.109-.925 2.912-.617.803-1.463 1.205-2.537 1.205-1.075 0-1.894-.447-2.457-1.34V17h-1.58V5.72h1.58zm9.908 11.104l-3.223-7.913h1.739l1.005 2.632 1.26 3.415c.096-.32.48-1.458 1.15-3.415l.909-2.632h1.66l-2.92 7.866c-.777 2.074-1.963 3.11-3.559 3.11a2.92 2.92 0 0 1-.734-.079v-1.34c.17.042.351.064.543.064 1.032 0 1.755-.57 2.17-1.708z"/>
254
+ <path fill="#5468FF" d="M78.988.938h16.594a2.968 2.968 0 0 1 2.966 2.966V20.5a2.967 2.967 0 0 1-2.966 2.964H78.988a2.967 2.967 0 0 1-2.966-2.964V3.897A2.961 2.961 0 0 1 78.988.938z"/>
255
+ <path fill="white" d="M89.632 5.967v-.772a.978.978 0 0 0-.978-.977h-2.28a.978.978 0 0 0-.978.977v.793c0 .088.082.15.171.13a7.127 7.127 0 0 1 1.984-.28c.65 0 1.295.088 1.917.259.082.02.164-.04.164-.13m-6.248 1.01l-.39-.389a.977.977 0 0 0-1.382 0l-.465.465a.973.973 0 0 0 0 1.38l.383.383c.062.061.15.047.205-.014.226-.307.472-.601.746-.874.281-.28.568-.526.883-.751.068-.042.075-.137.02-.2m4.16 2.453v3.341c0 .096.104.165.192.117l2.97-1.537c.068-.034.089-.117.055-.184a3.695 3.695 0 0 0-3.08-1.866c-.068 0-.136.054-.136.13m0 8.048a4.489 4.489 0 0 1-4.49-4.482 4.488 4.488 0 0 1 4.49-4.482 4.488 4.488 0 0 1 4.489 4.482 4.484 4.484 0 0 1-4.49 4.482m0-10.85a6.363 6.363 0 1 0 0 12.729 6.37 6.37 0 0 0 6.372-6.368 6.358 6.358 0 0 0-6.371-6.36"/>
256
+ </g>
257
+ </svg>
258
+ </a>
259
+ </div>
260
+ </div>
261
+
262
+ {searchResultState.items.length > 0 ? (<main>
263
+ {searchResultState.items.map(({ title, url, summary, breadcrumbs }, i) => (<article key={i} className={styles.searchResultItem}>
264
+ <h2 className={styles.searchResultItemHeading}>
265
+ <Link to={url} dangerouslySetInnerHTML={{ __html: title }}/>
266
+ </h2>
267
+
268
+ {breadcrumbs.length > 0 && (<nav aria-label="breadcrumbs">
269
+ <ul className={clsx('breadcrumbs', styles.searchResultItemPath)}>
270
+ {breadcrumbs.map((html, index) => (<li key={index} className="breadcrumbs__item"
271
+ // Developer provided the HTML, so assume it's safe.
272
+ // eslint-disable-next-line react/no-danger
273
+ dangerouslySetInnerHTML={{ __html: html }}/>))}
274
+ </ul>
275
+ </nav>)}
276
+
277
+ {summary && (<p className={styles.searchResultItemSummary}
278
+ // Developer provided the HTML, so assume it's safe.
279
+ // eslint-disable-next-line react/no-danger
280
+ dangerouslySetInnerHTML={{ __html: summary }}/>)}
281
+ </article>))}
282
+ </main>) : ([
283
+ searchQuery && !searchResultState.loading && (<p key="no-results">
284
+ <Translate id="theme.SearchPage.noResultsText" description="The paragraph for empty search result">
285
+ No results were found
286
+ </Translate>
287
+ </p>),
288
+ !!searchResultState.loading && (<div key="spinner" className={styles.loadingSpinner}/>),
289
+ ])}
290
+
291
+ {searchResultState.hasMore && (<div className={styles.loader} ref={setLoaderRef}>
292
+ <Translate id="theme.SearchPage.fetchingNewResults" description="The paragraph for fetching new search results">
293
+ Fetching new results...
294
+ </Translate>
295
+ </div>)}
296
+ </div>
297
+ </Layout>);
298
+ }
299
+ export default SearchPage;
@@ -0,0 +1,119 @@
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
+ .searchQueryInput,
9
+ .searchVersionInput {
10
+ border-radius: var(--ifm-global-radius);
11
+ border: 2px solid var(--ifm-toc-border-color);
12
+ font: var(--ifm-font-size-base) var(--ifm-font-family-base);
13
+ padding: 0.8rem;
14
+ width: 100%;
15
+ background: var(--docsearch-searchbox-focus-background);
16
+ color: var(--docsearch-text-color);
17
+ margin-bottom: 0.5rem;
18
+ transition: border var(--ifm-transition-fast) ease;
19
+ }
20
+
21
+ .searchQueryInput:focus,
22
+ .searchVersionInput:focus {
23
+ border-color: var(--docsearch-primary-color);
24
+ outline: none;
25
+ }
26
+
27
+ .searchQueryInput::placeholder {
28
+ color: var(--docsearch-muted-color);
29
+ }
30
+
31
+ .searchResultsColumn {
32
+ font-size: 0.9rem;
33
+ font-weight: bold;
34
+ }
35
+
36
+ .algoliaLogo {
37
+ max-width: 150px;
38
+ }
39
+
40
+ .algoliaLogoPathFill {
41
+ fill: var(--ifm-font-color-base);
42
+ }
43
+
44
+ .searchResultItem {
45
+ padding: 1rem 0;
46
+ border-bottom: 1px solid var(--ifm-toc-border-color);
47
+ }
48
+
49
+ .searchResultItemHeading {
50
+ font-weight: 400;
51
+ margin-bottom: 0;
52
+ }
53
+
54
+ .searchResultItemPath {
55
+ font-size: 0.8rem;
56
+ color: var(--ifm-color-content-secondary);
57
+ --ifm-breadcrumb-separator-size-multiplier: 1;
58
+ }
59
+
60
+ .searchResultItemSummary {
61
+ margin: 0.5rem 0 0 0;
62
+ font-style: italic;
63
+ }
64
+
65
+ @media only screen and (max-width: 996px) {
66
+ .searchQueryColumn {
67
+ max-width: 60% !important;
68
+ }
69
+
70
+ .searchVersionColumn {
71
+ max-width: 40% !important;
72
+ }
73
+
74
+ .searchResultsColumn {
75
+ max-width: 60% !important;
76
+ }
77
+
78
+ .searchLogoColumn {
79
+ max-width: 40% !important;
80
+ padding-left: 0 !important;
81
+ }
82
+ }
83
+
84
+ @media screen and (max-width: 576px) {
85
+ .searchQueryColumn {
86
+ max-width: 100% !important;
87
+ }
88
+
89
+ .searchVersionColumn {
90
+ max-width: 100% !important;
91
+ padding-left: var(--ifm-spacing-horizontal) !important;
92
+ }
93
+ }
94
+
95
+ .loadingSpinner {
96
+ width: 3rem;
97
+ height: 3rem;
98
+ border: 0.4em solid #eee;
99
+ border-top-color: var(--ifm-color-primary);
100
+ border-radius: 50%;
101
+ animation: loadingspin 1s linear infinite;
102
+ margin: 0 auto;
103
+ }
104
+
105
+ @keyframes loadingspin {
106
+ 100% {
107
+ transform: rotate(360deg);
108
+ }
109
+ }
110
+
111
+ .loader {
112
+ margin-top: 2rem;
113
+ }
114
+
115
+ :global(.search-result-match) {
116
+ color: var(--docsearch-hit-color);
117
+ background: rgba(255, 215, 142, 0.25);
118
+ padding: 0.09em 0;
119
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import type { useAlgoliaContextualFacetFiltersReturns } from '@theme/hooks/useAlgoliaContextualFacetFilters';
8
+ export default function useAlgoliaContextualFacetFilters(): useAlgoliaContextualFacetFiltersReturns;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import { useContextualSearchFilters } from '@docusaurus/theme-common';
8
+ // Translate search-engine agnostic search filters to Algolia search filters
9
+ export default function useAlgoliaContextualFacetFilters() {
10
+ const { locale, tags } = useContextualSearchFilters();
11
+ // seems safe to convert locale->language, see AlgoliaSearchMetadata comment
12
+ const languageFilter = `language:${locale}`;
13
+ const tagsFilter = tags.map((tag) => `docusaurus_tag:${tag}`);
14
+ return [languageFilter, tagsFilter];
15
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import type { SearchQuery } from '@theme/hooks/useSearchQuery';
8
+ declare function useSearchQuery(): SearchQuery;
9
+ export default useSearchQuery;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import { useHistory } from '@docusaurus/router';
8
+ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
9
+ import { useCallback, useEffect, useState } from 'react';
10
+ const SEARCH_PARAM_QUERY = 'q';
11
+ function useSearchQuery() {
12
+ const history = useHistory();
13
+ const { siteConfig: { baseUrl }, } = useDocusaurusContext();
14
+ const [searchQuery, setSearchQueryState] = useState('');
15
+ // Init search query just after React hydration
16
+ useEffect(() => {
17
+ var _a;
18
+ const searchQueryStringValue = (_a = new URLSearchParams(window.location.search).get(SEARCH_PARAM_QUERY)) !== null && _a !== void 0 ? _a : '';
19
+ setSearchQueryState(searchQueryStringValue);
20
+ }, []);
21
+ const setSearchQuery = useCallback((newSearchQuery) => {
22
+ const searchParams = new URLSearchParams(window.location.search);
23
+ if (newSearchQuery) {
24
+ searchParams.set(SEARCH_PARAM_QUERY, newSearchQuery);
25
+ }
26
+ else {
27
+ searchParams.delete(SEARCH_PARAM_QUERY);
28
+ }
29
+ history.replace({
30
+ search: searchParams.toString(),
31
+ });
32
+ setSearchQueryState(newSearchQuery);
33
+ }, [history]);
34
+ const generateSearchPageLink = useCallback((targetSearchQuery) =>
35
+ // Refer to https://github.com/facebook/docusaurus/pull/2838
36
+ `${baseUrl}search?q=${encodeURIComponent(targetSearchQuery)}`, [baseUrl]);
37
+ return {
38
+ searchQuery,
39
+ setSearchQuery,
40
+ generateSearchPageLink,
41
+ };
42
+ }
43
+ export default useSearchQuery;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import { Joi } from '@docusaurus/utils-validation';
8
+ import type { ThemeConfig, Validate, ValidationResult } from '@docusaurus/types';
9
+ export declare const Schema: Joi.ObjectSchema<any>;
10
+ export declare function validateThemeConfig({ validate, themeConfig, }: {
11
+ validate: Validate<ThemeConfig>;
12
+ themeConfig: ThemeConfig;
13
+ }): ValidationResult<ThemeConfig>;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.validateThemeConfig = exports.Schema = void 0;
10
+ const utils_validation_1 = require("@docusaurus/utils-validation");
11
+ const DEFAULT_CONFIG = {
12
+ contextualSearch: false,
13
+ // By default, all Docusaurus sites are using the same AppId
14
+ // This has been designed on purpose with Algolia.
15
+ appId: 'BH4D9OD16A',
16
+ searchParameters: {},
17
+ };
18
+ exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
19
+ exports.Schema = utils_validation_1.Joi.object({
20
+ algolia: utils_validation_1.Joi.object({
21
+ // Docusaurus attributes
22
+ contextualSearch: utils_validation_1.Joi.boolean().default(DEFAULT_CONFIG.contextualSearch),
23
+ externalUrlRegex: utils_validation_1.Joi.string().optional(),
24
+ // Algolia attributes
25
+ appId: utils_validation_1.Joi.string().default(DEFAULT_CONFIG.appId),
26
+ apiKey: utils_validation_1.Joi.string().required(),
27
+ indexName: utils_validation_1.Joi.string().required(),
28
+ searchParameters: utils_validation_1.Joi.object()
29
+ .default(DEFAULT_CONFIG.searchParameters)
30
+ .unknown(),
31
+ })
32
+ .label('themeConfig.algolia')
33
+ .required()
34
+ .unknown(), // DocSearch 3 is still alpha: don't validate the rest for now
35
+ });
36
+ function validateThemeConfig({ validate, themeConfig, }) {
37
+ return validate(exports.Schema, themeConfig);
38
+ }
39
+ exports.validateThemeConfig = validateThemeConfig;
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@docusaurus/theme-search-algolia",
3
- "version": "2.0.0-beta.1",
3
+ "version": "2.0.0-beta.10",
4
4
  "description": "Algolia search component for Docusaurus.",
5
- "main": "src/index.js",
5
+ "main": "lib/index.js",
6
+ "types": "src/theme-search-algolia.d.ts",
6
7
  "publishConfig": {
7
8
  "access": "public"
8
9
  },
@@ -12,24 +13,35 @@
12
13
  "directory": "packages/docusaurus-theme-search-algolia"
13
14
  },
14
15
  "license": "MIT",
16
+ "scripts": {
17
+ "build": "yarn build:server && yarn build:browser && yarn build:copy",
18
+ "build:server": "tsc --project tsconfig.server.json",
19
+ "build:browser": "tsc --project tsconfig.browser.json",
20
+ "build:copy": "node copyUntypedFiles.js"
21
+ },
15
22
  "dependencies": {
16
- "@docsearch/react": "^3.0.0-alpha.36",
17
- "@docusaurus/core": "2.0.0-beta.1",
18
- "@docusaurus/theme-common": "2.0.0-beta.1",
19
- "@docusaurus/utils": "2.0.0-beta.1",
20
- "@docusaurus/utils-validation": "2.0.0-beta.1",
21
- "algoliasearch": "^4.8.4",
22
- "algoliasearch-helper": "^3.3.4",
23
+ "@docsearch/react": "^3.0.0-alpha.39",
24
+ "@docusaurus/theme-common": "2.0.0-beta.10",
25
+ "@docusaurus/theme-translations": "2.0.0-beta.10",
26
+ "@docusaurus/utils": "2.0.0-beta.10",
27
+ "@docusaurus/utils-validation": "2.0.0-beta.10",
28
+ "algoliasearch": "^4.10.5",
29
+ "algoliasearch-helper": "^3.5.5",
23
30
  "clsx": "^1.1.1",
24
- "eta": "^1.12.1",
31
+ "eta": "^1.12.3",
25
32
  "lodash": "^4.17.20"
26
33
  },
34
+ "devDependencies": {
35
+ "@docusaurus/module-type-aliases": "2.0.0-beta.10",
36
+ "fs-extra": "^10.0.0"
37
+ },
27
38
  "peerDependencies": {
39
+ "@docusaurus/core": "2.0.0-beta.9",
28
40
  "react": "^16.8.4 || ^17.0.0",
29
41
  "react-dom": "^16.8.4 || ^17.0.0"
30
42
  },
31
43
  "engines": {
32
- "node": ">=12.13.0"
44
+ "node": ">=14"
33
45
  },
34
- "gitHead": "72ed3476bf4ffe1fc2d1025dd2def536a2de8f12"
46
+ "gitHead": "6f4869af5721435d6995e63e2f24ac41646e34ea"
35
47
  }
@@ -103,6 +103,20 @@ describe('validateThemeConfig', () => {
103
103
  });
104
104
  });
105
105
 
106
+ test('externalUrlRegex config', () => {
107
+ const algolia = {
108
+ indexName: 'index',
109
+ apiKey: 'apiKey',
110
+ externalUrlRegex: 'http://external-domain.com',
111
+ };
112
+ expect(testValidateThemeConfig({algolia})).toEqual({
113
+ algolia: {
114
+ ...DEFAULT_CONFIG,
115
+ ...algolia,
116
+ },
117
+ });
118
+ });
119
+
106
120
  test('searchParameters.facetFilters search config', () => {
107
121
  const algolia = {
108
122
  indexName: 'index',