67 | );
68 | }
69 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/ScreenState.tsx:
--------------------------------------------------------------------------------
1 | import type { AutocompleteApi, AutocompleteState, BaseItem } from '@algolia/autocomplete-core';
2 | import React from 'react';
3 |
4 | import type { DocSearchProps } from './DocSearch';
5 | import type { ErrorScreenTranslations } from './ErrorScreen';
6 | import { ErrorScreen } from './ErrorScreen';
7 | import type { NoResultsScreenTranslations } from './NoResultsScreen';
8 | import { NoResultsScreen } from './NoResultsScreen';
9 | import { ResultsScreen } from './ResultsScreen';
10 | import type { StartScreenTranslations } from './StartScreen';
11 | import { StartScreen } from './StartScreen';
12 | import type { StoredSearchPlugin } from './stored-searches';
13 | import type { InternalDocSearchHit, StoredDocSearchHit } from './types';
14 |
15 | export type ScreenStateTranslations = Partial<{
16 | errorScreen: ErrorScreenTranslations;
17 | startScreen: StartScreenTranslations;
18 | noResultsScreen: NoResultsScreenTranslations;
19 | }>;
20 |
21 | export interface ScreenStateProps
22 | extends AutocompleteApi {
23 | state: AutocompleteState;
24 | recentSearches: StoredSearchPlugin;
25 | favoriteSearches: StoredSearchPlugin;
26 | onItemClick: (item: InternalDocSearchHit, event: KeyboardEvent | MouseEvent) => void;
27 | inputRef: React.MutableRefObject;
28 | hitComponent: DocSearchProps['hitComponent'];
29 | indexName: DocSearchProps['indexName'];
30 | disableUserPersonalization: boolean;
31 | resultsFooterComponent: DocSearchProps['resultsFooterComponent'];
32 | translations: ScreenStateTranslations;
33 | getMissingResultsUrl?: DocSearchProps['getMissingResultsUrl'];
34 | }
35 |
36 | export const ScreenState = React.memo(
37 | ({ translations = {}, ...props }: ScreenStateProps) => {
38 | if (props.state.status === 'error') {
39 | return ;
40 | }
41 |
42 | const hasCollections = props.state.collections.some((collection) => collection.items.length > 0);
43 |
44 | if (!props.state.query) {
45 | return ;
46 | }
47 |
48 | if (hasCollections === false) {
49 | return ;
50 | }
51 |
52 | return ;
53 | },
54 | function areEqual(_prevProps, nextProps) {
55 | // We don't update the screen when Autocomplete is loading or stalled to
56 | // avoid UI flashes:
57 | // - Empty screen → Results screen
58 | // - NoResults screen → NoResults screen with another query
59 | return nextProps.state.status === 'loading' || nextProps.state.status === 'stalled';
60 | },
61 | );
62 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/SearchBox.tsx:
--------------------------------------------------------------------------------
1 | import type { AutocompleteApi, AutocompleteState } from '@algolia/autocomplete-core';
2 | import React, { type JSX, type RefObject } from 'react';
3 |
4 | import { MAX_QUERY_SIZE } from './constants';
5 | import { LoadingIcon } from './icons/LoadingIcon';
6 | import { ResetIcon } from './icons/ResetIcon';
7 | import { SearchIcon } from './icons/SearchIcon';
8 | import type { InternalDocSearchHit } from './types';
9 |
10 | export type SearchBoxTranslations = Partial<{
11 | resetButtonTitle: string;
12 | resetButtonAriaLabel: string;
13 | cancelButtonText: string;
14 | cancelButtonAriaLabel: string;
15 | searchInputLabel: string;
16 | }>;
17 |
18 | interface SearchBoxProps
19 | extends AutocompleteApi {
20 | state: AutocompleteState;
21 | autoFocus: boolean;
22 | inputRef: RefObject;
23 | onClose: () => void;
24 | isFromSelection: boolean;
25 | translations?: SearchBoxTranslations;
26 | }
27 |
28 | export function SearchBox({ translations = {}, ...props }: SearchBoxProps): JSX.Element {
29 | const {
30 | resetButtonTitle = 'Clear the query',
31 | resetButtonAriaLabel = 'Clear the query',
32 | cancelButtonText = 'Cancel',
33 | cancelButtonAriaLabel = 'Cancel',
34 | searchInputLabel = 'Search',
35 | } = translations;
36 | const { onReset } = props.getFormProps({
37 | inputElement: props.inputRef.current,
38 | });
39 |
40 | React.useEffect(() => {
41 | if (props.autoFocus && props.inputRef.current) {
42 | props.inputRef.current.focus();
43 | }
44 | }, [props.autoFocus, props.inputRef]);
45 |
46 | React.useEffect(() => {
47 | if (props.isFromSelection && props.inputRef.current) {
48 | props.inputRef.current.select();
49 | }
50 | }, [props.isFromSelection, props.inputRef]);
51 |
52 | return (
53 | <>
54 |
90 |
91 |
94 | >
95 | );
96 | }
97 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/Snippet.tsx:
--------------------------------------------------------------------------------
1 | import { type JSX, createElement } from 'react';
2 |
3 | import type { StoredDocSearchHit } from './types';
4 |
5 | function getPropertyByPath(object: Record, path: string): any {
6 | const parts = path.split('.');
7 |
8 | return parts.reduce((prev, current) => {
9 | if (prev?.[current]) return prev[current];
10 | return null;
11 | }, object);
12 | }
13 |
14 | interface SnippetProps {
15 | hit: TItem;
16 | attribute: string;
17 | tagName?: string;
18 | [prop: string]: unknown;
19 | }
20 |
21 | export function Snippet({
22 | hit,
23 | attribute,
24 | tagName = 'span',
25 | ...rest
26 | }: SnippetProps): JSX.Element {
27 | return createElement(tagName, {
28 | ...rest,
29 | dangerouslySetInnerHTML: {
30 | __html: getPropertyByPath(hit, `_snippetResult.${attribute}.value`) || getPropertyByPath(hit, attribute),
31 | },
32 | });
33 | }
34 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const MAX_QUERY_SIZE = 64;
2 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/icons/ControlKeyIcon.tsx:
--------------------------------------------------------------------------------
1 | import React, { type JSX } from 'react';
2 |
3 | export function ControlKeyIcon(): JSX.Element {
4 | return (
5 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/icons/ErrorIcon.tsx:
--------------------------------------------------------------------------------
1 | import React, { type JSX } from 'react';
2 |
3 | export function ErrorIcon(): JSX.Element {
4 | return (
5 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/icons/GoToExternalIcon.tsx:
--------------------------------------------------------------------------------
1 | import React, { type JSX } from 'react';
2 |
3 | export function GoToExternal(): JSX.Element {
4 | return (
5 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/icons/LoadingIcon.tsx:
--------------------------------------------------------------------------------
1 | import React, { type JSX } from 'react';
2 |
3 | export function LoadingIcon(): JSX.Element {
4 | return (
5 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/icons/NoResultsIcon.tsx:
--------------------------------------------------------------------------------
1 | import React, { type JSX } from 'react';
2 |
3 | export function NoResultsIcon(): JSX.Element {
4 | return (
5 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/icons/RecentIcon.tsx:
--------------------------------------------------------------------------------
1 | import React, { type JSX } from 'react';
2 |
3 | export function RecentIcon(): JSX.Element {
4 | return (
5 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/icons/ResetIcon.tsx:
--------------------------------------------------------------------------------
1 | import React, { type JSX } from 'react';
2 |
3 | export function ResetIcon(): JSX.Element {
4 | return (
5 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/icons/SearchIcon.tsx:
--------------------------------------------------------------------------------
1 | import React, { type JSX } from 'react';
2 |
3 | export function SearchIcon(): JSX.Element {
4 | return (
5 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/icons/SelectIcon.tsx:
--------------------------------------------------------------------------------
1 | import React, { type JSX } from 'react';
2 |
3 | export function SelectIcon(): JSX.Element {
4 | return (
5 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/icons/SourceIcon.tsx:
--------------------------------------------------------------------------------
1 | import React, { type JSX } from 'react';
2 |
3 | const LvlIcon: React.FC = () => {
4 | return (
5 |
14 | );
15 | };
16 |
17 | export function SourceIcon(props: { type: string }): JSX.Element {
18 | switch (props.type) {
19 | case 'lvl1':
20 | return ;
21 | case 'content':
22 | return ;
23 | default:
24 | return ;
25 | }
26 | }
27 |
28 | function AnchorIcon(): JSX.Element {
29 | return (
30 |
40 | );
41 | }
42 |
43 | function ContentIcon(): JSX.Element {
44 | return (
45 |
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/icons/StarIcon.tsx:
--------------------------------------------------------------------------------
1 | import React, { type JSX } from 'react';
2 |
3 | export function StarIcon(): JSX.Element {
4 | return (
5 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/icons/index.ts:
--------------------------------------------------------------------------------
1 | export * from './GoToExternalIcon';
2 | export * from './LoadingIcon';
3 | export * from './RecentIcon';
4 | export * from './ResetIcon';
5 | export * from './SearchIcon';
6 | export * from './SelectIcon';
7 | export * from './SourceIcon';
8 | export * from './StarIcon';
9 | export * from './ErrorIcon';
10 | export * from './NoResultsIcon';
11 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './DocSearch';
2 | export * from './DocSearchButton';
3 | export * from './DocSearchModal';
4 | export * from './useDocSearchKeyboardEvents';
5 | export * from './version';
6 | export * from './types';
7 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/stored-searches.ts:
--------------------------------------------------------------------------------
1 | import type { DocSearchHit, StoredDocSearchHit } from './types';
2 |
3 | function isLocalStorageSupported(): boolean {
4 | const key = '__TEST_KEY__';
5 |
6 | try {
7 | localStorage.setItem(key, '');
8 | localStorage.removeItem(key);
9 |
10 | return true;
11 | } catch {
12 | return false;
13 | }
14 | }
15 |
16 | // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
17 | function createStorage(key: string) {
18 | if (isLocalStorageSupported() === false) {
19 | return {
20 | setItem(): void {},
21 | getItem(): TItem[] {
22 | return [];
23 | },
24 | };
25 | }
26 |
27 | return {
28 | setItem(item: TItem[]): void {
29 | return window.localStorage.setItem(key, JSON.stringify(item));
30 | },
31 | getItem(): TItem[] {
32 | const item = window.localStorage.getItem(key);
33 |
34 | return item ? JSON.parse(item) : [];
35 | },
36 | };
37 | }
38 |
39 | type CreateStoredSearchesOptions = {
40 | key: string;
41 | limit?: number;
42 | };
43 |
44 | export type StoredSearchPlugin = {
45 | add: (item: TItem) => void;
46 | remove: (item: TItem) => void;
47 | getAll: () => TItem[];
48 | };
49 |
50 | export function createStoredSearches({
51 | key,
52 | limit = 5,
53 | }: CreateStoredSearchesOptions): StoredSearchPlugin {
54 | const storage = createStorage(key);
55 | let items = storage.getItem().slice(0, limit);
56 |
57 | return {
58 | add(item: TItem): void {
59 | const { _highlightResult, _snippetResult, ...hit } = item as unknown as DocSearchHit;
60 |
61 | const isQueryAlreadySaved = items.findIndex((x) => x.objectID === hit.objectID);
62 |
63 | if (isQueryAlreadySaved > -1) {
64 | items.splice(isQueryAlreadySaved, 1);
65 | }
66 |
67 | items.unshift(hit as TItem);
68 | items = items.slice(0, limit);
69 |
70 | storage.setItem(items);
71 | },
72 | remove(item: TItem): void {
73 | items = items.filter((x) => x.objectID !== item.objectID);
74 |
75 | storage.setItem(items);
76 | },
77 | getAll(): TItem[] {
78 | return items;
79 | },
80 | };
81 | }
82 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/types/DocSearchHit.ts:
--------------------------------------------------------------------------------
1 | type ContentType = 'content' | 'lvl0' | 'lvl1' | 'lvl2' | 'lvl3' | 'lvl4' | 'lvl5' | 'lvl6';
2 |
3 | interface DocSearchHitAttributeHighlightResult {
4 | value: string;
5 | matchLevel: 'full' | 'none' | 'partial';
6 | matchedWords: string[];
7 | fullyHighlighted?: boolean;
8 | }
9 |
10 | interface DocSearchHitHighlightResultHierarchy {
11 | lvl0: DocSearchHitAttributeHighlightResult;
12 | lvl1: DocSearchHitAttributeHighlightResult;
13 | lvl2: DocSearchHitAttributeHighlightResult;
14 | lvl3: DocSearchHitAttributeHighlightResult;
15 | lvl4: DocSearchHitAttributeHighlightResult;
16 | lvl5: DocSearchHitAttributeHighlightResult;
17 | lvl6: DocSearchHitAttributeHighlightResult;
18 | }
19 |
20 | interface DocSearchHitHighlightResult {
21 | content: DocSearchHitAttributeHighlightResult;
22 | hierarchy: DocSearchHitHighlightResultHierarchy;
23 | hierarchy_camel: DocSearchHitHighlightResultHierarchy[];
24 | }
25 |
26 | interface DocSearchHitAttributeSnippetResult {
27 | value: string;
28 | matchLevel: 'full' | 'none' | 'partial';
29 | }
30 |
31 | interface DocSearchHitSnippetResult {
32 | content: DocSearchHitAttributeSnippetResult;
33 | hierarchy: DocSearchHitHighlightResultHierarchy;
34 | hierarchy_camel: DocSearchHitHighlightResultHierarchy[];
35 | }
36 |
37 | export declare type DocSearchHit = {
38 | objectID: string;
39 | content: string | null;
40 | url: string;
41 | url_without_anchor: string;
42 | type: ContentType;
43 | anchor: string | null;
44 | hierarchy: {
45 | lvl0: string;
46 | lvl1: string;
47 | lvl2: string | null;
48 | lvl3: string | null;
49 | lvl4: string | null;
50 | lvl5: string | null;
51 | lvl6: string | null;
52 | };
53 | _highlightResult: DocSearchHitHighlightResult;
54 | _snippetResult: DocSearchHitSnippetResult;
55 | _rankingInfo?: {
56 | promoted: boolean;
57 | nbTypos: number;
58 | firstMatchedWord: number;
59 | proximityDistance?: number;
60 | geoDistance: number;
61 | geoPrecision?: number;
62 | nbExactWords: number;
63 | words: number;
64 | filters: number;
65 | userScore: number;
66 | matchedGeoLocation?: {
67 | lat: number;
68 | lng: number;
69 | distance: number;
70 | };
71 | };
72 | _distinctSeqID?: number;
73 | __autocomplete_indexName?: string;
74 | __autocomplete_queryID?: string;
75 | __autocomplete_algoliaCredentials?: {
76 | appId: string;
77 | apiKey: string;
78 | };
79 | __autocomplete_id?: number;
80 | };
81 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/types/DocSearchState.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | AutocompleteContext,
3 | AutocompleteInsightsApi,
4 | AutocompleteState,
5 | BaseItem,
6 | } from '@algolia/autocomplete-core';
7 |
8 | interface DocSearchContext extends AutocompleteContext {
9 | algoliaInsightsPlugin?: {
10 | insights: AutocompleteInsightsApi;
11 | };
12 | }
13 |
14 | export interface DocSearchState extends AutocompleteState {
15 | context: DocSearchContext;
16 | }
17 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/types/InternalDocSearchHit.ts:
--------------------------------------------------------------------------------
1 | import type { DocSearchHit } from './DocSearchHit';
2 |
3 | export type InternalDocSearchHit = DocSearchHit & {
4 | __docsearch_parent: InternalDocSearchHit | null;
5 | };
6 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/types/StoredDocSearchHit.ts:
--------------------------------------------------------------------------------
1 | import type { DocSearchHit } from './DocSearchHit';
2 |
3 | export type StoredDocSearchHit = Omit;
4 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from './DocSearchHit';
2 | export * from './DocSearchState';
3 | export * from './InternalDocSearchHit';
4 | export * from './StoredDocSearchHit';
5 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/useDocSearchKeyboardEvents.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export interface UseDocSearchKeyboardEventsProps {
4 | isOpen: boolean;
5 | onOpen: () => void;
6 | onClose: () => void;
7 | onInput?: (event: KeyboardEvent) => void;
8 | searchButtonRef: React.RefObject;
9 | }
10 |
11 | function isEditingContent(event: KeyboardEvent): boolean {
12 | const element = event.composedPath()[0] as HTMLElement;
13 | const tagName = element.tagName;
14 |
15 | return element.isContentEditable || tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA';
16 | }
17 |
18 | export function useDocSearchKeyboardEvents({
19 | isOpen,
20 | onOpen,
21 | onClose,
22 | onInput,
23 | searchButtonRef,
24 | }: UseDocSearchKeyboardEventsProps): void {
25 | React.useEffect(() => {
26 | function onKeyDown(event: KeyboardEvent): void {
27 | if (
28 | (event.code === 'Escape' && isOpen) ||
29 | // The `Cmd+K` shortcut both opens and closes the modal.
30 | // We need to check for `event.key` because it can be `undefined` with
31 | // Chrome's autofill feature.
32 | // See https://github.com/paperjs/paper.js/issues/1398
33 | (event.key?.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey)) ||
34 | // The `/` shortcut opens but doesn't close the modal because it's
35 | // a character.
36 | (!isEditingContent(event) && event.key === '/' && !isOpen)
37 | ) {
38 | event.preventDefault();
39 |
40 | if (isOpen) {
41 | onClose();
42 | } else if (!document.body.classList.contains('DocSearch--active')) {
43 | // We check that no other DocSearch modal is showing before opening
44 | // another one.
45 | onOpen();
46 | }
47 |
48 | return;
49 | }
50 |
51 | if (searchButtonRef && searchButtonRef.current === document.activeElement && onInput) {
52 | if (/[a-zA-Z0-9]/.test(String.fromCharCode(event.keyCode))) {
53 | onInput(event);
54 | }
55 | }
56 | }
57 |
58 | window.addEventListener('keydown', onKeyDown);
59 |
60 | return (): void => {
61 | window.removeEventListener('keydown', onKeyDown);
62 | };
63 | }, [isOpen, onOpen, onClose, onInput, searchButtonRef]);
64 | }
65 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/useSearchClient.ts:
--------------------------------------------------------------------------------
1 | import { liteClient } from 'algoliasearch/lite';
2 | import React from 'react';
3 |
4 | import type { DocSearchTransformClient } from './DocSearch';
5 | import { version } from './version';
6 |
7 | export function useSearchClient(
8 | appId: string,
9 | apiKey: string,
10 | transformSearchClient: (searchClient: DocSearchTransformClient) => DocSearchTransformClient,
11 | ): DocSearchTransformClient {
12 | const searchClient = React.useMemo(() => {
13 | const client = liteClient(appId, apiKey);
14 | client.addAlgoliaAgent('docsearch', version);
15 |
16 | // Since DocSearch.js relies on DocSearch React with an alias to Preact,
17 | // we cannot add the `docsearch-react` user agent by default, otherwise
18 | // it would also be sent on a DocSearch.js integration.
19 | // We therefore only add the `docsearch-react` user agent if `docsearch.js`
20 | // is not present.
21 | if (/docsearch.js \(.*\)/.test(client.transporter.algoliaAgent.value) === false) {
22 | client.addAlgoliaAgent('docsearch-react', version);
23 | }
24 |
25 | return transformSearchClient(client);
26 | }, [appId, apiKey, transformSearchClient]);
27 |
28 | return searchClient;
29 | }
30 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/useTouchEvents.ts:
--------------------------------------------------------------------------------
1 | import type { AutocompleteApi } from '@algolia/autocomplete-core';
2 | import React from 'react';
3 |
4 | interface UseTouchEventsProps {
5 | getEnvironmentProps: AutocompleteApi['getEnvironmentProps'];
6 | panelElement: HTMLDivElement | null;
7 | formElement: HTMLDivElement | null;
8 | inputElement: HTMLInputElement | null;
9 | }
10 |
11 | export function useTouchEvents({
12 | getEnvironmentProps,
13 | panelElement,
14 | formElement,
15 | inputElement,
16 | }: UseTouchEventsProps): void {
17 | React.useEffect(() => {
18 | if (!(panelElement && formElement && inputElement)) {
19 | return undefined;
20 | }
21 |
22 | const { onTouchStart, onTouchMove } = getEnvironmentProps({
23 | panelElement,
24 | formElement,
25 | inputElement,
26 | });
27 |
28 | window.addEventListener('touchstart', onTouchStart);
29 | window.addEventListener('touchmove', onTouchMove);
30 |
31 | return (): void => {
32 | window.removeEventListener('touchstart', onTouchStart);
33 | window.removeEventListener('touchmove', onTouchMove);
34 | };
35 | }, [getEnvironmentProps, panelElement, formElement, inputElement]);
36 | }
37 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/useTrapFocus.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | interface UseTrapFocusProps {
4 | container: HTMLElement | null;
5 | }
6 |
7 | export function useTrapFocus({ container }: UseTrapFocusProps): void {
8 | React.useEffect(() => {
9 | if (!container) {
10 | return undefined;
11 | }
12 |
13 | const focusableElements = container.querySelectorAll(
14 | 'a[href]:not([disabled]), button:not([disabled]), input:not([disabled])',
15 | );
16 | const firstElement = focusableElements[0];
17 | const lastElement = focusableElements[focusableElements.length - 1];
18 |
19 | function trapFocus(event: KeyboardEvent): void {
20 | if (event.key !== 'Tab') {
21 | return;
22 | }
23 |
24 | if (event.shiftKey) {
25 | if (document.activeElement === firstElement) {
26 | event.preventDefault();
27 | lastElement.focus();
28 | }
29 | } else if (document.activeElement === lastElement) {
30 | event.preventDefault();
31 | firstElement.focus();
32 | }
33 | }
34 |
35 | container.addEventListener('keydown', trapFocus);
36 |
37 | return (): void => {
38 | container.removeEventListener('keydown', trapFocus);
39 | };
40 | }, [container]);
41 | }
42 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/utils/groupBy.ts:
--------------------------------------------------------------------------------
1 | export function groupBy>(
2 | values: TValue[],
3 | predicate: (value: TValue) => string,
4 | maxResultsPerGroup?: number,
5 | ): Record {
6 | return values.reduce>((acc, item) => {
7 | const key = predicate(item);
8 |
9 | if (!acc.hasOwnProperty(key)) {
10 | acc[key] = [];
11 | }
12 |
13 | // We limit each section to show 5 hits maximum.
14 | // This acts as a frontend alternative to `distinct`.
15 | if (acc[key].length < (maxResultsPerGroup || 5)) {
16 | acc[key].push(item);
17 | }
18 |
19 | return acc;
20 | }, {});
21 | }
22 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/utils/identity.ts:
--------------------------------------------------------------------------------
1 | export function identity(x: TParam): TParam {
2 | return x;
3 | }
4 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './groupBy';
2 | export * from './identity';
3 | export * from './isModifierEvent';
4 | export * from './noop';
5 | export * from './removeHighlightTags';
6 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/utils/isModifierEvent.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Detect when an event is modified with a special key to let the browser
3 | * trigger its default behavior.
4 | */
5 | export function isModifierEvent(event: TEvent): boolean {
6 | const isMiddleClick = (event as MouseEvent).button === 1;
7 |
8 | return isMiddleClick || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
9 | }
10 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/utils/noop.ts:
--------------------------------------------------------------------------------
1 | export function noop(..._args: any[]): void {}
2 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/utils/removeHighlightTags.ts:
--------------------------------------------------------------------------------
1 | import type { DocSearchHit, InternalDocSearchHit } from '../types';
2 |
3 | const regexHighlightTags = /(|<\/mark>)/g;
4 | const regexHasHighlightTags = RegExp(regexHighlightTags.source);
5 |
6 | export function removeHighlightTags(hit: DocSearchHit | InternalDocSearchHit): string {
7 | const internalDocSearchHit = hit as InternalDocSearchHit;
8 |
9 | if (!internalDocSearchHit.__docsearch_parent && !hit._highlightResult) {
10 | return hit.hierarchy.lvl0;
11 | }
12 |
13 | const lvl0 = internalDocSearchHit.__docsearch_parent
14 | ? internalDocSearchHit.__docsearch_parent?._highlightResult?.hierarchy?.lvl0
15 | : hit._highlightResult?.hierarchy?.lvl0;
16 |
17 | if (!lvl0) {
18 | return hit.hierarchy.lvl0;
19 | }
20 |
21 | return lvl0.value && regexHasHighlightTags.test(lvl0.value) ? lvl0.value.replace(regexHighlightTags, '') : lvl0.value;
22 | }
23 |
--------------------------------------------------------------------------------
/packages/docsearch-react/src/version.ts:
--------------------------------------------------------------------------------
1 | export const version = '3.9.0';
2 |
--------------------------------------------------------------------------------
/packages/docsearch-react/style/button.js:
--------------------------------------------------------------------------------
1 | export * from '@docsearch/css/dist/button.css';
2 |
--------------------------------------------------------------------------------
/packages/docsearch-react/style/index.js:
--------------------------------------------------------------------------------
1 | export * from '@docsearch/css';
2 |
--------------------------------------------------------------------------------
/packages/docsearch-react/style/modal.js:
--------------------------------------------------------------------------------
1 | export * from '@docsearch/css/dist/modal.css';
2 |
--------------------------------------------------------------------------------
/packages/docsearch-react/style/variables.js:
--------------------------------------------------------------------------------
1 | export * from '@docsearch/css/dist/_variables.css';
2 |
--------------------------------------------------------------------------------
/packages/docsearch-react/tsconfig.declaration.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.declaration",
3 | }
4 |
--------------------------------------------------------------------------------
/packages/website/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | /node_modules
3 |
4 | # Production
5 | /build
6 |
7 | # Generated files
8 | .docusaurus
9 | .cache-loader
10 |
11 | # Misc
12 | .DS_Store
13 | .env.local
14 | .env.development.local
15 | .env.test.local
16 | .env.production.local
17 |
18 | npm-debug.log*
19 | yarn-debug.log*
20 | yarn-error.log*
21 |
--------------------------------------------------------------------------------
/packages/website/README.md:
--------------------------------------------------------------------------------
1 | [![DocSearch][1]][2]
2 |
3 | The easiest way to add search to your documentation. For free.
4 |
5 | [![Netlify Status][3]][4]
6 |
7 | Check out our [website][2] for a complete explanation and documentation.
8 |
9 | # Installation
10 |
11 | 1. In the root of this repository (two level above this directory), do `yarn install` and `yarn build`
12 | 1. In this directory, do `yarn start`.
13 | 1. A browser window will open up, pointing to the docs.
14 |
15 | # Deployment
16 |
17 | Netlify handles the deployment of this website. If you are part of the DocSearch core team. [Access the Netlify Dashboard][11].
18 |
19 | [1]: ./static/img/docsearch-logo.svg
20 | [2]: https://docsearch.algolia.com/
21 | [3]: https://api.netlify.com/api/v1/badges/30eacc09-d4b2-4a53-879b-04d40aaea454/deploy-status
22 | [4]: https://app.netlify.com/sites/docsearch/deploys
23 | [6]: https://github.com/algolia/docsearch
24 | [7]: https://github.com/algolia/docsearch-configs
25 | [8]: https://github.com/algolia/docsearch-scraper
26 | [9]: https://github.com/algolia/docsearch-website
27 | [10]: https://v2.docusaurus.io/
28 | [11]: https://app.netlify.com/sites/docsearch/overview
29 |
--------------------------------------------------------------------------------
/packages/website/babel.config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
3 | };
4 |
--------------------------------------------------------------------------------
/packages/website/docs/how-does-it-work.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: How does it work?
3 | ---
4 |
5 | import useBaseUrl from '@docusaurus/useBaseUrl';
6 |
7 | Getting up and ready with DocSearch is a straightforward process that requires three steps: you apply, we configure the crawler and the Algolia app for you, and you integrate our UI in your frontend. You only need to copy and paste a JavaScript snippet.
8 |
9 |
13 |
14 | ## You apply
15 |
16 | The first thing you'll need to do is to apply for DocSearch by [filling out the form on this page][1] (double check first that [you qualify][2]). We are receiving a lot of requests, so this form makes sure we won't be forgetting anyone.
17 |
18 | We guarantee that we will answer every request, but as we receive a lot of applications, please give us a couple of days to get back to you :)
19 |
20 | ## We create your Algolia application and a dedicated crawler
21 |
22 | Once we receive [your application][1], we'll have a look at your website, create an Algolia application and a dedicated [crawler][5] for it. Your crawler comes with [a configuration file][6] which defines which URLs we should crawl or ignore, as well as the specific CSS selectors to use for selecting headers, subheaders, etc.
23 |
24 | This step still requires some manual work and human brain, but thanks to the +4,000 configs we already created, we're able to automate most of it. Once this creation finishes, we'll run a first indexing of your website and have it run automatically at a random time of the week.
25 |
26 | **With the Crawler, comes [a dedicated interface][8] for you to:**
27 |
28 | - Start, schedule and monitor your crawls
29 | - Edit and test your config file directly with [DocSearch v3][7]
30 |
31 | **With the Algolia application comes access to the dashboard for you to:**
32 |
33 | - Browse your index and see how your content is indexed
34 | - Various analytics to understand how your search performs and ensure that your users are able to find what they’re searching for
35 | - Trials for other Algolia features
36 | - Team management
37 |
38 | ## You update your website
39 |
40 | We'll then get back to you with the JavaScript snippet you'll need to add to your website. This will bind your [DocSearch component][7] to display results from your Algolia index on each keystroke in a pop-up modal.
41 |
42 | Now that DocSearch is set, you don't have anything else to do. We'll keep crawling your website and update your search results automatically. All we ask is that you keep the "Search by Algolia" logo next to your search results.
43 |
44 | [1]: /apply
45 | [2]: /docs/who-can-apply
46 | [3]: https://github.com/algolia/docsearch-configs/tree/master/configs
47 | [4]: /docs/styling
48 | [5]: https://www.algolia.com/products/search-and-discovery/crawler/
49 | [6]: https://www.algolia.com/doc/tools/crawler/apis/configuration/
50 | [7]: /docs/docsearch-v3
51 | [8]: https://crawler.algolia.com/
52 |
--------------------------------------------------------------------------------
/packages/website/docs/integrations.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Supported Integrations
3 | ---
4 |
5 | We worked with **documentation website generators** to have DocSearch directly embedded as a first class citizen in the websites they produce.
6 |
7 | ## Our great integrations
8 |
9 | So, if you're using one of the following tools, checkout their documentation to see how to enable DocSearch on your website:
10 |
11 | - [Docusaurus v1][1] - [How to enable search][2]
12 | - [Docusaurus v2 & v3][3] - [Using Algolia DocSearch][4]
13 | - [VuePress][5] - [Algolia Search][6]
14 | - [VitePress][21] - [Search][22]
15 | - [Starlight][7] - [Algolia Search][8]
16 | - [LaRecipe][9] - [Algolia Search][10]
17 | - [Orchid][11] - [Algolia Search][12]
18 | - [Smooth DOC][13] - [DocSearch][14]
19 | - [Docsy][15] - [Configure Algolia DocSearch][16]
20 | - [Lotus Docs][19] - [Enabling the DocSearch Plugin][20]
21 | - [Sphinx](https://www.sphinx-doc.org/en/master/) - [Algolia DocSearch for Sphinx](https://sphinx-docsearch.readthedocs.io/)
22 |
23 | If you're maintaining a similar tool and want us to add you to the list, [feel free to make a pull request](https://github.com/algolia/docsearch/edit/main/packages/website/docs/integrations.md) and [contribute to Code Exchange](https://www.algolia.com/developers/code-exchange/contribute/). We're happy to help.
24 |
25 | [1]: https://v1.docusaurus.io/
26 | [2]: https://v1.docusaurus.io/docs/en/search
27 | [3]: https://docusaurus.io/
28 | [4]: https://docusaurus.io/docs/search#using-algolia-docsearch
29 | [5]: https://vuepress.vuejs.org/
30 | [6]: https://vuepress.vuejs.org/theme/default-theme-config.html#algolia-search
31 | [7]: https://starlight.astro.build/
32 | [8]: https://starlight.astro.build/guides/site-search/#algolia-docsearch
33 | [9]: https://larecipe.saleem.dev/docs/2.2/overview
34 | [10]: https://larecipe.saleem.dev/docs/2.2/search#available-engines
35 | [11]: https://orchid.run
36 | [12]: https://orchid.run/plugins/orchidsearch#algolia-docsearch
37 | [13]: https://next-smooth-doc.vercel.app/
38 | [14]: https://next-smooth-doc.vercel.app/docs/docsearch/
39 | [15]: https://www.docsy.dev/
40 | [16]: https://www.docsy.dev/docs/adding-content/search/#algolia-docsearch
41 | [19]: https://lotusdocs.dev/docs/
42 | [20]: https://lotusdocs.dev/docs/guides/features/docsearch/#enabling-the-docsearch-plugin
43 | [21]: https://vitepress.dev/
44 | [22]: https://vitepress.dev/reference/default-theme-search#algolia-search
45 |
--------------------------------------------------------------------------------
/packages/website/docs/manage-your-crawls.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Manage your crawls
3 | ---
4 |
5 | import useBaseUrl from '@docusaurus/useBaseUrl';
6 |
7 | DocSearch comes with the [Algolia Crawler web interface](https://crawler.algolia.com/) that allows you to configure how and when your Algolia index will be populated.
8 |
9 | ## Trigger a new crawl
10 |
11 | Head over to the `Overview` section to `start`, `restart` or `pause` your crawls and view a real-time summary.
12 |
13 |
14 |
18 |
19 |
20 | ## Monitor your crawls
21 |
22 | The `monitoring` section helps you find crawl errors or improve your search results.
23 |
24 |
25 |
29 |
30 |
31 | ## Update your config
32 |
33 | The live editor allows you to update your config file and test your URLs (`URL tester`).
34 |
35 |
36 |
40 |
41 |
42 | ## Search preview
43 |
44 | From the [`editor`](#update-your-config), you have access to a `Search preview` tab to browse search results with [`DocSearch v3`](/docs/docsearch-v3).
45 |
46 |
47 |
51 |
52 |
53 | ## URL tester
54 |
55 | From the [`editor`](#update-your-config), your can use the URL tester to [debug selectors](https://www.algolia.com/doc/tools/crawler/getting-started/crawler-configuration/#debugging-selectors) or how we crawl your website.
56 |
57 |
58 |
62 |
63 |
--------------------------------------------------------------------------------
/packages/website/docs/migrating-from-v2.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Migrating from DocSearch v2
3 | ---
4 |
5 | This page lists the differences between the [DocSearch v2](/docs/legacy/dropdown) and [DocSearch v3](/docs/docsearch-v3) API, you can also take a look at [the exhaustive `API reference` list](/docs/api) and [the `Getting started` guide](/docs/docsearch-v3).
6 |
7 | ```diff
8 | docsearch({
9 | indexName: 'YOUR_INDEX_NAME',
10 | apiKey: 'YOUR_SEARCH_API_KEY',
11 |
12 | - inputSelector: '',
13 | + container: '', -> We now require a container to be provided
14 |
15 | - appId: '',
16 | + appId: '', -> `appId` is now required
17 |
18 | - transformData: function(hits) {},
19 | + transformItems: function(items) {},
20 |
21 | - algoliaOptions: {},
22 | + searchParameters: {},
23 | });
24 | ```
25 |
--------------------------------------------------------------------------------
/packages/website/docs/styling.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Styling
3 | ---
4 |
5 | :::info
6 |
7 | The following content is for **[DocSearch v3][2]**. If you are using **[DocSearch v2][3]**, see the **[legacy][4]** documentation.
8 |
9 | :::
10 |
11 | :::caution
12 |
13 | This documentation is in progress.
14 |
15 | :::
16 |
17 | ## Introduction
18 |
19 | DocSearch v3 comes with a theme package called `@docsearch/css`, which offers a sleek out of the box theme!
20 |
21 | :::note
22 |
23 | This package is a dependency of [`@docsearch/js`][1] and [`@docsearch/react`][1], you don't need to install it if you are using a package manager!
24 |
25 | :::
26 |
27 | ## Installation
28 |
29 | ```bash
30 | yarn add @docsearch/css@3
31 | # or
32 | npm install @docsearch/css@3
33 | ```
34 |
35 | If you don’t want to use a package manager, you can use a standalone endpoint:
36 |
37 | ```html
38 |
39 | ```
40 |
41 | ## Files
42 |
43 | ```
44 | @docsearch/css
45 | ├── dist/style.css # all styles
46 | ├── dist/_variables.css # CSS variables
47 | ├── dist/button.css # CSS for the button
48 | └── dist/modal.css # CSS for the modal
49 | ```
50 |
51 | [1]: /docs/docsearch-v3
52 | [2]: https://github.com/algolia/docsearch/
53 | [3]: https://github.com/algolia/docsearch/tree/master
54 | [4]: /docs/legacy/dropdown
55 |
--------------------------------------------------------------------------------
/packages/website/docs/what-is-docsearch.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: What is DocSearch?
3 | sidebar_label: What is DocSearch?
4 | ---
5 |
6 | ## Why?
7 |
8 | We created DocSearch because we are scratching our own itch. As developers, we spend a lot of time reading documentation, and it can be hard to find relevant information in large documentations. We're not blaming anyone here: building good search is a challenge.
9 |
10 | It happens that we are a search company and we actually have a lot of experience building search interfaces. We wanted to use those skills to help others. That's why we created a way to automatically extract content from tech documentation and make it available to everyone from the first keystroke.
11 |
12 | ## Quick description
13 |
14 | We split DocSearch into a crawler and a frontend library.
15 |
16 | - Crawls are handled by the [Algolia Crawler][4] and scheduled to run once a week by default, you can then trigger new crawls yourself and monitor them directly from the [Crawler interface][5], which also offers a live editor where you can maintain your config.
17 | - The frontend library is built on top of [Algolia Autocomplete][6] and provides an immersive search experience through its modal.
18 |
19 | ## How to feature DocSearch?
20 |
21 | DocSearch is entirely free and automated. The one thing we'll need from you is to read [our checklist][2] and apply! After that, we'll share with you the snippet needed to add DocSearch to your website. We ask that you keep the "Search by Algolia" link displayed.
22 |
23 | DocSearch is [one of our ways][1] to give back to the open source community for everything it did for us already.
24 |
25 | You can now [apply to the program][3]
26 |
27 | [1]: https://opencollective.com/algolia
28 | [2]: /docs/who-can-apply
29 | [3]: /apply
30 | [4]: https://www.algolia.com/products/search-and-discovery/crawler/
31 | [5]: https://crawler.algolia.com/
32 | [6]: https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete/
33 |
--------------------------------------------------------------------------------
/packages/website/docs/who-can-apply.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Who can apply?
3 | ---
4 |
5 | **Open for all developer documentation and technical blogs.**
6 |
7 | We built DocSearch from the ground up with the idea of improving search on large technical documentations. For this reason, we are offering a free hosting version to all online technical documentations and technical blogs.
8 |
9 | We usually turn down applications when they are not production ready or have non-technical content on the website.
10 |
11 | ## The checklist
12 |
13 | To have your request validated, we’ll ask that you check all the following points.
14 |
15 | - Your website must be a **technical documentation or a technical blog**.
16 |
17 | - You must be the **owner** of the website, or at least have the permissions to update its content. You'll need to [include a JavaScript snippet][1] to your frontend to implement DocSearch.
18 |
19 | - Your website must be **publicly available**. We do not host search indices for websites that are only available after authentication or are hosted on a private network.
20 |
21 | - Your website must be **production ready**. We won't index empty websites nor those filled with placeholder content. Please, wait until you have written some documentation before applying. We would be happy to help you as soon as you have a steady design.
22 |
23 | If in doubt, don't hesitate to [apply][2] and we'll figure it out together.
24 |
25 | Even if we cannot accept your request, this does not mean that you cannot enjoy great search on your website. Legacy DocSearch is entirely open source and [you can run it yourself][3], or use any of [our other API clients][4] to take advantage of Algolia's features.
26 |
27 | ## Priority
28 |
29 | We're receiving dozens of requests every day, and while we strive to answer them all as fast as we can, we sometimes give priority to some based on the following criteria:
30 |
31 | - 🙂 If you're using one of our [official integrations][5], creating your config will be much faster for us.
32 |
33 | - 🙁 If we need to render your website in the browser through JavaScript, it means that we'll have to crawl it through a much slower browser emulation. We highly recommend that you put in place server-side rendering for the useful textual content.
34 |
35 | ## Process duration
36 |
37 | As stated in [the priority section](#priority), we receive a lot of requests every day. To ensure they all match [our checklist](#the-checklist), we manually review each of them, which means it can take a bit of time before you get an answer from us.
38 |
39 | The full process (from application to deployment) can take **up to two weeks**, applying multiple times or opening issues **only slows the process**, please be patient 🙏.
40 |
41 | [1]: /docs/docsearch-v3
42 | [2]: /apply
43 | [3]: /docs/legacy/run-your-own
44 | [4]: https://www.algolia.com/doc/api-client/getting-started/install/javascript/?client=javascript
45 | [5]: integrations.md
46 |
--------------------------------------------------------------------------------
/packages/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@docsearch/website",
3 | "version": "3.9.0",
4 | "private": true,
5 | "homepage": "https://docsearch.algolia.com/",
6 | "scripts": {
7 | "build:clean": "rm -rf dist build .docusaurus node_modules",
8 | "docusaurus": "docusaurus",
9 | "start": "docusaurus start",
10 | "build": "docusaurus build",
11 | "swizzle": "docusaurus swizzle",
12 | "deploy": "docusaurus deploy",
13 | "clear": "docusaurus clear",
14 | "serve": "docusaurus serve",
15 | "write-translations": "docusaurus write-translations",
16 | "write-heading-ids": "docusaurus write-heading-ids"
17 | },
18 | "dependencies": {
19 | "@algolia/ui-library": "5.86.0",
20 | "@docsearch/react": "3.9.0",
21 | "@docusaurus/core": "3.7.0",
22 | "@docusaurus/preset-classic": "3.7.0",
23 | "@mdx-js/react": "^3.1.0",
24 | "file-loader": "6.2.0",
25 | "postcss": "8.5.1",
26 | "postcss-import": "16.1.0",
27 | "postcss-preset-env": "10.1.3",
28 | "prism-react-renderer": "2.4.1",
29 | "react": "^18.0.0",
30 | "react-dom": "^18.0.0",
31 | "react-google-recaptcha": "^3.1.0"
32 | },
33 | "devDependencies": {
34 | "tailwindcss": "3.4.17"
35 | },
36 | "browserslist": {
37 | "production": [
38 | ">0.5%",
39 | "not dead",
40 | "not op_mini all"
41 | ],
42 | "development": [
43 | "last 1 chrome version",
44 | "last 1 firefox version",
45 | "last 1 safari version"
46 | ]
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/website/plugins/my-loaders.mjs:
--------------------------------------------------------------------------------
1 | export default function () {
2 | return {
3 | name: 'loaders',
4 | configureWebpack() {
5 | return {
6 | module: {
7 | rules: [
8 | {
9 | test: /\.(gif|png|jpe?g|svg)$/i,
10 | exclude: /\.(mdx?)$/i,
11 | use: ['file-loader', { loader: 'image-webpack-loader' }],
12 | },
13 | ],
14 | },
15 | };
16 | },
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/packages/website/plugins/tailwind-loader.mjs:
--------------------------------------------------------------------------------
1 | import postcssImport from 'postcss-import';
2 | import postcssPresetEnv from 'postcss-preset-env';
3 | import tailwindcss from 'tailwindcss';
4 |
5 | export default function () {
6 | return {
7 | name: 'postcss-tailwindcss-loader',
8 | configurePostCss(postcssOptions) {
9 | postcssOptions.plugins.push(
10 | postcssImport,
11 | tailwindcss,
12 | postcssPresetEnv({
13 | autoprefixer: {
14 | flexbox: 'no-2009',
15 | },
16 | stage: 4,
17 | }),
18 | );
19 | return postcssOptions;
20 | },
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/packages/website/sidebars.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Creating a sidebar enables you to:
3 | * - create an ordered group of docs
4 | * - render a sidebar for each doc of that group
5 | * - provide next/previous navigation.
6 | *
7 | * The sidebars can be generated from the filesystem, or explicitly defined here.
8 | *
9 | * Create as many sidebars as you want.
10 | */
11 |
12 | export default {
13 | docs: [
14 | {
15 | type: 'category',
16 | label: 'Introduction',
17 | items: ['what-is-docsearch', 'who-can-apply', 'migrating-from-legacy'],
18 | },
19 | {
20 | type: 'category',
21 | label: 'DocSearch v3',
22 | items: ['docsearch-v3', 'api', 'styling', 'migrating-from-v2'],
23 | },
24 | {
25 | type: 'category',
26 | label: 'Algolia Crawler',
27 | items: ['record-extractor', 'templates', 'manage-your-crawls'],
28 | },
29 | {
30 | type: 'category',
31 | label: 'Requirements, tips, FAQ',
32 | items: [
33 | {
34 | type: 'category',
35 | label: 'FAQ',
36 | items: ['crawler', 'docsearch-program'],
37 | },
38 | {
39 | type: 'doc',
40 | id: 'tips',
41 | },
42 | {
43 | type: 'doc',
44 | id: 'integrations',
45 | },
46 | ],
47 | },
48 | {
49 | type: 'category',
50 | label: 'Under the hood',
51 | items: ['how-does-it-work', 'required-configuration'],
52 | },
53 | ],
54 | };
55 |
--------------------------------------------------------------------------------
/packages/website/src/pages/apply.js:
--------------------------------------------------------------------------------
1 | import { Hero } from '@algolia/ui-library';
2 | import Layout from '@theme/Layout';
3 | import React from 'react';
4 |
5 | import ApplyForm from '../components/ApplyForm.js';
6 | import DocSearchLogo from '../components/DocSearchLogo';
7 |
8 | function ApplyPage() {
9 | return (
10 |
14 |