This commit is contained in:
2024-03-22 03:47:51 +05:30
parent 8bcf3d211e
commit 89819f6fe2
28440 changed files with 3211033 additions and 2 deletions

21
node_modules/@docusaurus/theme-search-algolia/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) Facebook, Inc. and its affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,7 @@
# `@docusaurus/theme-search-algolia`
Algolia search component for Docusaurus.
## Usage
See [`@docusaurus/theme-search-algolia` documentation](https://docusaurus.io/docs/api/themes/@docusaurus/theme-search-algolia).

View File

@@ -0,0 +1,9 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export { useAlgoliaThemeConfig } from './useAlgoliaThemeConfig';
export { useAlgoliaContextualFacetFilters } from './useAlgoliaContextualFacetFilters';
export { useSearchResultUrlProcessor } from './useSearchResultUrlProcessor';

View File

@@ -0,0 +1,9 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export { useAlgoliaThemeConfig } from './useAlgoliaThemeConfig';
export { useAlgoliaContextualFacetFilters } from './useAlgoliaContextualFacetFilters';
export { useSearchResultUrlProcessor } from './useSearchResultUrlProcessor';

View File

@@ -0,0 +1,7 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export declare function useAlgoliaContextualFacetFilters(): [string, string[]];

View File

@@ -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 { useContextualSearchFilters } from '@docusaurus/theme-common';
// Translate search-engine agnostic search filters to Algolia search filters
export function useAlgoliaContextualFacetFilters() {
const { locale, tags } = useContextualSearchFilters();
// Seems safe to convert locale->language, see AlgoliaSearchMetadata comment
const languageFilter = `language:${locale}`;
const tagsFilter = tags.map((tag) => `docusaurus_tag:${tag}`);
return [languageFilter, tagsFilter];
}

View File

@@ -0,0 +1,2 @@
import type { ThemeConfig } from '@docusaurus/theme-search-algolia';
export declare function useAlgoliaThemeConfig(): ThemeConfig;

View File

@@ -0,0 +1,11 @@
/**
* 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';
export function useAlgoliaThemeConfig() {
const { siteConfig: { themeConfig }, } = useDocusaurusContext();
return themeConfig;
}

View File

@@ -0,0 +1,11 @@
/**
* 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.
*/
/**
* Process the search result url from Algolia to its final form, ready to be
* navigated to or used as a link
*/
export declare function useSearchResultUrlProcessor(): (url: string) => string;

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { useCallback } from 'react';
import { isRegexpStringMatch } from '@docusaurus/theme-common';
import { useBaseUrlUtils } from '@docusaurus/useBaseUrl';
import { useAlgoliaThemeConfig } from './useAlgoliaThemeConfig';
function replacePathname(pathname, replaceSearchResultPathname) {
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() {
const { withBaseUrl } = useBaseUrlUtils();
const { algolia: { externalUrlRegex, replaceSearchResultPathname }, } = useAlgoliaThemeConfig();
return useCallback((url) => {
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]);
}

View File

@@ -0,0 +1,9 @@
/**
* 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 type { LoadContext, Plugin } from '@docusaurus/types';
export default function themeSearchAlgolia(context: LoadContext): Plugin<void>;
export { validateThemeConfig } from './validateThemeConfig';

View File

@@ -0,0 +1,90 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateThemeConfig = void 0;
const tslib_1 = require("tslib");
const path_1 = tslib_1.__importDefault(require("path"));
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const eta_1 = require("eta");
const utils_1 = require("@docusaurus/utils");
const theme_translations_1 = require("@docusaurus/theme-translations");
const opensearch_1 = tslib_1.__importDefault(require("./templates/opensearch"));
const getCompiledOpenSearchTemplate = lodash_1.default.memoize(() => (0, eta_1.compile)(opensearch_1.default.trim()));
function renderOpenSearchTemplate(data) {
const compiled = getCompiledOpenSearchTemplate();
return compiled(data, eta_1.defaultConfig);
}
const OPEN_SEARCH_FILENAME = 'opensearch.xml';
function themeSearchAlgolia(context) {
const { baseUrl, siteConfig: { title, url, favicon, themeConfig }, i18n: { currentLocale }, } = context;
const { algolia: { searchPagePath }, } = themeConfig;
return {
name: 'docusaurus-theme-search-algolia',
getThemePath() {
return '../lib/theme';
},
getTypeScriptThemePath() {
return '../src/theme';
},
getDefaultCodeTranslationMessages() {
return (0, theme_translations_1.readDefaultCodeTranslationMessages)({
locale: currentLocale,
name: 'theme-search-algolia',
});
},
contentLoaded({ actions: { addRoute } }) {
if (searchPagePath) {
addRoute({
path: (0, utils_1.normalizeUrl)([baseUrl, searchPagePath]),
component: '@theme/SearchPage',
exact: true,
});
}
},
async postBuild({ outDir }) {
if (searchPagePath) {
const siteUrl = (0, utils_1.normalizeUrl)([url, baseUrl]);
try {
await fs_extra_1.default.writeFile(path_1.default.join(outDir, OPEN_SEARCH_FILENAME), renderOpenSearchTemplate({
title,
siteUrl,
searchUrl: (0, utils_1.normalizeUrl)([siteUrl, searchPagePath]),
faviconUrl: favicon ? (0, utils_1.normalizeUrl)([siteUrl, favicon]) : null,
}));
}
catch (err) {
logger_1.default.error('Generating OpenSearch file failed.');
throw err;
}
}
},
injectHtmlTags() {
if (!searchPagePath) {
return {};
}
return {
headTags: [
{
tagName: 'link',
attributes: {
rel: 'search',
type: 'application/opensearchdescription+xml',
title,
href: (0, utils_1.normalizeUrl)([baseUrl, OPEN_SEARCH_FILENAME]),
},
},
],
};
},
};
}
exports.default = themeSearchAlgolia;
var validateThemeConfig_1 = require("./validateThemeConfig");
Object.defineProperty(exports, "validateThemeConfig", { enumerable: true, get: function () { return validateThemeConfig_1.validateThemeConfig; } });

View File

@@ -0,0 +1,8 @@
/**
* 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.
*/
declare const _default: "\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\"\n xmlns:moz=\"http://www.mozilla.org/2006/browser/search/\">\n <ShortName><%= it.title %></ShortName>\n <Description>Search <%= it.title %></Description>\n <InputEncoding>UTF-8</InputEncoding>\n <% if (it.faviconUrl) { _%>\n <Image width=\"16\" height=\"16\" type=\"image/x-icon\"><%= it.faviconUrl %></Image>\n <% } _%>\n <Url type=\"text/html\" method=\"get\" template=\"<%= it.searchUrl %>?q={searchTerms}\"/>\n <Url type=\"application/opensearchdescription+xml\" rel=\"self\" template=\"<%= it.siteUrl %>opensearch.xml\" />\n <moz:SearchForm><%= it.siteUrl %></moz:SearchForm>\n</OpenSearchDescription>\n";
export default _default;

View File

@@ -0,0 +1,23 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = `
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
xmlns:moz="http://www.mozilla.org/2006/browser/search/">
<ShortName><%= it.title %></ShortName>
<Description>Search <%= it.title %></Description>
<InputEncoding>UTF-8</InputEncoding>
<% if (it.faviconUrl) { _%>
<Image width="16" height="16" type="image/x-icon"><%= it.faviconUrl %></Image>
<% } _%>
<Url type="text/html" method="get" template="<%= it.searchUrl %>?q={searchTerms}"/>
<Url type="application/opensearchdescription+xml" rel="self" template="<%= it.siteUrl %>opensearch.xml" />
<moz:SearchForm><%= it.siteUrl %></moz:SearchForm>
</OpenSearchDescription>
`;

View File

@@ -0,0 +1,8 @@
/**
* 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.
*/
/// <reference types="react" />
export default function SearchBar(): JSX.Element;

View File

@@ -0,0 +1,194 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, {useCallback, useMemo, useRef, useState} from 'react';
import {createPortal} from 'react-dom';
import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
import Head from '@docusaurus/Head';
import Link from '@docusaurus/Link';
import {useHistory} from '@docusaurus/router';
import {
isRegexpStringMatch,
useSearchLinkCreator,
} from '@docusaurus/theme-common';
import {
useAlgoliaContextualFacetFilters,
useSearchResultUrlProcessor,
} from '@docusaurus/theme-search-algolia/client';
import Translate from '@docusaurus/Translate';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import translations from '@theme/SearchTranslations';
let DocSearchModal = null;
function Hit({hit, children}) {
return <Link to={hit.url}>{children}</Link>;
}
function ResultsFooter({state, onClose}) {
const createSearchLink = useSearchLinkCreator();
return (
<Link to={createSearchLink(state.query)} onClick={onClose}>
<Translate
id="theme.SearchBar.seeAll"
values={{count: state.context.nbHits}}>
{'See all {count} results'}
</Translate>
</Link>
);
}
function mergeFacetFilters(f1, f2) {
const normalize = (f) => (typeof f === 'string' ? [f] : f);
return [...normalize(f1), ...normalize(f2)];
}
function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
const {siteMetadata} = useDocusaurusContext();
const processSearchResultUrl = useSearchResultUrlProcessor();
const contextualSearchFacetFilters = useAlgoliaContextualFacetFilters();
const configFacetFilters = props.searchParameters?.facetFilters ?? [];
const facetFilters = contextualSearch
? // Merge contextual search filters with config filters
mergeFacetFilters(contextualSearchFacetFilters, configFacetFilters)
: // ... or use config facetFilters
configFacetFilters;
// We let user override default searchParameters if she wants to
const searchParameters = {
...props.searchParameters,
facetFilters,
};
const history = useHistory();
const searchContainer = useRef(null);
const searchButtonRef = useRef(null);
const [isOpen, setIsOpen] = useState(false);
const [initialQuery, setInitialQuery] = useState(undefined);
const importDocSearchModalIfNeeded = useCallback(() => {
if (DocSearchModal) {
return Promise.resolve();
}
return Promise.all([
import('@docsearch/react/modal'),
import('@docsearch/react/style'),
import('./styles.css'),
]).then(([{DocSearchModal: Modal}]) => {
DocSearchModal = Modal;
});
}, []);
const onOpen = useCallback(() => {
importDocSearchModalIfNeeded().then(() => {
searchContainer.current = document.createElement('div');
document.body.insertBefore(
searchContainer.current,
document.body.firstChild,
);
setIsOpen(true);
});
}, [importDocSearchModalIfNeeded, setIsOpen]);
const onClose = useCallback(() => {
setIsOpen(false);
searchContainer.current?.remove();
}, [setIsOpen]);
const onInput = useCallback(
(event) => {
importDocSearchModalIfNeeded().then(() => {
setIsOpen(true);
setInitialQuery(event.key);
});
},
[importDocSearchModalIfNeeded, setIsOpen, setInitialQuery],
);
const navigator = useRef({
navigate({itemUrl}) {
// Algolia results could contain URL's from other domains which cannot
// be served through history and should navigate with window.location
if (isRegexpStringMatch(externalUrlRegex, itemUrl)) {
window.location.href = itemUrl;
} else {
history.push(itemUrl);
}
},
}).current;
const transformItems = useRef((items) =>
props.transformItems
? // Custom transformItems
props.transformItems(items)
: // Default transformItems
items.map((item) => ({
...item,
url: processSearchResultUrl(item.url),
})),
).current;
const resultsFooterComponent = useMemo(
() =>
// eslint-disable-next-line react/no-unstable-nested-components
(footerProps) =>
<ResultsFooter {...footerProps} onClose={onClose} />,
[onClose],
);
const transformSearchClient = useCallback(
(searchClient) => {
searchClient.addAlgoliaAgent(
'docusaurus',
siteMetadata.docusaurusVersion,
);
return searchClient;
},
[siteMetadata.docusaurusVersion],
);
useDocSearchKeyboardEvents({
isOpen,
onOpen,
onClose,
onInput,
searchButtonRef,
});
return (
<>
<Head>
{/* This hints the browser that the website will load data from Algolia,
and allows it to preconnect to the DocSearch cluster. It makes the first
query faster, especially on mobile. */}
<link
rel="preconnect"
href={`https://${props.appId}-dsn.algolia.net`}
crossOrigin="anonymous"
/>
</Head>
<DocSearchButton
onTouchStart={importDocSearchModalIfNeeded}
onFocus={importDocSearchModalIfNeeded}
onMouseOver={importDocSearchModalIfNeeded}
onClick={onOpen}
ref={searchButtonRef}
translations={translations.button}
/>
{isOpen &&
DocSearchModal &&
searchContainer.current &&
createPortal(
<DocSearchModal
onClose={onClose}
initialScrollY={window.scrollY}
initialQuery={initialQuery}
navigator={navigator}
transformItems={transformItems}
hitComponent={Hit}
transformSearchClient={transformSearchClient}
{...(props.searchPagePath && {
resultsFooterComponent,
})}
{...props}
searchParameters={searchParameters}
placeholder={translations.placeholder}
translations={translations.modal}
/>,
searchContainer.current,
)}
</>
);
}
export default function SearchBar() {
const {siteConfig} = useDocusaurusContext();
return <DocSearch {...siteConfig.themeConfig.algolia} />;
}

View File

@@ -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.
*/
:root {
--docsearch-primary-color: var(--ifm-color-primary);
--docsearch-text-color: var(--ifm-font-color-base);
}
.DocSearch-Button {
margin: 0;
transition: all var(--ifm-transition-fast)
var(--ifm-transition-timing-default);
}
.DocSearch-Container {
z-index: calc(var(--ifm-z-index-fixed) + 1);
}

View File

@@ -0,0 +1,8 @@
/**
* 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.
*/
/// <reference types="react" />
export default function SearchPage(): JSX.Element;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,119 @@
/**
* 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.
*/
.searchQueryInput,
.searchVersionInput {
border-radius: var(--ifm-global-radius);
border: 2px solid var(--ifm-toc-border-color);
font: var(--ifm-font-size-base) var(--ifm-font-family-base);
padding: 0.8rem;
width: 100%;
background: var(--docsearch-searchbox-focus-background);
color: var(--docsearch-text-color);
margin-bottom: 0.5rem;
transition: border var(--ifm-transition-fast) ease;
}
.searchQueryInput:focus,
.searchVersionInput:focus {
border-color: var(--docsearch-primary-color);
outline: none;
}
.searchQueryInput::placeholder {
color: var(--docsearch-muted-color);
}
.searchResultsColumn {
font-size: 0.9rem;
font-weight: bold;
}
.algoliaLogo {
max-width: 150px;
}
.algoliaLogoPathFill {
fill: var(--ifm-font-color-base);
}
.searchResultItem {
padding: 1rem 0;
border-bottom: 1px solid var(--ifm-toc-border-color);
}
.searchResultItemHeading {
font-weight: 400;
margin-bottom: 0;
}
.searchResultItemPath {
font-size: 0.8rem;
color: var(--ifm-color-content-secondary);
--ifm-breadcrumb-separator-size-multiplier: 1;
}
.searchResultItemSummary {
margin: 0.5rem 0 0;
font-style: italic;
}
@media only screen and (max-width: 996px) {
.searchQueryColumn {
max-width: 60% !important;
}
.searchVersionColumn {
max-width: 40% !important;
}
.searchResultsColumn {
max-width: 60% !important;
}
.searchLogoColumn {
max-width: 40% !important;
padding-left: 0 !important;
}
}
@media screen and (max-width: 576px) {
.searchQueryColumn {
max-width: 100% !important;
}
.searchVersionColumn {
max-width: 100% !important;
padding-left: var(--ifm-spacing-horizontal) !important;
}
}
.loadingSpinner {
width: 3rem;
height: 3rem;
border: 0.4em solid #eee;
border-top-color: var(--ifm-color-primary);
border-radius: 50%;
animation: loading-spin 1s linear infinite;
margin: 0 auto;
}
@keyframes loading-spin {
100% {
transform: rotate(360deg);
}
}
.loader {
margin-top: 2rem;
}
:global(.search-result-match) {
color: var(--docsearch-hit-color);
background: rgb(255 215 142 / 25%);
padding: 0.09em 0;
}

View File

@@ -0,0 +1,11 @@
/**
* 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 type { DocSearchTranslations } from '@docsearch/react';
declare const translations: DocSearchTranslations & {
placeholder: string;
};
export default translations;

View File

@@ -0,0 +1,167 @@
/**
* 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 {translate} from '@docusaurus/Translate';
const translations = {
button: {
buttonText: translate({
id: 'theme.SearchBar.label',
message: 'Search',
description: 'The ARIA label and placeholder for search button',
}),
buttonAriaLabel: translate({
id: 'theme.SearchBar.label',
message: 'Search',
description: 'The ARIA label and placeholder for search button',
}),
},
modal: {
searchBox: {
resetButtonTitle: translate({
id: 'theme.SearchModal.searchBox.resetButtonTitle',
message: 'Clear the query',
description: 'The label and ARIA label for search box reset button',
}),
resetButtonAriaLabel: translate({
id: 'theme.SearchModal.searchBox.resetButtonTitle',
message: 'Clear the query',
description: 'The label and ARIA label for search box reset button',
}),
cancelButtonText: translate({
id: 'theme.SearchModal.searchBox.cancelButtonText',
message: 'Cancel',
description: 'The label and ARIA label for search box cancel button',
}),
cancelButtonAriaLabel: translate({
id: 'theme.SearchModal.searchBox.cancelButtonText',
message: 'Cancel',
description: 'The label and ARIA label for search box cancel button',
}),
},
startScreen: {
recentSearchesTitle: translate({
id: 'theme.SearchModal.startScreen.recentSearchesTitle',
message: 'Recent',
description: 'The title for recent searches',
}),
noRecentSearchesText: translate({
id: 'theme.SearchModal.startScreen.noRecentSearchesText',
message: 'No recent searches',
description: 'The text when no recent searches',
}),
saveRecentSearchButtonTitle: translate({
id: 'theme.SearchModal.startScreen.saveRecentSearchButtonTitle',
message: 'Save this search',
description: 'The label for save recent search button',
}),
removeRecentSearchButtonTitle: translate({
id: 'theme.SearchModal.startScreen.removeRecentSearchButtonTitle',
message: 'Remove this search from history',
description: 'The label for remove recent search button',
}),
favoriteSearchesTitle: translate({
id: 'theme.SearchModal.startScreen.favoriteSearchesTitle',
message: 'Favorite',
description: 'The title for favorite searches',
}),
removeFavoriteSearchButtonTitle: translate({
id: 'theme.SearchModal.startScreen.removeFavoriteSearchButtonTitle',
message: 'Remove this search from favorites',
description: 'The label for remove favorite search button',
}),
},
errorScreen: {
titleText: translate({
id: 'theme.SearchModal.errorScreen.titleText',
message: 'Unable to fetch results',
description: 'The title for error screen of search modal',
}),
helpText: translate({
id: 'theme.SearchModal.errorScreen.helpText',
message: 'You might want to check your network connection.',
description: 'The help text for error screen of search modal',
}),
},
footer: {
selectText: translate({
id: 'theme.SearchModal.footer.selectText',
message: 'to select',
description: 'The explanatory text of the action for the enter key',
}),
selectKeyAriaLabel: translate({
id: 'theme.SearchModal.footer.selectKeyAriaLabel',
message: 'Enter key',
description:
'The ARIA label for the Enter key button that makes the selection',
}),
navigateText: translate({
id: 'theme.SearchModal.footer.navigateText',
message: 'to navigate',
description:
'The explanatory text of the action for the Arrow up and Arrow down key',
}),
navigateUpKeyAriaLabel: translate({
id: 'theme.SearchModal.footer.navigateUpKeyAriaLabel',
message: 'Arrow up',
description:
'The ARIA label for the Arrow up key button that makes the navigation',
}),
navigateDownKeyAriaLabel: translate({
id: 'theme.SearchModal.footer.navigateDownKeyAriaLabel',
message: 'Arrow down',
description:
'The ARIA label for the Arrow down key button that makes the navigation',
}),
closeText: translate({
id: 'theme.SearchModal.footer.closeText',
message: 'to close',
description: 'The explanatory text of the action for Escape key',
}),
closeKeyAriaLabel: translate({
id: 'theme.SearchModal.footer.closeKeyAriaLabel',
message: 'Escape key',
description:
'The ARIA label for the Escape key button that close the modal',
}),
searchByText: translate({
id: 'theme.SearchModal.footer.searchByText',
message: 'Search by',
description: 'The text explain that the search is making by Algolia',
}),
},
noResultsScreen: {
noResultsText: translate({
id: 'theme.SearchModal.noResultsScreen.noResultsText',
message: 'No results for',
description:
'The text explains that there are no results for the following search',
}),
suggestedQueryText: translate({
id: 'theme.SearchModal.noResultsScreen.suggestedQueryText',
message: 'Try searching for',
description:
'The text for the suggested query when no results are found for the following search',
}),
reportMissingResultsText: translate({
id: 'theme.SearchModal.noResultsScreen.reportMissingResultsText',
message: 'Believe this query should return results?',
description:
'The text for the question where the user thinks there are missing results',
}),
reportMissingResultsLinkText: translate({
id: 'theme.SearchModal.noResultsScreen.reportMissingResultsLinkText',
message: 'Let us know.',
description: 'The text for the link to report missing results',
}),
},
},
placeholder: translate({
id: 'theme.SearchModal.placeholder',
message: 'Search docs',
description: 'The placeholder of the input of the DocSearch pop-up modal',
}),
};
export default translations;

View File

@@ -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 { Joi } from '@docusaurus/utils-validation';
import type { ThemeConfig, ThemeConfigValidationContext } from '@docusaurus/types';
export declare const DEFAULT_CONFIG: {
contextualSearch: boolean;
searchParameters: {};
searchPagePath: string;
};
export declare const Schema: Joi.ObjectSchema<ThemeConfig>;
export declare function validateThemeConfig({ validate, themeConfig, }: ThemeConfigValidationContext<ThemeConfig>): ThemeConfig;

View File

@@ -0,0 +1,57 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateThemeConfig = exports.Schema = exports.DEFAULT_CONFIG = void 0;
const utils_1 = require("@docusaurus/utils");
const utils_validation_1 = require("@docusaurus/utils-validation");
exports.DEFAULT_CONFIG = {
// Enabled by default, as it makes sense in most cases
// see also https://github.com/facebook/docusaurus/issues/5880
contextualSearch: true,
searchParameters: {},
searchPagePath: 'search',
};
exports.Schema = utils_validation_1.Joi.object({
algolia: utils_validation_1.Joi.object({
// Docusaurus attributes
contextualSearch: utils_validation_1.Joi.boolean().default(exports.DEFAULT_CONFIG.contextualSearch),
externalUrlRegex: utils_validation_1.Joi.string().optional(),
// Algolia attributes
appId: utils_validation_1.Joi.string().required().messages({
'any.required': '"algolia.appId" is required. If you haven\'t migrated to the new DocSearch infra, please refer to the blog post for instructions: https://docusaurus.io/blog/2021/11/21/algolia-docsearch-migration',
}),
apiKey: utils_validation_1.Joi.string().required(),
indexName: utils_validation_1.Joi.string().required(),
searchParameters: utils_validation_1.Joi.object()
.default(exports.DEFAULT_CONFIG.searchParameters)
.unknown(),
searchPagePath: utils_validation_1.Joi.alternatives()
.try(utils_validation_1.Joi.boolean().invalid(true), utils_validation_1.Joi.string())
.allow(null)
.default(exports.DEFAULT_CONFIG.searchPagePath),
replaceSearchResultPathname: utils_validation_1.Joi.object({
from: utils_validation_1.Joi.custom((from) => {
if (typeof from === 'string') {
return (0, utils_1.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: utils_validation_1.Joi.string().required(),
}).optional(),
})
.label('themeConfig.algolia')
.required()
.unknown(), // DocSearch 3 is still alpha: don't validate the rest for now
});
function validateThemeConfig({ validate, themeConfig, }) {
return validate(exports.Schema, themeConfig);
}
exports.validateThemeConfig = validateThemeConfig;

View File

@@ -0,0 +1,64 @@
{
"name": "@docusaurus/theme-search-algolia",
"version": "3.1.1",
"description": "Algolia search component for Docusaurus.",
"main": "lib/index.js",
"sideEffects": [
"*.css"
],
"exports": {
"./client": {
"types": "./lib/client/index.d.ts",
"default": "./lib/client/index.js"
},
".": {
"types": "./src/theme-search-algolia.d.ts",
"default": "./lib/index.js"
}
},
"types": "src/theme-search-algolia.d.ts",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/facebook/docusaurus.git",
"directory": "packages/docusaurus-theme-search-algolia"
},
"license": "MIT",
"scripts": {
"build": "tsc --build && node ../../admin/scripts/copyUntypedFiles.js && prettier --config ../../.prettierrc --write \"lib/theme/**/*.js\"",
"watch": "run-p -c copy:watch build:watch",
"build:watch": "tsc --build --watch",
"copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
},
"dependencies": {
"@docsearch/react": "^3.5.2",
"@docusaurus/core": "3.1.1",
"@docusaurus/logger": "3.1.1",
"@docusaurus/plugin-content-docs": "3.1.1",
"@docusaurus/theme-common": "3.1.1",
"@docusaurus/theme-translations": "3.1.1",
"@docusaurus/utils": "3.1.1",
"@docusaurus/utils-validation": "3.1.1",
"algoliasearch": "^4.18.0",
"algoliasearch-helper": "^3.13.3",
"clsx": "^2.0.0",
"eta": "^2.2.0",
"fs-extra": "^11.1.1",
"lodash": "^4.17.21",
"tslib": "^2.6.0",
"utility-types": "^3.10.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.1.1"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"engines": {
"node": ">=18.0"
},
"gitHead": "8017f6a6776ba1bd7065e630a52fe2c2654e2f1b"
}

