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

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 (34) hide show
  1. package/lib/client/index.d.ts +7 -0
  2. package/lib/client/index.js +7 -0
  3. package/lib/client/useAlgoliaContextualFacetFilters.d.ts +7 -0
  4. package/lib/client/useAlgoliaContextualFacetFilters.js +15 -0
  5. package/lib/index.d.ts +9 -0
  6. package/lib/index.js +90 -0
  7. package/lib/templates/opensearch.d.ts +8 -0
  8. package/lib/templates/opensearch.js +23 -0
  9. package/lib/theme/SearchBar/index.d.ts +8 -0
  10. package/{src → lib}/theme/SearchBar/index.js +57 -65
  11. package/lib/theme/SearchBar/styles.css +21 -0
  12. package/lib/theme/SearchPage/index.d.ts +8 -0
  13. package/{src → lib}/theme/SearchPage/index.js +63 -92
  14. package/lib/theme/SearchPage/styles.module.css +119 -0
  15. package/lib/validateThemeConfig.d.ts +15 -0
  16. package/lib/validateThemeConfig.js +44 -0
  17. package/package.json +36 -13
  18. package/src/client/index.ts +8 -0
  19. package/src/{theme/hooks/useAlgoliaContextualFacetFilters.js → client/useAlgoliaContextualFacetFilters.ts} +3 -3
  20. package/src/deps.d.ts +10 -0
  21. package/src/index.ts +116 -0
  22. package/src/templates/{opensearch.js → opensearch.ts} +7 -5
  23. package/src/theme/SearchBar/index.tsx +276 -0
  24. package/src/theme/SearchPage/index.tsx +533 -0
  25. package/src/theme/SearchPage/styles.module.css +4 -4
  26. package/src/theme-search-algolia.d.ts +35 -0
  27. package/src/types.d.ts +10 -0
  28. package/src/validateThemeConfig.ts +53 -0
  29. package/src/__tests__/validateThemeConfig.test.js +0 -121
  30. package/src/index.js +0 -92
  31. package/src/theme/SearchBar/styles.module.css +0 -20
  32. package/src/theme/SearchMetadatas/index.js +0 -25
  33. package/src/theme/hooks/useSearchQuery.js +0 -44
  34. package/src/validateThemeConfig.js +0 -45
