mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-02 11:47:23 +02:00
feat(search-algolia): algolia externalUrl regex to navigate with window.href (#5795)
This commit is contained in:
parent
8c12983a2a
commit
adbc02ea38
9 changed files with 69 additions and 17 deletions
|
@ -16,6 +16,7 @@ import type {
|
||||||
} from '@theme/NavbarItem/DefaultNavbarItem';
|
} from '@theme/NavbarItem/DefaultNavbarItem';
|
||||||
import IconExternalLink from '@theme/IconExternalLink';
|
import IconExternalLink from '@theme/IconExternalLink';
|
||||||
import isInternalUrl from '@docusaurus/isInternalUrl';
|
import isInternalUrl from '@docusaurus/isInternalUrl';
|
||||||
|
import {isRegexpStringMatch} from '@docusaurus/theme-common';
|
||||||
import {getInfimaActiveClassName} from './index';
|
import {getInfimaActiveClassName} from './index';
|
||||||
|
|
||||||
const dropdownLinkActiveClass = 'dropdown__link--active';
|
const dropdownLinkActiveClass = 'dropdown__link--active';
|
||||||
|
@ -54,7 +55,7 @@ export function NavLink({
|
||||||
? {
|
? {
|
||||||
isActive: (_match, location) =>
|
isActive: (_match, location) =>
|
||||||
activeBaseRegex
|
activeBaseRegex
|
||||||
? new RegExp(activeBaseRegex).test(location.pathname)
|
? isRegexpStringMatch(activeBaseRegex, location.pathname)
|
||||||
: location.pathname.startsWith(activeBaseUrl),
|
: location.pathname.startsWith(activeBaseUrl),
|
||||||
}
|
}
|
||||||
: null),
|
: null),
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
isSamePath,
|
isSamePath,
|
||||||
useCollapsible,
|
useCollapsible,
|
||||||
Collapsible,
|
Collapsible,
|
||||||
|
isRegexpStringMatch,
|
||||||
useLocalPathname,
|
useLocalPathname,
|
||||||
} from '@docusaurus/theme-common';
|
} from '@docusaurus/theme-common';
|
||||||
import type {
|
import type {
|
||||||
|
@ -31,10 +32,7 @@ function isItemActive(
|
||||||
if (isSamePath(item.to, localPathname)) {
|
if (isSamePath(item.to, localPathname)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (
|
if (isRegexpStringMatch(item.activeBaseRegex, localPathname)) {
|
||||||
item.activeBaseRegex &&
|
|
||||||
new RegExp(item.activeBaseRegex).test(localPathname)
|
|
||||||
) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (item.activeBasePath && localPathname.startsWith(item.activeBasePath)) {
|
if (item.activeBasePath && localPathname.startsWith(item.activeBasePath)) {
|
||||||
|
|
|
@ -93,3 +93,5 @@ export {
|
||||||
useIsomorphicLayoutEffect,
|
useIsomorphicLayoutEffect,
|
||||||
useDynamicCallback,
|
useDynamicCallback,
|
||||||
} from './utils/reactUtils';
|
} from './utils/reactUtils';
|
||||||
|
|
||||||
|
export {isRegexpStringMatch} from './utils/regexpUtils';
|
||||||
|
|
23
packages/docusaurus-theme-common/src/utils/regexpUtils.ts
Normal file
23
packages/docusaurus-theme-common/src/utils/regexpUtils.ts
Normal file
|
@ -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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to convert an optional string into a Regex case sensitive and global
|
||||||
|
*/
|
||||||
|
export function isRegexpStringMatch(
|
||||||
|
regexAsString?: string,
|
||||||
|
valueToTest?: string,
|
||||||
|
): boolean {
|
||||||
|
if (
|
||||||
|
typeof regexAsString === 'undefined' ||
|
||||||
|
typeof valueToTest === 'undefined'
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RegExp(regexAsString, 'gi').test(valueToTest);
|
||||||
|
}
|
|
@ -103,6 +103,20 @@ describe('validateThemeConfig', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('externalUrlRegex config', () => {
|
||||||
|
const algolia = {
|
||||||
|
indexName: 'index',
|
||||||
|
apiKey: 'apiKey',
|
||||||
|
externalUrlRegex: 'http://external-domain.com',
|
||||||
|
};
|
||||||
|
expect(testValidateThemeConfig({algolia})).toEqual({
|
||||||
|
algolia: {
|
||||||
|
...DEFAULT_CONFIG,
|
||||||
|
...algolia,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('searchParameters.facetFilters search config', () => {
|
test('searchParameters.facetFilters search config', () => {
|
||||||
const algolia = {
|
const algolia = {
|
||||||
indexName: 'index',
|
indexName: 'index',
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
import Head from '@docusaurus/Head';
|
import Head from '@docusaurus/Head';
|
||||||
import useSearchQuery from '@theme/hooks/useSearchQuery';
|
import useSearchQuery from '@theme/hooks/useSearchQuery';
|
||||||
|
import {isRegexpStringMatch} from '@docusaurus/theme-common';
|
||||||
import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
|
import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
|
||||||
import useAlgoliaContextualFacetFilters from '@theme/hooks/useAlgoliaContextualFacetFilters';
|
import useAlgoliaContextualFacetFilters from '@theme/hooks/useAlgoliaContextualFacetFilters';
|
||||||
import {translate} from '@docusaurus/Translate';
|
import {translate} from '@docusaurus/Translate';
|
||||||
|
@ -34,7 +35,7 @@ function ResultsFooter({state, onClose}) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DocSearch({contextualSearch, ...props}) {
|
function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
|
||||||
const {siteMetadata} = useDocusaurusContext();
|
const {siteMetadata} = useDocusaurusContext();
|
||||||
|
|
||||||
const contextualSearchFacetFilters = useAlgoliaContextualFacetFilters();
|
const contextualSearchFacetFilters = useAlgoliaContextualFacetFilters();
|
||||||
|
@ -102,21 +103,28 @@ function DocSearch({contextualSearch, ...props}) {
|
||||||
|
|
||||||
const navigator = useRef({
|
const navigator = useRef({
|
||||||
navigate({itemUrl}) {
|
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 (isRegexpStringMatch(externalUrlRegex, itemUrl)) {
|
||||||
|
window.location.href = itemUrl;
|
||||||
|
} else {
|
||||||
|
history.push(itemUrl);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}).current;
|
}).current;
|
||||||
|
|
||||||
const transformItems = useRef((items) => {
|
const transformItems = useRef((items) => {
|
||||||
return items.map((item) => {
|
return items.map((item) => {
|
||||||
// We transform the absolute URL into a relative URL.
|
// If Algolia contains a external domain, we should navigate without relative URL
|
||||||
// Alternatively, we can use `new URL(item.url)` but it's not
|
if (isRegexpStringMatch(externalUrlRegex, item.url)) {
|
||||||
// supported in IE.
|
return item;
|
||||||
const a = document.createElement('a');
|
}
|
||||||
a.href = item.url;
|
|
||||||
|
|
||||||
|
// We transform the absolute URL into a relative URL.
|
||||||
|
const url = new URL(item.url);
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
url: withBaseUrl(`${a.pathname}${a.hash}`),
|
url: withBaseUrl(`${url.pathname}${url.hash}`),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}).current;
|
}).current;
|
||||||
|
|
|
@ -19,6 +19,7 @@ import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
||||||
import {
|
import {
|
||||||
useTitleFormatter,
|
useTitleFormatter,
|
||||||
usePluralForm,
|
usePluralForm,
|
||||||
|
isRegexpStringMatch,
|
||||||
useDynamicCallback,
|
useDynamicCallback,
|
||||||
} from '@docusaurus/theme-common';
|
} from '@docusaurus/theme-common';
|
||||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||||
|
@ -121,7 +122,7 @@ function SearchPage() {
|
||||||
const {
|
const {
|
||||||
siteConfig: {
|
siteConfig: {
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
algolia: {appId, apiKey, indexName},
|
algolia: {appId, apiKey, indexName, externalUrlRegex},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
i18n: {currentLocale},
|
i18n: {currentLocale},
|
||||||
|
@ -205,14 +206,16 @@ function SearchPage() {
|
||||||
_highlightResult: {hierarchy},
|
_highlightResult: {hierarchy},
|
||||||
_snippetResult: snippet = {},
|
_snippetResult: snippet = {},
|
||||||
}) => {
|
}) => {
|
||||||
const {pathname, hash} = new URL(url);
|
const parsedURL = new URL(url);
|
||||||
const titles = Object.keys(hierarchy).map((key) => {
|
const titles = Object.keys(hierarchy).map((key) => {
|
||||||
return sanitizeValue(hierarchy[key].value);
|
return sanitizeValue(hierarchy[key].value);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: titles.pop(),
|
title: titles.pop(),
|
||||||
url: pathname + hash,
|
url: isRegexpStringMatch(externalUrlRegex, parsedURL.href)
|
||||||
|
? parsedURL.href
|
||||||
|
: parsedURL.pathname + parsedURL.hash,
|
||||||
summary: snippet.content
|
summary: snippet.content
|
||||||
? `${sanitizeValue(snippet.content.value)}...`
|
? `${sanitizeValue(snippet.content.value)}...`
|
||||||
: '',
|
: '',
|
||||||
|
|
|
@ -22,7 +22,7 @@ const Schema = Joi.object({
|
||||||
algolia: Joi.object({
|
algolia: Joi.object({
|
||||||
// Docusaurus attributes
|
// Docusaurus attributes
|
||||||
contextualSearch: Joi.boolean().default(DEFAULT_CONFIG.contextualSearch),
|
contextualSearch: Joi.boolean().default(DEFAULT_CONFIG.contextualSearch),
|
||||||
|
externalUrlRegex: Joi.string().optional(),
|
||||||
// Algolia attributes
|
// Algolia attributes
|
||||||
appId: Joi.string().default(DEFAULT_CONFIG.appId),
|
appId: Joi.string().default(DEFAULT_CONFIG.appId),
|
||||||
apiKey: Joi.string().required(),
|
apiKey: Joi.string().required(),
|
||||||
|
|
|
@ -86,6 +86,9 @@ module.exports = {
|
||||||
// Optional: see doc section below
|
// Optional: see doc section below
|
||||||
contextualSearch: true,
|
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.
|
||||||
|
externalUrlRegex: 'external\\.com|domain\\.com',
|
||||||
|
|
||||||
// Optional: see doc section below
|
// Optional: see doc section below
|
||||||
appId: 'YOUR_APP_ID',
|
appId: 'YOUR_APP_ID',
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue