close
  • 中文
  • @rspress/plugin-algolia new

    基于 docsearch,将 Rspress 内置的搜索功能替换为 algolia

    安装

    npm
    yarn
    pnpm
    bun
    deno
    npm add @rspress/plugin-algolia -D

    使用

    首先在 rspress.config.ts中写入以下的配置:

    // rspress.config.ts
    import path from 'path';
    import { defineConfig } from '@rspress/core';
    import { pluginAlgolia } from '@rspress/plugin-algolia';
    
    export default defineConfig({
      plugins: [pluginAlgolia()],
    });

    然后通过 自定义主题Search 组件覆盖为支持 algolia 的搜索框。

    // theme/index.tsx
    import {
      Search as PluginAlgoliaSearch,
      ZH_LOCALES,
    } from '@rspress/plugin-algolia/runtime';
    
    const Search = () => {
      return (
        <PluginAlgoliaSearch
          docSearchProps={{
            appId: 'R2IYF7ETH7', // 替换为自己的 algolia appId
            apiKey: '599cec31baffa4868cae4e79f180729b', // 替换为自己的 algolia apiKey
            indexName: 'docsearch', // 替换为自己的 algolia indexName
          }}
          locales={ZH_LOCALES} // 默认支持 zh 和 en
        />
      );
    };
    export { Search };
    export * from '@rspress/core/theme-original';

    配置

    这个插件接受一个对象参数,类型如下:

    interface Options {
      verificationContent?: string;
    }

    verificationContent

    • 类型: string | undefined
    • 默认值: undefined

    创建 algolia 爬虫时,用于 meta 标签验证。格式为 <meta name="algolia-site-verification" content="YOUR_VERIFICATION_CONTENT" />,具体信息参考 Create a new crawler - algolia

    SearchProps

    @rspress/plugin-algolia/runtimeSearchProps 的类型如下:

    import type { DocSearchProps } from '@docsearch/react';
    
    type Locales = Record<
      string,
      { translations: DocSearchProps['translations']; placeholder: string }
    >;
    type SearchProps = {
      /**
       * @link https://docsearch.algolia.com/docs/api
       */
      docSearchProps?: DocSearchProps;
      locales?: Locales;
    };

    docSearchProps

    • 类型: import('@docsearch/react').DocSearchProps
    • 默认值: undefined

    docSearchProps 会直接透传给 @docsearch/react 中的 <DocSearch /> 组件,具体类型信息可参考 docsearch 文档

    locales

    • 类型:
    type Locales = Record<
      string,
      { translations: DocSearchProps['translations']; placeholder: string }
    >;
    • 默认值:{}

    用于自定义不同语言的翻译文本,Rspress 提供了以下翻译文本,可以通过 import 导入使用。

    import type { DocSearchProps } from '@docsearch/react';
    
    export type Locales = Record<
      string,
      { translations: DocSearchProps['translations']; placeholder: string }
    >;
    
    // cspell:disable
    export const ZH_LOCALES: Locales = {
      zh: {
        placeholder: '搜索文档',
        translations: {
          button: {
            buttonText: '搜索',
            buttonAriaLabel: '搜索',
          },
          modal: {
            searchBox: {
              clearButtonTitle: '清除查询条件',
              clearButtonAriaLabel: '清除查询条件',
              closeButtonText: '取消',
              closeButtonAriaLabel: '取消',
            },
            startScreen: {
              recentSearchesTitle: '搜索历史',
              noRecentSearchesText: '没有搜索历史',
              saveRecentSearchButtonTitle: '保存至搜索历史',
              removeRecentSearchButtonTitle: '从搜索历史中移除',
              favoriteSearchesTitle: '收藏',
              removeFavoriteSearchButtonTitle: '从收藏中移除',
            },
            errorScreen: {
              titleText: '无法获取结果',
              helpText: '你可能需要检查你的网络连接',
            },
            footer: {
              selectText: '选择',
              navigateText: '切换',
              closeText: '关闭',
              poweredByText: '搜索提供者',
            },
            noResultsScreen: {
              noResultsText: '无法找到相关结果',
              suggestedQueryText: '你可以尝试查询',
              reportMissingResultsText: '你认为该查询应该有结果?',
              reportMissingResultsLinkText: '点击反馈',
            },
          },
        },
      },
    } as const;
    
    export const RU_LOCALES: Locales = {
      ru: {
        placeholder: 'Поиск в документации',
        translations: {
          button: {
            buttonText: 'Поиск',
            buttonAriaLabel: 'Поиск',
          },
          modal: {
            searchBox: {
              clearButtonTitle: 'Очистить поиск',
              clearButtonAriaLabel: 'Очистить поиск',
              closeButtonText: 'Закрыть',
              closeButtonAriaLabel: 'Закрыть',
            },
            startScreen: {
              recentSearchesTitle: 'История поиска',
              noRecentSearchesText: 'Нет истории поиска',
              saveRecentSearchButtonTitle: 'Сохранить в истории поиска',
              removeRecentSearchButtonTitle: 'Удалить из истории поиска',
              favoriteSearchesTitle: 'Избранное',
              removeFavoriteSearchButtonTitle: 'Удалить из избранного',
            },
            errorScreen: {
              titleText: 'Невозможно получить результаты',
              helpText: 'Проверьте подключение к Интернету',
            },
            footer: {
              selectText: 'выбрать',
              navigateText: 'перейти',
              closeText: 'закрыть',
              poweredByText: 'поиск от',
            },
            noResultsScreen: {
              noResultsText: 'Ничего не найдено',
              suggestedQueryText: 'Попробуйте изменить запрос',
              reportMissingResultsText: 'Считаете, что результаты должны быть?',
              reportMissingResultsLinkText: 'Сообщите об этом',
            },
          },
        },
      },
    } as const;
    // cspell:enable
    
    • 示例:
    import { Search as PluginAlgoliaSearch, ZH_LOCALES } from '@rspress/plugin-algolia/runtime';
    
    <PluginAlgoliaSearch locales={ZH_LOCALES} />
    // 或者
    <PluginAlgoliaSearch
      locales={{
        en: {
          placeholder: 'Search Documentation',
          translations: {
            button: {
              buttonText: 'Search',
              buttonAriaLabel: 'Search',
            }
          }
        },
        ...ZH_LOCALES,
      }}
    />

    Algolia crawler 配置

    以下是一个基于本站使用的示例配置:

    new Crawler({
      appId: 'YOUR_APP_ID',
      apiKey: 'YOUR_API_KEY',
      rateLimit: 8,
      maxDepth: 10,
      startUrls: ['https://rspress.rs'],
      sitemaps: ['https://rspress.rs/sitemap.xml'],
      discoveryPatterns: ['https://rspress.rs/**'],
      actions: [
        {
          indexName: 'doc_search_rspress_v2_pages',
          pathsToMatch: ['https://rspress.rs/**'],
          recordExtractor: ({ $, helpers }) => {
            // 移除徽章元素,避免其文本被索引
            $('.rp-badge').remove();
            // 移除 h1 中的非文档元素(如版本切换器),确保标题文本干净
            $('.rspress-doc h1 .rp-not-doc').remove();
    
            const $activeNavItem = $('.rp-nav-menu__item.rp-nav-menu__item--active')
              .first()
              .clone();
    
            const lvl0 = $activeNavItem.text().trim() || 'Documentation';
    
            return helpers.docsearch({
              recordProps: {
                lvl0: {
                  selectors: '',
                  defaultValue: lvl0,
                },
                lvl1: '.rspress-doc h1',
                lvl2: '.rspress-doc h2',
                lvl3: '.rspress-doc h3',
                lvl4: '.rspress-doc h4',
                lvl5: '.rspress-doc h5',
                lvl6: '.rspress-doc pre > code', // 如果要搜索到代码块中的内容,增加这一行
                content: '.rspress-doc p, .rspress-doc li',
              },
              indexHeadings: true,
              aggregateContent: true,
              recordVersion: 'v3',
            });
          },
        },
      ],
      initialIndexSettings: {
        doc_search_rspress_v2_pages: {
          attributesForFaceting: ['type', 'lang'],
          attributesToRetrieve: ['hierarchy', 'content', 'anchor', 'url'],
          attributesToHighlight: ['hierarchy', 'content'],
          attributesToSnippet: ['content:10'],
          camelCaseAttributes: ['hierarchy', 'content'],
          searchableAttributes: [
            'unordered(hierarchy.lvl0)',
            'unordered(hierarchy.lvl1)',
            'unordered(hierarchy.lvl2)',
            'unordered(hierarchy.lvl3)',
            'unordered(hierarchy.lvl4)',
            'unordered(hierarchy.lvl5)',
            'unordered(hierarchy.lvl6)',
            'content',
          ],
          distinct: true,
          attributeForDistinct: 'url',
          customRanking: [
            'desc(weight.pageRank)',
            'desc(weight.level)',
            'asc(weight.position)',
          ],
          ranking: [
            'words',
            'filters',
            'typo',
            'attribute',
            'proximity',
            'exact',
            'custom',
          ],
          minWordSizefor1Typo: 3,
          minWordSizefor2Typos: 7,
          allowTyposOnNumericTokens: false,
          minProximity: 1,
          ignorePlurals: true,
          advancedSyntax: true,
          attributeCriteriaComputedByMinProximity: true,
          removeWordsIfNoResults: 'allOptional',
        },
      },
      schedule: 'on tuesday',
      indexPrefix: 'rspress-v2-crawler-',
    });

    基于国际化区分搜索结果

    通过 Runtime API 组合 docSearchProps 可以实现搜索结果的国际化。

    以下是通过 docSearchProps.searchParameters 实现的一个示例:

    // theme/index.tsx
    import { useLang } from '@rspress/core/runtime';
    import { Search as PluginAlgoliaSearch } from '@rspress/plugin-algolia/runtime';
    
    const Search = () => {
      const lang = useLang();
      return (
        <PluginAlgoliaSearch
          docSearchProps={{
            appId: 'R2IYF7ETH7',
            apiKey: '599cec31baffa4868cae4e79f180729b',
            indexName: 'docsearch',
            searchParameters: {
              facetFilters: [`lang:${lang}`],
            },
          }}
        />
      );
    };
    export { Search };
    export * from '@rspress/core/theme-original';