View File

@@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export {useAlgoliaThemeConfig} from './useAlgoliaThemeConfig';
export {useAlgoliaContextualFacetFilters} from './useAlgoliaContextualFacetFilters';
export {useSearchResultUrlProcessor} from './useSearchResultUrlProcessor';

View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {useContextualSearchFilters} from '@docusaurus/theme-common';
// Translate search-engine agnostic search filters to Algolia search filters
export function useAlgoliaContextualFacetFilters(): [string, string[]] {
const {locale, tags} = useContextualSearchFilters();
// Seems safe to convert locale->language, see AlgoliaSearchMetadata comment
const languageFilter = `language:${locale}`;
const tagsFilter = tags.map((tag) => `docusaurus_tag:${tag}`);
return [languageFilter, tagsFilter];
}

View File

@@ -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;
}

View File

@@ -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],
);
}

View File

@@ -0,0 +1,20 @@
/**
* 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.
*/
declare module '@docsearch/react/modal';
declare module '@docsearch/react/style';
// TODO incompatible declaration file
declare module 'eta' {
export const defaultConfig: object;
export function compile(
template: string,
options?: object,
): (data: object, config: object) => string;
}

View File

@@ -0,0 +1,116 @@
/**
* 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 path from 'path';
import fs from 'fs-extra';
import _ from 'lodash';
import logger from '@docusaurus/logger';
import {defaultConfig, compile} from 'eta';
import {normalizeUrl} from '@docusaurus/utils';
import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations';
import openSearchTemplate from './templates/opensearch';
import type {LoadContext, Plugin} from '@docusaurus/types';
import type {ThemeConfig} from '@docusaurus/theme-search-algolia';
const getCompiledOpenSearchTemplate = _.memoize(() =>
compile(openSearchTemplate.trim()),
);
function renderOpenSearchTemplate(data: {
title: string;
siteUrl: string;
searchUrl: string;
faviconUrl: string | null;
}) {
const compiled = getCompiledOpenSearchTemplate();
return compiled(data, defaultConfig);
}
const OPEN_SEARCH_FILENAME = 'opensearch.xml';
export default function themeSearchAlgolia(context: LoadContext): Plugin<void> {
const {
baseUrl,
siteConfig: {title, url, favicon, themeConfig},
i18n: {currentLocale},
} = context;
const {
algolia: {searchPagePath},
} = themeConfig as ThemeConfig;
return {
name: 'docusaurus-theme-search-algolia',
getThemePath() {
return '../lib/theme';
},
getTypeScriptThemePath() {
return '../src/theme';
},
getDefaultCodeTranslationMessages() {
return readDefaultCodeTranslationMessages({
locale: currentLocale,
name: 'theme-search-algolia',
});
},
contentLoaded({actions: {addRoute}}) {
if (searchPagePath) {
addRoute({
path: normalizeUrl([baseUrl, searchPagePath]),
component: '@theme/SearchPage',
exact: true,
});
}
},
async postBuild({outDir}) {
if (searchPagePath) {
const siteUrl = normalizeUrl([url, baseUrl]);
try {
await fs.writeFile(
path.join(outDir, OPEN_SEARCH_FILENAME),
renderOpenSearchTemplate({
title,
siteUrl,
searchUrl: normalizeUrl([siteUrl, searchPagePath]),
faviconUrl: favicon ? normalizeUrl([siteUrl, favicon]) : null,
}),
);
} catch (err) {
logger.error('Generating OpenSearch file failed.');
throw err;
}
}
},
injectHtmlTags() {
if (!searchPagePath) {
return {};
}
return {
headTags: [
{
tagName: 'link',
attributes: {
rel: 'search',
type: 'application/opensearchdescription+xml',
title,
href: normalizeUrl([baseUrl, OPEN_SEARCH_FILENAME]),
},
},
],
};
},
};
}
export {validateThemeConfig} from './validateThemeConfig';

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export default `
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
xmlns:moz="http://www.mozilla.org/2006/browser/search/">
<ShortName><%= it.title %></ShortName>
<Description>Search <%= it.title %></Description>
<InputEncoding>UTF-8</InputEncoding>
<% if (it.faviconUrl) { _%>
<Image width="16" height="16" type="image/x-icon"><%= it.faviconUrl %></Image>
<% } _%>
<Url type="text/html" method="get" template="<%= it.searchUrl %>?q={searchTerms}"/>
<Url type="application/opensearchdescription+xml" rel="self" template="<%= it.siteUrl %>opensearch.xml" />
<moz:SearchForm><%= it.siteUrl %></moz:SearchForm>
</OpenSearchDescription>
`;

View File

@@ -0,0 +1,52 @@
/**
* 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.
*/
declare module '@docusaurus/theme-search-algolia' {
import type {DeepPartial} from 'utility-types';
export type ThemeConfig = {
algolia: {
contextualSearch: boolean;
externalUrlRegex?: string;
appId: string;
apiKey: string;
indexName: string;
searchParameters: {[key: string]: unknown};
searchPagePath: string | false | null;
replaceSearchResultPathname?: {
from: string;
to: string;
};
};
};
export type UserThemeConfig = DeepPartial<ThemeConfig>;
}
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 useSearchResultUrlProcessor(): (url: string) => string;
}
declare module '@theme/SearchPage' {
export default function SearchPage(): JSX.Element;
}
declare module '@theme/SearchBar' {
export default function SearchBar(): JSX.Element;
}
declare module '@theme/SearchTranslations' {
import type {DocSearchTranslations} from '@docsearch/react';
const translations: DocSearchTranslations & {placeholder: string};
export default translations;
}

