From b3f8f194af8d91a261e01e7489727ce8c12a7fbb Mon Sep 17 00:00:00 2001 From: Anders Bech Mellson Date: Mon, 12 Dec 2022 15:44:54 +0100 Subject: [PATCH 01/11] Make it possible to replace in the item URL --- .../src/__tests__/validateThemeConfig.test.ts | 18 ++++++++++++++++++ .../src/theme-search-algolia.d.ts | 4 ++++ .../src/theme/SearchBar/index.tsx | 14 ++++++++++++-- .../src/theme/SearchPage/index.tsx | 8 ++++++-- .../src/validateThemeConfig.ts | 4 ++++ website/docs/search.md | 6 ++++++ website/versioned_docs/version-2.0.1/search.md | 6 ++++++ website/versioned_docs/version-2.1.0/search.md | 6 ++++++ website/versioned_docs/version-2.2.0/search.md | 6 ++++++ 9 files changed, 68 insertions(+), 4 deletions(-) diff --git a/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts b/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts index 9fc315e1c3ba..c4510daf5f4c 100644 --- a/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts +++ b/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts @@ -121,6 +121,24 @@ describe('validateThemeConfig', () => { }); }); + it('replaceInItemUrl config', () => { + const algolia = { + appId: 'BH4D9OD16A', + indexName: 'index', + apiKey: 'apiKey', + replaceInItemUrl: { + from: '/docs/', + to: '/', + }, + }; + expect(testValidateThemeConfig({algolia})).toEqual({ + algolia: { + ...DEFAULT_CONFIG, + ...algolia, + }, + }); + }); + it('searchParameters.facetFilters search config', () => { const algolia = { appId: 'BH4D9OD16A', diff --git a/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts b/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts index 9cf09fed4d05..e590ff0cfbbb 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts +++ b/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts @@ -17,6 +17,10 @@ declare module '@docusaurus/theme-search-algolia' { indexName: string; searchParameters: {[key: string]: unknown}; searchPagePath: string | false | null; + replaceInItemUrl?: { + from: string; + to: string; + }; }; }; export type UserThemeConfig = DeepPartial; diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx index a63d6bc2cbf0..13949b3dcc0a 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx @@ -37,6 +37,10 @@ type DocSearchProps = Omit< contextualSearch?: string; externalUrlRegex?: string; searchPagePath: boolean | string; + replaceInItemUrl?: { + from: string; + to: string; + }; }; let DocSearchModal: typeof DocSearchModalType | null = null; @@ -85,6 +89,7 @@ function mergeFacetFilters(f1: FacetFilters, f2: FacetFilters): FacetFilters { function DocSearch({ contextualSearch, externalUrlRegex, + replaceInItemUrl, ...props }: DocSearchProps) { const {siteMetadata} = useDocusaurusContext(); @@ -173,14 +178,19 @@ function DocSearch({ const transformItems = useRef( (items) => items.map((item) => { + // Replace parts of the URL if the user has configured it in the config + const itemUrl = replaceInItemUrl + ? item.url.replace(replaceInItemUrl.from, replaceInItemUrl.to) + : item.url; + // If Algolia contains a external domain, we should navigate without // relative URL - if (isRegexpStringMatch(externalUrlRegex, item.url)) { + if (isRegexpStringMatch(externalUrlRegex, itemUrl)) { return item; } // We transform the absolute URL into a relative URL. - const url = new URL(item.url); + const url = new URL(itemUrl); return { ...item, url: withBaseUrl(`${url.pathname}${url.hash}`), diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx index c9aaa62f4c03..2b47f2636187 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx @@ -161,7 +161,7 @@ function SearchPageContent(): JSX.Element { i18n: {currentLocale}, } = useDocusaurusContext(); const { - algolia: {appId, apiKey, indexName, externalUrlRegex}, + algolia: {appId, apiKey, indexName, externalUrlRegex, replaceInItemUrl}, } = themeConfig as ThemeConfig; const documentsFoundPlural = useDocumentsFoundPlural(); @@ -245,7 +245,11 @@ function SearchPageContent(): JSX.Element { _highlightResult: {hierarchy: {[key: string]: {value: string}}}; _snippetResult: {content?: {value: string}}; }) => { - const parsedURL = new URL(url); + const parsedURL = new URL( + replaceInItemUrl + ? url.replace(replaceInItemUrl.from, replaceInItemUrl.to) + : url, + ); const titles = Object.keys(hierarchy).map((key) => sanitizeValue(hierarchy[key]!.value), ); diff --git a/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts b/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts index a2a2ccd7869e..b2cc5240d098 100644 --- a/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts +++ b/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts @@ -39,6 +39,10 @@ export const Schema = Joi.object({ .try(Joi.boolean().invalid(true), Joi.string()) .allow(null) .default(DEFAULT_CONFIG.searchPagePath), + replaceInItemUrl: Joi.object({ + from: Joi.string().required(), + to: Joi.string().required(), + }).optional(), }) .label('themeConfig.algolia') .required() diff --git a/website/docs/search.md b/website/docs/search.md index e00e022cfaca..89f5ea8fdb5b 100644 --- a/website/docs/search.md +++ b/website/docs/search.md @@ -104,6 +104,12 @@ module.exports = { // 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: Replace parts of the item URLs from Algolia. Useful when you deploy to something like domain.com/docs/, but your preview deploys are served from root /. + replaceInItemUrl: { + from: '/docs/', + to: '/', + }, + // Optional: Algolia search parameters searchParameters: {}, diff --git a/website/versioned_docs/version-2.0.1/search.md b/website/versioned_docs/version-2.0.1/search.md index e00e022cfaca..89f5ea8fdb5b 100644 --- a/website/versioned_docs/version-2.0.1/search.md +++ b/website/versioned_docs/version-2.0.1/search.md @@ -104,6 +104,12 @@ module.exports = { // 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: Replace parts of the item URLs from Algolia. Useful when you deploy to something like domain.com/docs/, but your preview deploys are served from root /. + replaceInItemUrl: { + from: '/docs/', + to: '/', + }, + // Optional: Algolia search parameters searchParameters: {}, diff --git a/website/versioned_docs/version-2.1.0/search.md b/website/versioned_docs/version-2.1.0/search.md index e00e022cfaca..89f5ea8fdb5b 100644 --- a/website/versioned_docs/version-2.1.0/search.md +++ b/website/versioned_docs/version-2.1.0/search.md @@ -104,6 +104,12 @@ module.exports = { // 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: Replace parts of the item URLs from Algolia. Useful when you deploy to something like domain.com/docs/, but your preview deploys are served from root /. + replaceInItemUrl: { + from: '/docs/', + to: '/', + }, + // Optional: Algolia search parameters searchParameters: {}, diff --git a/website/versioned_docs/version-2.2.0/search.md b/website/versioned_docs/version-2.2.0/search.md index e00e022cfaca..89f5ea8fdb5b 100644 --- a/website/versioned_docs/version-2.2.0/search.md +++ b/website/versioned_docs/version-2.2.0/search.md @@ -104,6 +104,12 @@ module.exports = { // 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: Replace parts of the item URLs from Algolia. Useful when you deploy to something like domain.com/docs/, but your preview deploys are served from root /. + replaceInItemUrl: { + from: '/docs/', + to: '/', + }, + // Optional: Algolia search parameters searchParameters: {}, From 598be66d16e6bed8cc31f3ea78587cab1a245b91 Mon Sep 17 00:00:00 2001 From: Anders Bech Mellson Date: Thu, 15 Dec 2022 22:07:29 +0100 Subject: [PATCH 02/11] Implement suggestions --- .../src/__tests__/validateThemeConfig.test.ts | 4 +-- .../src/theme-search-algolia.d.ts | 2 +- .../src/theme/SearchBar/index.tsx | 29 ++++++++++--------- .../src/theme/SearchPage/index.tsx | 29 ++++++++++++------- .../src/validateThemeConfig.ts | 2 +- website/docs/search.md | 4 +-- .../versioned_docs/version-2.0.1/search.md | 4 +-- .../versioned_docs/version-2.1.0/search.md | 4 +-- .../versioned_docs/version-2.2.0/search.md | 4 +-- 9 files changed, 47 insertions(+), 35 deletions(-) diff --git a/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts b/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts index c4510daf5f4c..816bdc3e4cf2 100644 --- a/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts +++ b/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts @@ -121,12 +121,12 @@ describe('validateThemeConfig', () => { }); }); - it('replaceInItemUrl config', () => { + it('replaceSearchResultPathname config', () => { const algolia = { appId: 'BH4D9OD16A', indexName: 'index', apiKey: 'apiKey', - replaceInItemUrl: { + replaceSearchResultPathname: { from: '/docs/', to: '/', }, diff --git a/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts b/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts index e590ff0cfbbb..d0c2b16ee123 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts +++ b/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts @@ -17,7 +17,7 @@ declare module '@docusaurus/theme-search-algolia' { indexName: string; searchParameters: {[key: string]: unknown}; searchPagePath: string | false | null; - replaceInItemUrl?: { + replaceSearchResultPathname?: { from: string; to: string; }; diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx index 13949b3dcc0a..e32797ee8a25 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx @@ -5,20 +5,21 @@ * LICENSE file in the root directory of this source tree. */ -import React, {useState, useRef, useCallback, useMemo} from 'react'; -import {createPortal} from 'react-dom'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import {useHistory} from '@docusaurus/router'; -import {useBaseUrlUtils} from '@docusaurus/useBaseUrl'; -import Link from '@docusaurus/Link'; +import React, {useCallback, useMemo, useRef, useState} from 'react'; +import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react'; import Head from '@docusaurus/Head'; +import Link from '@docusaurus/Link'; +import {useHistory} from '@docusaurus/router'; import {isRegexpStringMatch} from '@docusaurus/theme-common'; import {useSearchPage} from '@docusaurus/theme-common/internal'; -import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react'; import {useAlgoliaContextualFacetFilters} from '@docusaurus/theme-search-algolia/client'; import Translate from '@docusaurus/Translate'; +import {useBaseUrlUtils} from '@docusaurus/useBaseUrl'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import {createPortal} from 'react-dom'; import translations from '@theme/SearchTranslations'; +import type {AutocompleteState} from '@algolia/autocomplete-core'; import type { DocSearchModal as DocSearchModalType, DocSearchModalProps, @@ -28,7 +29,6 @@ import type { StoredDocSearchHit, } from '@docsearch/react/dist/esm/types'; import type {SearchClient} from 'algoliasearch/lite'; -import type {AutocompleteState} from '@algolia/autocomplete-core'; type DocSearchProps = Omit< DocSearchModalProps, @@ -37,7 +37,7 @@ type DocSearchProps = Omit< contextualSearch?: string; externalUrlRegex?: string; searchPagePath: boolean | string; - replaceInItemUrl?: { + replaceSearchResultPathname?: { from: string; to: string; }; @@ -89,7 +89,7 @@ function mergeFacetFilters(f1: FacetFilters, f2: FacetFilters): FacetFilters { function DocSearch({ contextualSearch, externalUrlRegex, - replaceInItemUrl, + replaceSearchResultPathname, ...props }: DocSearchProps) { const {siteMetadata} = useDocusaurusContext(); @@ -178,9 +178,12 @@ function DocSearch({ const transformItems = useRef( (items) => items.map((item) => { - // Replace parts of the URL if the user has configured it in the config - const itemUrl = replaceInItemUrl - ? item.url.replace(replaceInItemUrl.from, replaceInItemUrl.to) + // Replace parts of the URL if the user has added it in the config + const itemUrl = replaceSearchResultPathname + ? item.url.replace( + new RegExp(replaceSearchResultPathname.from, 'g'), + replaceSearchResultPathname.to, + ) : item.url; // If Algolia contains a external domain, we should navigate without diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx index 2b47f2636187..4015b937f7a6 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx @@ -7,28 +7,28 @@ /* eslint-disable jsx-a11y/no-autofocus */ -import React, {useEffect, useState, useReducer, useRef} from 'react'; +import React, {useEffect, useReducer, useRef, useState} from 'react'; import clsx from 'clsx'; -import algoliaSearch from 'algoliasearch/lite'; import algoliaSearchHelper from 'algoliasearch-helper'; +import algoliaSearch from 'algoliasearch/lite'; +import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; import Head from '@docusaurus/Head'; import Link from '@docusaurus/Link'; -import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; +import {useAllDocsData} from '@docusaurus/plugin-content-docs/client'; import { HtmlClassNameProvider, - usePluralForm, isRegexpStringMatch, useEvent, + usePluralForm, } from '@docusaurus/theme-common'; import { - useTitleFormatter, useSearchPage, + useTitleFormatter, } from '@docusaurus/theme-common/internal'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import {useAllDocsData} from '@docusaurus/plugin-content-docs/client'; import Translate, {translate} from '@docusaurus/Translate'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import Layout from '@theme/Layout'; import type {ThemeConfig} from '@docusaurus/theme-search-algolia'; @@ -161,7 +161,13 @@ function SearchPageContent(): JSX.Element { i18n: {currentLocale}, } = useDocusaurusContext(); const { - algolia: {appId, apiKey, indexName, externalUrlRegex, replaceInItemUrl}, + algolia: { + appId, + apiKey, + indexName, + externalUrlRegex, + replaceSearchResultPathname, + }, } = themeConfig as ThemeConfig; const documentsFoundPlural = useDocumentsFoundPlural(); @@ -246,8 +252,11 @@ function SearchPageContent(): JSX.Element { _snippetResult: {content?: {value: string}}; }) => { const parsedURL = new URL( - replaceInItemUrl - ? url.replace(replaceInItemUrl.from, replaceInItemUrl.to) + replaceSearchResultPathname + ? url.replace( + new RegExp(replaceSearchResultPathname.from, 'g'), + replaceSearchResultPathname.to, + ) : url, ); const titles = Object.keys(hierarchy).map((key) => diff --git a/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts b/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts index b2cc5240d098..15d09a9c2141 100644 --- a/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts +++ b/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts @@ -39,7 +39,7 @@ export const Schema = Joi.object({ .try(Joi.boolean().invalid(true), Joi.string()) .allow(null) .default(DEFAULT_CONFIG.searchPagePath), - replaceInItemUrl: Joi.object({ + replaceSearchResultPathname: Joi.object({ from: Joi.string().required(), to: Joi.string().required(), }).optional(), diff --git a/website/docs/search.md b/website/docs/search.md index 89f5ea8fdb5b..5e8f32aa2549 100644 --- a/website/docs/search.md +++ b/website/docs/search.md @@ -104,8 +104,8 @@ module.exports = { // 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: Replace parts of the item URLs from Algolia. Useful when you deploy to something like domain.com/docs/, but your preview deploys are served from root /. - replaceInItemUrl: { + // Optional: Replace parts of the item URLs from Algolia. Useful when using the same search index for multiple deployments using a different baseUrl. For example: localhost:3000 vs myCompany.com/docs + replaceSearchResultPathname: { from: '/docs/', to: '/', }, diff --git a/website/versioned_docs/version-2.0.1/search.md b/website/versioned_docs/version-2.0.1/search.md index 89f5ea8fdb5b..5e8f32aa2549 100644 --- a/website/versioned_docs/version-2.0.1/search.md +++ b/website/versioned_docs/version-2.0.1/search.md @@ -104,8 +104,8 @@ module.exports = { // 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: Replace parts of the item URLs from Algolia. Useful when you deploy to something like domain.com/docs/, but your preview deploys are served from root /. - replaceInItemUrl: { + // Optional: Replace parts of the item URLs from Algolia. Useful when using the same search index for multiple deployments using a different baseUrl. For example: localhost:3000 vs myCompany.com/docs + replaceSearchResultPathname: { from: '/docs/', to: '/', }, diff --git a/website/versioned_docs/version-2.1.0/search.md b/website/versioned_docs/version-2.1.0/search.md index 89f5ea8fdb5b..5e8f32aa2549 100644 --- a/website/versioned_docs/version-2.1.0/search.md +++ b/website/versioned_docs/version-2.1.0/search.md @@ -104,8 +104,8 @@ module.exports = { // 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: Replace parts of the item URLs from Algolia. Useful when you deploy to something like domain.com/docs/, but your preview deploys are served from root /. - replaceInItemUrl: { + // Optional: Replace parts of the item URLs from Algolia. Useful when using the same search index for multiple deployments using a different baseUrl. For example: localhost:3000 vs myCompany.com/docs + replaceSearchResultPathname: { from: '/docs/', to: '/', }, diff --git a/website/versioned_docs/version-2.2.0/search.md b/website/versioned_docs/version-2.2.0/search.md index 89f5ea8fdb5b..5e8f32aa2549 100644 --- a/website/versioned_docs/version-2.2.0/search.md +++ b/website/versioned_docs/version-2.2.0/search.md @@ -104,8 +104,8 @@ module.exports = { // 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: Replace parts of the item URLs from Algolia. Useful when you deploy to something like domain.com/docs/, but your preview deploys are served from root /. - replaceInItemUrl: { + // Optional: Replace parts of the item URLs from Algolia. Useful when using the same search index for multiple deployments using a different baseUrl. For example: localhost:3000 vs myCompany.com/docs + replaceSearchResultPathname: { from: '/docs/', to: '/', }, From d418b39b5ca3598201884b02c8c7178a909e9278 Mon Sep 17 00:00:00 2001 From: Anders Bech Mellson Date: Fri, 16 Dec 2022 14:57:46 +0100 Subject: [PATCH 03/11] Make it possible to provide regexp or string --- packages/docusaurus-theme-common/src/index.ts | 2 ++ .../src/utils/configUtils.ts | 21 +++++++++++++++++++ .../src/theme/SearchBar/index.tsx | 4 ++-- .../src/theme/SearchPage/index.tsx | 3 ++- website/docs/search.md | 2 +- website/docusaurus.config.js | 4 ++++ .../versioned_docs/version-2.0.1/search.md | 2 +- .../versioned_docs/version-2.1.0/search.md | 2 +- .../versioned_docs/version-2.2.0/search.md | 2 +- 9 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 packages/docusaurus-theme-common/src/utils/configUtils.ts diff --git a/packages/docusaurus-theme-common/src/index.ts b/packages/docusaurus-theme-common/src/index.ts index b736244ca618..4827edce6083 100644 --- a/packages/docusaurus-theme-common/src/index.ts +++ b/packages/docusaurus-theme-common/src/index.ts @@ -28,6 +28,8 @@ export {createStorageSlot, listStorageKeys} from './utils/storageUtils'; export {useContextualSearchFilters} from './utils/searchUtils'; +export {getRegexpOrString} from './utils/configUtils'; + export { useCurrentSidebarCategory, filterDocCardListItems, diff --git a/packages/docusaurus-theme-common/src/utils/configUtils.ts b/packages/docusaurus-theme-common/src/utils/configUtils.ts new file mode 100644 index 000000000000..aec6f63919f5 --- /dev/null +++ b/packages/docusaurus-theme-common/src/utils/configUtils.ts @@ -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. + */ + +/** + * Tries to create a RegExp from a string, coming from the Docusaurus config. + * If it fails to create a RegExp it returns the input string. + * + * @param possibleRegexp string that is possibly a regex + * @returns a Regex if possible, otherwise the string + */ +export function getRegexpOrString(possibleRegexp: string): RegExp | string { + try { + return new RegExp(new RegExp(possibleRegexp).source, 'g'); + } catch (e) { + return possibleRegexp; + } +} diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx index e32797ee8a25..46915210b726 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx @@ -10,7 +10,7 @@ import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react'; import Head from '@docusaurus/Head'; import Link from '@docusaurus/Link'; import {useHistory} from '@docusaurus/router'; -import {isRegexpStringMatch} from '@docusaurus/theme-common'; +import {isRegexpStringMatch, getRegexpOrString} from '@docusaurus/theme-common'; import {useSearchPage} from '@docusaurus/theme-common/internal'; import {useAlgoliaContextualFacetFilters} from '@docusaurus/theme-search-algolia/client'; import Translate from '@docusaurus/Translate'; @@ -181,7 +181,7 @@ function DocSearch({ // Replace parts of the URL if the user has added it in the config const itemUrl = replaceSearchResultPathname ? item.url.replace( - new RegExp(replaceSearchResultPathname.from, 'g'), + getRegexpOrString(replaceSearchResultPathname.from), replaceSearchResultPathname.to, ) : item.url; diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx index 4015b937f7a6..d8b8e3094745 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx @@ -18,6 +18,7 @@ import Head from '@docusaurus/Head'; import Link from '@docusaurus/Link'; import {useAllDocsData} from '@docusaurus/plugin-content-docs/client'; import { + getRegexpOrString, HtmlClassNameProvider, isRegexpStringMatch, useEvent, @@ -254,7 +255,7 @@ function SearchPageContent(): JSX.Element { const parsedURL = new URL( replaceSearchResultPathname ? url.replace( - new RegExp(replaceSearchResultPathname.from, 'g'), + getRegexpOrString(replaceSearchResultPathname.from), replaceSearchResultPathname.to, ) : url, diff --git a/website/docs/search.md b/website/docs/search.md index 5e8f32aa2549..cebc8f016459 100644 --- a/website/docs/search.md +++ b/website/docs/search.md @@ -104,7 +104,7 @@ module.exports = { // 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: Replace parts of the item URLs from Algolia. Useful when using the same search index for multiple deployments using a different baseUrl. For example: localhost:3000 vs myCompany.com/docs + // Optional: Replace parts of the item URLs from Algolia. Useful when using the same search index for multiple deployments using a different baseUrl. You can use regexp or string in the `from` param. For example: localhost:3000 vs myCompany.com/docs replaceSearchResultPathname: { from: '/docs/', to: '/', diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 6e00175fb995..2171cad78c68 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -409,6 +409,10 @@ const config = { appId: 'X1Z85QJPUV', apiKey: 'bf7211c161e8205da2f933a02534105a', indexName: 'docusaurus-2', + replaceSearchResultPathname: (isDev || isDeployPreview) ? { + from: '\\/docs\\/next', + to:'/docs' + } : undefined, }, navbar: { hideOnScroll: true, diff --git a/website/versioned_docs/version-2.0.1/search.md b/website/versioned_docs/version-2.0.1/search.md index 5e8f32aa2549..cebc8f016459 100644 --- a/website/versioned_docs/version-2.0.1/search.md +++ b/website/versioned_docs/version-2.0.1/search.md @@ -104,7 +104,7 @@ module.exports = { // 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: Replace parts of the item URLs from Algolia. Useful when using the same search index for multiple deployments using a different baseUrl. For example: localhost:3000 vs myCompany.com/docs + // Optional: Replace parts of the item URLs from Algolia. Useful when using the same search index for multiple deployments using a different baseUrl. You can use regexp or string in the `from` param. For example: localhost:3000 vs myCompany.com/docs replaceSearchResultPathname: { from: '/docs/', to: '/', diff --git a/website/versioned_docs/version-2.1.0/search.md b/website/versioned_docs/version-2.1.0/search.md index 5e8f32aa2549..cebc8f016459 100644 --- a/website/versioned_docs/version-2.1.0/search.md +++ b/website/versioned_docs/version-2.1.0/search.md @@ -104,7 +104,7 @@ module.exports = { // 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: Replace parts of the item URLs from Algolia. Useful when using the same search index for multiple deployments using a different baseUrl. For example: localhost:3000 vs myCompany.com/docs + // Optional: Replace parts of the item URLs from Algolia. Useful when using the same search index for multiple deployments using a different baseUrl. You can use regexp or string in the `from` param. For example: localhost:3000 vs myCompany.com/docs replaceSearchResultPathname: { from: '/docs/', to: '/', diff --git a/website/versioned_docs/version-2.2.0/search.md b/website/versioned_docs/version-2.2.0/search.md index 5e8f32aa2549..cebc8f016459 100644 --- a/website/versioned_docs/version-2.2.0/search.md +++ b/website/versioned_docs/version-2.2.0/search.md @@ -104,7 +104,7 @@ module.exports = { // 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: Replace parts of the item URLs from Algolia. Useful when using the same search index for multiple deployments using a different baseUrl. For example: localhost:3000 vs myCompany.com/docs + // Optional: Replace parts of the item URLs from Algolia. Useful when using the same search index for multiple deployments using a different baseUrl. You can use regexp or string in the `from` param. For example: localhost:3000 vs myCompany.com/docs replaceSearchResultPathname: { from: '/docs/', to: '/', From 5df658b8b0955a86648d45ed6b113c2bd8560314 Mon Sep 17 00:00:00 2001 From: Anders Bech Mellson Date: Sat, 17 Dec 2022 12:16:45 +0100 Subject: [PATCH 04/11] Implemented suggested improvements --- packages/docusaurus-theme-common/src/index.ts | 2 -- .../src/utils/configUtils.ts | 21 ------------------- .../src/theme/SearchBar/index.tsx | 4 ++-- .../src/theme/SearchPage/index.tsx | 3 +-- .../src/validateThemeConfig.ts | 15 ++++++++++++- website/docusaurus.config.js | 2 +- 6 files changed, 18 insertions(+), 29 deletions(-) delete mode 100644 packages/docusaurus-theme-common/src/utils/configUtils.ts diff --git a/packages/docusaurus-theme-common/src/index.ts b/packages/docusaurus-theme-common/src/index.ts index 4827edce6083..b736244ca618 100644 --- a/packages/docusaurus-theme-common/src/index.ts +++ b/packages/docusaurus-theme-common/src/index.ts @@ -28,8 +28,6 @@ export {createStorageSlot, listStorageKeys} from './utils/storageUtils'; export {useContextualSearchFilters} from './utils/searchUtils'; -export {getRegexpOrString} from './utils/configUtils'; - export { useCurrentSidebarCategory, filterDocCardListItems, diff --git a/packages/docusaurus-theme-common/src/utils/configUtils.ts b/packages/docusaurus-theme-common/src/utils/configUtils.ts deleted file mode 100644 index aec6f63919f5..000000000000 --- a/packages/docusaurus-theme-common/src/utils/configUtils.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * 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. - */ - -/** - * Tries to create a RegExp from a string, coming from the Docusaurus config. - * If it fails to create a RegExp it returns the input string. - * - * @param possibleRegexp string that is possibly a regex - * @returns a Regex if possible, otherwise the string - */ -export function getRegexpOrString(possibleRegexp: string): RegExp | string { - try { - return new RegExp(new RegExp(possibleRegexp).source, 'g'); - } catch (e) { - return possibleRegexp; - } -} diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx index 46915210b726..06f4950f347a 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx @@ -10,7 +10,7 @@ import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react'; import Head from '@docusaurus/Head'; import Link from '@docusaurus/Link'; import {useHistory} from '@docusaurus/router'; -import {isRegexpStringMatch, getRegexpOrString} from '@docusaurus/theme-common'; +import {isRegexpStringMatch} from '@docusaurus/theme-common'; import {useSearchPage} from '@docusaurus/theme-common/internal'; import {useAlgoliaContextualFacetFilters} from '@docusaurus/theme-search-algolia/client'; import Translate from '@docusaurus/Translate'; @@ -181,7 +181,7 @@ function DocSearch({ // Replace parts of the URL if the user has added it in the config const itemUrl = replaceSearchResultPathname ? item.url.replace( - getRegexpOrString(replaceSearchResultPathname.from), + new RegExp(replaceSearchResultPathname.from), replaceSearchResultPathname.to, ) : item.url; diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx index d8b8e3094745..e277aa2eaf0e 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx @@ -18,7 +18,6 @@ import Head from '@docusaurus/Head'; import Link from '@docusaurus/Link'; import {useAllDocsData} from '@docusaurus/plugin-content-docs/client'; import { - getRegexpOrString, HtmlClassNameProvider, isRegexpStringMatch, useEvent, @@ -255,7 +254,7 @@ function SearchPageContent(): JSX.Element { const parsedURL = new URL( replaceSearchResultPathname ? url.replace( - getRegexpOrString(replaceSearchResultPathname.from), + new RegExp(replaceSearchResultPathname.from), replaceSearchResultPathname.to, ) : url, diff --git a/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts b/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts index 15d09a9c2141..5a2c3a485fdf 100644 --- a/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts +++ b/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts @@ -20,6 +20,10 @@ export const DEFAULT_CONFIG = { searchPagePath: 'search', }; +function escapeRegExp(s: string): string { + return s.replace(/[-[\]{}()*+?.\\^$|/]/g, '\\$&'); +} + export const Schema = Joi.object({ algolia: Joi.object({ // Docusaurus attributes @@ -40,7 +44,16 @@ export const Schema = Joi.object({ .allow(null) .default(DEFAULT_CONFIG.searchPagePath), replaceSearchResultPathname: Joi.object({ - from: Joi.string().required(), + from: Joi.custom((from) => { + if (typeof from === 'string') { + return escapeRegExp(from); + } else if (from instanceof RegExp) { + return from.source; + } + throw new Error( + `it should be a RegExp or a string, but received ${from}`, + ); + }).required(), to: Joi.string().required(), }).optional(), }) diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 2171cad78c68..2a5cab97b3ea 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -410,7 +410,7 @@ const config = { apiKey: 'bf7211c161e8205da2f933a02534105a', indexName: 'docusaurus-2', replaceSearchResultPathname: (isDev || isDeployPreview) ? { - from: '\\/docs\\/next', + from: /\/docs\/next/g, to:'/docs' } : undefined, }, From 0709d6924395468a67724e46a0549b1869d94638 Mon Sep 17 00:00:00 2001 From: Anders Bech Mellson Date: Thu, 22 Dec 2022 15:55:51 +0100 Subject: [PATCH 05/11] fix test --- .../src/__tests__/validateThemeConfig.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts b/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts index 816bdc3e4cf2..46ad3a84c395 100644 --- a/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts +++ b/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {validateThemeConfig, DEFAULT_CONFIG} from '../validateThemeConfig'; +import {DEFAULT_CONFIG, validateThemeConfig} from '../validateThemeConfig'; import type {Joi} from '@docusaurus/utils-validation'; function testValidateThemeConfig(themeConfig: {[key: string]: unknown}) { @@ -135,6 +135,10 @@ describe('validateThemeConfig', () => { algolia: { ...DEFAULT_CONFIG, ...algolia, + replaceSearchResultPathname: { + from: '\\/docs\\/', + to: '/', + }, }, }); }); From 682fae25688b33655ffa39b7b7e745199bf5639d Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 22 Dec 2022 16:11:12 +0100 Subject: [PATCH 06/11] factorize usage of escapeRegexp lib --- packages/docusaurus-plugin-content-blog/package.json | 3 --- .../src/__tests__/frontMatter.test.ts | 4 ++-- packages/docusaurus-plugin-content-docs/package.json | 1 - .../src/__tests__/frontMatter.test.ts | 4 ++-- .../src/validateThemeConfig.ts | 7 ++----- packages/docusaurus-utils/package.json | 1 + packages/docusaurus-utils/src/index.ts | 1 + packages/docusaurus-utils/src/regExpUtils.ts | 12 ++++++++++++ 8 files changed, 20 insertions(+), 13 deletions(-) create mode 100644 packages/docusaurus-utils/src/regExpUtils.ts diff --git a/packages/docusaurus-plugin-content-blog/package.json b/packages/docusaurus-plugin-content-blog/package.json index f19896abbd48..7d788be730ee 100644 --- a/packages/docusaurus-plugin-content-blog/package.json +++ b/packages/docusaurus-plugin-content-blog/package.json @@ -35,9 +35,6 @@ "utility-types": "^3.10.0", "webpack": "^5.74.0" }, - "devDependencies": { - "escape-string-regexp": "^4.0.0" - }, "peerDependencies": { "react": "^16.8.4 || ^17.0.0", "react-dom": "^16.8.4 || ^17.0.0" diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts index 3e3580d92c5c..9c05dcca944a 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import escapeStringRegexp from 'escape-string-regexp'; +import {escapeRegexp} from '@docusaurus/utils'; import {validateBlogPostFrontMatter} from '../frontMatter'; import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog'; @@ -54,7 +54,7 @@ function testField(params: { } catch (err) { // eslint-disable-next-line jest/no-conditional-expect expect((err as Error).message).toMatch( - new RegExp(escapeStringRegexp(message)), + new RegExp(escapeRegexp(message)), ); } }); diff --git a/packages/docusaurus-plugin-content-docs/package.json b/packages/docusaurus-plugin-content-docs/package.json index 3dc78f844165..62d25ef7f99b 100644 --- a/packages/docusaurus-plugin-content-docs/package.json +++ b/packages/docusaurus-plugin-content-docs/package.json @@ -56,7 +56,6 @@ "@types/js-yaml": "^4.0.5", "@types/picomatch": "^2.3.0", "commander": "^5.1.0", - "escape-string-regexp": "^4.0.0", "picomatch": "^2.3.1", "shelljs": "^0.8.5" }, diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts index 7aeada8201b8..8589a707681b 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import escapeStringRegexp from 'escape-string-regexp'; +import {escapeRegexp} from '@docusaurus/utils'; import {validateDocFrontMatter} from '../frontMatter'; import type {DocFrontMatter} from '@docusaurus/plugin-content-docs'; @@ -54,7 +54,7 @@ function testField(params: { } catch (err) { // eslint-disable-next-line jest/no-conditional-expect expect((err as Error).message).toMatch( - new RegExp(escapeStringRegexp(message)), + new RegExp(escapeRegexp(message)), ); } }); diff --git a/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts b/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts index 5a2c3a485fdf..2e1061d86429 100644 --- a/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts +++ b/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import {escapeRegexp} from '@docusaurus/utils'; import {Joi} from '@docusaurus/utils-validation'; import type { ThemeConfig, @@ -20,10 +21,6 @@ export const DEFAULT_CONFIG = { searchPagePath: 'search', }; -function escapeRegExp(s: string): string { - return s.replace(/[-[\]{}()*+?.\\^$|/]/g, '\\$&'); -} - export const Schema = Joi.object({ algolia: Joi.object({ // Docusaurus attributes @@ -46,7 +43,7 @@ export const Schema = Joi.object({ replaceSearchResultPathname: Joi.object({ from: Joi.custom((from) => { if (typeof from === 'string') { - return escapeRegExp(from); + return escapeRegexp(from); } else if (from instanceof RegExp) { return from.source; } diff --git a/packages/docusaurus-utils/package.json b/packages/docusaurus-utils/package.json index cef5263c8367..5434576aa5dc 100644 --- a/packages/docusaurus-utils/package.json +++ b/packages/docusaurus-utils/package.json @@ -20,6 +20,7 @@ "dependencies": { "@docusaurus/logger": "^3.0.0-alpha.0", "@svgr/webpack": "^6.3.1", + "escape-string-regexp": "^4.0.0", "file-loader": "^6.2.0", "fs-extra": "^10.1.0", "github-slugger": "^1.4.0", diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts index ba74f8d3d134..2d9349d48c33 100644 --- a/packages/docusaurus-utils/src/index.ts +++ b/packages/docusaurus-utils/src/index.ts @@ -105,3 +105,4 @@ export { getFolderContainingFile, } from './dataFileUtils'; export {isDraft, isUnlisted} from './contentVisibilityUtils'; +export {escapeRegexp} from './regExpUtils'; diff --git a/packages/docusaurus-utils/src/regExpUtils.ts b/packages/docusaurus-utils/src/regExpUtils.ts new file mode 100644 index 000000000000..7240188ced96 --- /dev/null +++ b/packages/docusaurus-utils/src/regExpUtils.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. + */ + +import escapeStringRegexp from 'escape-string-regexp'; + +export function escapeRegexp(string: string): string { + return escapeStringRegexp(string); +} From 8fb0dae3d5fbd2214d2f6bbf55ff409423780611 Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 22 Dec 2022 16:29:40 +0100 Subject: [PATCH 07/11] better replaceSearchResultPathname tests --- .../src/__tests__/validateThemeConfig.test.ts | 59 +++++++++++++------ 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts b/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts index 46ad3a84c395..7559aef403ba 100644 --- a/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts +++ b/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts @@ -121,25 +121,50 @@ describe('validateThemeConfig', () => { }); }); - it('replaceSearchResultPathname config', () => { - const algolia = { - appId: 'BH4D9OD16A', - indexName: 'index', - apiKey: 'apiKey', - replaceSearchResultPathname: { - from: '/docs/', - to: '/', - }, - }; - expect(testValidateThemeConfig({algolia})).toEqual({ - algolia: { - ...DEFAULT_CONFIG, - ...algolia, + describe('replaceSearchResultPathname', () => { + it('escapes from string', () => { + const algolia = { + appId: 'BH4D9OD16A', + indexName: 'index', + apiKey: 'apiKey', replaceSearchResultPathname: { - from: '\\/docs\\/', - to: '/', + from: '/docs/some-\\special-.[regexp]{chars*}', + to: '/abc', }, - }, + }; + expect(testValidateThemeConfig({algolia})).toEqual({ + algolia: { + ...DEFAULT_CONFIG, + ...algolia, + replaceSearchResultPathname: { + from: '/docs/some\\x2d\\\\special\\x2d\\.\\[regexp\\]\\{chars\\*\\}', + to: '/abc', + }, + }, + }); + }); + + it('converts from regexp to string', () => { + const algolia = { + appId: 'BH4D9OD16A', + indexName: 'index', + apiKey: 'apiKey', + replaceSearchResultPathname: { + from: /^\/docs\/(?:1\.0|next)/, + to: '/abc', + }, + }; + + expect(testValidateThemeConfig({algolia})).toEqual({ + algolia: { + ...DEFAULT_CONFIG, + ...algolia, + replaceSearchResultPathname: { + from: '^\\/docs\\/(?:1\\.0|next)', + to: '/abc', + }, + }, + }); }); }); From a8f680ddb1e94fdeba544f7e83df3a4a3e20f6f4 Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 22 Dec 2022 17:31:58 +0100 Subject: [PATCH 08/11] memoize withBaseUrl --- packages/docusaurus/src/client/exports/useBaseUrl.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/docusaurus/src/client/exports/useBaseUrl.ts b/packages/docusaurus/src/client/exports/useBaseUrl.ts index c5481e57670f..0ba33b8c24ea 100644 --- a/packages/docusaurus/src/client/exports/useBaseUrl.ts +++ b/packages/docusaurus/src/client/exports/useBaseUrl.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import {useCallback} from 'react'; import useDocusaurusContext from './useDocusaurusContext'; import {hasProtocol} from './isInternalUrl'; import type {BaseUrlOptions, BaseUrlUtils} from '@docusaurus/useBaseUrl'; @@ -43,8 +44,15 @@ export function useBaseUrlUtils(): BaseUrlUtils { const { siteConfig: {baseUrl, url: siteUrl}, } = useDocusaurusContext(); + + const withBaseUrl = useCallback( + (url: string, options?: BaseUrlOptions) => + addBaseUrl(siteUrl, baseUrl, url, options), + [siteUrl, baseUrl], + ); + return { - withBaseUrl: (url, options) => addBaseUrl(siteUrl, baseUrl, url, options), + withBaseUrl, }; } From e08762c0f7f191e83397ec21d77e876217da38fb Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 22 Dec 2022 17:37:12 +0100 Subject: [PATCH 09/11] factorize code across SearchBar & SearchPage - handling of pathname replacement - handling of external urls --- .../src/client/index.ts | 2 + .../src/client/useAlgoliaThemeConfig.ts | 15 ++++++ .../src/client/useSearchResultUrlExtractor.ts | 53 +++++++++++++++++++ .../src/theme-search-algolia.d.ts | 6 +++ .../src/theme/SearchBar/index.tsx | 37 +++---------- .../src/theme/SearchPage/index.tsx | 32 +++-------- website/docusaurus.config.js | 11 ++-- 7 files changed, 99 insertions(+), 57 deletions(-) create mode 100644 packages/docusaurus-theme-search-algolia/src/client/useAlgoliaThemeConfig.ts create mode 100644 packages/docusaurus-theme-search-algolia/src/client/useSearchResultUrlExtractor.ts diff --git a/packages/docusaurus-theme-search-algolia/src/client/index.ts b/packages/docusaurus-theme-search-algolia/src/client/index.ts index a2b338bf27f4..28bef067fb31 100644 --- a/packages/docusaurus-theme-search-algolia/src/client/index.ts +++ b/packages/docusaurus-theme-search-algolia/src/client/index.ts @@ -5,4 +5,6 @@ * LICENSE file in the root directory of this source tree. */ +export {useAlgoliaThemeConfig} from './useAlgoliaThemeConfig'; export {useAlgoliaContextualFacetFilters} from './useAlgoliaContextualFacetFilters'; +export {useSearchResultUrlExtractor} from './useSearchResultUrlExtractor'; diff --git a/packages/docusaurus-theme-search-algolia/src/client/useAlgoliaThemeConfig.ts b/packages/docusaurus-theme-search-algolia/src/client/useAlgoliaThemeConfig.ts new file mode 100644 index 000000000000..80fe16e34ecb --- /dev/null +++ b/packages/docusaurus-theme-search-algolia/src/client/useAlgoliaThemeConfig.ts @@ -0,0 +1,15 @@ +/** + * 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 useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import type {ThemeConfig} from '@docusaurus/theme-search-algolia'; + +export function useAlgoliaThemeConfig(): ThemeConfig { + const { + siteConfig: {themeConfig}, + } = useDocusaurusContext(); + return themeConfig as ThemeConfig; +} diff --git a/packages/docusaurus-theme-search-algolia/src/client/useSearchResultUrlExtractor.ts b/packages/docusaurus-theme-search-algolia/src/client/useSearchResultUrlExtractor.ts new file mode 100644 index 000000000000..7938a52dce39 --- /dev/null +++ b/packages/docusaurus-theme-search-algolia/src/client/useSearchResultUrlExtractor.ts @@ -0,0 +1,53 @@ +/** + * 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 {useCallback} from 'react'; +import {isRegexpStringMatch} from '@docusaurus/theme-common'; +import {useBaseUrlUtils} from '@docusaurus/useBaseUrl'; +import {useAlgoliaThemeConfig} from './useAlgoliaThemeConfig'; +import type {ThemeConfig} from '@docusaurus/theme-search-algolia'; + +type Replacer = (pathname: string) => string; + +function replacePathname( + pathname: string, + replaceSearchResultPathname: ThemeConfig['algolia']['replaceSearchResultPathname'], +): string { + return replaceSearchResultPathname + ? pathname.replaceAll( + new RegExp(replaceSearchResultPathname.from, 'g'), + replaceSearchResultPathname.to, + ) + : pathname; +} + +// Translate search-engine agnostic search filters to Algolia search filters +export function useSearchResultUrlExtractor(): Replacer { + const {withBaseUrl} = useBaseUrlUtils(); + const { + algolia: {externalUrlRegex, replaceSearchResultPathname}, + } = useAlgoliaThemeConfig(); + + return useCallback( + (url: string) => { + const parsedURL = new URL(url); + + // Algolia contains an external domain => navigate to URL + if (isRegexpStringMatch(externalUrlRegex, parsedURL.href)) { + return url; + } + + // Otherwise => transform to relative URL for SPA navigation + const relativeUrl = `${parsedURL.pathname + parsedURL.hash}`; + + return withBaseUrl( + replacePathname(relativeUrl, replaceSearchResultPathname), + ); + }, + [withBaseUrl, externalUrlRegex, replaceSearchResultPathname], + ); +} diff --git a/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts b/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts index d0c2b16ee123..f9fe6a0f02dd 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts +++ b/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts @@ -27,7 +27,13 @@ declare module '@docusaurus/theme-search-algolia' { } declare module '@docusaurus/theme-search-algolia/client' { + import type {ThemeConfig} from '@docusaurus/theme-search-algolia'; + + export function useAlgoliaThemeConfig(): ThemeConfig; + export function useAlgoliaContextualFacetFilters(): [string, string[]]; + + export function useSearchResultUrlExtractor(): (url: string) => string; } declare module '@theme/SearchPage' { diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx index 06f4950f347a..1d54895ba933 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx @@ -12,9 +12,11 @@ import Link from '@docusaurus/Link'; import {useHistory} from '@docusaurus/router'; import {isRegexpStringMatch} from '@docusaurus/theme-common'; import {useSearchPage} from '@docusaurus/theme-common/internal'; -import {useAlgoliaContextualFacetFilters} from '@docusaurus/theme-search-algolia/client'; +import { + useAlgoliaContextualFacetFilters, + useSearchResultUrlExtractor, +} from '@docusaurus/theme-search-algolia/client'; import Translate from '@docusaurus/Translate'; -import {useBaseUrlUtils} from '@docusaurus/useBaseUrl'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import {createPortal} from 'react-dom'; import translations from '@theme/SearchTranslations'; @@ -37,10 +39,6 @@ type DocSearchProps = Omit< contextualSearch?: string; externalUrlRegex?: string; searchPagePath: boolean | string; - replaceSearchResultPathname?: { - from: string; - to: string; - }; }; let DocSearchModal: typeof DocSearchModalType | null = null; @@ -89,10 +87,10 @@ function mergeFacetFilters(f1: FacetFilters, f2: FacetFilters): FacetFilters { function DocSearch({ contextualSearch, externalUrlRegex, - replaceSearchResultPathname, ...props }: DocSearchProps) { const {siteMetadata} = useDocusaurusContext(); + const extractSearchResultUrl = useSearchResultUrlExtractor(); const contextualSearchFacetFilters = useAlgoliaContextualFacetFilters() as FacetFilters; @@ -112,7 +110,6 @@ function DocSearch({ facetFilters, }; - const {withBaseUrl} = useBaseUrlUtils(); const history = useHistory(); const searchContainer = useRef(null); const searchButtonRef = useRef(null); @@ -177,28 +174,10 @@ function DocSearch({ const transformItems = useRef( (items) => - items.map((item) => { - // Replace parts of the URL if the user has added it in the config - const itemUrl = replaceSearchResultPathname - ? item.url.replace( - new RegExp(replaceSearchResultPathname.from), - replaceSearchResultPathname.to, - ) - : item.url; - - // If Algolia contains a external domain, we should navigate without - // relative URL - if (isRegexpStringMatch(externalUrlRegex, itemUrl)) { - return item; - } - - // We transform the absolute URL into a relative URL. - const url = new URL(itemUrl); - return { + items.map((item) => ({ ...item, - url: withBaseUrl(`${url.pathname}${url.hash}`), - }; - }), + url: extractSearchResultUrl(item.url), + })), ).current; const resultsFooterComponent: DocSearchProps['resultsFooterComponent'] = diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx index e277aa2eaf0e..e044db51cfcc 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx @@ -19,7 +19,6 @@ import Link from '@docusaurus/Link'; import {useAllDocsData} from '@docusaurus/plugin-content-docs/client'; import { HtmlClassNameProvider, - isRegexpStringMatch, useEvent, usePluralForm, } from '@docusaurus/theme-common'; @@ -29,10 +28,12 @@ import { } from '@docusaurus/theme-common/internal'; import Translate, {translate} from '@docusaurus/Translate'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import { + useAlgoliaThemeConfig, + useSearchResultUrlExtractor, +} from '@docusaurus/theme-search-algolia/client'; import Layout from '@theme/Layout'; -import type {ThemeConfig} from '@docusaurus/theme-search-algolia'; - import styles from './styles.module.css'; // Very simple pluralization: probably good enough for now @@ -157,18 +158,12 @@ type ResultDispatcher = function SearchPageContent(): JSX.Element { const { - siteConfig: {themeConfig}, i18n: {currentLocale}, } = useDocusaurusContext(); const { - algolia: { - appId, - apiKey, - indexName, - externalUrlRegex, - replaceSearchResultPathname, - }, - } = themeConfig as ThemeConfig; + algolia: {appId, apiKey, indexName}, + } = useAlgoliaThemeConfig(); + const extractSearchResultUrl = useSearchResultUrlExtractor(); const documentsFoundPlural = useDocumentsFoundPlural(); const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers(); @@ -251,23 +246,12 @@ function SearchPageContent(): JSX.Element { _highlightResult: {hierarchy: {[key: string]: {value: string}}}; _snippetResult: {content?: {value: string}}; }) => { - const parsedURL = new URL( - replaceSearchResultPathname - ? url.replace( - new RegExp(replaceSearchResultPathname.from), - replaceSearchResultPathname.to, - ) - : url, - ); const titles = Object.keys(hierarchy).map((key) => sanitizeValue(hierarchy[key]!.value), ); - return { title: titles.pop()!, - url: isRegexpStringMatch(externalUrlRegex, parsedURL.href) - ? parsedURL.href - : parsedURL.pathname + parsedURL.hash, + url: extractSearchResultUrl(url), summary: snippet.content ? `${sanitizeValue(snippet.content.value)}...` : '', diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 2a5cab97b3ea..870cbff8b812 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -409,10 +409,13 @@ const config = { appId: 'X1Z85QJPUV', apiKey: 'bf7211c161e8205da2f933a02534105a', indexName: 'docusaurus-2', - replaceSearchResultPathname: (isDev || isDeployPreview) ? { - from: /\/docs\/next/g, - to:'/docs' - } : undefined, + replaceSearchResultPathname: + isDev || isDeployPreview + ? { + from: /^\/docs\/next/g, + to: '/docs', + } + : undefined, }, navbar: { hideOnScroll: true, From f5244a1dffe9aadb5367a20806aea51e55bdf070 Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 22 Dec 2022 17:40:59 +0100 Subject: [PATCH 10/11] improve regexp docs + remove versioned docs --- website/docs/search.md | 2 +- website/versioned_docs/version-2.0.1/search.md | 6 ------ website/versioned_docs/version-2.1.0/search.md | 6 ------ website/versioned_docs/version-2.2.0/search.md | 6 ------ 4 files changed, 1 insertion(+), 19 deletions(-) diff --git a/website/docs/search.md b/website/docs/search.md index cebc8f016459..af520241a446 100644 --- a/website/docs/search.md +++ b/website/docs/search.md @@ -106,7 +106,7 @@ module.exports = { // Optional: Replace parts of the item URLs from Algolia. Useful when using the same search index for multiple deployments using a different baseUrl. You can use regexp or string in the `from` param. For example: localhost:3000 vs myCompany.com/docs replaceSearchResultPathname: { - from: '/docs/', + from: '/docs/', // or as RegExp: /\/docs\// to: '/', }, diff --git a/website/versioned_docs/version-2.0.1/search.md b/website/versioned_docs/version-2.0.1/search.md index cebc8f016459..e00e022cfaca 100644 --- a/website/versioned_docs/version-2.0.1/search.md +++ b/website/versioned_docs/version-2.0.1/search.md @@ -104,12 +104,6 @@ module.exports = { // 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: Replace parts of the item URLs from Algolia. Useful when using the same search index for multiple deployments using a different baseUrl. You can use regexp or string in the `from` param. For example: localhost:3000 vs myCompany.com/docs - replaceSearchResultPathname: { - from: '/docs/', - to: '/', - }, - // Optional: Algolia search parameters searchParameters: {}, diff --git a/website/versioned_docs/version-2.1.0/search.md b/website/versioned_docs/version-2.1.0/search.md index cebc8f016459..e00e022cfaca 100644 --- a/website/versioned_docs/version-2.1.0/search.md +++ b/website/versioned_docs/version-2.1.0/search.md @@ -104,12 +104,6 @@ module.exports = { // 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: Replace parts of the item URLs from Algolia. Useful when using the same search index for multiple deployments using a different baseUrl. You can use regexp or string in the `from` param. For example: localhost:3000 vs myCompany.com/docs - replaceSearchResultPathname: { - from: '/docs/', - to: '/', - }, - // Optional: Algolia search parameters searchParameters: {}, diff --git a/website/versioned_docs/version-2.2.0/search.md b/website/versioned_docs/version-2.2.0/search.md index cebc8f016459..e00e022cfaca 100644 --- a/website/versioned_docs/version-2.2.0/search.md +++ b/website/versioned_docs/version-2.2.0/search.md @@ -104,12 +104,6 @@ module.exports = { // 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: Replace parts of the item URLs from Algolia. Useful when using the same search index for multiple deployments using a different baseUrl. You can use regexp or string in the `from` param. For example: localhost:3000 vs myCompany.com/docs - replaceSearchResultPathname: { - from: '/docs/', - to: '/', - }, - // Optional: Algolia search parameters searchParameters: {}, From 78477340c6d94cb825799141263a7f3ff2811ddf Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 22 Dec 2022 17:46:53 +0100 Subject: [PATCH 11/11] rename to useSearchResultUrlProcessor --- .../src/client/index.ts | 2 +- ...tUrlExtractor.ts => useSearchResultUrlProcessor.ts} | 9 +++++---- .../src/theme-search-algolia.d.ts | 2 +- .../src/theme/SearchBar/index.tsx | 10 +++++----- .../src/theme/SearchPage/index.tsx | 6 +++--- 5 files changed, 15 insertions(+), 14 deletions(-) rename packages/docusaurus-theme-search-algolia/src/client/{useSearchResultUrlExtractor.ts => useSearchResultUrlProcessor.ts} (88%) diff --git a/packages/docusaurus-theme-search-algolia/src/client/index.ts b/packages/docusaurus-theme-search-algolia/src/client/index.ts index 28bef067fb31..5050ce6aa057 100644 --- a/packages/docusaurus-theme-search-algolia/src/client/index.ts +++ b/packages/docusaurus-theme-search-algolia/src/client/index.ts @@ -7,4 +7,4 @@ export {useAlgoliaThemeConfig} from './useAlgoliaThemeConfig'; export {useAlgoliaContextualFacetFilters} from './useAlgoliaContextualFacetFilters'; -export {useSearchResultUrlExtractor} from './useSearchResultUrlExtractor'; +export {useSearchResultUrlProcessor} from './useSearchResultUrlProcessor'; diff --git a/packages/docusaurus-theme-search-algolia/src/client/useSearchResultUrlExtractor.ts b/packages/docusaurus-theme-search-algolia/src/client/useSearchResultUrlProcessor.ts similarity index 88% rename from packages/docusaurus-theme-search-algolia/src/client/useSearchResultUrlExtractor.ts rename to packages/docusaurus-theme-search-algolia/src/client/useSearchResultUrlProcessor.ts index 7938a52dce39..0414d19f2a42 100644 --- a/packages/docusaurus-theme-search-algolia/src/client/useSearchResultUrlExtractor.ts +++ b/packages/docusaurus-theme-search-algolia/src/client/useSearchResultUrlProcessor.ts @@ -11,8 +11,6 @@ import {useBaseUrlUtils} from '@docusaurus/useBaseUrl'; import {useAlgoliaThemeConfig} from './useAlgoliaThemeConfig'; import type {ThemeConfig} from '@docusaurus/theme-search-algolia'; -type Replacer = (pathname: string) => string; - function replacePathname( pathname: string, replaceSearchResultPathname: ThemeConfig['algolia']['replaceSearchResultPathname'], @@ -25,8 +23,11 @@ function replacePathname( : pathname; } -// Translate search-engine agnostic search filters to Algolia search filters -export function useSearchResultUrlExtractor(): Replacer { +/** + * Process the search result url from Algolia to its final form, ready to be + * navigated to or used as a link + */ +export function useSearchResultUrlProcessor(): (url: string) => string { const {withBaseUrl} = useBaseUrlUtils(); const { algolia: {externalUrlRegex, replaceSearchResultPathname}, diff --git a/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts b/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts index f9fe6a0f02dd..a6ce183b5dd7 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts +++ b/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts @@ -33,7 +33,7 @@ declare module '@docusaurus/theme-search-algolia/client' { export function useAlgoliaContextualFacetFilters(): [string, string[]]; - export function useSearchResultUrlExtractor(): (url: string) => string; + export function useSearchResultUrlProcessor(): (url: string) => string; } declare module '@theme/SearchPage' { diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx index 1d54895ba933..5e7db3fc6ec2 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx @@ -14,7 +14,7 @@ import {isRegexpStringMatch} from '@docusaurus/theme-common'; import {useSearchPage} from '@docusaurus/theme-common/internal'; import { useAlgoliaContextualFacetFilters, - useSearchResultUrlExtractor, + useSearchResultUrlProcessor, } from '@docusaurus/theme-search-algolia/client'; import Translate from '@docusaurus/Translate'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; @@ -90,7 +90,7 @@ function DocSearch({ ...props }: DocSearchProps) { const {siteMetadata} = useDocusaurusContext(); - const extractSearchResultUrl = useSearchResultUrlExtractor(); + const processSearchResultUrl = useSearchResultUrlProcessor(); const contextualSearchFacetFilters = useAlgoliaContextualFacetFilters() as FacetFilters; @@ -175,9 +175,9 @@ function DocSearch({ const transformItems = useRef( (items) => items.map((item) => ({ - ...item, - url: extractSearchResultUrl(item.url), - })), + ...item, + url: processSearchResultUrl(item.url), + })), ).current; const resultsFooterComponent: DocSearchProps['resultsFooterComponent'] = diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx index e044db51cfcc..c85953a62e40 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx @@ -30,7 +30,7 @@ import Translate, {translate} from '@docusaurus/Translate'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import { useAlgoliaThemeConfig, - useSearchResultUrlExtractor, + useSearchResultUrlProcessor, } from '@docusaurus/theme-search-algolia/client'; import Layout from '@theme/Layout'; @@ -163,7 +163,7 @@ function SearchPageContent(): JSX.Element { const { algolia: {appId, apiKey, indexName}, } = useAlgoliaThemeConfig(); - const extractSearchResultUrl = useSearchResultUrlExtractor(); + const processSearchResultUrl = useSearchResultUrlProcessor(); const documentsFoundPlural = useDocumentsFoundPlural(); const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers(); @@ -251,7 +251,7 @@ function SearchPageContent(): JSX.Element { ); return { title: titles.pop()!, - url: extractSearchResultUrl(url), + url: processSearchResultUrl(url), summary: snippet.content ? `${sanitizeValue(snippet.content.value)}...` : '',