@@ -0,0 +1,7 @@
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
+ export { useAlgoliaContextualFacetFilters } from './useAlgoliaContextualFacetFilters';
@@ -0,0 +1,7 @@
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
+ export { useAlgoliaContextualFacetFilters } from './useAlgoliaContextualFacetFilters';
@@ -0,0 +1,7 @@
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
+ export declare function useAlgoliaContextualFacetFilters(): [string, string[]];
@@ -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 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
+ }
package/lib/index.d.ts ADDED
@@ -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 { LoadContext, Plugin } from '@docusaurus/types';
8
+ export default function themeSearchAlgolia(context: LoadContext): Plugin<void>;
9
+ export { validateThemeConfig } from './validateThemeConfig';
package/lib/index.js ADDED
@@ -0,0 +1,90 @@
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 = void 0;
10
+ const tslib_1 = require("tslib");
11
+ const path_1 = tslib_1.__importDefault(require("path"));
12
+ const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
13
+ const lodash_1 = tslib_1.__importDefault(require("lodash"));
14
+ const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
15
+ const eta_1 = require("eta");
16
+ const utils_1 = require("@docusaurus/utils");
17
+ const theme_translations_1 = require("@docusaurus/theme-translations");
18
+ const opensearch_1 = tslib_1.__importDefault(require("./templates/opensearch"));
19
+ const getCompiledOpenSearchTemplate = lodash_1.default.memoize(() => (0, eta_1.compile)(opensearch_1.default.trim()));
20
+ function renderOpenSearchTemplate(data) {
21
+ const compiled = getCompiledOpenSearchTemplate();
22
+ return compiled(data, eta_1.defaultConfig);
23
+ }
24
+ const OPEN_SEARCH_FILENAME = 'opensearch.xml';
25
+ function themeSearchAlgolia(context) {
26
+ const { baseUrl, siteConfig: { title, url, favicon, themeConfig }, i18n: { currentLocale }, } = context;
27
+ const { algolia: { searchPagePath }, } = themeConfig;
28
+ return {
29
+ name: 'docusaurus-theme-search-algolia',
30
+ getThemePath() {
31
+ return '../lib/theme';
32
+ },
33
+ getTypeScriptThemePath() {
34
+ return '../src/theme';
35
+ },
36
+ getDefaultCodeTranslationMessages() {
37
+ return (0, theme_translations_1.readDefaultCodeTranslationMessages)({
38
+ locale: currentLocale,
39
+ name: 'theme-search-algolia',
40
+ });
41
+ },
42
+ contentLoaded({ actions: { addRoute } }) {
43
+ if (searchPagePath) {
44
+ addRoute({
45
+ path: (0, utils_1.normalizeUrl)([baseUrl, searchPagePath]),
46
+ component: '@theme/SearchPage',
47
+ exact: true,
48
+ });
49
+ }
50
+ },
51
+ async postBuild({ outDir }) {
52
+ if (searchPagePath) {
53
+ const siteUrl = (0, utils_1.normalizeUrl)([url, baseUrl]);
54
+ try {
55
+ await fs_extra_1.default.writeFile(path_1.default.join(outDir, OPEN_SEARCH_FILENAME), renderOpenSearchTemplate({
56
+ title,
57
+ siteUrl,
58
+ searchUrl: (0, utils_1.normalizeUrl)([siteUrl, searchPagePath]),
59
+ faviconUrl: favicon ? (0, utils_1.normalizeUrl)([siteUrl, favicon]) : null,
60
+ }));
61
+ }
62
+ catch (err) {
63
+ logger_1.default.error('Generating OpenSearch file failed.');
64
+ throw err;
65
+ }
66
+ }
67
+ },
68
+ injectHtmlTags() {
69
+ if (!searchPagePath) {
70
+ return {};
71
+ }
72
+ return {
73
+ headTags: [
74
+ {
75
+ tagName: 'link',
76
+ attributes: {
77
+ rel: 'search',
78
+ type: 'application/opensearchdescription+xml',
79
+ title,
80
+ href: (0, utils_1.normalizeUrl)([baseUrl, OPEN_SEARCH_FILENAME]),
81
+ },
82
+ },
83
+ ],
84
+ };
85
+ },
86
+ };
87
+ }
88
+ exports.default = themeSearchAlgolia;
89
+ var validateThemeConfig_1 = require("./validateThemeConfig");
90
+ Object.defineProperty(exports, "validateThemeConfig", { enumerable: true, get: function () { return validateThemeConfig_1.validateThemeConfig; } });
@@ -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
+ declare const _default: "\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\"\n xmlns:moz=\"http://www.mozilla.org/2006/browser/search/\">\n <ShortName><%= it.title %></ShortName>\n <Description>Search <%= it.title %></Description>\n <InputEncoding>UTF-8</InputEncoding>\n <% if (it.faviconUrl) { _%>\n <Image width=\"16\" height=\"16\" type=\"image/x-icon\"><%= it.faviconUrl %></Image>\n <% } _%>\n <Url type=\"text/html\" method=\"get\" template=\"<%= it.searchUrl %>?q={searchTerms}\"/>\n <Url type=\"application/opensearchdescription+xml\" rel=\"self\" template=\"<%= it.siteUrl %>opensearch.xml\" />\n <moz:SearchForm><%= it.siteUrl %></moz:SearchForm>\n</OpenSearchDescription>\n";
8
+ export default _default;
@@ -0,0 +1,23 @@
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.default = `
10
+ <?xml version="1.0" encoding="UTF-8"?>
11
+ <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
12
+ xmlns:moz="http://www.mozilla.org/2006/browser/search/">
13
+ <ShortName><%= it.title %></ShortName>
14
+ <Description>Search <%= it.title %></Description>
15
+ <InputEncoding>UTF-8</InputEncoding>
16
+ <% if (it.faviconUrl) { _%>
17
+ <Image width="16" height="16" type="image/x-icon"><%= it.faviconUrl %></Image>
18
+ <% } _%>
19
+ <Url type="text/html" method="get" template="<%= it.searchUrl %>?q={searchTerms}"/>
20
+ <Url type="application/opensearchdescription+xml" rel="self" template="<%= it.siteUrl %>opensearch.xml" />
21
+ <moz:SearchForm><%= it.siteUrl %></moz:SearchForm>
22
+ </OpenSearchDescription>
23
+ `;
@@ -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
+ /// <reference types="react" />
8
+ export default function SearchBar(): JSX.Element;
@@ -4,7 +4,6 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
-
8
7
  import React, {useState, useRef, useCallback, useMemo} from 'react';
