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 f6df87f5a1fe4..947cb7cbf7951 100644 --- a/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.js +++ b/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.js @@ -103,6 +103,20 @@ describe('validateThemeConfig', () => { }); }); + test('externalUrlMatch config', () => { + const algolia = { + indexName: 'index', + apiKey: 'apiKey', + externalUrlMatch: 'http://external-domain.com', + }; + expect(testValidateThemeConfig({algolia})).toEqual({ + algolia: { + ...DEFAULT_CONFIG, + ...algolia, + }, + }); + }); + test('searchParameters.facetFilters search config', () => { const algolia = { indexName: 'index', 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 bde8d2d68dfbc..b518ceab0f784 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.js +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.js @@ -13,6 +13,7 @@ import {useBaseUrlUtils} from '@docusaurus/useBaseUrl'; import Link from '@docusaurus/Link'; import Head from '@docusaurus/Head'; import useSearchQuery from '@theme/hooks/useSearchQuery'; +import {withRegex} from '@theme/utils'; import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react'; import useAlgoliaContextualFacetFilters from '@theme/hooks/useAlgoliaContextualFacetFilters'; import {translate} from '@docusaurus/Translate'; @@ -34,7 +35,7 @@ function ResultsFooter({state, onClose}) { ); } -function DocSearch({contextualSearch, ...props}) { +function DocSearch({contextualSearch, externalUrlMatch, ...props}) { const {siteMetadata} = useDocusaurusContext(); const contextualSearchFacetFilters = useAlgoliaContextualFacetFilters(); @@ -102,21 +103,28 @@ function DocSearch({contextualSearch, ...props}) { const navigator = useRef({ navigate({itemUrl}) { - history.push(itemUrl); + // Algolia results could contain URL's from other domains which cannot + // be served through history and should navigate with window.location + if (withRegex(externalUrlMatch).test(itemUrl)) { + window.location.href = itemUrl; + } else { + history.push(itemUrl); + } }, }).current; const transformItems = useRef((items) => { return items.map((item) => { - // We transform the absolute URL into a relative URL. - // Alternatively, we can use `new URL(item.url)` but it's not - // supported in IE. - const a = document.createElement('a'); - a.href = item.url; + // If Algolia contains a external domain, we should navigate without relative URL + if (withRegex(externalUrlMatch).test(item.url)) { + return item; + } + // We transform the absolute URL into a relative URL. + const url = new URL(item.url); return { ...item, - url: withBaseUrl(`${a.pathname}${a.hash}`), + url: withBaseUrl(`${url.pathname}${url.hash}`), }; }); }).current; diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.js b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.js index 8c3ac8b7d7a68..ce7f5fea9efd5 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.js +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.js @@ -24,6 +24,7 @@ import { import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import {useAllDocsData} from '@theme/hooks/useDocs'; import useSearchQuery from '@theme/hooks/useSearchQuery'; +import {withRegex} from '@theme/utils'; import Layout from '@theme/Layout'; import Translate, {translate} from '@docusaurus/Translate'; import styles from './styles.module.css'; @@ -121,7 +122,7 @@ function SearchPage() { const { siteConfig: { themeConfig: { - algolia: {appId, apiKey, indexName}, + algolia: {appId, apiKey, indexName, externalUrlMatch}, }, }, i18n: {currentLocale}, @@ -205,14 +206,16 @@ function SearchPage() { _highlightResult: {hierarchy}, _snippetResult: snippet = {}, }) => { - const {pathname, hash} = new URL(url); + const parsedURL = new URL(url); const titles = Object.keys(hierarchy).map((key) => { return sanitizeValue(hierarchy[key].value); }); return { title: titles.pop(), - url: pathname + hash, + url: withRegex(externalUrlMatch).test(parsedURL.href) + ? parsedURL.href + : parsedURL.pathname + parsedURL.hash, summary: snippet.content ? `${sanitizeValue(snippet.content.value)}...` : '', diff --git a/packages/docusaurus-theme-search-algolia/src/theme/utils/index.js b/packages/docusaurus-theme-search-algolia/src/theme/utils/index.js new file mode 100644 index 0000000000000..c505158efd28e --- /dev/null +++ b/packages/docusaurus-theme-search-algolia/src/theme/utils/index.js @@ -0,0 +1,18 @@ +/** + * 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. + */ + +/** + * Utility to convert an optional string into a Regex case sensitive and global + * @param {string} regexAsString + * @returns { {test: () => boolean }} + */ +export function withRegex(regexAsString) { + if (!regexAsString) { + return {test: () => false}; + } + return new RegExp(regexAsString, 'gi'); +} diff --git a/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.js b/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.js index 1c514229b732b..303f3b298ae10 100644 --- a/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.js +++ b/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.js @@ -22,7 +22,7 @@ const Schema = Joi.object({ algolia: Joi.object({ // Docusaurus attributes contextualSearch: Joi.boolean().default(DEFAULT_CONFIG.contextualSearch), - + externalUrlMatch: Joi.string().optional(), // Algolia attributes appId: Joi.string().default(DEFAULT_CONFIG.appId), apiKey: Joi.string().required(), diff --git a/website/docs/search.md b/website/docs/search.md index 26fd0e483bc3b..777adca3d8961 100644 --- a/website/docs/search.md +++ b/website/docs/search.md @@ -86,6 +86,9 @@ module.exports = { // Optional: see doc section below contextualSearch: true, + // Optional: Specify domains where the navigation should occur through window.location instead on history.push. Useful when our Algolia config crawls multiple documentation sites and we want to navigate with window.location.href to them. + externalUrlMatch: 'external.com|domain.com', + // Optional: see doc section below appId: 'YOUR_APP_ID',