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

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 (37) 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 +51 -60
  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 +100 -131
  14. package/lib/theme/SearchPage/styles.module.css +119 -0
  15. package/lib/theme/SearchTranslations/index.d.ts +11 -0
  16. package/lib/theme/SearchTranslations/index.js +167 -0
  17. package/lib/validateThemeConfig.d.ts +15 -0
  18. package/lib/validateThemeConfig.js +44 -0
  19. package/package.json +43 -14
  20. package/src/client/index.ts +8 -0
  21. package/src/{theme/hooks/useAlgoliaContextualFacetFilters.js → client/useAlgoliaContextualFacetFilters.ts} +3 -3
  22. package/src/deps.d.ts +20 -0
  23. package/src/index.ts +116 -0
  24. package/src/templates/{opensearch.js → opensearch.ts} +7 -5
  25. package/src/theme/SearchBar/index.tsx +271 -0
  26. package/src/theme/SearchBar/styles.css +1 -0
  27. package/src/theme/SearchPage/index.tsx +535 -0
  28. package/src/theme/SearchPage/styles.module.css +15 -34
  29. package/src/theme/SearchTranslations/index.ts +172 -0
  30. package/src/theme-search-algolia.d.ts +42 -0
  31. package/src/types.d.ts +10 -0
  32. package/src/validateThemeConfig.ts +53 -0
  33. package/src/__tests__/validateThemeConfig.test.js +0 -121
  34. package/src/index.js +0 -92
  35. package/src/theme/SearchMetadatas/index.js +0 -25
  36. package/src/theme/hooks/useSearchQuery.js +0 -44
  37. 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,58 +11,56 @@ 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} from '@docusaurus/theme-common';
15
+ import {useSearchPage} from '@docusaurus/theme-common/internal';
16
16
  import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
17
- import useAlgoliaContextualFacetFilters from '@theme/hooks/useAlgoliaContextualFacetFilters';
18
- import {translate} from '@docusaurus/Translate';
19
-
17
+ import {useAlgoliaContextualFacetFilters} from '@docusaurus/theme-search-algolia/client';
18
+ import Translate from '@docusaurus/Translate';
19
+ import translations from '@theme/SearchTranslations';
20
20
  let DocSearchModal = null;
21
-
22
21
  function Hit({hit, children}) {
23
22
  return <Link to={hit.url}>{children}</Link>;
24
23
  }
25
-
26
24
  function ResultsFooter({state, onClose}) {
27
- const {generateSearchPageLink} = useSearchQuery();
28
-
25
+ const {generateSearchPageLink} = useSearchPage();
29
26
  return (
30
27
  <Link to={generateSearchPageLink(state.query)} onClick={onClose}>
31
- See all {state.context.nbHits} results
28
+ <Translate
29
+ id="theme.SearchBar.seeAll"
30
+ values={{count: state.context.nbHits}}>
31
+ {'See all {count} results'}
32
+ </Translate>
32
33
  </Link>
33
34
  );
34
35
  }