9
8
  import {createPortal} from 'react-dom';
10
9
  import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
@@ -12,59 +11,54 @@ import {useHistory} from '@docusaurus/router';
12
11
  import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
13
12
  import Link from '@docusaurus/Link';
14
13
  import Head from '@docusaurus/Head';
15
- import useSearchQuery from '@theme/hooks/useSearchQuery';
14
+ import {isRegexpStringMatch, useSearchPage} from '@docusaurus/theme-common';
16
15
  import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
17
- import useAlgoliaContextualFacetFilters from '@theme/hooks/useAlgoliaContextualFacetFilters';
18
- import {translate} from '@docusaurus/Translate';
19
- import styles from './styles.module.css';
20
-
16
+ import {useAlgoliaContextualFacetFilters} from '@docusaurus/theme-search-algolia/client';
17
+ import Translate, {translate} from '@docusaurus/Translate';
21
18
  let DocSearchModal = null;
22
-
23
19
  function Hit({hit, children}) {
24
20
  return <Link to={hit.url}>{children}</Link>;
25
21
  }
26
-
27
22
  function ResultsFooter({state, onClose}) {
28
- const {generateSearchPageLink} = useSearchQuery();
29
-
23
+ const {generateSearchPageLink} = useSearchPage();
30
24
  return (
31
25
  <Link to={generateSearchPageLink(state.query)} onClick={onClose}>
32
- See all {state.context.nbHits} results
26
+ <Translate
27
+ id="theme.SearchBar.seeAll"
28
+ values={{count: state.context.nbHits}}>
29
+ {'See all {count} results'}
30
+ </Translate>
33
31
  </Link>
34
32
  );
35
33
  }
