@vuepress-plume/plugin-search 1.0.0-rc.192 → 1.0.0-rc.193

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,12 +2,42 @@ import { ComputedRef, MaybeRef, ShallowRef } from "vue";
2
2
  import { SearchBoxLocales, SearchLocaleOptions } from "../../shared/index.js";
3
3
 
4
4
  //#region src/client/composables/locale.d.ts
5
+ /**
6
+ * Get locale configuration for the current route.
7
+ *
8
+ * 获取当前路由的语言配置。
9
+ *
10
+ * @param locales - Locale configuration object / 语言配置对象
11
+ * @returns Computed ref to the current locale settings / 当前语言设置的计算引用
12
+ * @example
13
+ * const locale = useLocale(locales)
14
+ * console.log(locale.value.placeholder) // 'Search' or localized value
15
+ */
5
16
  declare function useLocale(locales: MaybeRef<SearchBoxLocales>): ComputedRef<Partial<SearchLocaleOptions>>;
6
17
  //#endregion
7
18
  //#region src/client/composables/searchIndex.d.ts
19
+ /**
20
+ * Type definition for search index data.
21
+ *
22
+ * 搜索索引数据的类型定义。
23
+ *
24
+ * Maps locale paths to functions that load the corresponding search index.
25
+ *
26
+ * 将语言路径映射到加载相应搜索索引的函数。
27
+ */
8
28
  type SearchIndexData = Record<string, () => Promise<{
9
29
  default: string;
10
30
  }>>;
31
+ /**
32
+ * Get the search index data for all locales.
33
+ *
34
+ * 获取所有语言的搜索索引数据。
35
+ *
36
+ * @returns Shallow ref to the search index data / 搜索索引数据的浅层引用
37
+ * @example
38
+ * const indexData = useSearchIndex()
39
+ * const localeIndex = await indexData.value['/zh/']()
40
+ */
11
41
  declare function useSearchIndex(): ShallowRef<SearchIndexData>;
12
42
  //#endregion
13
43
  export { useLocale, useSearchIndex };
@@ -1,8 +1,12 @@
1
1
  import { computed, shallowRef, toRef } from "vue";
2
2
  import { useRouteLocale } from "vuepress/client";
3
3
  import { searchIndex } from "@internal/minisearchIndex";
4
-
5
4
  //#region src/client/composables/locale.ts
5
+ /**
6
+ * Default locale configuration for search.
7
+ *
8
+ * 搜索的默认语言配置。
9
+ */
6
10
  const defaultLocales = { "/": {
7
11
  placeholder: "Search",
8
12
  resetButtonTitle: "Reset search",
@@ -18,21 +22,45 @@ const defaultLocales = { "/": {
18
22
  closeKeyAriaLabel: "escape"
19
23
  }
20
24
  } };
25
+ /**
26
+ * Get locale configuration for the current route.
27
+ *
28
+ * 获取当前路由的语言配置。
29
+ *
30
+ * @param locales - Locale configuration object / 语言配置对象
31
+ * @returns Computed ref to the current locale settings / 当前语言设置的计算引用
32
+ * @example
33
+ * const locale = useLocale(locales)
34
+ * console.log(locale.value.placeholder) // 'Search' or localized value
35
+ */
21
36
  function useLocale(locales) {
22
37
  const localesRef = toRef(locales);
23
38
  const routeLocale = useRouteLocale();
24
39
  return computed(() => localesRef.value[routeLocale.value] ?? defaultLocales[routeLocale.value] ?? defaultLocales["/"]);
25
40
  }
26
-
27
41
  //#endregion
28
42
  //#region src/client/composables/searchIndex.ts
43
+ /**
44
+ * Reactive reference to the search index data.
45
+ *
46
+ * 搜索索引数据的响应式引用。
47
+ */
29
48
  const searchIndexData = shallowRef(searchIndex);
49
+ /**
50
+ * Get the search index data for all locales.
51
+ *
52
+ * 获取所有语言的搜索索引数据。
53
+ *
54
+ * @returns Shallow ref to the search index data / 搜索索引数据的浅层引用
55
+ * @example
56
+ * const indexData = useSearchIndex()
57
+ * const localeIndex = await indexData.value['/zh/']()
58
+ */
30
59
  function useSearchIndex() {
31
60
  return searchIndexData;
32
61
  }
33
62
  if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) __VUE_HMR_RUNTIME__.updateSearchIndex = (data) => {
34
63
  searchIndexData.value = data;
35
64
  };
36
-
37
65
  //#endregion
38
- export { useLocale, useSearchIndex };
66
+ export { useLocale, useSearchIndex };
@@ -1,7 +1,6 @@
1
1
  import { h } from "vue";
2
2
  import { defineClientConfig } from "vuepress/client";
3
3
  import Search from "./components/Search.vue";
4
-
5
4
  //#region src/client/config.ts
6
5
  const locales = __SEARCH_LOCALES__;
7
6
  const searchOptions = __SEARCH_OPTIONS__;
@@ -12,6 +11,5 @@ var config_default = defineClientConfig({ enhance({ app }) {
12
11
  ...props
13
12
  }));
14
13
  } });
15
-
16
14
  //#endregion
17
- export { config_default as default };
15
+ export { config_default as default };
@@ -1,4 +1,17 @@
1
1
  import SearchBox from "./components/Search.vue";
2
2
  import { useSearchIndex } from "./composables/index.js";
3
-
4
- export { SearchBox, useSearchIndex };
3
+ //#region src/client/index.ts
4
+ /**
5
+ * VuePress Search Plugin - Client Side Entry
6
+ *
7
+ * VuePress 搜索插件 - 客户端入口
8
+ *
9
+ * Exports the SearchBox component and search index composable for use in
10
+ * VuePress theme components.
11
+ *
12
+ * 导出 SearchBox 组件和搜索索引组合式函数,供 VuePress 主题组件使用。
13
+ *
14
+ * @module plugin-search/client
15
+ */
16
+ //#endregion
17
+ export { SearchBox, useSearchIndex };
@@ -1,11 +1,86 @@
1
1
  //#region src/client/utils/lru.d.ts