35
-
36
- function DocSearch({contextualSearch, ...props}) {
36
+ function mergeFacetFilters(f1, f2) {
37
+ const normalize = (f) => (typeof f === 'string' ? [f] : f);
38
+ return [...normalize(f1), ...normalize(f2)];
39
+ }
40
+ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
37
41
  const {siteMetadata} = useDocusaurusContext();
38
-
39
42
  const contextualSearchFacetFilters = useAlgoliaContextualFacetFilters();
40
-
41
43
  const configFacetFilters = props.searchParameters?.facetFilters ?? [];
42
-
43
44
  const facetFilters = contextualSearch
44
45
  ? // Merge contextual search filters with config filters
45
- [...contextualSearchFacetFilters, ...configFacetFilters]
46
+ mergeFacetFilters(contextualSearchFacetFilters, configFacetFilters)
46
47
  : // ... or use config facetFilters
47
48
  configFacetFilters;
48
-
49
- // we let user override default searchParameters if he wants to
49
+ // We let user override default searchParameters if she wants to
50
50
  const searchParameters = {
51
51
  ...props.searchParameters,
52
52
  facetFilters,
53
53
  };
54
-
55
54
  const {withBaseUrl} = useBaseUrlUtils();
56
55
  const history = useHistory();
57
56
  const searchContainer = useRef(null);
58
57
  const searchButtonRef = useRef(null);
59
58
  const [isOpen, setIsOpen] = useState(false);
60
- const [initialQuery, setInitialQuery] = useState(null);
61
-
59
+ const [initialQuery, setInitialQuery] = useState(undefined);
62
60
  const importDocSearchModalIfNeeded = useCallback(() => {
63
61
  if (DocSearchModal) {
64
62
  return Promise.resolve();
65
63
  }
66
-
67
64
  return Promise.all([
68
65
  import('@docsearch/react/modal'),
69
66
  import('@docsearch/react/style'),
@@ -72,7 +69,6 @@ function DocSearch({contextualSearch, ...props}) {
72
69
  DocSearchModal = Modal;
73
70
  });
74
71
  }, []);
75
-
76
72
  const onOpen = useCallback(() => {
77
73
  importDocSearchModalIfNeeded().then(() => {
78
74
  searchContainer.current = document.createElement('div');
@@ -83,12 +79,10 @@ function DocSearch({contextualSearch, ...props}) {
83
79
  setIsOpen(true);
84
80
  });
85
81
  }, [importDocSearchModalIfNeeded, setIsOpen]);
86
-
87
82
  const onClose = useCallback(() => {
88
83
  setIsOpen(false);
89
- searchContainer.current.remove();
84
+ searchContainer.current?.remove();
90
85
  }, [setIsOpen]);
91
-
92
86
  const onInput = useCallback(
93
87
  (event) => {
94
88
  importDocSearchModalIfNeeded().then(() => {
@@ -98,45 +92,49 @@ function DocSearch({contextualSearch, ...props}) {
98
92
  },
99
93
  [importDocSearchModalIfNeeded, setIsOpen, setInitialQuery],
100
94
  );
101
-
102
95
  const navigator = useRef({
103
96
  navigate({itemUrl}) {
104
- history.push(itemUrl);
97
+ // Algolia results could contain URL's from other domains which cannot
98
+ // be served through history and should navigate with window.location
99
+ if (isRegexpStringMatch(externalUrlRegex, itemUrl)) {
100
+ window.location.href = itemUrl;
101
+ } else {
102
+ history.push(itemUrl);
103
+ }
105
104
  },
106
105
  }).current;
107
-
108
- const transformItems = useRef((items) => {
109
- return items.map((item) => {
106
+ const transformItems = useRef((items) =>
107
+ items.map((item) => {
108
+ // If Algolia contains a external domain, we should navigate without
109
+ // relative URL
110
+ if (isRegexpStringMatch(externalUrlRegex, item.url)) {
111
+ return item;
112
+ }
110
113
  // We transform the absolute URL into a relative URL.
111
- // Alternatively, we can use `new URL(item.url)` but it's not
112
- // supported in IE.
113
- const a = document.createElement('a');
114
- a.href = item.url;
115
-
114
+ const url = new URL(item.url);
116
115
  return {
117
116
  ...item,
118
- url: withBaseUrl(`${a.pathname}${a.hash}`),
117
+ url: withBaseUrl(`${url.pathname}${url.hash}`),
119
118
  };
120
- });
121
- }).current;
122
-
119
+ }),
120
+ ).current;
123
121
  const resultsFooterComponent = useMemo(
124
- () => (footerProps) => <ResultsFooter {...footerProps} onClose={onClose} />,
122
+ () =>
123
+ // eslint-disable-next-line react/no-unstable-nested-components
124
+ (footerProps) =>
125
+ <ResultsFooter {...footerProps} onClose={onClose} />,
125
126
  [onClose],
126
127
  );
127
-
128
128
  const transformSearchClient = useCallback(
129
129
  (searchClient) => {
130
130
  searchClient.addAlgoliaAgent(
131
131
  'docusaurus',
132
132
  siteMetadata.docusaurusVersion,
133
133
  );
134
-
135
134
  return searchClient;
136
135
  },
137
136
  [siteMetadata.docusaurusVersion],
138
137
  );
139
-
140
138
  useDocSearchKeyboardEvents({
141
139
  isOpen,
142
140
  onOpen,
@@ -144,13 +142,6 @@ function DocSearch({contextualSearch, ...props}) {
144
142
  onInput,
145
143
  searchButtonRef,
146
144
  });
147
-
148
- const translatedSearchLabel = translate({
149
- id: 'theme.SearchBar.label',
150
- message: 'Search',
151
- description: 'The ARIA label and placeholder for search button',
152
- });
153
-
154
145
  return (
155
146
  <>
156
147
  <Head>
@@ -170,13 +161,12 @@ function DocSearch({contextualSearch, ...props}) {
170
161
  onMouseOver={importDocSearchModalIfNeeded}
171
162
  onClick={onOpen}
172
163
  ref={searchButtonRef}
173
- translations={{
174
- buttonText: translatedSearchLabel,
175
- buttonAriaLabel: translatedSearchLabel,
176
- }}
164
+ translations={translations.button}
177
165
  />
178
166
 
179
167
  {isOpen &&
168
+ DocSearchModal &&
169
+ searchContainer.current &&
180
170
  createPortal(
181
171
  <DocSearchModal
182
172
  onClose={onClose}
@@ -185,20 +175,21 @@ function DocSearch({contextualSearch, ...props}) {
185
175
  navigator={navigator}
186
176
  transformItems={transformItems}
187
177
  hitComponent={Hit}
188
- resultsFooterComponent={resultsFooterComponent}
189
178
  transformSearchClient={transformSearchClient}
179
+ {...(props.searchPagePath && {
180
+ resultsFooterComponent,
181
+ })}
190
182
  {...props}
191
183
  searchParameters={searchParameters}
184
+ placeholder={translations.placeholder}
185
+ translations={translations.modal}
192
186
  />,
193
187
  searchContainer.current,
194
188
  )}
195
189
  </>
196
190
  );
197
191
  }
198
-
199
- function SearchBar() {
192
+ export default function SearchBar() {
200
193
  const {siteConfig} = useDocusaurusContext();
201
194
  return <DocSearch {...siteConfig.themeConfig.algolia} />;
202
195
  }
203
-
204
- 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;