docs: use counter for search bar query (#9429)

Part 2 of https://github.com/puppeteer/puppeteer/pull/9428
This commit is contained in:
jrandolf 2022-12-16 18:15:49 +01:00 committed by GitHub
parent 3235b60dda
commit 65aedcdfc4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 249 additions and 12 deletions

View File

@ -0,0 +1,204 @@
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 Head from '@docusaurus/Head';
import {isRegexpStringMatch} from '@docusaurus/theme-common';
import {useSearchPage} from '@docusaurus/theme-common/internal';
import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
import Translate from '@docusaurus/Translate';
import translations from '@theme/SearchTranslations';
import {useContextualSearchFilters} from '@docusaurus/theme-common';
// eslint-disable-next-line import/extensions
import {tagToCounter} from '../SearchMetadata';
let DocSearchModal = null;
function Hit({hit, children}) {
return <Link to={hit.url}>{children}</Link>;
}
function ResultsFooter({state, onClose}) {
const {generateSearchPageLink} = useSearchPage();
return (
<Link to={generateSearchPageLink(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 => {
return typeof f === 'string' ? [f] : f;
};
return [...normalize(f1), ...normalize(f2)];
}
function usePuppeteerSearchFilters() {
const {locale, tags} = useContextualSearchFilters();
const languageFilter = `language:${locale}`;
return [
languageFilter,
tags.map(tag => {
return `counter:${tagToCounter.get(tag)}`;
}),
];
}
function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
const {siteMetadata} = useDocusaurusContext();
const contextualSearchFacetFilters = usePuppeteerSearchFilters();
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 {withBaseUrl} = useBaseUrlUtils();
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 => {
return items.map(item => {
// If Algolia contains a external domain, we should navigate without
// relative 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;
const resultsFooterComponent = useMemo(() => {
return footerProps => {
return <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,14 @@
: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

@ -4,18 +4,29 @@ import Head from '@docusaurus/Head';
// Tracks the global package version as a local, monotonic counter. This // Tracks the global package version as a local, monotonic counter. This
// prevents Algolia from deleting based on differing package versions and // prevents Algolia from deleting based on differing package versions and
// instead delete based on the number of versions we intend to keep documented. // instead delete based on the number of versions we intend to keep documented.
let globalCounter = -1;
const versionToCounter = new Map();
export default function SearchMetadata({locale, version}) { class MonotonicCountMap {
#counter = -1;
#map = new Map();
get(key) {
console.log(key);
let counter = this.#map.get(key);
if (!counter) {
counter = ++this.#counter;
this.#map.set(key, counter);
}
return counter;
}
}
export const tagToCounter = new MonotonicCountMap();
export default function SearchMetadata({locale, tag}) {
const language = locale; const language = locale;
let counter; let counter;
if (version) { if (tag) {
counter = versionToCounter.get(version); counter = tagToCounter.get(tag);
if (!counter) {
counter = ++globalCounter;
versionToCounter.set(version, counter);
}
} }
return ( return (
<Head> <Head>

View File

@ -20,6 +20,8 @@ import {useAllDocsData} from '@docusaurus/plugin-content-docs/client';
import Translate, {translate} from '@docusaurus/Translate'; import Translate, {translate} from '@docusaurus/Translate';
import Layout from '@theme/Layout'; import Layout from '@theme/Layout';
import styles from './styles.module.css'; import styles from './styles.module.css';
// eslint-disable-next-line import/extensions
import {tagToCounter} from '../SearchMetadata';
// Very simple pluralization: probably good enough for now // Very simple pluralization: probably good enough for now
function useDocumentsFoundPlural() { function useDocumentsFoundPlural() {
const {selectMessage} = usePluralForm(); const {selectMessage} = usePluralForm();
@ -263,11 +265,17 @@ function SearchPageContent() {
const makeSearch = useEvent((page = 0) => { const makeSearch = useEvent((page = 0) => {
algoliaHelper.addDisjunctiveFacetRefinement( algoliaHelper.addDisjunctiveFacetRefinement(
'counter', 'counter',
document tagToCounter.get('default')
.querySelector('meta[name="docsearch:counter"]')
.getAttribute('content')
); );
algoliaHelper.addDisjunctiveFacetRefinement('language', currentLocale); algoliaHelper.addDisjunctiveFacetRefinement('language', currentLocale);
Object.entries(docsSearchVersionsHelpers.searchVersions).forEach(
([pluginId, searchVersion]) => {
algoliaHelper.addDisjunctiveFacetRefinement(
'counter',
tagToCounter.get(`docs-${pluginId}-${searchVersion}`)
);
}
);
algoliaHelper.setQuery(searchQuery).setPage(page).search(); algoliaHelper.setQuery(searchQuery).setPage(page).search();
}); });
useEffect(() => { useEffect(() => {