@docusaurus/theme-search-algolia 2.0.0-beta.15d451942 → 2.0.0-beta.16

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 +109 -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 +56 -51
  11. package/lib/theme/SearchBar/styles.css +21 -0
  12. package/lib/theme/SearchBar/styles.module.css +20 -0
  13. package/lib/theme/SearchPage/index.d.ts +8 -0
  14. package/{src → lib}/theme/SearchPage/index.js +85 -126
  15. package/lib/theme/SearchPage/styles.module.css +119 -0
  16. package/lib/validateThemeConfig.d.ts +18 -0
  17. package/lib/validateThemeConfig.js +50 -0
  18. package/package.json +33 -13
  19. package/src/client/index.ts +8 -0
  20. package/src/{theme/hooks/useAlgoliaContextualFacetFilters.js → client/useAlgoliaContextualFacetFilters.ts} +3 -3
  21. package/src/deps.d.ts +10 -0
  22. package/src/index.ts +116 -0
  23. package/src/templates/{opensearch.js → opensearch.ts} +7 -5
  24. package/src/theme/SearchBar/index.tsx +276 -0
  25. package/src/theme/SearchPage/index.tsx +518 -0
  26. package/src/theme/SearchPage/styles.module.css +15 -34
  27. package/src/theme-search-algolia.d.ts +35 -0
  28. package/src/types.d.ts +10 -0
  29. package/src/validateThemeConfig.ts +53 -0
  30. package/src/__tests__/validateThemeConfig.test.js +0 -121
  31. package/src/index.js +0 -92
  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,109 @@
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 = (0, tslib_1.__importDefault)(require('path'));
12
+ const fs_extra_1 = (0, tslib_1.__importDefault)(require('fs-extra'));
13
+ const eta_1 = require('eta');
14
+ const utils_1 = require('@docusaurus/utils');
15
+ const theme_translations_1 = require('@docusaurus/theme-translations');
16
+ const logger_1 = (0, tslib_1.__importDefault)(require('@docusaurus/logger'));
17
+ const opensearch_1 = (0, tslib_1.__importDefault)(
18
+ require('./templates/opensearch'),
19
+ );
20
+ const lodash_1 = (0, tslib_1.__importDefault)(require('lodash'));
21
+ const getCompiledOpenSearchTemplate = lodash_1.default.memoize(() =>
22
+ (0, eta_1.compile)(opensearch_1.default.trim()),
23
+ );
24
+ function renderOpenSearchTemplate(data) {
25
+ const compiled = getCompiledOpenSearchTemplate();
26
+ return compiled(data, eta_1.defaultConfig);
27
+ }
28
+ const OPEN_SEARCH_FILENAME = 'opensearch.xml';
29
+ function themeSearchAlgolia(context) {
30
+ const {
31
+ baseUrl,
32
+ siteConfig: {title, url, favicon, themeConfig},
33
+ i18n: {currentLocale},
34
+ } = context;
35
+ const {
36
+ algolia: {searchPagePath},
37
+ } = themeConfig;
38
+ return {
39
+ name: 'docusaurus-theme-search-algolia',
40
+ getThemePath() {
41
+ return path_1.default.resolve(__dirname, '../lib/theme');
42
+ },
43
+ getTypeScriptThemePath() {
44
+ return path_1.default.resolve(__dirname, '../src/theme');
45
+ },
46
+ getDefaultCodeTranslationMessages() {
47
+ return (0, theme_translations_1.readDefaultCodeTranslationMessages)({
48
+ locale: currentLocale,
49
+ name: 'theme-search-algolia',
50
+ });
51
+ },
52
+ async contentLoaded({actions: {addRoute}}) {
53
+ if (searchPagePath) {
54
+ addRoute({
55
+ path: (0, utils_1.normalizeUrl)([baseUrl, searchPagePath]),
56
+ component: '@theme/SearchPage',
57
+ exact: true,
58
+ });
59
+ }
60
+ },
61
+ async postBuild({outDir}) {
62
+ if (searchPagePath) {
63
+ const siteUrl = (0, utils_1.normalizeUrl)([url, baseUrl]);
64
+ try {
65
+ await fs_extra_1.default.writeFile(
66
+ path_1.default.join(outDir, OPEN_SEARCH_FILENAME),
67
+ renderOpenSearchTemplate({
68
+ title,
69
+ siteUrl,
70
+ searchUrl: (0, utils_1.normalizeUrl)([siteUrl, searchPagePath]),
71
+ faviconUrl: favicon
72
+ ? (0, utils_1.normalizeUrl)([siteUrl, favicon])
73
+ : null,
74
+ }),
75
+ );
76
+ } catch (err) {
77
+ logger_1.default.error('Generating OpenSearch file failed.');
78
+ throw err;
79
+ }
80
+ }
81
+ },
82
+ injectHtmlTags() {
83
+ if (!searchPagePath) {
84
+ return {};
85
+ }
86
+ return {
87
+ headTags: [
88
+ {
89
+ tagName: 'link',
90
+ attributes: {
91
+ rel: 'search',
92
+ type: 'application/opensearchdescription+xml',
93
+ title,
94
+ href: (0, utils_1.normalizeUrl)([baseUrl, OPEN_SEARCH_FILENAME]),
95
+ },
96
+ },
97
+ ],
98
+ };
99
+ },
100
+ };
101
+ }
102
+ exports.default = themeSearchAlgolia;
103
+ var validateThemeConfig_1 = require('./validateThemeConfig');
104
+ Object.defineProperty(exports, 'validateThemeConfig', {
105
+ enumerable: true,
106
+ get: function () {
107
+ return validateThemeConfig_1.validateThemeConfig;
108
+ },
109
+ });
@@ -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,62 @@ 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';
16
+ import {useAlgoliaContextualFacetFilters} from '@docusaurus/theme-search-algolia/client';
17
+ import Translate, {translate} from '@docusaurus/Translate';
19
18
  import styles from './styles.module.css';
