mirror of
https://github.com/facebook/docusaurus.git
synced 2025-04-30 02:37:59 +02:00
feat(theme-algolia): add option.replaceSearchResultPathname to process/replaceAll search result urls
#8428
This commit is contained in:
parent
e5b0707fab
commit
deb376e4a6
18 changed files with 214 additions and 51 deletions
|
@ -35,9 +35,6 @@
|
||||||
"utility-types": "^3.10.0",
|
"utility-types": "^3.10.0",
|
||||||
"webpack": "^5.73.0"
|
"webpack": "^5.73.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
|
||||||
"escape-string-regexp": "^4.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.8.4 || ^17.0.0",
|
"react": "^16.8.4 || ^17.0.0",
|
||||||
"react-dom": "^16.8.4 || ^17.0.0"
|
"react-dom": "^16.8.4 || ^17.0.0"
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* 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 {validateBlogPostFrontMatter} from '../frontMatter';
|
||||||
import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';
|
import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ function testField(params: {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line jest/no-conditional-expect
|
// eslint-disable-next-line jest/no-conditional-expect
|
||||||
expect((err as Error).message).toMatch(
|
expect((err as Error).message).toMatch(
|
||||||
new RegExp(escapeStringRegexp(message)),
|
new RegExp(escapeRegexp(message)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -56,7 +56,6 @@
|
||||||
"@types/js-yaml": "^4.0.5",
|
"@types/js-yaml": "^4.0.5",
|
||||||
"@types/picomatch": "^2.3.0",
|
"@types/picomatch": "^2.3.0",
|
||||||
"commander": "^5.1.0",
|
"commander": "^5.1.0",
|
||||||
"escape-string-regexp": "^4.0.0",
|
|
||||||
"picomatch": "^2.3.1",
|
"picomatch": "^2.3.1",
|
||||||
"shelljs": "^0.8.5"
|
"shelljs": "^0.8.5"
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* 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 {validateDocFrontMatter} from '../frontMatter';
|
||||||
import type {DocFrontMatter} from '@docusaurus/plugin-content-docs';
|
import type {DocFrontMatter} from '@docusaurus/plugin-content-docs';
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ function testField(params: {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line jest/no-conditional-expect
|
// eslint-disable-next-line jest/no-conditional-expect
|
||||||
expect((err as Error).message).toMatch(
|
expect((err as Error).message).toMatch(
|
||||||
new RegExp(escapeStringRegexp(message)),
|
new RegExp(escapeRegexp(message)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* 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';
|
import type {Joi} from '@docusaurus/utils-validation';
|
||||||
|
|
||||||
function testValidateThemeConfig(themeConfig: {[key: string]: unknown}) {
|
function testValidateThemeConfig(themeConfig: {[key: string]: unknown}) {
|
||||||
|
@ -121,6 +121,53 @@ describe('validateThemeConfig', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('replaceSearchResultPathname', () => {
|
||||||
|
it('escapes from string', () => {
|
||||||
|
const algolia = {
|
||||||
|
appId: 'BH4D9OD16A',
|
||||||
|
indexName: 'index',
|
||||||
|
apiKey: 'apiKey',
|
||||||
|
replaceSearchResultPathname: {
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('searchParameters.facetFilters search config', () => {
|
it('searchParameters.facetFilters search config', () => {
|
||||||
const algolia = {
|
const algolia = {
|
||||||
appId: 'BH4D9OD16A',
|
appId: 'BH4D9OD16A',
|
||||||
|
|
|
@ -5,4 +5,6 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export {useAlgoliaThemeConfig} from './useAlgoliaThemeConfig';
|
||||||
export {useAlgoliaContextualFacetFilters} from './useAlgoliaContextualFacetFilters';
|
export {useAlgoliaContextualFacetFilters} from './useAlgoliaContextualFacetFilters';
|
||||||
|
export {useSearchResultUrlProcessor} from './useSearchResultUrlProcessor';
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/**
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
function replacePathname(
|
||||||
|
pathname: string,
|
||||||
|
replaceSearchResultPathname: ThemeConfig['algolia']['replaceSearchResultPathname'],
|
||||||
|
): string {
|
||||||
|
return replaceSearchResultPathname
|
||||||
|
? pathname.replaceAll(
|
||||||
|
new RegExp(replaceSearchResultPathname.from, 'g'),
|
||||||
|
replaceSearchResultPathname.to,
|
||||||
|
)
|
||||||
|
: pathname;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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},
|
||||||
|
} = 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],
|
||||||
|
);
|
||||||
|
}
|
|
@ -17,13 +17,23 @@ declare module '@docusaurus/theme-search-algolia' {
|
||||||
indexName: string;
|
indexName: string;
|
||||||
searchParameters: {[key: string]: unknown};
|
searchParameters: {[key: string]: unknown};
|
||||||
searchPagePath: string | false | null;
|
searchPagePath: string | false | null;
|
||||||
|
replaceSearchResultPathname?: {
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type UserThemeConfig = DeepPartial<ThemeConfig>;
|
export type UserThemeConfig = DeepPartial<ThemeConfig>;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@docusaurus/theme-search-algolia/client' {
|
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 useAlgoliaContextualFacetFilters(): [string, string[]];
|
||||||
|
|
||||||
|
export function useSearchResultUrlProcessor(): (url: string) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@theme/SearchPage' {
|
declare module '@theme/SearchPage' {
|
||||||
|
|
|
@ -5,20 +5,23 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useState, useRef, useCallback, useMemo} from 'react';
|
import React, {useCallback, useMemo, useRef, useState} from 'react';
|
||||||
import {createPortal} from 'react-dom';
|
import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
|
||||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
||||||
import {useHistory} from '@docusaurus/router';
|
|
||||||
import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
|
|
||||||
import Link from '@docusaurus/Link';
|
|
||||||
import Head from '@docusaurus/Head';
|
import Head from '@docusaurus/Head';
|
||||||
|
import Link from '@docusaurus/Link';
|
||||||
|
import {useHistory} from '@docusaurus/router';
|
||||||
import {isRegexpStringMatch} from '@docusaurus/theme-common';
|
import {isRegexpStringMatch} from '@docusaurus/theme-common';
|
||||||
import {useSearchPage} from '@docusaurus/theme-common/internal';
|
import {useSearchPage} from '@docusaurus/theme-common/internal';
|
||||||
import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
|
import {
|
||||||
import {useAlgoliaContextualFacetFilters} from '@docusaurus/theme-search-algolia/client';
|
useAlgoliaContextualFacetFilters,
|
||||||
|
useSearchResultUrlProcessor,
|
||||||
|
} from '@docusaurus/theme-search-algolia/client';
|
||||||
import Translate from '@docusaurus/Translate';
|
import Translate from '@docusaurus/Translate';
|
||||||
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||||
|
import {createPortal} from 'react-dom';
|
||||||
import translations from '@theme/SearchTranslations';
|
import translations from '@theme/SearchTranslations';
|
||||||
|
|
||||||
|
import type {AutocompleteState} from '@algolia/autocomplete-core';
|
||||||
import type {
|
import type {
|
||||||
DocSearchModal as DocSearchModalType,
|
DocSearchModal as DocSearchModalType,
|
||||||
DocSearchModalProps,
|
DocSearchModalProps,
|
||||||
|
@ -28,7 +31,6 @@ import type {
|
||||||
StoredDocSearchHit,
|
StoredDocSearchHit,
|
||||||
} from '@docsearch/react/dist/esm/types';
|
} from '@docsearch/react/dist/esm/types';
|
||||||
import type {SearchClient} from 'algoliasearch/lite';
|
import type {SearchClient} from 'algoliasearch/lite';
|
||||||
import type {AutocompleteState} from '@algolia/autocomplete-core';
|
|
||||||
|
|
||||||
type DocSearchProps = Omit<
|
type DocSearchProps = Omit<
|
||||||
DocSearchModalProps,
|
DocSearchModalProps,
|
||||||
|
@ -88,6 +90,7 @@ function DocSearch({
|
||||||
...props
|
...props
|
||||||
}: DocSearchProps) {
|
}: DocSearchProps) {
|
||||||
const {siteMetadata} = useDocusaurusContext();
|
const {siteMetadata} = useDocusaurusContext();
|
||||||
|
const processSearchResultUrl = useSearchResultUrlProcessor();
|
||||||
|
|
||||||
const contextualSearchFacetFilters =
|
const contextualSearchFacetFilters =
|
||||||
useAlgoliaContextualFacetFilters() as FacetFilters;
|
useAlgoliaContextualFacetFilters() as FacetFilters;
|
||||||
|
@ -107,7 +110,6 @@ function DocSearch({
|
||||||
facetFilters,
|
facetFilters,
|
||||||
};
|
};
|
||||||
|
|
||||||
const {withBaseUrl} = useBaseUrlUtils();
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const searchContainer = useRef<HTMLDivElement | null>(null);
|
const searchContainer = useRef<HTMLDivElement | null>(null);
|
||||||
const searchButtonRef = useRef<HTMLButtonElement>(null);
|
const searchButtonRef = useRef<HTMLButtonElement>(null);
|
||||||
|
@ -172,20 +174,10 @@ function DocSearch({
|
||||||
|
|
||||||
const transformItems = useRef<DocSearchModalProps['transformItems']>(
|
const transformItems = useRef<DocSearchModalProps['transformItems']>(
|
||||||
(items) =>
|
(items) =>
|
||||||
items.map((item) => {
|
items.map((item) => ({
|
||||||
// If Algolia contains a external domain, we should navigate without
|
...item,
|
||||||
// relative URL
|
url: processSearchResultUrl(item.url),
|
||||||
if (isRegexpStringMatch(externalUrlRegex, item.url)) {
|
})),
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We transform the absolute URL into a relative URL.
|
|
||||||
const url = new URL(item.url);
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
url: withBaseUrl(`${url.pathname}${url.hash}`),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
).current;
|
).current;
|
||||||
|
|
||||||
const resultsFooterComponent: DocSearchProps['resultsFooterComponent'] =
|
const resultsFooterComponent: DocSearchProps['resultsFooterComponent'] =
|
||||||
|
|
|
@ -7,32 +7,34 @@
|
||||||
|
|
||||||
/* eslint-disable jsx-a11y/no-autofocus */
|
/* 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 clsx from 'clsx';
|
||||||
|
|
||||||
import algoliaSearch from 'algoliasearch/lite';
|
|
||||||
import algoliaSearchHelper from 'algoliasearch-helper';
|
import algoliaSearchHelper from 'algoliasearch-helper';
|
||||||
|
import algoliaSearch from 'algoliasearch/lite';
|
||||||
|
|
||||||
|
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
||||||
import Head from '@docusaurus/Head';
|
import Head from '@docusaurus/Head';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
import {useAllDocsData} from '@docusaurus/plugin-content-docs/client';
|
||||||
import {
|
import {
|
||||||
HtmlClassNameProvider,
|
HtmlClassNameProvider,
|
||||||
usePluralForm,
|
|
||||||
isRegexpStringMatch,
|
|
||||||
useEvent,
|
useEvent,
|
||||||
|
usePluralForm,
|
||||||
} from '@docusaurus/theme-common';
|
} from '@docusaurus/theme-common';
|
||||||
import {
|
import {
|
||||||
useTitleFormatter,
|
|
||||||
useSearchPage,
|
useSearchPage,
|
||||||
|
useTitleFormatter,
|
||||||
} from '@docusaurus/theme-common/internal';
|
} 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 Translate, {translate} from '@docusaurus/Translate';
|
||||||
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||||
|
import {
|
||||||
|
useAlgoliaThemeConfig,
|
||||||
|
useSearchResultUrlProcessor,
|
||||||
|
} from '@docusaurus/theme-search-algolia/client';
|
||||||
import Layout from '@theme/Layout';
|
import Layout from '@theme/Layout';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
import type {ThemeConfig} from '@docusaurus/theme-search-algolia';
|
|
||||||
|
|
||||||
// Very simple pluralization: probably good enough for now
|
// Very simple pluralization: probably good enough for now
|
||||||
function useDocumentsFoundPlural() {
|
function useDocumentsFoundPlural() {
|
||||||
|
@ -156,12 +158,12 @@ type ResultDispatcher =
|
||||||
|
|
||||||
function SearchPageContent(): JSX.Element {
|
function SearchPageContent(): JSX.Element {
|
||||||
const {
|
const {
|
||||||
siteConfig: {themeConfig},
|
|
||||||
i18n: {currentLocale},
|
i18n: {currentLocale},
|
||||||
} = useDocusaurusContext();
|
} = useDocusaurusContext();
|
||||||
const {
|
const {
|
||||||
algolia: {appId, apiKey, indexName, externalUrlRegex},
|
algolia: {appId, apiKey, indexName},
|
||||||
} = themeConfig as ThemeConfig;
|
} = useAlgoliaThemeConfig();
|
||||||
|
const processSearchResultUrl = useSearchResultUrlProcessor();
|
||||||
const documentsFoundPlural = useDocumentsFoundPlural();
|
const documentsFoundPlural = useDocumentsFoundPlural();
|
||||||
|
|
||||||
const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers();
|
const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers();
|
||||||
|
@ -244,16 +246,12 @@ function SearchPageContent(): JSX.Element {
|
||||||
_highlightResult: {hierarchy: {[key: string]: {value: string}}};
|
_highlightResult: {hierarchy: {[key: string]: {value: string}}};
|
||||||
_snippetResult: {content?: {value: string}};
|
_snippetResult: {content?: {value: string}};
|
||||||
}) => {
|
}) => {
|
||||||
const parsedURL = new URL(url);
|
|
||||||
const titles = Object.keys(hierarchy).map((key) =>
|
const titles = Object.keys(hierarchy).map((key) =>
|
||||||
sanitizeValue(hierarchy[key]!.value),
|
sanitizeValue(hierarchy[key]!.value),
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: titles.pop()!,
|
title: titles.pop()!,
|
||||||
url: isRegexpStringMatch(externalUrlRegex, parsedURL.href)
|
url: processSearchResultUrl(url),
|
||||||
? parsedURL.href
|
|
||||||
: parsedURL.pathname + parsedURL.hash,
|
|
||||||
summary: snippet.content
|
summary: snippet.content
|
||||||
? `${sanitizeValue(snippet.content.value)}...`
|
? `${sanitizeValue(snippet.content.value)}...`
|
||||||
: '',
|
: '',
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {escapeRegexp} from '@docusaurus/utils';
|
||||||
import {Joi} from '@docusaurus/utils-validation';
|
import {Joi} from '@docusaurus/utils-validation';
|
||||||
import type {
|
import type {
|
||||||
ThemeConfig,
|
ThemeConfig,
|
||||||
|
@ -39,6 +40,19 @@ export const Schema = Joi.object<ThemeConfig>({
|
||||||
.try(Joi.boolean().invalid(true), Joi.string())
|
.try(Joi.boolean().invalid(true), Joi.string())
|
||||||
.allow(null)
|
.allow(null)
|
||||||
.default(DEFAULT_CONFIG.searchPagePath),
|
.default(DEFAULT_CONFIG.searchPagePath),
|
||||||
|
replaceSearchResultPathname: Joi.object({
|
||||||
|
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(),
|
||||||
})
|
})
|
||||||
.label('themeConfig.algolia')
|
.label('themeConfig.algolia')
|
||||||
.required()
|
.required()
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/logger": "2.2.0",
|
"@docusaurus/logger": "2.2.0",
|
||||||
"@svgr/webpack": "^6.2.1",
|
"@svgr/webpack": "^6.2.1",
|
||||||
|
"escape-string-regexp": "^4.0.0",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"fs-extra": "^10.1.0",
|
"fs-extra": "^10.1.0",
|
||||||
"github-slugger": "^1.4.0",
|
"github-slugger": "^1.4.0",
|
||||||
|
|
|
@ -103,3 +103,4 @@ export {
|
||||||
findFolderContainingFile,
|
findFolderContainingFile,
|
||||||
getFolderContainingFile,
|
getFolderContainingFile,
|
||||||
} from './dataFileUtils';
|
} from './dataFileUtils';
|
||||||
|
export {escapeRegexp} from './regExpUtils';
|
||||||
|
|
12
packages/docusaurus-utils/src/regExpUtils.ts
Normal file
12
packages/docusaurus-utils/src/regExpUtils.ts
Normal file
|
@ -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);
|
||||||
|
}
|
|
@ -5,6 +5,7 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {useCallback} from 'react';
|
||||||
import useDocusaurusContext from './useDocusaurusContext';
|
import useDocusaurusContext from './useDocusaurusContext';
|
||||||
import {hasProtocol} from './isInternalUrl';
|
import {hasProtocol} from './isInternalUrl';
|
||||||
import type {BaseUrlOptions, BaseUrlUtils} from '@docusaurus/useBaseUrl';
|
import type {BaseUrlOptions, BaseUrlUtils} from '@docusaurus/useBaseUrl';
|
||||||
|
@ -43,8 +44,15 @@ export function useBaseUrlUtils(): BaseUrlUtils {
|
||||||
const {
|
const {
|
||||||
siteConfig: {baseUrl, url: siteUrl},
|
siteConfig: {baseUrl, url: siteUrl},
|
||||||
} = useDocusaurusContext();
|
} = useDocusaurusContext();
|
||||||
|
|
||||||
|
const withBaseUrl = useCallback(
|
||||||
|
(url: string, options?: BaseUrlOptions) =>
|
||||||
|
addBaseUrl(siteUrl, baseUrl, url, options),
|
||||||
|
[siteUrl, baseUrl],
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
withBaseUrl: (url, options) => addBaseUrl(siteUrl, baseUrl, url, options),
|
withBaseUrl,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
// 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',
|
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/', // or as RegExp: /\/docs\//
|
||||||
|
to: '/',
|
||||||
|
},
|
||||||
|
|
||||||
// Optional: Algolia search parameters
|
// Optional: Algolia search parameters
|
||||||
searchParameters: {},
|
searchParameters: {},
|
||||||
|
|
||||||
|
|
|
@ -401,6 +401,13 @@ const config = {
|
||||||
appId: 'X1Z85QJPUV',
|
appId: 'X1Z85QJPUV',
|
||||||
apiKey: 'bf7211c161e8205da2f933a02534105a',
|
apiKey: 'bf7211c161e8205da2f933a02534105a',
|
||||||
indexName: 'docusaurus-2',
|
indexName: 'docusaurus-2',
|
||||||
|
replaceSearchResultPathname:
|
||||||
|
isDev || isDeployPreview
|
||||||
|
? {
|
||||||
|
from: /^\/docs\/next/g,
|
||||||
|
to: '/docs',
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
navbar: {
|
navbar: {
|
||||||
hideOnScroll: true,
|
hideOnScroll: true,
|
||||||
|
|
Loading…
Add table
Reference in a new issue