diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap
index ad14dbad2062..995f2f5c63db 100644
--- a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap
+++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap
@@ -302,6 +302,7 @@ Object {
\\"version\\": \\"current\\"
}",
"version-current-metadata-prop-751.json": "{
+ \\"pluginId\\": \\"default\\",
\\"version\\": \\"current\\",
\\"label\\": \\"Next\\",
\\"isLast\\": true,
@@ -640,6 +641,7 @@ Object {
\\"sidebar\\": \\"version-1.0.0/community\\"
}",
"version-1-0-0-metadata-prop-608.json": "{
+ \\"pluginId\\": \\"community\\",
\\"version\\": \\"1.0.0\\",
\\"label\\": \\"1.0.0\\",
\\"isLast\\": true,
@@ -657,6 +659,7 @@ Object {
}
}",
"version-current-metadata-prop-751.json": "{
+ \\"pluginId\\": \\"community\\",
\\"version\\": \\"current\\",
\\"label\\": \\"Next\\",
\\"isLast\\": false,
@@ -1102,6 +1105,7 @@ Object {
\\"version\\": \\"withSlugs\\"
}",
"version-1-0-0-metadata-prop-608.json": "{
+ \\"pluginId\\": \\"default\\",
\\"version\\": \\"1.0.0\\",
\\"label\\": \\"1.0.0\\",
\\"isLast\\": false,
@@ -1145,6 +1149,7 @@ Object {
}
}",
"version-1-0-1-metadata-prop-e87.json": "{
+ \\"pluginId\\": \\"default\\",
\\"version\\": \\"1.0.1\\",
\\"label\\": \\"1.0.1\\",
\\"isLast\\": true,
@@ -1182,6 +1187,7 @@ Object {
}
}",
"version-current-metadata-prop-751.json": "{
+ \\"pluginId\\": \\"default\\",
\\"version\\": \\"current\\",
\\"label\\": \\"Next\\",
\\"isLast\\": false,
@@ -1219,6 +1225,7 @@ Object {
}
}",
"version-with-slugs-metadata-prop-2bf.json": "{
+ \\"pluginId\\": \\"default\\",
\\"version\\": \\"withSlugs\\",
\\"label\\": \\"withSlugs\\",
\\"isLast\\": false,
diff --git a/packages/docusaurus-plugin-content-docs/src/index.ts b/packages/docusaurus-plugin-content-docs/src/index.ts
index 08b95a73ee2a..794527817937 100644
--- a/packages/docusaurus-plugin-content-docs/src/index.ts
+++ b/packages/docusaurus-plugin-content-docs/src/index.ts
@@ -267,7 +267,11 @@ export default function pluginContentDocs(
`${docuHash(
`version-${loadedVersion.versionName}-metadata-prop`,
)}.json`,
- JSON.stringify(toVersionMetadataProp(loadedVersion), null, 2),
+ JSON.stringify(
+ toVersionMetadataProp(pluginId, loadedVersion),
+ null,
+ 2,
+ ),
);
addRoute({
diff --git a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts
index d4c8edd112cb..77d91c9008e2 100644
--- a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts
+++ b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts
@@ -13,6 +13,7 @@ declare module '@docusaurus/plugin-content-docs-types' {
};
export type PropVersionMetadata = {
+ pluginId: string;
version: string;
label: string;
isLast: boolean;
diff --git a/packages/docusaurus-plugin-content-docs/src/props.ts b/packages/docusaurus-plugin-content-docs/src/props.ts
index 5ff9bdb2eba6..f1d6914ef710 100644
--- a/packages/docusaurus-plugin-content-docs/src/props.ts
+++ b/packages/docusaurus-plugin-content-docs/src/props.ts
@@ -62,9 +62,11 @@ Available document ids=
}
export function toVersionMetadataProp(
+ pluginId: string,
loadedVersion: LoadedVersion,
): PropVersionMetadata {
return {
+ pluginId,
version: loadedVersion.versionName,
label: loadedVersion.versionLabel,
isLast: loadedVersion.isLast,
diff --git a/packages/docusaurus-plugin-content-docs/src/theme/hooks/useDocs.ts b/packages/docusaurus-plugin-content-docs/src/theme/hooks/useDocs.ts
index bbf3cff1e81b..6e14f3106c84 100644
--- a/packages/docusaurus-plugin-content-docs/src/theme/hooks/useDocs.ts
+++ b/packages/docusaurus-plugin-content-docs/src/theme/hooks/useDocs.ts
@@ -19,6 +19,7 @@ import {
getActiveDocContext,
getDocVersionSuggestions,
GetActivePluginOptions,
+ ActivePlugin,
} from '../../client/docsClientUtils';
export const useAllDocsData = (): Record =>
@@ -33,6 +34,23 @@ export const useActivePlugin = (options: GetActivePluginOptions = {}) => {
return getActivePlugin(data, pathname, options);
};
+export const useActivePluginAndVersion = (
+ options: GetActivePluginOptions = {},
+):
+ | undefined
+ | {activePlugin: ActivePlugin; activeVersion: GlobalVersion | undefined} => {
+ const activePlugin = useActivePlugin(options);
+ const {pathname} = useLocation();
+ if (activePlugin) {
+ const activeVersion = getActiveVersion(activePlugin.pluginData, pathname);
+ return {
+ activePlugin,
+ activeVersion,
+ };
+ }
+ return undefined;
+};
+
// versions are returned ordered (most recent first)
export const useVersions = (pluginId: string | undefined): GlobalVersion[] => {
const data = useDocsData(pluginId);
diff --git a/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx
index bc2a44f64ddf..8d122c58a24e 100644
--- a/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx
@@ -18,9 +18,9 @@ import NotFound from '@theme/NotFound';
import type {DocumentRoute} from '@theme/DocItem';
import type {Props} from '@theme/DocPage';
import {matchPath} from '@docusaurus/router';
-import Head from '@docusaurus/Head';
import styles from './styles.module.css';
+import {docVersionSearchTag} from '../../utils/searchUtils';
type DocPageContentProps = {
readonly currentDocRoute: DocumentRoute;
@@ -28,66 +28,44 @@ type DocPageContentProps = {
readonly children: ReactNode;
};
-// This theme is not coupled to Algolia, but can we do something else?
-// Note the last version is also indexed with "last", to avoid breaking search on new releases
-// See https://github.com/facebook/docusaurus/issues/3391
-function DocSearchVersionHeader({
- version,
- isLast,
-}: {
- version: string;
- isLast: boolean;
-}) {
- const versions = isLast ? [version, 'latest'] : [version];
- return (
-
-
-
- );
-}
-
function DocPageContent({
currentDocRoute,
versionMetadata,
children,
}: DocPageContentProps): JSX.Element {
const {siteConfig, isClient} = useDocusaurusContext();
- const {permalinkToSidebar, docsSidebars, version, isLast} = versionMetadata;
+ const {pluginId, permalinkToSidebar, docsSidebars, version} = versionMetadata;
const sidebarName = permalinkToSidebar[currentDocRoute.path];
const sidebar = docsSidebars[sidebarName];
return (
- <>
-
-
-
- {sidebar && (
-
-
-
- )}
-
- {children}
-
-
-
- >
+
+
+ {sidebar && (
+
+
+
+ )}
+
+ {children}
+
+
+
);
}
diff --git a/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx b/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx
index 5ac0799497a0..108f03e47d77 100644
--- a/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx
@@ -7,99 +7,26 @@
import React from 'react';
import clsx from 'clsx';
-import Head from '@docusaurus/Head';
-import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
-import useBaseUrl from '@docusaurus/useBaseUrl';
-
-import ThemeProvider from '@theme/ThemeProvider';
-import UserPreferencesProvider from '@theme/UserPreferencesProvider';
import AnnouncementBar from '@theme/AnnouncementBar';
import Navbar from '@theme/Navbar';
import Footer from '@theme/Footer';
+import LayoutProviders from '@theme/LayoutProviders';
+import LayoutHead from '@theme/LayoutHead';
import type {Props} from '@theme/Layout';
-
import './styles.css';
-import DocsPreferredVersionContextProvider from '../../utils/docsPreferredVersion/DocsPreferredVersionProvider';
-
-function Providers({children}) {
- return (
-
-
-
- {children}
-
-
-
- );
-}
function Layout(props: Props): JSX.Element {
- const {siteConfig} = useDocusaurusContext();
- const {
- favicon,
- title: siteTitle,
- themeConfig: {image: defaultImage, metadatas},
- url: siteUrl,
- titleDelimiter,
- } = siteConfig;
- const {
- children,
- title,
- noFooter,
- description,
- image,
- keywords,
- permalink,
- wrapperClassName,
- } = props;
- const metaTitle = title
- ? `${title} ${titleDelimiter} ${siteTitle}`
- : siteTitle;
- const metaImage = image || defaultImage;
- const metaImageUrl = useBaseUrl(metaImage, {absolute: true});
- const faviconUrl = useBaseUrl(favicon);
+ const {children, noFooter, wrapperClassName} = props;
return (
-
-
- {/* TODO: Do not assume that it is in english language */}
-
- {metaTitle && {metaTitle}}
- {metaTitle && }
- {favicon && }
- {description && }
- {description && (
-
- )}
- {keywords && keywords.length && (
-
- )}
- {metaImage && }
- {metaImage && }
- {metaImage && (
-
- )}
- {permalink && }
- {permalink && }
-
-
-
- element here,
- // as it allows react-helmet to override values set in previous
- // ie we can override default metadatas such as "twitter:card"
- // In same Head, the same meta would appear twice instead of overriding
- // See react-helmet doc
- >
- {metadatas.map((metadata, i) => (
-
- ))}
-
+
+
{children}
+
{!noFooter && }
-
+
);
}
diff --git a/packages/docusaurus-theme-classic/src/theme/LayoutHead/index.tsx b/packages/docusaurus-theme-classic/src/theme/LayoutHead/index.tsx
new file mode 100644
index 000000000000..da5bf3aefb7f
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/LayoutHead/index.tsx
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import React from 'react';
+import Head from '@docusaurus/Head';
+import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import type {Props} from '@theme/Layout';
+import SearchMetadatas from '@theme/SearchMetadatas';
+import {DEFAULT_SEARCH_TAG} from '../../utils/searchUtils';
+
+export default function LayoutHead(props: Props): JSX.Element {
+ const {siteConfig} = useDocusaurusContext();
+ const {
+ favicon,
+ title: siteTitle,
+ themeConfig: {image: defaultImage, metadatas},
+ url: siteUrl,
+ titleDelimiter,
+ } = siteConfig;
+ const {
+ title,
+ description,
+ image,
+ keywords,
+ permalink,
+ searchMetadatas,
+ } = props;
+ const metaTitle = title
+ ? `${title} ${titleDelimiter} ${siteTitle}`
+ : siteTitle;
+ const metaImage = image || defaultImage;
+ const metaImageUrl = useBaseUrl(metaImage, {absolute: true});
+ const faviconUrl = useBaseUrl(favicon);
+ return (
+ <>
+
+ {/* TODO: Do not assume that it is in english language */}
+
+ {metaTitle &&
{metaTitle}}
+ {metaTitle && }
+ {favicon && }
+ {description && }
+ {description && (
+
+ )}
+ {keywords && keywords.length && (
+
+ )}
+ {metaImage && }
+ {metaImage && }
+ {metaImage && (
+
+ )}
+ {permalink && }
+ {permalink && }
+
+
+
+
+
+ element here,
+ // as it allows react-helmet to override values set in previous
+ // ie we can override default metadatas such as "twitter:card"
+ // In same Head, the same meta would appear twice instead of overriding
+ // See react-helmet doc
+ >
+ {metadatas.map((metadata, i) => (
+
+ ))}
+
+ >
+ );
+}
diff --git a/packages/docusaurus-theme-classic/src/theme/LayoutProviders/index.tsx b/packages/docusaurus-theme-classic/src/theme/LayoutProviders/index.tsx
new file mode 100644
index 000000000000..b592d4f90e0e
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/LayoutProviders/index.tsx
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import React from 'react';
+import ThemeProvider from '@theme/ThemeProvider';
+import UserPreferencesProvider from '@theme/UserPreferencesProvider';
+import DocsPreferredVersionContextProvider from '../../utils/docsPreferredVersion/DocsPreferredVersionProvider';
+
+export default function LayoutProviders({children}) {
+ return (
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/packages/docusaurus-theme-classic/src/theme/SearchMetadatas/index.tsx b/packages/docusaurus-theme-classic/src/theme/SearchMetadatas/index.tsx
new file mode 100644
index 000000000000..aa18b5d10619
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/SearchMetadatas/index.tsx
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import React from 'react';
+
+import Head from '@docusaurus/Head';
+
+type SearchTagMetaProps = {
+ language?: string;
+ version?: string;
+ tag?: string;
+};
+
+// Note: we don't couple this to Algolia/DocSearch on purpose
+// We may want to support other search engine plugins too
+// Search plugins should swizzle/override this comp to add their behavior
+export default function SearchMetadatas({
+ language,
+ version,
+ tag,
+}: SearchTagMetaProps) {
+ return (
+
+ {language && }
+ {version && }
+ {tag && }
+
+ );
+}
diff --git a/packages/docusaurus-theme-classic/src/theme/hooks/useContextualSearchFilters.ts b/packages/docusaurus-theme-classic/src/theme/hooks/useContextualSearchFilters.ts
new file mode 100644
index 000000000000..e15473d6c79f
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/hooks/useContextualSearchFilters.ts
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+import {useAllDocsData, useActivePluginAndVersion} from '@theme/hooks/useDocs';
+import {useDocsPreferredVersionByPluginId} from '../../utils/docsPreferredVersion/useDocsPreferredVersion';
+import {DEFAULT_SEARCH_TAG, docVersionSearchTag} from '../../utils/searchUtils';
+
+type ContextualSearchFilters = {
+ language: string;
+ tags: string[];
+};
+
+// We may want to support multiple search engines, don't couple that to Algolia/DocSearch
+// Maybe users will want to use its own search engine solution
+export default function useContextualSearchFilters(): ContextualSearchFilters {
+ const allDocsData = useAllDocsData();
+ const activePluginAndVersion = useActivePluginAndVersion();
+ const docsPreferredVersionByPluginId = useDocsPreferredVersionByPluginId();
+
+ function getDocPluginTags(pluginId: string) {
+ const activeVersion =
+ activePluginAndVersion?.activePlugin?.pluginId === pluginId
+ ? activePluginAndVersion.activeVersion
+ : undefined;
+
+ const preferredVersion = docsPreferredVersionByPluginId[pluginId];
+
+ const latestVersion = allDocsData[pluginId].versions.find((v) => v.isLast);
+
+ const version = activeVersion ?? preferredVersion ?? latestVersion;
+
+ return docVersionSearchTag(pluginId, version.name);
+ }
+
+ const language = 'en'; // TODO i18n
+
+ const tags = [
+ DEFAULT_SEARCH_TAG,
+ ...Object.keys(allDocsData).map(getDocPluginTags),
+ ];
+
+ return {
+ language,
+ tags,
+ };
+}
diff --git a/packages/docusaurus-theme-classic/src/types.d.ts b/packages/docusaurus-theme-classic/src/types.d.ts
index 326bbef2461b..4493f7431bb7 100644
--- a/packages/docusaurus-theme-classic/src/types.d.ts
+++ b/packages/docusaurus-theme-classic/src/types.d.ts
@@ -246,6 +246,10 @@ declare module '@theme/Layout' {
keywords?: string[];
permalink?: string;
wrapperClassName?: string;
+ searchMetadatas?: {
+ version?: string;
+ tag?: string;
+ };
};
const Layout: (props: Props) => JSX.Element;
diff --git a/packages/docusaurus-theme-classic/src/utils/docsPreferredVersion/useDocsPreferredVersion.ts b/packages/docusaurus-theme-classic/src/utils/docsPreferredVersion/useDocsPreferredVersion.ts
index a6a14778b2a5..6708c782b1f2 100644
--- a/packages/docusaurus-theme-classic/src/utils/docsPreferredVersion/useDocsPreferredVersion.ts
+++ b/packages/docusaurus-theme-classic/src/utils/docsPreferredVersion/useDocsPreferredVersion.ts
@@ -6,7 +6,7 @@
*/
import {useCallback} from 'react';
import {useDocsPreferredVersionContext} from './DocsPreferredVersionProvider';
-import {useDocsData} from '@theme/hooks/useDocs';
+import {useAllDocsData, useDocsData} from '@theme/hooks/useDocs';
import {DEFAULT_PLUGIN_ID} from '@docusaurus/constants';
@@ -32,3 +32,31 @@ export default function useDocsPreferredVersion(
return {preferredVersion, savePreferredVersionName} as const;
}
+
+export function useDocsPreferredVersionByPluginId() {
+ const allDocsData = useAllDocsData();
+ const [state] = useDocsPreferredVersionContext();
+
+ function getPluginIdPreferredVersion(pluginId: string) {
+ const docsData = allDocsData[pluginId];
+ const {preferredVersionName} = state[pluginId];
+
+ return preferredVersionName
+ ? docsData.versions.find(
+ (version) => version.name === preferredVersionName,
+ )
+ : null;
+ }
+
+ const pluginIds = Object.keys(allDocsData);
+
+ const result: Record<
+ string,
+ any // TODO find a way to type this properly!
+ > = {};
+ pluginIds.forEach((pluginId) => {
+ result[pluginId] = getPluginIdPreferredVersion(pluginId);
+ });
+
+ return result;
+}
diff --git a/packages/docusaurus-theme-classic/src/utils/searchUtils.ts b/packages/docusaurus-theme-classic/src/utils/searchUtils.ts
new file mode 100644
index 000000000000..ae1dba0261ca
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/utils/searchUtils.ts
@@ -0,0 +1,12 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+export const DEFAULT_SEARCH_TAG = 'default';
+
+export function docVersionSearchTag(pluginId: string, versionName: string) {
+ return `docs-${pluginId}-${versionName}`;
+}
diff --git a/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.js b/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.js
index cb842e823352..f9b53c5f2eb2 100644
--- a/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.js
+++ b/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.js
@@ -88,4 +88,50 @@ describe('validateThemeConfig', () => {
testValidateThemeConfig({algolia}),
).toThrowErrorMatchingInlineSnapshot(`"\\"algolia.apiKey\\" is required"`);
});
+
+ test('contextualSearch config', () => {
+ const algolia = {
+ indexName: 'index',
+ apiKey: 'apiKey',
+ contextualSearch: true,
+ };
+ expect(testValidateThemeConfig({algolia})).toEqual({
+ algolia: {
+ ...DEFAULT_CONFIG,
+ ...algolia,
+ },
+ });
+ });
+
+ test('searchParameters.facetFilters search config', () => {
+ const algolia = {
+ indexName: 'index',
+ apiKey: 'apiKey',
+ searchParameters: {
+ facetFilters: ['version:1.0'],
+ },
+ };
+ expect(testValidateThemeConfig({algolia})).toEqual({
+ algolia: {
+ ...DEFAULT_CONFIG,
+ ...algolia,
+ },
+ });
+ });
+
+ test('contextualSearch + searchParameters.facetFilters config', () => {
+ const algolia = {
+ indexName: 'index',
+ apiKey: 'apiKey',
+ contextualSearch: true,
+ searchParameters: {
+ facetFilters: ['version:1.0'],
+ },
+ };
+ expect(() =>
+ testValidateThemeConfig({algolia}),
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"If you are using algolia.contextualSearch: true, you should not provide algolia.searchParameters.facetFilters, as it is computed for you dynamically"`,
+ );
+ });
});
diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.js b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.js
index d7a6eeeced7c..005782de4d64 100644
--- a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.js
+++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.js
@@ -14,6 +14,7 @@ import Link from '@docusaurus/Link';
import Head from '@docusaurus/Head';
import useSearchQuery from '@theme/hooks/useSearchQuery';
import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
+import {useAlgoliaContextualSearchParameters} from '../../utils/algoliaSearchUtils';
let DocSearchModal = null;
@@ -31,8 +32,19 @@ function ResultsFooter({state, onClose}) {
);
}
-function DocSearch(props) {
+function DocSearch({contextualSearch, ...props}) {
const {siteMetadata} = useDocusaurusContext();
+
+ const contextualSearchParameters = useAlgoliaContextualSearchParameters();
+
+ // we let user override default searchParameters if he wants to
+ const searchParameters = {
+ ...(contextualSearch ? contextualSearchParameters : {}),
+ ...props.searchParameters,
+ };
+
+ console.log('searchParameters', contextualSearch, searchParameters);
+
const {withBaseUrl} = useBaseUrlUtils();
const history = useHistory();
const searchButtonRef = useRef(null);
@@ -152,6 +164,7 @@ function DocSearch(props) {
resultsFooterComponent={resultsFooterComponent}
transformSearchClient={transformSearchClient}
{...props}
+ searchParameters={searchParameters}
/>,
document.body,
)}
diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchMetadatas/index.js b/packages/docusaurus-theme-search-algolia/src/theme/SearchMetadatas/index.js
new file mode 100644
index 000000000000..16fe29c37e8e
--- /dev/null
+++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchMetadatas/index.js
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import React from 'react';
+
+import Head from '@docusaurus/Head';
+
+// Override default/agnostic SearchMetas to use Algolia-specific metadatas
+export default function AlgoliaSearchMetadatas({language, version, tag}) {
+ return (
+
+ {language && }
+ {version && }
+ {tag && }
+
+ );
+}
diff --git a/packages/docusaurus-theme-search-algolia/src/utils/algoliaSearchUtils.js b/packages/docusaurus-theme-search-algolia/src/utils/algoliaSearchUtils.js
new file mode 100644
index 000000000000..45c0fb19ba3d
--- /dev/null
+++ b/packages/docusaurus-theme-search-algolia/src/utils/algoliaSearchUtils.js
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import useContextualSearchFilters from '@theme/hooks/useContextualSearchFilters';
+
+// Translate search-engine agnostic seach filters to Algolia search filters
+export function useAlgoliaContextualSearchParameters() {
+ const {language, tags} = useContextualSearchFilters();
+
+ const languageFilter = `language:${language}`;
+
+ const tagsFilter = tags.map((tag) => `docusaurus_tag:${tag}`);
+
+ return {facetFilters: [languageFilter, tagsFilter]};
+}
diff --git a/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.js b/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.js
index adda5956c5f8..df83366227e8 100644
--- a/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.js
+++ b/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.js
@@ -8,17 +8,28 @@
const Joi = require('@hapi/joi');
const DEFAULT_CONFIG = {
+ contextualSearch: false, // future: maybe we want to enable this by default
+
// By default, all Docusaurus sites are using the same AppId
// This has been designed on purpose with Algolia.
appId: 'BH4D9OD16A',
+
+ searchParameters: {},
};
exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
const Schema = Joi.object({
algolia: Joi.object({
+ // Docusaurus attributes
+ contextualSearch: Joi.boolean().default(DEFAULT_CONFIG.contextualSearch),
+
+ // Algolia attributes
appId: Joi.string().default(DEFAULT_CONFIG.appId),
apiKey: Joi.string().required(),
indexName: Joi.string().required(),
+ searchParameters: Joi.object()
+ .default(DEFAULT_CONFIG.searchParameters)
+ .unknown(),
})
.label('themeConfig.algolia')
.required()
@@ -30,5 +41,17 @@ exports.validateThemeConfig = function validateThemeConfig({
validate,
themeConfig,
}) {
- return validate(Schema, themeConfig);
+ const normalizedThemeConfig = validate(Schema, themeConfig);
+
+ if (
+ normalizedThemeConfig &&
+ normalizedThemeConfig.algolia.contextualSearch &&
+ normalizedThemeConfig.algolia.searchParameters &&
+ normalizedThemeConfig.algolia.searchParameters.facetFilters
+ ) {
+ throw new Error(
+ 'If you are using algolia.contextualSearch: true, you should not provide algolia.searchParameters.facetFilters, as it is computed for you dynamically',
+ );
+ }
+ return normalizedThemeConfig;
};
diff --git a/website/docs/search.md b/website/docs/search.md
index 8e99c8afbecd..d3334f89434d 100644
--- a/website/docs/search.md
+++ b/website/docs/search.md
@@ -27,7 +27,14 @@ module.exports = {
algolia: {
apiKey: 'YOUR_API_KEY',
indexName: 'YOUR_INDEX_NAME',
- searchParameters: {}, // Optional (if provided by Algolia)
+
+ // Optional: see doc section bellow
+ contextualSearch: true,
+
+ // Optional: Algolia search parameters
+ searchParameters: {},
+
+ //... other Algolia params
},
// highlight-end
},
@@ -40,6 +47,37 @@ The `searchParameters` option used to be named `algoliaOptions` in Docusaurus v1
:::
+### Contextual search
+
+Contextual search is mostly useful for versioned Docusaurus sites.
+
+Let's consider you have 2 docs versions, v1 and v2. When you are browsing v2 docs, it would be odd to return search results for the v1 documentation. Sometimes v1 and v2 docs are quite similar, and you would end up with duplicate search results for the same query (one result per version).
+
+To solve this problem, the contextual search feature understands that you are browsing a specific docs version, and will create the search query filters dynamically.
+
+- browsing `/docs/v1/myDoc`, search results will only include **v1** docs (+ other unversioned pages)
+- browsing `/docs/v2/myDoc`, search results will only include **v2** docs (+ other unversioned pages)
+
+```jsx title="docusaurus.config.js"
+module.exports = {
+ // ...
+ themeConfig: {
+ // ...
+ // highlight-start
+ algolia: {
+ contextualSearch: true,
+ },
+ // highlight-end
+ },
+};
+```
+
+:::caution
+
+If you decide to use contextual search, you can't provide `algolia.searchParameters.facetFilters`, as we compute this `facetFilters` attribute for you dynamically.
+
+:::
+
### Styling your Algolia search
By default, DocSearch comes with a fine-tuned theme that was designed for accessibility, making sure that colors and contrasts respect standards.
diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js
index ce36b7b5e14f..73bf9cd887a3 100644
--- a/website/docusaurus.config.js
+++ b/website/docusaurus.config.js
@@ -249,8 +249,9 @@ module.exports = {
algolia: {
apiKey: '47ecd3b21be71c5822571b9f59e52544',
indexName: 'docusaurus-2',
+ // contextualSearch: true,
searchParameters: {
- facetFilters: [`version:latest`],
+ facetFilters: [`version:current`],
},
},
navbar: {