20
-
21
19
  let DocSearchModal = null;
22
-
23
20
  function Hit({hit, children}) {
24
21
  return <Link to={hit.url}>{children}</Link>;
25
22
  }
26
-
27
23
  function ResultsFooter({state, onClose}) {
28
- const {generateSearchPageLink} = useSearchQuery();
29
-
24
+ const {generateSearchPageLink} = useSearchPage();
30
25
  return (
31
26
  <Link to={generateSearchPageLink(state.query)} onClick={onClose}>
32
- See all {state.context.nbHits} results
27
+ <Translate
28
+ id="theme.SearchBar.seeAll"
29
+ values={{count: state.context.nbHits}}>
30
+ {'See all {count} results'}
31
+ </Translate>
33
32
  </Link>
34
33
  );
35
34
  }
36
-
37
- function DocSearch({contextualSearch, ...props}) {
35
+ function mergeFacetFilters(f1, f2) {
36
+ const normalize = (f) => (typeof f === 'string' ? [f] : f);
37
+ return [...normalize(f1), ...normalize(f2)];
38
+ }
39
+ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
40
+ var _a, _b;
38
41
  const {siteMetadata} = useDocusaurusContext();
39
-
40
42
  const contextualSearchFacetFilters = useAlgoliaContextualFacetFilters();
41
-
42
- const configFacetFilters = props.searchParameters?.facetFilters ?? [];
43
-
43
+ const configFacetFilters =
44
+ (_b =
45
+ (_a = props.searchParameters) === null || _a === void 0
46
+ ? void 0
47
+ : _a.facetFilters) !== null && _b !== void 0
48
+ ? _b
49
+ : [];
44
50
  const facetFilters = contextualSearch
45
51
  ? // Merge contextual search filters with config filters
46
- [...contextualSearchFacetFilters, ...configFacetFilters]
52
+ mergeFacetFilters(contextualSearchFacetFilters, configFacetFilters)
47
53
  : // ... or use config facetFilters
48
54
  configFacetFilters;
49
-
50
55
  // we let user override default searchParameters if he wants to
51
56
  const searchParameters = {
52
57
  ...props.searchParameters,
53
58
  facetFilters,
54
59
  };
55
-
56
60
  const {withBaseUrl} = useBaseUrlUtils();
57
61
  const history = useHistory();
58
62
  const searchContainer = useRef(null);
59
63
  const searchButtonRef = useRef(null);
60
64
  const [isOpen, setIsOpen] = useState(false);
61
- const [initialQuery, setInitialQuery] = useState(null);
62
-
65
+ const [initialQuery, setInitialQuery] = useState(undefined);
63
66
  const importDocSearchModalIfNeeded = useCallback(() => {
64
67
  if (DocSearchModal) {
65
68
  return Promise.resolve();
66
69
  }
67
-
68
70
  return Promise.all([
69
71
  import('@docsearch/react/modal'),
70
72
  import('@docsearch/react/style'),
@@ -73,7 +75,6 @@ function DocSearch({contextualSearch, ...props}) {
73
75
  DocSearchModal = Modal;
74
76
  });
75
77
  }, []);
76
-
77
78
  const onOpen = useCallback(() => {
78
79
  importDocSearchModalIfNeeded().then(() => {
79
80
  searchContainer.current = document.createElement('div');
@@ -84,12 +85,13 @@ function DocSearch({contextualSearch, ...props}) {
84
85
  setIsOpen(true);
85
86
  });
86
87
  }, [importDocSearchModalIfNeeded, setIsOpen]);
87
-
88
88
  const onClose = useCallback(() => {
89
+ var _a;
89
90
  setIsOpen(false);
90
- searchContainer.current.remove();
91
+ (_a = searchContainer.current) === null || _a === void 0
92
+ ? void 0
93
+ : _a.remove();
91
94
  }, [setIsOpen]);
92
-
93
95
  const onInput = useCallback(
94
96
  (event) => {
95
97
  importDocSearchModalIfNeeded().then(() => {
@@ -99,45 +101,49 @@ function DocSearch({contextualSearch, ...props}) {
99
101
  },
100
102
  [importDocSearchModalIfNeeded, setIsOpen, setInitialQuery],
101
103
  );
102
-
103
104
  const navigator = useRef({
104
105
  navigate({itemUrl}) {
105
- history.push(itemUrl);
106
+ // Algolia results could contain URL's from other domains which cannot
107
+ // be served through history and should navigate with window.location
108
+ if (isRegexpStringMatch(externalUrlRegex, itemUrl)) {
109
+ window.location.href = itemUrl;
110
+ } else {
111
+ history.push(itemUrl);
112
+ }
106
113
  },
107
114
  }).current;
108
-
109
- const transformItems = useRef((items) => {
110
- return items.map((item) => {
115
+ const transformItems = useRef((items) =>
116
+ items.map((item) => {
117
+ // If Algolia contains a external domain, we should navigate without
118
+ // relative URL
119
+ if (isRegexpStringMatch(externalUrlRegex, item.url)) {
120
+ return item;
121
+ }
111
122
  // 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
-
123
+ const url = new URL(item.url);
117
124
  return {
118
125
  ...item,
119
- url: withBaseUrl(`${a.pathname}${a.hash}`),
126
+ url: withBaseUrl(`${url.pathname}${url.hash}`),
120
127
  };
121
- });
122
- }).current;
123
-
128
+ }),
129
+ ).current;
124
130
  const resultsFooterComponent = useMemo(
125
- () => (footerProps) => <ResultsFooter {...footerProps} onClose={onClose} />,
131
+ () =>
132
+ // eslint-disable-next-line react/no-unstable-nested-components
133
+ (footerProps) =>
134
+ <ResultsFooter {...footerProps} onClose={onClose} />,
126
135
  [onClose],
127
136
  );
128
-
129
137
  const transformSearchClient = useCallback(
130
138
  (searchClient) => {
131
139
  searchClient.addAlgoliaAgent(
132
140
  'docusaurus',
133
141
  siteMetadata.docusaurusVersion,
134
142
  );
135
-
136
143
  return searchClient;
137
144
  },
138
145
  [siteMetadata.docusaurusVersion],
139
146
  );
140
-
141
147
  useDocSearchKeyboardEvents({
142
148
  isOpen,
143
149
  onOpen,
@@ -145,13 +151,11 @@ function DocSearch({contextualSearch, ...props}) {
145
151
  onInput,
146
152
  searchButtonRef,
147
153
  });
148
-
149
154
  const translatedSearchLabel = translate({
150
155
  id: 'theme.SearchBar.label',
151
156
  message: 'Search',
152
157
  description: 'The ARIA label and placeholder for search button',
153
158
  });
154
-
155
159
  return (
156
160
  <>
157
161
  <Head>
@@ -180,6 +184,8 @@ function DocSearch({contextualSearch, ...props}) {
180
184
  </div>
181
185
 
182
186
  {isOpen &&
187
+ DocSearchModal &&
188
+ searchContainer.current &&
183
189
  createPortal(
184
190
  <DocSearchModal
185
191
  onClose={onClose}
@@ -188,8 +194,10 @@ function DocSearch({contextualSearch, ...props}) {
188
194
  navigator={navigator}
189
195
  transformItems={transformItems}
190
196
  hitComponent={Hit}
191
- resultsFooterComponent={resultsFooterComponent}
192
197
  transformSearchClient={transformSearchClient}
198
+ {...(props.searchPagePath && {
199
+ resultsFooterComponent,
200
+ })}
193
201
  {...props}
194
202
  searchParameters={searchParameters}
195
203
  />,
@@ -198,10 +206,7 @@ function DocSearch({contextualSearch, ...props}) {
198
206
  </>
199
207
  );
200
208
  }
201
-
202
- function SearchBar() {
209
+ export default function SearchBar() {
203
210
  const {siteConfig} = useDocusaurusContext();
204
211
  return <DocSearch {...siteConfig.themeConfig.algolia} />;
205
212
  }
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,20 @@
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
+ @media (max-width: 996px) {
9
+ .searchBox {
10
+ position: absolute;
11
+ right: var(--ifm-navbar-padding-horizontal);
12
+ }
13
+ }
14
+
15
+ @media (min-width: 997px) {
16
+ .searchBox {
17
+ padding: var(--ifm-navbar-item-padding-vertical)
18
+ var(--ifm-navbar-item-padding-horizontal);
19
+ }
20
+ }
@@ -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;