Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(algolia): simplify SearchBar component #10801

Merged
merged 6 commits into from
Dec 26, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import type {
DocSearchModalProps,
StoredDocSearchHit,
DocSearchTransformClient,
DocSearchHit,
} from '@docsearch/react';

import type {AutocompleteState} from '@algolia/autocomplete-core';
Expand All @@ -50,6 +51,85 @@ type DocSearchProps = Omit<

let DocSearchModal: typeof DocSearchModalType | null = null;

function importDocSearchModalIfNeeded() {
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;
});
}

function useNavigator({
externalUrlRegex,
}: Pick<DocSearchProps, 'externalUrlRegex'>) {
const history = useHistory();
const [navigator] = useState<DocSearchModalProps['navigator']>(() => {
return {
navigate(params) {
// Algolia results could contain URL's from other domains which cannot
// be served through history and should navigate with window.location
if (isRegexpStringMatch(externalUrlRegex, params.itemUrl)) {
window.location.href = params.itemUrl;
} else {
history.push(params.itemUrl);
}
},
};
});
return navigator;
}

function useTransformSearchClient(): DocSearchModalProps['transformSearchClient'] {
const {
siteMetadata: {docusaurusVersion},
} = useDocusaurusContext();
return useCallback(
(searchClient: DocSearchTransformClient) => {
searchClient.addAlgoliaAgent('docusaurus', docusaurusVersion);
return searchClient;
},
[docusaurusVersion],
);
}

function useTransformItems(props: Pick<DocSearchProps, 'transformItems'>) {
const processSearchResultUrl = useSearchResultUrlProcessor();
const [transformItems] = useState<DocSearchModalProps['transformItems']>(
() => {
return (items: DocSearchHit[]) =>
props.transformItems
? // Custom transformItems
props.transformItems(items)
: // Default transformItems
items.map((item) => ({
...item,
url: processSearchResultUrl(item.url),
}));
},
);
return transformItems;
}

function useResultsFooterComponent({
closeModal,
}: {
closeModal: () => void;
}): DocSearchProps['resultsFooterComponent'] {
return useMemo(
() =>
({state}) =>
<ResultsFooter state={state} onClose={closeModal} />,
[closeModal],
);
}

function Hit({
hit,
children,
Expand Down Expand Up @@ -79,19 +159,15 @@ function ResultsFooter({state, onClose}: ResultsFooterProps) {
);
}

function mergeFacetFilters(f1: FacetFilters, f2: FacetFilters): FacetFilters {
const normalize = (f: FacetFilters): FacetFilters =>
typeof f === 'string' ? [f] : f;
return [...normalize(f1), ...normalize(f2)];
}

function DocSearch({
function useSearchParameters({
contextualSearch,
externalUrlRegex,
...props
}: DocSearchProps) {
const {siteMetadata} = useDocusaurusContext();
const processSearchResultUrl = useSearchResultUrlProcessor();
}: DocSearchProps): DocSearchProps['searchParameters'] {
function mergeFacetFilters(f1: FacetFilters, f2: FacetFilters): FacetFilters {
const normalize = (f: FacetFilters): FacetFilters =>
typeof f === 'string' ? [f] : f;
return [...normalize(f1), ...normalize(f2)];
}

const contextualSearchFacetFilters =
useAlgoliaContextualFacetFilters() as FacetFilters;
Expand All @@ -105,37 +181,27 @@ function DocSearch({
: // ... or use config facetFilters
configFacetFilters;

// We let user override default searchParameters if she wants to
const searchParameters: DocSearchProps['searchParameters'] = {
// We let users override default searchParameters if they want to
return {
...props.searchParameters,
facetFilters,
};
}

function DocSearch({externalUrlRegex, ...props}: DocSearchProps) {
const navigator = useNavigator({externalUrlRegex});
const searchParameters = useSearchParameters({...props});
const transformItems = useTransformItems(props);
const transformSearchClient = useTransformSearchClient();

const history = useHistory();
const searchContainer = useRef<HTMLDivElement | null>(null);
// TODO remove after React 19 upgrade?
// TODO remove "as any" after React 19 upgrade
const searchButtonRef = useRef<HTMLButtonElement>(null as any);
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 prepareSearchContainer = useCallback(() => {
if (!searchContainer.current) {
const divElement = document.createElement('div');
Expand All @@ -147,7 +213,7 @@ function DocSearch({
const openModal = useCallback(() => {
prepareSearchContainer();
importDocSearchModalIfNeeded().then(() => setIsOpen(true));
}, [importDocSearchModalIfNeeded, prepareSearchContainer]);
}, [prepareSearchContainer]);

const closeModal = useCallback(() => {
setIsOpen(false);
Expand All @@ -169,51 +235,7 @@ function DocSearch({
[openModal],
);

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;

// @ts-expect-error: TODO fix lib issue after React 19, using JSX.Element
const resultsFooterComponent: DocSearchProps['resultsFooterComponent'] =
useMemo(
() =>
// eslint-disable-next-line react/no-unstable-nested-components
(footerProps: Omit<ResultsFooterProps, 'onClose'>): ReactNode =>
<ResultsFooter {...footerProps} onClose={closeModal} />,
[closeModal],
);

const transformSearchClient = useCallback(
(searchClient: DocSearchTransformClient) => {
searchClient.addAlgoliaAgent(
'docusaurus',
siteMetadata.docusaurusVersion,
);

return searchClient;
},
[siteMetadata.docusaurusVersion],
);
const resultsFooterComponent = useResultsFooterComponent({closeModal});

useDocSearchKeyboardEvents({
isOpen,
Expand Down
Loading