View File

@@ -0,0 +1,269 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, {useCallback, useMemo, useRef, useState} from 'react';
import {createPortal} from 'react-dom';
import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
import Head from '@docusaurus/Head';
import Link from '@docusaurus/Link';
import {useHistory} from '@docusaurus/router';
import {
isRegexpStringMatch,
useSearchLinkCreator,
} from '@docusaurus/theme-common';
import {
useAlgoliaContextualFacetFilters,
useSearchResultUrlProcessor,
} from '@docusaurus/theme-search-algolia/client';
import Translate from '@docusaurus/Translate';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import translations from '@theme/SearchTranslations';
import type {AutocompleteState} from '@algolia/autocomplete-core';
import type {
DocSearchModal as DocSearchModalType,
DocSearchModalProps,
} from '@docsearch/react';
import type {
InternalDocSearchHit,
StoredDocSearchHit,
} from '@docsearch/react/dist/esm/types';
import type {SearchClient} from 'algoliasearch/lite';
type DocSearchProps = Omit<
DocSearchModalProps,
'onClose' | 'initialScrollY'
> & {
contextualSearch?: string;
externalUrlRegex?: string;
searchPagePath: boolean | string;
};
let DocSearchModal: typeof DocSearchModalType | null = null;
function Hit({
hit,
children,
}: {
hit: InternalDocSearchHit | StoredDocSearchHit;
children: React.ReactNode;
}) {
return <Link to={hit.url}>{children}</Link>;
}
type ResultsFooterProps = {
state: AutocompleteState<InternalDocSearchHit>;
onClose: () => void;
};
function ResultsFooter({state, onClose}: ResultsFooterProps) {
const createSearchLink = useSearchLinkCreator();
return (
<Link to={createSearchLink(state.query)} onClick={onClose}>
<Translate
id="theme.SearchBar.seeAll"
values={{count: state.context.nbHits}}>
{'See all {count} results'}
</Translate>
</Link>
);
}
type FacetFilters = Required<
Required<DocSearchProps>['searchParameters']
>['facetFilters'];
function mergeFacetFilters(f1: FacetFilters, f2: FacetFilters): FacetFilters {
const normalize = (
f: FacetFilters,
): readonly string[] | readonly (string | readonly string[])[] =>
typeof f === 'string' ? [f] : f;
return [...normalize(f1), ...normalize(f2)] as FacetFilters;
}
function DocSearch({
contextualSearch,
externalUrlRegex,
...props
}: DocSearchProps) {
const {siteMetadata} = useDocusaurusContext();
const processSearchResultUrl = useSearchResultUrlProcessor();
const contextualSearchFacetFilters =
useAlgoliaContextualFacetFilters() as FacetFilters;
const configFacetFilters: FacetFilters =
props.searchParameters?.facetFilters ?? [];
const facetFilters: FacetFilters = contextualSearch
? // Merge contextual search filters with config filters
mergeFacetFilters(contextualSearchFacetFilters, configFacetFilters)
: // ... or use config facetFilters
configFacetFilters;
// We let user override default searchParameters if she wants to
const searchParameters: DocSearchProps['searchParameters'] = {
...props.searchParameters,
facetFilters,
};
const history = useHistory();
const searchContainer = useRef<HTMLDivElement | null>(null);
const searchButtonRef = useRef<HTMLButtonElement>(null);
const [isOpen, setIsOpen] = useState(false);
const [initialQuery, setInitialQuery] = useState<string | undefined>(
undefined,
);
const importDocSearchModalIfNeeded = useCallback(() => {
if (DocSearchModal) {
return Promise.resolve();
}
return Promise.all([
import('@docsearch/react/modal') as Promise<
typeof import('@docsearch/react')
>,
import('@docsearch/react/style'),
import('./styles.css'),
]).then(([{DocSearchModal: Modal}]) => {
DocSearchModal = Modal;
});
}, []);
const onOpen = useCallback(() => {
importDocSearchModalIfNeeded().then(() => {
searchContainer.current = document.createElement('div');
document.body.insertBefore(
searchContainer.current,
document.body.firstChild,
);
setIsOpen(true);
});
}, [importDocSearchModalIfNeeded, setIsOpen]);
const onClose = useCallback(() => {
setIsOpen(false);
searchContainer.current?.remove();
}, [setIsOpen]);
const onInput = useCallback(
(event: KeyboardEvent) => {
importDocSearchModalIfNeeded().then(() => {
setIsOpen(true);
setInitialQuery(event.key);
});
},
[importDocSearchModalIfNeeded, setIsOpen, setInitialQuery],
);
const navigator = useRef({
navigate({itemUrl}: {itemUrl?: string}) {
// Algolia results could contain URL's from other domains which cannot
// be served through history and should navigate with window.location
if (isRegexpStringMatch(externalUrlRegex, itemUrl)) {
window.location.href = itemUrl!;
} else {
history.push(itemUrl!);
}
},
}).current;
const transformItems = useRef<DocSearchModalProps['transformItems']>(
(items) =>
props.transformItems
? // Custom transformItems
props.transformItems(items)
: // Default transformItems
items.map((item) => ({
...item,
url: processSearchResultUrl(item.url),
})),
).current;
const resultsFooterComponent: DocSearchProps['resultsFooterComponent'] =
useMemo(
() =>
// eslint-disable-next-line react/no-unstable-nested-components
(footerProps: Omit<ResultsFooterProps, 'onClose'>): JSX.Element =>
<ResultsFooter {...footerProps} onClose={onClose} />,
[onClose],
);
const transformSearchClient = useCallback(
(searchClient: SearchClient) => {
searchClient.addAlgoliaAgent(
'docusaurus',
siteMetadata.docusaurusVersion,
);
return searchClient;
},
[siteMetadata.docusaurusVersion],
);
useDocSearchKeyboardEvents({
isOpen,
onOpen,
onClose,
onInput,
searchButtonRef,
});
return (
<>
<Head>
{/* This hints the browser that the website will load data from Algolia,
and allows it to preconnect to the DocSearch cluster. It makes the first
query faster, especially on mobile. */}
<link
rel="preconnect"
href={`https://${props.appId}-dsn.algolia.net`}
crossOrigin="anonymous"
/>
</Head>
<DocSearchButton
onTouchStart={importDocSearchModalIfNeeded}
onFocus={importDocSearchModalIfNeeded}
onMouseOver={importDocSearchModalIfNeeded}
onClick={onOpen}
ref={searchButtonRef}
translations={translations.button}
/>
{isOpen &&
DocSearchModal &&
searchContainer.current &&
createPortal(
<DocSearchModal
onClose={onClose}
initialScrollY={window.scrollY}
initialQuery={initialQuery}
navigator={navigator}
transformItems={transformItems}
hitComponent={Hit}
transformSearchClient={transformSearchClient}
{...(props.searchPagePath && {
resultsFooterComponent,
})}
{...props}
searchParameters={searchParameters}
placeholder={translations.placeholder}
translations={translations.modal}
/>,
searchContainer.current,
)}
</>
);
}
export default function SearchBar(): JSX.Element {
const {siteConfig} = useDocusaurusContext();
return <DocSearch {...(siteConfig.themeConfig.algolia as DocSearchProps)} />;
}