2
+ /**
3
+ * LRU (Least Recently Used) Cache Implementation
4
+ *
5
+ * LRU(最近最少使用)缓存实现
6
+ *
7
+ * Adapted from https://stackoverflow.com/a/46432113/11613622
8
+ *
9
+ * @module plugin-search/client/utils/lru
10
+ */
11
+ /**
12
+ * Generic LRU Cache implementation using Map.
13
+ *
14
+ * 使用 Map 实现的通用 LRU 缓存。
15
+ *
16
+ * Automatically evicts the least recently used item when the cache reaches
17
+ * its maximum size.
18
+ *
19
+ * 当缓存达到最大容量时,自动淘汰最近最少使用的项。
20
+ *
21
+ * @template K - Key type / 键类型
22
+ * @template V - Value type / 值类型
23
+ * @example
24
+ * const cache = new LRUCache<string, number>(3)
25
+ * cache.set('a', 1)
26
+ * cache.set('b', 2)
27
+ * cache.set('c', 3)
28
+ * cache.set('d', 4) // 'a' is evicted
29
+ * cache.get('b') // returns 2, 'b' becomes most recent
30
+ */
2
31
  declare class LRUCache<K, V> {
32
+ /** Maximum number of items in the cache / 缓存中的最大项数 */
3
33
  private max;
34
+ /** Internal Map storage / 内部 Map 存储 */
4
35
  private cache;
36
+ /**
37
+ * Create a new LRU Cache instance.
38
+ *
39
+ * 创建新的 LRU 缓存实例。
40
+ *
41
+ * @param max - Maximum cache size (default: 10) / 最大缓存大小(默认:10)
42
+ */
5
43
  constructor(max?: number);
44
+ /**
45
+ * Get a value from the cache.
46
+ *
47
+ * 从缓存中获取值。
48
+ *
49
+ * Accessing an item moves it to the end (most recently used position).
50
+ *
51
+ * 访问项会将其移动到末尾(最近使用的位置)。
52
+ *
53
+ * @param key - Cache key / 缓存键
54
+ * @returns Cached value or undefined if not found / 缓存值,未找到则返回 undefined
55
+ */
6
56
  get(key: K): V | undefined;
57
+ /**
58
+ * Set a value in the cache.
59
+ *
60
+ * 在缓存中设置值。
61
+ *
62
+ * If the key already exists, it is moved to the end. If the cache is full,
63
+ * the oldest item is evicted.
64
+ *
65
+ * 如果键已存在,则将其移动到末尾。如果缓存已满,则淘汰最旧的项。
66
+ *
67
+ * @param key - Cache key / 缓存键
68
+ * @param val - Value to cache / 要缓存的值
69
+ */
7
70
  set(key: K, val: V): void;
71
+ /**
72
+ * Get the first (oldest) key in the cache.
73
+ *
74
+ * 获取缓存中的第一个(最旧的)键。
75
+ *
76
+ * @returns The oldest key or undefined if cache is empty / 最旧的键,缓存为空则返回 undefined
77
+ */
8
78
  first(): K | undefined;
79
+ /**
80
+ * Clear all items from the cache.
81
+ *
82
+ * 清空缓存中的所有项。
83
+ */
9
84
  clear(): void;
10
85
  }
11
86
  //#endregion
@@ -1,11 +1,61 @@
1
1
  //#region src/client/utils/lru.ts