36
-
37
- function DocSearch({contextualSearch, ...props}) {
34
+ function mergeFacetFilters(f1, f2) {
35
+ const normalize = (f) => (typeof f === 'string' ? [f] : f);
36
+ return [...normalize(f1), ...normalize(f2)];
37
+ }
38
+ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
38
39
  const {siteMetadata} = useDocusaurusContext();
39
-
40
40
  const contextualSearchFacetFilters = useAlgoliaContextualFacetFilters();
41
-
42
41
  const configFacetFilters = props.searchParameters?.facetFilters ?? [];
43
-
44
42
  const facetFilters = contextualSearch
45
43
  ? // Merge contextual search filters with config filters
46
- [...contextualSearchFacetFilters, ...configFacetFilters]
44
+ mergeFacetFilters(contextualSearchFacetFilters, configFacetFilters)
47
45
  : // ... or use config facetFilters
48
46
  configFacetFilters;
49
-
50
- // we let user override default searchParameters if he wants to
47
+ // We let user override default searchParameters if she wants to
51
48
  const searchParameters = {
52
49
  ...props.searchParameters,
53
50
  facetFilters,
54
51
  };
55
-
56
52
  const {withBaseUrl} = useBaseUrlUtils();
57
53
  const history = useHistory();
58
54
  const searchContainer = useRef(null);
59
55
  const searchButtonRef = useRef(null);
60
56
  const [isOpen, setIsOpen] = useState(false);
61
- const [initialQuery, setInitialQuery] = useState(null);
62
-
57
+ const [initialQuery, setInitialQuery] = useState(undefined);
63
58
  const importDocSearchModalIfNeeded = useCallback(() => {
64
59
  if (DocSearchModal) {
65
60
  return Promise.resolve();
66
61
  }
67
-
68
62
  return Promise.all([
69
63
  import('@docsearch/react/modal'),
70
64
  import('@docsearch/react/style'),
@@ -73,7 +67,6 @@ function DocSearch({contextualSearch, ...props}) {
73
67
  DocSearchModal = Modal;
74
68
  });
75
69
  }, []);
76
-
77
70
  const onOpen = useCallback(() => {
78
71
  importDocSearchModalIfNeeded().then(() => {
79
72
  searchContainer.current = document.createElement('div');
@@ -84,12 +77,10 @@ function DocSearch({contextualSearch, ...props}) {
84
77
  setIsOpen(true);
85
78
  });
86
79
  }, [importDocSearchModalIfNeeded, setIsOpen]);
87
-
88
80
  const onClose = useCallback(() => {
89
81
  setIsOpen(false);
90
- searchContainer.current.remove();
82
+ searchContainer.current?.remove();
91
83
  }, [setIsOpen]);
92
-
93
84
  const onInput = useCallback(
94
85
  (event) => {
95
86
  importDocSearchModalIfNeeded().then(() => {
@@ -99,45 +90,49 @@ function DocSearch({contextualSearch, ...props}) {
99
90
  },
100
91
  [importDocSearchModalIfNeeded, setIsOpen, setInitialQuery],
101
92
  );
102
-
103
93
  const navigator = useRef({
104
94
  navigate({itemUrl}) {
105
- history.push(itemUrl);
95
+ // Algolia results could contain URL's from other domains which cannot
96
+ // be served through history and should navigate with window.location
97
+ if (isRegexpStringMatch(externalUrlRegex, itemUrl)) {
98
+ window.location.href = itemUrl;
99
+ } else {
100
+ history.push(itemUrl);
101
+ }
106
102
  },
107
103
  }).current;
108
-
109
- const transformItems = useRef((items) => {
110
- return items.map((item) => {
104
+ const transformItems = useRef((items) =>
105
+ items.map((item) => {
106
+ // If Algolia contains a external domain, we should navigate without
107
+ // relative URL
108
+ if (isRegexpStringMatch(externalUrlRegex, item.url)) {
109
+ return item;
110
+ }
111
111
  // We transform the absolute URL into a relative URL.
112
- // Alternatively, we can use `new URL(item.url)` but it's not
113
- // supported in IE.
114
- const a = document.createElement('a');
115
- a.href = item.url;
116
-
112
+ const url = new URL(item.url);
117
113
  return {
118
114
  ...item,
119
- url: withBaseUrl(`${a.pathname}${a.hash}`),
115
+ url: withBaseUrl(`${url.pathname}${url.hash}`),
120
116
  };
121
- });
122
- }).current;
123
-
117
+ }),
118
+ ).current;
124
119
  const resultsFooterComponent = useMemo(
125
- () => (footerProps) => <ResultsFooter {...footerProps} onClose={onClose} />,
120
+ () =>
121
+ // eslint-disable-next-line react/no-unstable-nested-components
122
+ (footerProps) =>
123
+ <ResultsFooter {...footerProps} onClose={onClose} />,
126
124
  [onClose],
127
125
  );
128
-
129
126
  const transformSearchClient = useCallback(
130
127
  (searchClient) => {
131
128
  searchClient.addAlgoliaAgent(
132
129
  'docusaurus',
133
130
  siteMetadata.docusaurusVersion,
134
131
  );
135
-
136
132
  return searchClient;
137
133
  },
138
134
  [siteMetadata.docusaurusVersion],
139
135
  );
140
-
141
136
  useDocSearchKeyboardEvents({
142
137
  isOpen,
143
138
  onOpen,
@@ -145,13 +140,11 @@ function DocSearch({contextualSearch, ...props}) {
145
140
  onInput,
146
141
  searchButtonRef,
147
142
  });
148
-
149
143
  const translatedSearchLabel = translate({
150
144
  id: 'theme.SearchBar.label',
151
145
  message: 'Search',
152
146
  description: 'The ARIA label and placeholder for search button',
153
147
  });
154
-
155
148
  return (
156
149
  <>
157
150
  <Head>
@@ -165,21 +158,21 @@ function DocSearch({contextualSearch, ...props}) {
165
158
  />
166
159
  </Head>
167
160
 
168
- <div className={styles.searchBox}>
169
- <DocSearchButton
170
- onTouchStart={importDocSearchModalIfNeeded}
171
- onFocus={importDocSearchModalIfNeeded}
172
- onMouseOver={importDocSearchModalIfNeeded}
173
- onClick={onOpen}
174
- ref={searchButtonRef}
175
- translations={{
176
- buttonText: translatedSearchLabel,
177
- buttonAriaLabel: translatedSearchLabel,
178
- }}
179
- />
180
- </div>
161
+ <DocSearchButton
162
+ onTouchStart={importDocSearchModalIfNeeded}
163
+ onFocus={importDocSearchModalIfNeeded}
164
+ onMouseOver={importDocSearchModalIfNeeded}
165
+ onClick={onOpen}
166
+ ref={searchButtonRef}
167
+ translations={{
168
+ buttonText: translatedSearchLabel,
169
+ buttonAriaLabel: translatedSearchLabel,
170
+ }}
171
+ />
181
172
 
182
173
  {isOpen &&
174
+ DocSearchModal &&
175
+ searchContainer.current &&
183
176
  createPortal(
184
177
  <DocSearchModal
185
178
  onClose={onClose}
@@ -188,8 +181,10 @@ function DocSearch({contextualSearch, ...props}) {
188
181
  navigator={navigator}
189
182
  transformItems={transformItems}
190
183
  hitComponent={Hit}
191
- resultsFooterComponent={resultsFooterComponent}
192
184
  transformSearchClient={transformSearchClient}
185
+ {...(props.searchPagePath && {
186
+ resultsFooterComponent,
187
+ })}
193
188
  {...props}
194
189
  searchParameters={searchParameters}
195
190
  />,
@@ -198,10 +193,7 @@ function DocSearch({contextualSearch, ...props}) {
198
193
  </>
199
194
  );
200
195
  }
201
-
202
- function SearchBar() {
196
+ export default function SearchBar() {
203
197
  const {siteConfig} = useDocusaurusContext();
204
198
  return <DocSearch {...siteConfig.themeConfig.algolia} />;
205
199
  }
206
-
207
- export default SearchBar;
@@ -0,0 +1,21 @@
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
+ :root {
9
+ --docsearch-primary-color: var(--ifm-color-primary);
10
+ --docsearch-text-color: var(--ifm-font-color-base);
11
+ }
12
+
13
+ .DocSearch-Button {
14
+ margin: 0;
15
+ transition: all var(--ifm-transition-fast)
16
+ var(--ifm-transition-timing-default);
17
+ }
18
+
19
+ .DocSearch-Container {
20
+ z-index: calc(var(--ifm-z-index-fixed) + 1);
21
+ }
@@ -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
+ /// <reference types="react" />
8
+ export default function SearchPage(): JSX.Element;