View File

@@ -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.
*/
:root {
--docsearch-primary-color: var(--ifm-color-primary);
--docsearch-text-color: var(--ifm-font-color-base);
}
.DocSearch-Button {
margin: 0;
transition: all var(--ifm-transition-fast)
var(--ifm-transition-timing-default);
}
.DocSearch-Container {
z-index: calc(var(--ifm-z-index-fixed) + 1);
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,119 @@
/**
* 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.
*/
.searchQueryInput,
.searchVersionInput {
border-radius: var(--ifm-global-radius);
border: 2px solid var(--ifm-toc-border-color);
font: var(--ifm-font-size-base) var(--ifm-font-family-base);
padding: 0.8rem;
width: 100%;
background: var(--docsearch-searchbox-focus-background);
color: var(--docsearch-text-color);
margin-bottom: 0.5rem;
transition: border var(--ifm-transition-fast) ease;
}
.searchQueryInput:focus,
.searchVersionInput:focus {
border-color: var(--docsearch-primary-color);
outline: none;
}
.searchQueryInput::placeholder {
color: var(--docsearch-muted-color);
}
.searchResultsColumn {
font-size: 0.9rem;
font-weight: bold;
}
.algoliaLogo {
max-width: 150px;
}
.algoliaLogoPathFill {
fill: var(--ifm-font-color-base);
}
.searchResultItem {
padding: 1rem 0;
border-bottom: 1px solid var(--ifm-toc-border-color);
}
.searchResultItemHeading {
font-weight: 400;
margin-bottom: 0;
}
.searchResultItemPath {
font-size: 0.8rem;
color: var(--ifm-color-content-secondary);
--ifm-breadcrumb-separator-size-multiplier: 1;
}
.searchResultItemSummary {
margin: 0.5rem 0 0;
font-style: italic;
}
@media only screen and (max-width: 996px) {
.searchQueryColumn {
max-width: 60% !important;
}
.searchVersionColumn {
max-width: 40% !important;
}
.searchResultsColumn {
max-width: 60% !important;
}
.searchLogoColumn {
max-width: 40% !important;
padding-left: 0 !important;
}
}
@media screen and (max-width: 576px) {
.searchQueryColumn {
max-width: 100% !important;
}
.searchVersionColumn {
max-width: 100% !important;
padding-left: var(--ifm-spacing-horizontal) !important;
}
}
.loadingSpinner {
width: 3rem;
height: 3rem;
border: 0.4em solid #eee;
border-top-color: var(--ifm-color-primary);
border-radius: 50%;
animation: loading-spin 1s linear infinite;
margin: 0 auto;
}
@keyframes loading-spin {
100% {
transform: rotate(360deg);
}
}
.loader {
margin-top: 2rem;
}
:global(.search-result-match) {
color: var(--docsearch-hit-color);
background: rgb(255 215 142 / 25%);
padding: 0.09em 0;
}

View File

@@ -0,0 +1,172 @@
/**
* 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 {translate} from '@docusaurus/Translate';
import type {DocSearchTranslations} from '@docsearch/react';
const translations: DocSearchTranslations & {placeholder: string} = {
button: {
buttonText: translate({
id: 'theme.SearchBar.label',
message: 'Search',
description: 'The ARIA label and placeholder for search button',
}),
buttonAriaLabel: translate({
id: 'theme.SearchBar.label',
message: 'Search',
description: 'The ARIA label and placeholder for search button',
}),
},
modal: {
searchBox: {
resetButtonTitle: translate({
id: 'theme.SearchModal.searchBox.resetButtonTitle',
message: 'Clear the query',
description: 'The label and ARIA label for search box reset button',
}),
resetButtonAriaLabel: translate({
id: 'theme.SearchModal.searchBox.resetButtonTitle',
message: 'Clear the query',
description: 'The label and ARIA label for search box reset button',
}),
cancelButtonText: translate({
id: 'theme.SearchModal.searchBox.cancelButtonText',
message: 'Cancel',
description: 'The label and ARIA label for search box cancel button',
}),
cancelButtonAriaLabel: translate({
id: 'theme.SearchModal.searchBox.cancelButtonText',
message: 'Cancel',
description: 'The label and ARIA label for search box cancel button',
}),
},
startScreen: {
recentSearchesTitle: translate({
id: 'theme.SearchModal.startScreen.recentSearchesTitle',
message: 'Recent',
description: 'The title for recent searches',
}),
noRecentSearchesText: translate({
id: 'theme.SearchModal.startScreen.noRecentSearchesText',
message: 'No recent searches',
description: 'The text when no recent searches',
}),
saveRecentSearchButtonTitle: translate({
id: 'theme.SearchModal.startScreen.saveRecentSearchButtonTitle',
message: 'Save this search',
description: 'The label for save recent search button',
}),
removeRecentSearchButtonTitle: translate({
id: 'theme.SearchModal.startScreen.removeRecentSearchButtonTitle',
message: 'Remove this search from history',
description: 'The label for remove recent search button',
}),
favoriteSearchesTitle: translate({
id: 'theme.SearchModal.startScreen.favoriteSearchesTitle',
message: 'Favorite',
description: 'The title for favorite searches',
}),
removeFavoriteSearchButtonTitle: translate({
id: 'theme.SearchModal.startScreen.removeFavoriteSearchButtonTitle',
message: 'Remove this search from favorites',
description: 'The label for remove favorite search button',
}),
},
errorScreen: {
titleText: translate({
id: 'theme.SearchModal.errorScreen.titleText',
message: 'Unable to fetch results',
description: 'The title for error screen of search modal',
}),
helpText: translate({
id: 'theme.SearchModal.errorScreen.helpText',
message: 'You might want to check your network connection.',
description: 'The help text for error screen of search modal',
}),
},
footer: {
selectText: translate({
id: 'theme.SearchModal.footer.selectText',
message: 'to select',
description: 'The explanatory text of the action for the enter key',
}),
selectKeyAriaLabel: translate({
id: 'theme.SearchModal.footer.selectKeyAriaLabel',
message: 'Enter key',
description:
'The ARIA label for the Enter key button that makes the selection',
}),
navigateText: translate({
id: 'theme.SearchModal.footer.navigateText',
message: 'to navigate',
description:
'The explanatory text of the action for the Arrow up and Arrow down key',
}),
navigateUpKeyAriaLabel: translate({
id: 'theme.SearchModal.footer.navigateUpKeyAriaLabel',
message: 'Arrow up',
description:
'The ARIA label for the Arrow up key button that makes the navigation',
}),
navigateDownKeyAriaLabel: translate({
id: 'theme.SearchModal.footer.navigateDownKeyAriaLabel',
message: 'Arrow down',
description:
'The ARIA label for the Arrow down key button that makes the navigation',
}),
closeText: translate({
id: 'theme.SearchModal.footer.closeText',
message: 'to close',
description: 'The explanatory text of the action for Escape key',
}),
closeKeyAriaLabel: translate({
id: 'theme.SearchModal.footer.closeKeyAriaLabel',
message: 'Escape key',
description:
'The ARIA label for the Escape key button that close the modal',
}),
searchByText: translate({
id: 'theme.SearchModal.footer.searchByText',
message: 'Search by',
description: 'The text explain that the search is making by Algolia',
}),
},
noResultsScreen: {
noResultsText: translate({
id: 'theme.SearchModal.noResultsScreen.noResultsText',
message: 'No results for',
description:
'The text explains that there are no results for the following search',
}),
suggestedQueryText: translate({
id: 'theme.SearchModal.noResultsScreen.suggestedQueryText',
message: 'Try searching for',
description:
'The text for the suggested query when no results are found for the following search',
}),
reportMissingResultsText: translate({
id: 'theme.SearchModal.noResultsScreen.reportMissingResultsText',
message: 'Believe this query should return results?',
description:
'The text for the question where the user thinks there are missing results',
}),
reportMissingResultsLinkText: translate({
id: 'theme.SearchModal.noResultsScreen.reportMissingResultsLinkText',
message: 'Let us know.',
description: 'The text for the link to report missing results',
}),
},
},
placeholder: translate({
id: 'theme.SearchModal.placeholder',
message: 'Search docs',
description: 'The placeholder of the input of the DocSearch pop-up modal',
}),
};
export default translations;

View File

@@ -0,0 +1,10 @@
/**
* 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.
*/
/// <reference types="@docusaurus/module-type-aliases" />
/// <reference types="@docusaurus/theme-common" />
/// <reference types="@docusaurus/theme-classic" />

View File

@@ -0,0 +1,67 @@
/**
* 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 {escapeRegexp} from '@docusaurus/utils';
import {Joi} from '@docusaurus/utils-validation';
import type {
ThemeConfig,
ThemeConfigValidationContext,
} from '@docusaurus/types';
export const DEFAULT_CONFIG = {
// Enabled by default, as it makes sense in most cases
// see also https://github.com/facebook/docusaurus/issues/5880
contextualSearch: true,
searchParameters: {},
searchPagePath: 'search',
};
export const Schema = Joi.object<ThemeConfig>({
algolia: Joi.object({
// Docusaurus attributes
contextualSearch: Joi.boolean().default(DEFAULT_CONFIG.contextualSearch),
externalUrlRegex: Joi.string().optional(),
// Algolia attributes
appId: Joi.string().required().messages({
'any.required':
'"algolia.appId" is required. If you haven\'t migrated to the new DocSearch infra, please refer to the blog post for instructions: https://docusaurus.io/blog/2021/11/21/algolia-docsearch-migration',
}),
apiKey: Joi.string().required(),
indexName: Joi.string().required(),
searchParameters: Joi.object()
.default(DEFAULT_CONFIG.searchParameters)
.unknown(),
searchPagePath: Joi.alternatives()
.try(Joi.boolean().invalid(true), Joi.string())
.allow(null)
.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')
.required()
.unknown(), // DocSearch 3 is still alpha: don't validate the rest for now
});
export function validateThemeConfig({
validate,
themeConfig,
}: ThemeConfigValidationContext<ThemeConfig>): ThemeConfig {
return validate(Schema, themeConfig);
}