2
+ /**
3
+ * LRU (Least Recently Used) Cache Implementation
4
+ *
5
+ * LRU(最近最少使用)缓存实现
6
+ *
7
+ * Adapted from https://stackoverflow.com/a/46432113/11613622
8
+ *
9
+ * @module plugin-search/client/utils/lru
10
+ */
11
+ /**
12
+ * Generic LRU Cache implementation using Map.
13
+ *
14
+ * 使用 Map 实现的通用 LRU 缓存。
15
+ *
16
+ * Automatically evicts the least recently used item when the cache reaches
17
+ * its maximum size.
18
+ *
19
+ * 当缓存达到最大容量时,自动淘汰最近最少使用的项。
20
+ *
21
+ * @template K - Key type / 键类型
22
+ * @template V - Value type / 值类型
23
+ * @example
24
+ * const cache = new LRUCache<string, number>(3)
25
+ * cache.set('a', 1)
26
+ * cache.set('b', 2)
27
+ * cache.set('c', 3)
28
+ * cache.set('d', 4) // 'a' is evicted
29
+ * cache.get('b') // returns 2, 'b' becomes most recent
30
+ */
2
31
  var LRUCache = class {
32
+ /** Maximum number of items in the cache / 缓存中的最大项数 */
3
33
  max;
34
+ /** Internal Map storage / 内部 Map 存储 */
4
35
  cache;
36
+ /**
37
+ * Create a new LRU Cache instance.
38
+ *
39
+ * 创建新的 LRU 缓存实例。
40
+ *
41
+ * @param max - Maximum cache size (default: 10) / 最大缓存大小(默认:10)
42
+ */
5
43
  constructor(max = 10) {
6
44
  this.max = max;
7
45
  this.cache = /* @__PURE__ */ new Map();
8
46
  }
47
+ /**
48
+ * Get a value from the cache.
49
+ *
50
+ * 从缓存中获取值。
51
+ *
52
+ * Accessing an item moves it to the end (most recently used position).
53
+ *
54
+ * 访问项会将其移动到末尾(最近使用的位置)。
55
+ *
56
+ * @param key - Cache key / 缓存键
57
+ * @returns Cached value or undefined if not found / 缓存值,未找到则返回 undefined
58
+ */
9
59
  get(key) {
10
60
  const item = this.cache.get(key);
11
61
  if (item !== void 0) {
@@ -14,18 +64,42 @@ var LRUCache = class {
14
64
  }
15
65
  return item;
16
66
  }
67
+ /**
68
+ * Set a value in the cache.
69
+ *
70
+ * 在缓存中设置值。
71
+ *
72
+ * If the key already exists, it is moved to the end. If the cache is full,
73
+ * the oldest item is evicted.
74
+ *
75
+ * 如果键已存在,则将其移动到末尾。如果缓存已满,则淘汰最旧的项。
76
+ *
77
+ * @param key - Cache key / 缓存键
78
+ * @param val - Value to cache / 要缓存的值
79
+ */
17
80
  set(key, val) {
18
81
  if (this.cache.has(key)) this.cache.delete(key);
19
82
  else if (this.cache.size === this.max) this.cache.delete(this.first());
20
83
  this.cache.set(key, val);
21
84
  }
85
+ /**
86
+ * Get the first (oldest) key in the cache.
87
+ *
88
+ * 获取缓存中的第一个(最旧的)键。
89
+ *
90
+ * @returns The oldest key or undefined if cache is empty / 最旧的键,缓存为空则返回 undefined
91
+ */
22
92
  first() {
23
93
  return this.cache.keys().next().value;
24
94
  }
95
+ /**
96
+ * Clear all items from the cache.
97
+ *
98
+ * 清空缓存中的所有项。
99
+ */
25
100
  clear() {
26
101
  this.cache.clear();
27
102
  }
28
103
  };
29
-
30
104
  //#endregion
31
- export { LRUCache };
105
+ export { LRUCache };
@@ -3,11 +3,35 @@ import { App, Plugin } from "vuepress/core";
3
3
  export * from "../shared/index.js";
4
4
 
5
5
  //#region src/node/prepareSearchIndex.d.ts
6
+ /**
7
+ * Options for search index preparation.
8
+ *
9
+ * 搜索索引准备的配置选项。
10
+ */
6
11
  interface SearchIndexOptions {
12
+ /** VuePress application instance / VuePress 应用实例 */
7
13
  app: App;
14
+ /** MiniSearch configuration options / MiniSearch 配置选项 */
8
15
  searchOptions: SearchOptions;
16
+ /** Function to filter searchable pages / 过滤可搜索页面的函数 */
9
17
  isSearchable: SearchPluginOptions['isSearchable'];
10
18
  }
19
+ /**
20
+ * Prepare search indexes for all pages in the VuePress application.
21
+ *
22
+ * 为 VuePress 应用中的所有页面准备搜索索引。
23
+ *
24
+ * This function indexes all pages concurrently, creating separate MiniSearch
25
+ * instances for each locale, and writes the indexes to temporary files.
26
+ *
27
+ * 此函数并发索引所有页面,为每个语言创建独立的 MiniSearch 实例,
28
+ * 并将索引写入临时文件。
29
+ *
30
+ * @param options - Search index preparation options / 搜索索引准备选项
31
+ * @param options.app - VuePress application instance / VuePress 应用实例
32
+ * @param options.isSearchable - Function to filter searchable pages / 过滤可搜索页面的函数
33
+ * @param options.searchOptions - MiniSearch configuration / MiniSearch 配置
34
+ */
11
35
  declare function prepareSearchIndex({
12
36
  app,
13
37
  isSearchable,
@@ -15,6 +39,36 @@ declare function prepareSearchIndex({
15
39
  }: SearchIndexOptions): Promise<void>;
16
40
  //#endregion
17
41
  //#region src/node/searchPlugin.d.ts
42
+ /**
43
+ * Create a VuePress search plugin instance.
44
+ *
45
+ * 创建 VuePress 搜索插件实例。
46
+ *
47
+ * @param options - Plugin configuration options / 插件配置选项
48
+ * @param options.locales - Locale-specific search configurations / 特定语言的搜索配置
49
+ * @param options.isSearchable - Function to determine if a page should be indexed / 判断页面是否应被索引的函数
50
+ * @param options.searchOptions - MiniSearch options / MiniSearch 配置选项
51
+ * @returns VuePress plugin object / VuePress 插件对象
52
+ * @example
53
+ * // Basic usage
54
+ * export default {
55
+ * plugins: [
56
+ * searchPlugin()
57
+ * ]
58
+ * }
59
+ *
60
+ * // With custom options
61
+ * export default {
62
+ * plugins: [
63
+ * searchPlugin({
64
+ * locales: {
65
+ * '/zh/': { placeholder: '搜索文档' }
66
+ * },
67
+ * isSearchable: (page) => page.path !== '/secret/'
68
+ * })
69
+ * ]
70
+ * }
71
+ */
18
72
  declare function searchPlugin({
19
73
  locales,
20
74
  isSearchable,
package/lib/node/index.js CHANGED
@@ -2,14 +2,24 @@ import MiniSearch from "minisearch";
2
2
  import pMap from "p-map";
3
3
  import { colors, getDirname, logger, path } from "vuepress/utils";
4
4
  import { addViteOptimizeDepsInclude, getFullLocaleConfig } from "@vuepress/helper";
5
- import chokidar from "chokidar";
6
-
7
- export * from "../shared/index.js"
8
-
5
+ export * from "../shared/index.js";
9
6
  //#region src/node/prepareSearchIndex.ts
7
+ /** Directory path for storing search index files / 存储搜索索引文件的目录路径 */
10
8
  const SEARCH_INDEX_DIR = "internal/minisearchIndex/";
9
+ /** Map of locale paths to their MiniSearch instances / 语言路径到 MiniSearch 实例的映射 */
11
10
  const indexByLocales = /* @__PURE__ */ new Map();
11
+ /** Cache for index objects by file path / 按文件路径缓存索引对象 */
12
12
  const indexCache = /* @__PURE__ */ new Map();
13
+ /**
14
+ * Get or create a MiniSearch index for a specific locale.
15
+ *
16
+ * 获取或创建特定语言的 MiniSearch 索引。
17
+ *
18
+ * @param locale - Locale path (e.g., '/', '/zh/') / 语言路径
19
+ * @param lang - Language code for tokenization / 用于分词的语言代码
20
+ * @param options - Search index options / 搜索索引选项
21
+ * @returns MiniSearch instance for the locale / 该语言的 MiniSearch 实例
22
+ */
13
23
  function getIndexByLocale(locale, lang, options) {
14
24
  let index = indexByLocales.get(locale);
15
25
  if (!index) {
@@ -30,6 +40,14 @@ function getIndexByLocale(locale, lang, options) {
30
40
  }
31
41
  return index;
32
42
  }
43
+ /**
44
+ * Get or create an index cache for a specific file path.
45
+ *
46
+ * 获取或创建特定文件路径的索引缓存。
47
+ *
48
+ * @param filepath - File path to get cache for / 要获取缓存的文件路径
49
+ * @returns Array of index objects for the file / 该文件的索引对象数组
50
+ */
33
51
  function getIndexCache(filepath) {
34
52
  let index = indexCache.get(filepath);
35
53
  if (!index) {
@@ -38,9 +56,38 @@ function getIndexCache(filepath) {
38
56
  }
39
57
  return index;
40
58
  }
59
+ /**
60
+ * Write a placeholder search index file for development mode.
61
+ *
62
+ * 为开发模式写入占位符搜索索引文件。
63
+ *
64
+ * This is used to provide an empty index before the actual index is prepared,
65
+ * preventing errors when the search component loads before indexing completes.
66
+ *
67
+ * 用于在实际索引准备完成之前提供空索引,
68
+ * 防止搜索组件在索引完成之前加载时出错。
69
+ *
70
+ * @param app - VuePress application instance / VuePress 应用实例
71
+ */
41
72
  async function prepareSearchIndexPlaceholder(app) {
42
73
  await app.writeTemp(`${SEARCH_INDEX_DIR}index.js`, "export const searchIndex = {}");
43
74
  }
75
+ /**
76
+ * Prepare search indexes for all pages in the VuePress application.
77
+ *
78
+ * 为 VuePress 应用中的所有页面准备搜索索引。
79
+ *
80
+ * This function indexes all pages concurrently, creating separate MiniSearch
81
+ * instances for each locale, and writes the indexes to temporary files.
82
+ *
83
+ * 此函数并发索引所有页面,为每个语言创建独立的 MiniSearch 实例,
84
+ * 并将索引写入临时文件。
85
+ *
86
+ * @param options - Search index preparation options / 搜索索引准备选项
87
+ * @param options.app - VuePress application instance / VuePress 应用实例
88
+ * @param options.isSearchable - Function to filter searchable pages / 过滤可搜索页面的函数
89
+ * @param options.searchOptions - MiniSearch configuration / MiniSearch 配置
90
+ */
44
91
  async function prepareSearchIndex({ app, isSearchable, searchOptions }) {
45
92
  const start = performance.now();
46
93
  indexByLocales.clear();
@@ -53,15 +100,36 @@ async function prepareSearchIndex({ app, isSearchable, searchOptions }) {
53
100
  }
54
101
  if (app.env.isDebug) logger.info(`\n[${colors.green("@vuepress-plume/plugin-search")}] prepare search time spent: ${(performance.now() - start).toFixed(2)}ms`);
55
102
  }
56
- async function onSearchIndexUpdated(filepath, { app, isSearchable, searchOptions }) {
57
- if ((isSearchable ? app.pages.filter(isSearchable) : app.pages).some((p) => p.filePathRelative?.endsWith(filepath))) {
58
- await indexFile(app.pages.find((p) => p.filePathRelative?.endsWith(filepath)), searchOptions, isSearchable);
59
- await writeTemp(app);
60
- }
103
+ /**
104
+ * Handle search index update when a page is modified.
105
+ *
106
+ * 当页面被修改时处理搜索索引更新。
107
+ *
108
+ * @param app - VuePress application instance / VuePress 应用实例
109
+ * @param options - Search index preparation options / 搜索索引准备选项
110
+ * @param options.page - VuePress page instance / VuePress 页面实例
111
+ * @param options.isSearchable - Function to filter searchable pages / 过滤可搜索页面的函数
112
+ * @param options.searchOptions - MiniSearch configuration / MiniSearch 配置
113
+ */
114
+ async function onSearchIndexUpdated(app, { page, isSearchable, searchOptions }) {
115
+ if (isSearchable && !isSearchable(page)) return;
116
+ await indexFile(page, searchOptions, isSearchable);
117
+ await writeTemp(app);
61
118
  }
62
- async function onSearchIndexRemoved(filepath, { app, isSearchable, searchOptions }) {
63
- if ((isSearchable ? app.pages.filter(isSearchable) : app.pages).some((p) => p.filePathRelative?.endsWith(filepath))) {
64
- const page = app.pages.find((p) => p.filePathRelative?.endsWith(filepath));
119
+ /**
120
+ * Handle search index update when a page is removed.
121
+ *
122
+ * 当页面被删除时处理搜索索引更新。
123
+ *
124
+ * @param app - VuePress application instance / VuePress 应用实例
125
+ * @param options - Search index preparation options / 搜索索引准备选项
126
+ * @param options.page - VuePress page instance / VuePress 页面实例
127
+ * @param options.isSearchable - Function to filter searchable pages / 过滤可搜索页面的函数
128
+ * @param options.searchOptions - MiniSearch configuration / MiniSearch 配置
129
+ */
130
+ async function onSearchIndexRemoved(app, { page, isSearchable, searchOptions }) {
131
+ if (isSearchable && !isSearchable(page)) return;
132
+ if (page.filePathRelative) {
65
133
  const fileId = page.path;
66
134
  const locale = page.pathLocale;
67
135
  const lang = page.lang;
@@ -71,6 +139,19 @@ async function onSearchIndexRemoved(filepath, { app, isSearchable, searchOptions
71
139
  await writeTemp(app);
72
140
  }
73
141
  }
142
+ /**
143
+ * Write all search indexes to temporary files.
144
+ *
145
+ * 将所有搜索索引写入临时文件。
146
+ *
147
+ * Creates separate JavaScript files for each locale's search index,
148
+ * enabling lazy loading of indexes in the client.
149
+ *
150
+ * 为每个语言的搜索索引创建独立的 JavaScript 文件,
151
+ * 支持客户端延迟加载索引。
152
+ *
153
+ * @param app - VuePress application instance / VuePress 应用实例
154
+ */
74
155
  async function writeTemp(app) {
75
156
  const records = [];
76
157
  const promises = [];
@@ -83,6 +164,21 @@ async function writeTemp(app) {
83
164
  promises.push(app.writeTemp(`${SEARCH_INDEX_DIR}index.js`, `export const searchIndex = {${records.join(",")}}${app.env.isDev ? `\n${genHmrCode("searchIndex")}` : ""}`));
84
165
  await Promise.all(promises);
85
166
  }
167
+ /**
168
+ * Index a single page for search.
169
+ *
170
+ * 为单个页面建立搜索索引。
171
+ *
172
+ * Extracts content from the page, splits it into sections based on headings,
173
+ * and adds each section to the appropriate locale's MiniSearch index.
174
+ *
175
+ * 从页面提取内容,根据标题将其分割为章节,
176
+ * 并将每个章节添加到相应语言的 MiniSearch 索引中。
177
+ *
178
+ * @param page - VuePress page object / VuePress 页面对象
179
+ * @param options - MiniSearch options / MiniSearch 选项
180
+ * @param isSearchable - Function to check if page should be indexed / 检查页面是否应被索引的函数
181
+ */
86
182
  async function indexFile(page, options, isSearchable) {
87
183
  if (!page.filePath || page.frontmatter?.search === false) return;
88
184
  if (isSearchable && !isSearchable(page)) return;
@@ -112,11 +208,24 @@ ${page.contentRendered}`);
112
208
  }
113
209
  }
114
210
  }
211
+ /** Regex pattern for matching heading tags / 匹配标题标签的正则表达式 */
115
212
  const headingRegex = /<h(\d*).*?>(<a.*? href="#.*?".*?>[\s\S]*?<\/a>)<\/h\1>/gi;
213
+ /** Regex pattern for extracting heading content / 提取标题内容的正则表达式 */
116
214
  const headingContentRegex = /<a.*? href="#(.*?)".*?><span>([\s\S]*?)<\/span><\/a>/i;
215
+ /** Regex pattern for ignoring template content / 忽略模板内容的正则表达式 */
117
216
  const ignoreHeadingRegex = /<template[^>]*>[\s\S]*<\/template>/gi;
118
217
  /**
119
- * Splits HTML into sections based on headings
218
+ * Split HTML content into sections based on heading elements.
219
+ *
220
+ * 根据标题元素将 HTML 内容分割为章节。
221
+ *
222
+ * This generator function parses HTML content and yields section objects
223
+ * containing anchor, titles hierarchy, and searchable text.
224
+ *
225
+ * 此生成器函数解析 HTML 内容并生成包含锚点、标题层级和可搜索文本的章节对象。
226
+ *
227
+ * @param html - HTML content to split / 要分割的 HTML 内容
228
+ * @yields Section objects with anchor, titles, and text / 包含锚点、标题和文本的章节对象
120
229
  */
121
230
  function* splitPageIntoSections(html) {
122
231
  const result = html.split(headingRegex);
@@ -142,14 +251,38 @@ function* splitPageIntoSections(html) {
142
251
  };
143
252
  }
144
253
  }
254
+ /**
255
+ * Extract searchable text from HTML content by removing tags.
256
+ *
257
+ * 通过移除标签从 HTML 内容中提取可搜索文本。
258
+ *
259
+ * @param content - HTML content / HTML 内容
260
+ * @returns Plain text content / 纯文本内容
261
+ */
145
262
  function getSearchableText(content) {
146
263
  content = clearHtmlTags(content);
147
264
  return content;
148
265
  }
266
+ /**
267
+ * Remove all HTML tags from a string.
268
+ *
269
+ * 移除字符串中的所有 HTML 标签。
270
+ *
271
+ * @param str - String containing HTML / 包含 HTML 的字符串
272
+ * @returns String with HTML tags removed / 移除 HTML 标签后的字符串
273
+ */
149
274
  function clearHtmlTags(str) {
150
275
  str = str.replace(ignoreHeadingRegex, "");
151
276
  return str.replace(/<[^>]*>/g, "");
152
277
  }
278
+ /**
279
+ * Generate HMR (Hot Module Replacement) code for the search index.
280
+ *
281
+ * 为搜索索引生成 HMR(热模块替换)代码。
282
+ *
283
+ * @param m - Module name / 模块名称
284
+ * @returns HMR code string / HMR 代码字符串
285
+ */
153
286
  function genHmrCode(m) {
154
287
  const func = `update${m[0].toUpperCase()}${m.slice(1)}`;
155
288
  return `
@@ -167,153 +300,162 @@ if (import.meta.hot) {
167
300
  }
168
301
  `;
169
302
  }
170
-
171
- //#endregion
172
- //#region src/node/locales/de.ts
173
- const deSearchLocale = {
174
- placeholder: "Dokumente durchsuchen",
175
- resetButtonTitle: "Suche zurücksetzen",
176
- backButtonTitle: "Schließen",
177
- noResultsText: "Keine Suchergebnisse:",
178
- footer: {
179
- selectText: "Auswählen",
180
- selectKeyAriaLabel: "Eingabe",
181
- navigateText: "Wechseln",
182
- navigateUpKeyAriaLabel: "Nach oben",
183
- navigateDownKeyAriaLabel: "Nach unten",
184
- closeText: "Schließen",
185
- closeKeyAriaLabel: "Beenden"
186
- }
187
- };
188
-
189
- //#endregion
190
- //#region src/node/locales/en.ts
191
- const enSearchLocale = {
192
- placeholder: "Search",
193
- resetButtonTitle: "Reset search",
194
- backButtonTitle: "Close search",
195
- noResultsText: "No results for",
196
- footer: {
197
- selectText: "to select",
198
- selectKeyAriaLabel: "enter",
199
- navigateText: "to navigate",
200
- navigateUpKeyAriaLabel: "up arrow",
201
- navigateDownKeyAriaLabel: "down arrow",
202
- closeText: "to close",
203
- closeKeyAriaLabel: "escape"
204
- }
205
- };
206
-
207
- //#endregion
208
- //#region src/node/locales/fr.ts
209
- const frSearchLocale = {
210
- placeholder: "Rechercher dans la documentation",
211
- resetButtonTitle: "Réinitialiser la recherche",
212
- backButtonTitle: "Fermer",
213
- noResultsText: "Aucun résultat trouvé :",
214
- footer: {
215
- selectText: "sélectionner",
216
- selectKeyAriaLabel: "Entrée",
217
- navigateText: "naviguer",
218
- navigateUpKeyAriaLabel: "haut",
219
- navigateDownKeyAriaLabel: "bas",
220
- closeText: "fermer",
221
- closeKeyAriaLabel: "sortie"
222
- }
223
- };
224
-
225
- //#endregion
226
- //#region src/node/locales/ja.ts
227
- const jaSearchLocale = {
228
- placeholder: "ドキュメントを検索",
229
- resetButtonTitle: "検索をリセット",
230
- backButtonTitle: "閉じる",
231
- noResultsText: "検索結果がありません:",
232
- footer: {
233
- selectText: "選択",
234
- selectKeyAriaLabel: "入力",
235
- navigateText: "切り替え",
236
- navigateUpKeyAriaLabel: "上へ",
237
- navigateDownKeyAriaLabel: "下へ",
238
- closeText: "閉じる",
239
- closeKeyAriaLabel: "終了"
240
- }
241
- };
242
-
243
- //#endregion
244
- //#region src/node/locales/ru.ts
245
- const ruSearchLocale = {
246
- placeholder: "Поиск по документации",
247
- resetButtonTitle: "Сбросить поиск",
248
- backButtonTitle: "Закрыть",
249
- noResultsText: "Нет результатов поиска:",
250
- footer: {
251
- selectText: "Выбрать",
252
- selectKeyAriaLabel: "Ввод",
253
- navigateText: "Переключить",
254
- navigateUpKeyAriaLabel: "Вверх",
255
- navigateDownKeyAriaLabel: "Вниз",
256
- closeText: "Закрыть",
257
- closeKeyAriaLabel: "Выход"
258
- }
259
- };
260
-
261
- //#endregion
262
- //#region src/node/locales/zh-tw.ts
263
- const zhTwSearchLocale = {
264
- placeholder: "搜尋文件",
265
- resetButtonTitle: "重設搜尋",
266
- backButtonTitle: "關閉",
267
- noResultsText: "無搜尋結果:",
268
- footer: {
269
- selectText: "選擇",
270
- selectKeyAriaLabel: "輸入",
271
- navigateText: "切換",
272
- navigateUpKeyAriaLabel: "向上",
273
- navigateDownKeyAriaLabel: "向下",
274
- closeText: "關閉",
275
- closeKeyAriaLabel: "退出"
276
- }
277
- };
278
-
279
- //#endregion
280
- //#region src/node/locales/zh.ts
281
- const zhSearchLocale = {
282
- placeholder: "搜索文档",
283
- resetButtonTitle: "重置搜索",
284
- backButtonTitle: "关闭",
285
- noResultsText: "无搜索结果:",
286
- footer: {
287
- selectText: "选择",
288
- selectKeyAriaLabel: "输入",
289
- navigateText: "切换",
290
- navigateUpKeyAriaLabel: "向上",
291
- navigateDownKeyAriaLabel: "向下",
292
- closeText: "关闭",
293
- closeKeyAriaLabel: "退出"
294
- }
295
- };
296
-
297
303
  //#endregion
298
304
  //#region src/node/locales/index.ts
305
+ /**
306
+ * Default locale configurations for search plugin.
307
+ *
308
+ * 搜索插件的默认语言配置。
309
+ *
310
+ * Maps language codes to their respective locale strings.
311
+ *
312
+ * 将语言代码映射到相应的本地化字符串。
313
+ */
299
314
  const SEARCH_LOCALES = [
300
- [["en", "en-US"], enSearchLocale],
315
+ [["en", "en-US"], {
316
+ placeholder: "Search",
317
+ resetButtonTitle: "Reset search",
318
+ backButtonTitle: "Close search",
319
+ noResultsText: "No results for",
320
+ footer: {
321
+ selectText: "to select",
322
+ selectKeyAriaLabel: "enter",
323
+ navigateText: "to navigate",
324
+ navigateUpKeyAriaLabel: "up arrow",
325
+ navigateDownKeyAriaLabel: "down arrow",
326
+ closeText: "to close",
327
+ closeKeyAriaLabel: "escape"
328
+ }
329
+ }],
301
330
  [[
302
331
  "zh",
303
332
  "zh-CN",
304
333
  "zh-Hans",
305
334
  "zh-Hant"
306
- ], zhSearchLocale],
307
- [["zh-TW"], zhTwSearchLocale],
308
- [["de", "de-DE"], deSearchLocale],
309
- [["fr", "fr-FR"], frSearchLocale],
310
- [["ru", "ru-RU"], ruSearchLocale],
311
- [["ja", "ja-JP"], jaSearchLocale]
335
+ ], {
336
+ placeholder: "搜索文档",
337
+ resetButtonTitle: "重置搜索",
338
+ backButtonTitle: "关闭",
339
+ noResultsText: "无搜索结果:",
340
+ footer: {
341
+ selectText: "选择",
342
+ selectKeyAriaLabel: "输入",
343
+ navigateText: "切换",
344
+ navigateUpKeyAriaLabel: "向上",
345
+ navigateDownKeyAriaLabel: "向下",
346
+ closeText: "关闭",
347
+ closeKeyAriaLabel: "退出"
348
+ }
349
+ }],
350
+ [["zh-TW"], {
351
+ placeholder: "搜尋文件",
352
+ resetButtonTitle: "重設搜尋",
353
+ backButtonTitle: "關閉",
354
+ noResultsText: "無搜尋結果:",
355
+ footer: {
356
+ selectText: "選擇",
357
+ selectKeyAriaLabel: "輸入",
358
+ navigateText: "切換",
359
+ navigateUpKeyAriaLabel: "向上",
360
+ navigateDownKeyAriaLabel: "向下",
361
+ closeText: "關閉",
362
+ closeKeyAriaLabel: "退出"
363
+ }
364
+ }],
365
+ [["de", "de-DE"], {
366
+ placeholder: "Dokumente durchsuchen",
367
+ resetButtonTitle: "Suche zurücksetzen",
368
+ backButtonTitle: "Schließen",
369
+ noResultsText: "Keine Suchergebnisse:",
370
+ footer: {
371
+ selectText: "Auswählen",
372
+ selectKeyAriaLabel: "Eingabe",
373
+ navigateText: "Wechseln",
374
+ navigateUpKeyAriaLabel: "Nach oben",
375
+ navigateDownKeyAriaLabel: "Nach unten",
376
+ closeText: "Schließen",
377
+ closeKeyAriaLabel: "Beenden"
378
+ }
379
+ }],
380
+ [["fr", "fr-FR"], {
381
+ placeholder: "Rechercher dans la documentation",
382
+ resetButtonTitle: "Réinitialiser la recherche",
383
+ backButtonTitle: "Fermer",
384
+ noResultsText: "Aucun résultat trouvé :",
385
+ footer: {
386
+ selectText: "sélectionner",
387
+ selectKeyAriaLabel: "Entrée",
388
+ navigateText: "naviguer",
389
+ navigateUpKeyAriaLabel: "haut",
390
+ navigateDownKeyAriaLabel: "bas",
391
+ closeText: "fermer",
392
+ closeKeyAriaLabel: "sortie"
393
+ }
394
+ }],
395
+ [["ru", "ru-RU"], {
396
+ placeholder: "Поиск по документации",
397
+ resetButtonTitle: "Сбросить поиск",
398
+ backButtonTitle: "Закрыть",
399
+ noResultsText: "Нет результатов поиска:",
400
+ footer: {
401
+ selectText: "Выбрать",
402
+ selectKeyAriaLabel: "Ввод",
403
+ navigateText: "Переключить",
404
+ navigateUpKeyAriaLabel: "Вверх",
405
+ navigateDownKeyAriaLabel: "Вниз",
406
+ closeText: "Закрыть",
407
+ closeKeyAriaLabel: "Выход"
408
+ }
409
+ }],
410
+ [["ja", "ja-JP"], {
411
+ placeholder: "ドキュメントを検索",
412
+ resetButtonTitle: "検索をリセット",
413
+ backButtonTitle: "閉じる",
414
+ noResultsText: "検索結果がありません:",
415
+ footer: {
416
+ selectText: "選択",
417
+ selectKeyAriaLabel: "入力",
418
+ navigateText: "切り替え",
419
+ navigateUpKeyAriaLabel: "上へ",
420
+ navigateDownKeyAriaLabel: "下へ",
421
+ closeText: "閉じる",
422
+ closeKeyAriaLabel: "終了"
423
+ }
424
+ }]
312
425
  ];
313
-
314
426
  //#endregion
315
427
  //#region src/node/searchPlugin.ts
316
428
  const __dirname = getDirname(import.meta.url);
429
+ /**
430
+ * Create a VuePress search plugin instance.
431
+ *
432
+ * 创建 VuePress 搜索插件实例。
433
+ *
434
+ * @param options - Plugin configuration options / 插件配置选项
435
+ * @param options.locales - Locale-specific search configurations / 特定语言的搜索配置
436
+ * @param options.isSearchable - Function to determine if a page should be indexed / 判断页面是否应被索引的函数
437
+ * @param options.searchOptions - MiniSearch options / MiniSearch 配置选项
438
+ * @returns VuePress plugin object / VuePress 插件对象
439
+ * @example
440
+ * // Basic usage
441
+ * export default {
442
+ * plugins: [
443
+ * searchPlugin()
444
+ * ]
445
+ * }
446
+ *
447
+ * // With custom options
448
+ * export default {
449
+ * plugins: [
450
+ * searchPlugin({
451
+ * locales: {
452
+ * '/zh/': { placeholder: '搜索文档' }
453
+ * },
454
+ * isSearchable: (page) => page.path !== '/secret/'
455
+ * })
456
+ * ]
457
+ * }
458
+ */
317
459
  function searchPlugin({ locales = {}, isSearchable, ...searchOptions } = {}) {
318
460
  return (app) => ({
319
461
  name: "@vuepress-plume/plugin-search",
@@ -349,37 +491,20 @@ function searchPlugin({ locales = {}, isSearchable, ...searchOptions } = {}) {
349
491
  });
350
492
  }
351
493
  },
352
- onWatched: (app, watchers) => {
353
- const searchIndexWatcher = chokidar.watch("pages", {
354
- cwd: app.dir.temp(),
355
- ignoreInitial: true,
356
- ignored: (filepath, stats) => Boolean(stats?.isFile()) && !filepath.endsWith(".js")
357
- });
358
- searchIndexWatcher.on("add", (filepath) => {
359
- onSearchIndexUpdated(filepath, {
360
- app,
361
- isSearchable,
362
- searchOptions
363
- });
364
- });
365
- searchIndexWatcher.on("change", (filepath) => {
366
- onSearchIndexUpdated(filepath, {
367
- app,
368
- isSearchable,
369
- searchOptions
370
- });
494
+ onPageUpdated: async (app, type, page) => {
495
+ if (!page?.filePathRelative) return;
496
+ if (type === "create" || type === "update") await onSearchIndexUpdated(app, {
497
+ page,
498
+ isSearchable,
499
+ searchOptions
371
500
  });
372
- searchIndexWatcher.on("unlink", (filepath) => {
373
- onSearchIndexRemoved(filepath, {
374
- app,
375
- isSearchable,
376
- searchOptions
377
- });
501
+ else if (type === "delete") await onSearchIndexRemoved(app, {
502
+ page,
503
+ isSearchable,
504
+ searchOptions
378
505
  });
379
- watchers.push(searchIndexWatcher);
380
506
  }
381
507
  });
382
508
  }
383
-
384
509
  //#endregion
385
- export { prepareSearchIndex, searchPlugin };
510
+ export { prepareSearchIndex, searchPlugin };
@@ -2,38 +2,90 @@ import { Options } from "minisearch";
2
2
  import { LocaleConfig, Page } from "vuepress/core";
3
3
 
4
4
  //#region src/shared/index.d.ts
5
+ /**
6
+ * Locale options for search UI strings.
7
+ *
8
+ * 搜索 UI 字符串的语言选项。
9
+ */
5
10
  interface SearchLocaleOptions {
11
+ /** Placeholder text for search input / 搜索输入框的占位文本 */
6
12
  placeholder: string;
13
+ /** Text for search button / 搜索按钮的文本 */
7
14
  buttonText: string;
15
+ /** Title for reset button / 重置按钮的标题 */
8
16
  resetButtonTitle: string;
17
+ /** Title for back/close button / 返回/关闭按钮的标题 */
9
18
  backButtonTitle: string;
19
+ /** Text shown when no results found / 无搜索结果时显示的文本 */
10
20
  noResultsText: string;
21
+ /** Footer keyboard shortcut hints / 底部键盘快捷键提示 */
11
22
  footer: {
12
- selectText: string;
13
- selectKeyAriaLabel: string;
14
- navigateText: string;
15
- navigateUpKeyAriaLabel: string;
16
- navigateDownKeyAriaLabel: string;
17
- closeText: string;
23
+ /** Text for select action / 选择操作的文本 */selectText: string; /** ARIA label for select key / 选择键的 ARIA 标签 */
24
+ selectKeyAriaLabel: string; /** Text for navigate action / 导航操作的文本 */
25
+ navigateText: string; /** ARIA label for navigate up key / 向上导航键的 ARIA 标签 */
26
+ navigateUpKeyAriaLabel: string; /** ARIA label for navigate down key / 向下导航键的 ARIA 标签 */
27
+ navigateDownKeyAriaLabel: string; /** Text for close action / 关闭操作的文本 */
28
+ closeText: string; /** ARIA label for close key / 关闭键的 ARIA 标签 */
18
29
  closeKeyAriaLabel: string;
19
30
  };
20
31
  }
32
+ /**
33
+ * Locale configuration type for search box.
34
+ *
35
+ * 搜索框的语言配置类型。
36
+ */
21
37
  type SearchBoxLocales = LocaleConfig<SearchLocaleOptions>;
38
+ /**
39
+ * Options for the search plugin.
40
+ *
41
+ * 搜索插件的选项。
42
+ */
22
43
  interface SearchPluginOptions extends SearchOptions {
44
+ /** Locale-specific search configurations / 特定语言的搜索配置 */
23
45
  locales?: SearchBoxLocales;
46
+ /**
47
+ * Function to determine if a page should be indexed.
48
+ *
49
+ * 判断页面是否应被索引的函数。
50
+ *
51
+ * @param page - VuePress page object / VuePress 页面对象
52
+ * @returns Whether the page should be searchable / 页面是否可搜索
53
+ */
24
54
  isSearchable?: (page: Page) => boolean;
25
55
  }
56
+ /**
57
+ * Search configuration options.
58
+ *
59
+ * 搜索配置选项。
60
+ */
26
61
  interface SearchOptions {
27
62
  /**
63
+ * Whether to disable query persistence in session storage.
64
+ *
65
+ * 是否禁用会话存储中的查询持久化。
66
+ *
28
67
  * @default false
29
68
  */
30
69
  disableQueryPersistence?: boolean;
70
+ /**
71
+ * MiniSearch configuration options.
72
+ *
73
+ * MiniSearch 配置选项。
74
+ */
31
75
  miniSearch?: {
32
76
  /**
77
+ * MiniSearch instance options.
78
+ *
79
+ * MiniSearch 实例选项。
80
+ *
33
81
  * @see https://lucaong.github.io/minisearch/modules/_minisearch_.html#options
34
82
  */
35
83
  options?: Pick<Options, 'extractField' | 'tokenize' | 'processTerm'>;
36
84
  /**
85
+ * MiniSearch search options.
86
+ *
87
+ * MiniSearch 搜索选项。
88
+ *
37
89
  * @see https://lucaong.github.io/minisearch/modules/_minisearch_.html#searchoptions-1
38
90
  */
39
91
  searchOptions?: Options['searchOptions'];
@@ -1 +1 @@
1
- export { };
1
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vuepress-plume/plugin-search",
3
3
  "type": "module",
4
- "version": "1.0.0-rc.192",
4
+ "version": "1.0.0-rc.193",
5
5
  "description": "The Plugin for VuePress 2 - local search",
6
6
  "author": "pengzhanbo <volodymyr@foxmail.com>",
7
7
  "license": "MIT",
@@ -31,18 +31,18 @@
31
31
  "lib"
32
32
  ],
33
33
  "peerDependencies": {
34
- "vuepress": "2.0.0-rc.26"
34
+ "vuepress": "2.0.0-rc.28"
35
35
  },
36
36
  "dependencies": {
37
- "@vuepress/helper": "2.0.0-rc.123",
37
+ "@vuepress/helper": "2.0.0-rc.128",
38
38
  "@vueuse/core": "^14.2.1",
39
39
  "@vueuse/integrations": "^14.2.1",
40
40
  "chokidar": "5.0.0",
41
- "focus-trap": "^8.0.0",
41
+ "focus-trap": "^8.0.1",
42
42
  "mark.js": "^8.11.1",
43
43
  "minisearch": "^7.2.0",
44
44
  "p-map": "^7.0.4",
45
- "vue": "^3.5.29"
45
+ "vue": "^3.5.31"
46
46
  },
47
47
  "publishConfig": {
48
48
  "access": "public",
@@ -58,6 +58,6 @@
58
58
  "build": "pnpm run tsdown && pnpm run copy",
59
59
  "clean": "rimraf --glob ./lib",
60
60
  "copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib",
61
- "tsdown": "tsdown"
61
+ "tsdown": "tsdown --config-loader unrun"
62
62
  }
63
63
  }