ezal-theme-example 0.0.1
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.
- package/LICENSE +21 -0
- package/assets/scripts/404.ts +353 -0
- package/assets/scripts/_article.ts +290 -0
- package/assets/scripts/_base.ts +65 -0
- package/assets/scripts/_pagefind.d.ts +424 -0
- package/assets/scripts/_search.ts +88 -0
- package/assets/scripts/_utils.ts +74 -0
- package/assets/scripts/archive.ts +143 -0
- package/assets/scripts/article.ts +18 -0
- package/assets/scripts/category.ts +4 -0
- package/assets/scripts/home.ts +73 -0
- package/assets/scripts/links.ts +14 -0
- package/assets/scripts/main.ts +11 -0
- package/assets/scripts/page.ts +11 -0
- package/assets/scripts/tag.ts +4 -0
- package/assets/scripts/tsconfig.json +10 -0
- package/assets/styles/404.styl +31 -0
- package/assets/styles/_article/fold.styl +15 -0
- package/assets/styles/_article/footnote.styl +12 -0
- package/assets/styles/_article/heading.styl +29 -0
- package/assets/styles/_article/image.styl +30 -0
- package/assets/styles/_article/kbd.styl +10 -0
- package/assets/styles/_article/links.styl +31 -0
- package/assets/styles/_article/list.styl +19 -0
- package/assets/styles/_article/note.styl +18 -0
- package/assets/styles/_article/other.styl +44 -0
- package/assets/styles/_article/table.styl +29 -0
- package/assets/styles/_article/tabs.styl +25 -0
- package/assets/styles/_code.styl +83 -0
- package/assets/styles/_index/contact.styl +20 -0
- package/assets/styles/_index/footer.styl +5 -0
- package/assets/styles/_index/header.styl +40 -0
- package/assets/styles/_index/nav.styl +59 -0
- package/assets/styles/_index/search.styl +64 -0
- package/assets/styles/_index.styl +91 -0
- package/assets/styles/_var.styl +96 -0
- package/assets/styles/archive.styl +35 -0
- package/assets/styles/article.styl +138 -0
- package/assets/styles/category.styl +4 -0
- package/assets/styles/home.styl +124 -0
- package/assets/styles/links.styl +121 -0
- package/assets/styles/page.styl +12 -0
- package/assets/styles/tag.styl +4 -0
- package/dist/config.d.ts +128 -0
- package/dist/feed.d.ts +4 -0
- package/dist/image/asset.d.ts +22 -0
- package/dist/image/db.d.ts +18 -0
- package/dist/image/index.d.ts +10 -0
- package/dist/image/metadata.d.ts +2 -0
- package/dist/image/utils.d.ts +1 -0
- package/dist/index-now.d.ts +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2066 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +2 -0
- package/dist/markdown/codeblock/data.d.ts +1 -0
- package/dist/markdown/codeblock/index.d.ts +6 -0
- package/dist/markdown/codeblock/style.d.ts +2 -0
- package/dist/markdown/fold.d.ts +6 -0
- package/dist/markdown/footnote.d.ts +15 -0
- package/dist/markdown/image.d.ts +12 -0
- package/dist/markdown/index.d.ts +2 -0
- package/dist/markdown/kbd.d.ts +6 -0
- package/dist/markdown/link.d.ts +2 -0
- package/dist/markdown/links.d.ts +12 -0
- package/dist/markdown/note.d.ts +8 -0
- package/dist/markdown/table.d.ts +3 -0
- package/dist/markdown/tabs.d.ts +7 -0
- package/dist/markdown/tex.d.ts +9 -0
- package/dist/page/404.d.ts +1 -0
- package/dist/page/archive.d.ts +1 -0
- package/dist/page/category.d.ts +1 -0
- package/dist/page/home.d.ts +2 -0
- package/dist/page/tag.d.ts +1 -0
- package/dist/pagefind.d.ts +20 -0
- package/dist/sitemap.d.ts +2 -0
- package/dist/transform/script.d.ts +2 -0
- package/dist/transform/stylus.d.ts +2 -0
- package/dist/utils.d.ts +2 -0
- package/layouts/404.tsx +8 -0
- package/layouts/archive.tsx +81 -0
- package/layouts/article.tsx +145 -0
- package/layouts/base.tsx +20 -0
- package/layouts/category.tsx +18 -0
- package/layouts/components/ArchiveArticleList.tsx +14 -0
- package/layouts/components/Article.tsx +46 -0
- package/layouts/components/Contact.tsx +14 -0
- package/layouts/components/Footer.tsx +44 -0
- package/layouts/components/Head.tsx +119 -0
- package/layouts/components/Image.tsx +42 -0
- package/layouts/components/Nav.tsx +33 -0
- package/layouts/components/Search.tsx +20 -0
- package/layouts/components/Waline.tsx +22 -0
- package/layouts/context.d.ts +54 -0
- package/layouts/home.tsx +74 -0
- package/layouts/links.tsx +53 -0
- package/layouts/page.tsx +19 -0
- package/layouts/tag.tsx +18 -0
- package/layouts/tsconfig.json +11 -0
- package/package.json +47 -0
- package/readme.md +17 -0
- package/readme_zh.md +17 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { initSearch } from './_search';
|
|
2
|
+
import { $, $$, doc, handle, sleep } from './_utils';
|
|
3
|
+
|
|
4
|
+
function initNav() {
|
|
5
|
+
const docE = doc.documentElement;
|
|
6
|
+
const nav = $('nav')!;
|
|
7
|
+
let prev = docE.scrollTop;
|
|
8
|
+
function scrollHandler() {
|
|
9
|
+
nav.classList.remove('nav-show');
|
|
10
|
+
const top = docE.scrollTop;
|
|
11
|
+
nav.classList.toggle('glass', top > 64);
|
|
12
|
+
|
|
13
|
+
const hide = top > prev && top > 64;
|
|
14
|
+
nav.classList.toggle('nav-hide', hide);
|
|
15
|
+
|
|
16
|
+
prev = top;
|
|
17
|
+
}
|
|
18
|
+
handle(window, 'scroll', scrollHandler);
|
|
19
|
+
scrollHandler();
|
|
20
|
+
|
|
21
|
+
handle(doc.getElementById('nav')!, 'click', () => {
|
|
22
|
+
nav.classList.add('glass');
|
|
23
|
+
if (nav.classList.toggle('nav-show')) return;
|
|
24
|
+
if (docE.scrollTop > 64) return;
|
|
25
|
+
nav.classList.remove('glass');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
handle(docE, 'click', async ({ target }) => {
|
|
29
|
+
if ((target as HTMLElement).tagName !== 'A') return;
|
|
30
|
+
if (!(target as HTMLElement).closest('.toc')) return;
|
|
31
|
+
await sleep(10);
|
|
32
|
+
nav.classList.add('nav-hide');
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function initImage() {
|
|
37
|
+
const done = (img: HTMLImageElement) => img.classList.add('loaded');
|
|
38
|
+
for (const img of $$<HTMLImageElement>('img')) {
|
|
39
|
+
if (img.complete) done(img);
|
|
40
|
+
else handle(img, 'load', () => done(img));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const TIME_OF_DAY = 1000 * 60 * 60 * 24;
|
|
45
|
+
|
|
46
|
+
function initFooter() {
|
|
47
|
+
const now = new Date();
|
|
48
|
+
const diff = (date: Date) =>
|
|
49
|
+
Math.floor((now.getTime() - date.getTime()) / TIME_OF_DAY);
|
|
50
|
+
const nowE = $<HTMLTimeElement>('footer #now')!;
|
|
51
|
+
nowE.textContent = now.getFullYear().toString();
|
|
52
|
+
const lastE = $<HTMLTimeElement>('footer #last')!;
|
|
53
|
+
const last = new Date(lastE.dateTime);
|
|
54
|
+
lastE.textContent = `${diff(last)} 天前`;
|
|
55
|
+
const sinceE = $<HTMLTimeElement>('footer #since')!;
|
|
56
|
+
const since = new Date(sinceE.dateTime);
|
|
57
|
+
sinceE.textContent = ` ${diff(since)} `;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function initBase() {
|
|
61
|
+
initNav();
|
|
62
|
+
initImage();
|
|
63
|
+
initFooter();
|
|
64
|
+
initSearch();
|
|
65
|
+
}
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
// Type definitions for Pagefind
|
|
2
|
+
// Project: https://github.com/Pagefind/pagefind
|
|
3
|
+
// Definitions by: Based on official Pagefind documentation
|
|
4
|
+
|
|
5
|
+
declare module '@pagefind' {
|
|
6
|
+
/**
|
|
7
|
+
* 初始化 Pagefind
|
|
8
|
+
* @description 加载 Pagefind 依赖项和站点元数据。此方法是可选的,如果省略,将在调用第一个搜索或过滤函数时进行初始化。
|
|
9
|
+
* 当搜索界面获得焦点时调用此方法,有助于在用户输入搜索查询时加载核心依赖项。
|
|
10
|
+
*/
|
|
11
|
+
export function init(): void;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 配置 Pagefind 选项
|
|
15
|
+
* @description 在运行 pagefind.init() 之前设置 Pagefind 选项。在初始化后调用 pagefind.options 也可以,但传递 bundlePath 等设置将在初始化后无效。
|
|
16
|
+
* @param options 配置选项
|
|
17
|
+
*/
|
|
18
|
+
export function options(options: PagefindIndexOptions): Promise<void>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 执行搜索
|
|
22
|
+
* @description 执行搜索查询并返回匹配的结果
|
|
23
|
+
* @param query 搜索查询字符串
|
|
24
|
+
* @param options 搜索选项
|
|
25
|
+
* @returns 搜索结果
|
|
26
|
+
*/
|
|
27
|
+
export function search(
|
|
28
|
+
query: string | null,
|
|
29
|
+
options?: PagefindSearchOptions,
|
|
30
|
+
): Promise<PagefindSearchResults>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 防抖搜索
|
|
34
|
+
* @description 等待指定持续时间后执行搜索,如果在等待期间进行了后续调用,则返回 null
|
|
35
|
+
* @param query 搜索查询字符串
|
|
36
|
+
* @param options 搜索选项
|
|
37
|
+
* @param timeout 防抖超时时间(毫秒),默认为 300
|
|
38
|
+
* @returns 搜索结果或 null(如果被后续搜索调用取消)
|
|
39
|
+
*/
|
|
40
|
+
export function debouncedSearch(
|
|
41
|
+
query: string,
|
|
42
|
+
options?: PagefindSearchOptions,
|
|
43
|
+
timeout?: number,
|
|
44
|
+
): Promise<PagefindSearchResults | null>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 预加载搜索词
|
|
48
|
+
* @description 在用户输入时预加载索引,以加速后续搜索查询。不会导致冗余网络请求。
|
|
49
|
+
* @param query 要预加载的搜索查询
|
|
50
|
+
* @param options 搜索选项
|
|
51
|
+
*/
|
|
52
|
+
export function preload(query: string, options?: PagefindSearchOptions): void;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 获取可用过滤器
|
|
56
|
+
* @description 加载可用的过滤器及其结果计数
|
|
57
|
+
* @returns 过滤器及其计数字典
|
|
58
|
+
*/
|
|
59
|
+
export function filters(): Promise<PagefindFilterCounts>;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 销毁 Pagefind 实例
|
|
63
|
+
* @description 卸载活动的 Pagefind 实例,并重置所有通过 pagefind.options() 传递的设置
|
|
64
|
+
*/
|
|
65
|
+
export function destroy(): Promise<void>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Pagefind 索引配置选项
|
|
69
|
+
*/
|
|
70
|
+
export interface PagefindIndexOptions {
|
|
71
|
+
/**
|
|
72
|
+
* 基础 URL
|
|
73
|
+
* @description 默认为 "/"。如果站点托管在子路径上,可以提供此选项,它将附加到所有搜索结果 URL 的前面。
|
|
74
|
+
* @default "/"
|
|
75
|
+
*/
|
|
76
|
+
baseUrl?: string;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 包路径
|
|
80
|
+
* @description 覆盖包目录。在大多数情况下,这应该通过导入 URL 自动检测。如果搜索不工作并且看到控制台警告无法检测到此路径,请设置此选项。
|
|
81
|
+
*/
|
|
82
|
+
bundlePath?: string;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 摘要长度
|
|
86
|
+
* @description 设置生成的摘要的最大长度
|
|
87
|
+
* @default 30
|
|
88
|
+
*/
|
|
89
|
+
excerptLength?: number;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 高亮查询参数
|
|
93
|
+
* @description 如果设置,Pagefind 将搜索词作为同名的查询参数添加
|
|
94
|
+
*/
|
|
95
|
+
highlightParam?: string;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 自定义排名权重
|
|
99
|
+
* @description 提供微调 Pagefind 排名算法的能力,以更好地适应您的数据集
|
|
100
|
+
*/
|
|
101
|
+
ranking?: PagefindRankingWeights;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 索引权重
|
|
105
|
+
* @description 将此索引的所有排名乘以给定的权重。仅适用于多站点设置。
|
|
106
|
+
*/
|
|
107
|
+
indexWeight?: number;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 合并过滤器
|
|
111
|
+
* @description 将所有搜索查询中的过滤器对象合并到此索引中。仅适用于多站点设置。
|
|
112
|
+
*/
|
|
113
|
+
mergeFilter?: object;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 禁用 Web Worker
|
|
117
|
+
* @description 默认为 false。如果设置为 true,强制 Pagefind 在主线程上运行所有搜索操作而不是使用 Web Worker。
|
|
118
|
+
* @default false
|
|
119
|
+
*/
|
|
120
|
+
noWorker?: boolean;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Pagefind 排名权重配置
|
|
125
|
+
*/
|
|
126
|
+
export interface PagefindRankingWeights {
|
|
127
|
+
/**
|
|
128
|
+
* 术语相似性权重
|
|
129
|
+
* @description 控制基于术语与搜索查询相似性(长度)的页面排名。
|
|
130
|
+
* 增加此数值意味着当页面包含与查询非常接近的单词时排名更高,
|
|
131
|
+
* 例如,如果搜索 "part",那么包含 "party" 的页面将比包含 "partition" 的页面排名更高。
|
|
132
|
+
* 最小值为 0.0,此时 "party" 和 "partition" 将被视为同等重要。
|
|
133
|
+
*/
|
|
134
|
+
termSimilarity?: number;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 页面长度权重
|
|
138
|
+
* @description 控制平均页面长度对排名的影响程度。
|
|
139
|
+
* 最大值为 1.0,排名将强烈偏向比站点平均页面短的页面。
|
|
140
|
+
* 最小值为 0.0,排名将仅查看术语频率,而不考虑文档长度。
|
|
141
|
+
*/
|
|
142
|
+
pageLength?: number;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 术语饱和度权重
|
|
146
|
+
* @description 控制术语在页面上饱和并减少对排名影响的速度。
|
|
147
|
+
* 最大值为 2.0,页面需要很长时间才能饱和,具有非常高术语频率的页面将占据主导地位。
|
|
148
|
+
* 当此数值趋近于 0 时,不需要很多术语就会饱和,并允许其他参数影响排名。
|
|
149
|
+
* 最小值为 0.0,术语将立即饱和,结果不会区分一个术语和多个术语。
|
|
150
|
+
*/
|
|
151
|
+
termSaturation?: number;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 术语频率权重
|
|
155
|
+
* @description 控制排名使用术语频率与原始术语计数的程度。
|
|
156
|
+
* 最大值为 1.0,术语频率完全适用并且是主要排名因素。
|
|
157
|
+
* 最小值为 0.0,术语频率不适用,页面基于单词和权重的原始总和进行排名。
|
|
158
|
+
* 0.0 到 1.0 之间的值将在两种排名方法之间进行插值。
|
|
159
|
+
* 减少此数值是提升较长文档在搜索结果中排名的好方法,因为它们不再因术语频率低而受到惩罚。
|
|
160
|
+
*/
|
|
161
|
+
termFrequency?: number;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Pagefind 搜索选项
|
|
166
|
+
*/
|
|
167
|
+
export interface PagefindSearchOptions {
|
|
168
|
+
/**
|
|
169
|
+
* 过滤器配置
|
|
170
|
+
* @description 与此搜索一起执行的过滤器集。输入类型非常灵活,请参阅过滤文档了解详细信息。
|
|
171
|
+
*/
|
|
172
|
+
filters?: object;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 排序配置
|
|
176
|
+
* @description 用于此搜索的排序集,而不是相关性排序
|
|
177
|
+
*/
|
|
178
|
+
sort?: object;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 预加载模式
|
|
182
|
+
* @description 如果设置,此调用将加载所有资源但在搜索之前返回。建议改用 pagefind.preload()
|
|
183
|
+
*/
|
|
184
|
+
preload?: boolean;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 详细模式
|
|
188
|
+
* @description 如果设置,此搜索查询将在控制台输出更详细的日志记录
|
|
189
|
+
*/
|
|
190
|
+
verbose?: boolean;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 过滤器计数字典
|
|
195
|
+
*/
|
|
196
|
+
export interface PagefindFilterCounts {
|
|
197
|
+
[filter: string]: {
|
|
198
|
+
[value: string]: number;
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Pagefind 搜索结果
|
|
204
|
+
*/
|
|
205
|
+
export interface PagefindSearchResults {
|
|
206
|
+
/**
|
|
207
|
+
* 匹配搜索查询和提供的过滤器的所有页面
|
|
208
|
+
*/
|
|
209
|
+
results: PagefindSearchResult[];
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* 如果省略过滤器,将会有多少结果
|
|
213
|
+
*/
|
|
214
|
+
unfilteredResultCount: number;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* 给定查询和提供的过滤器,每个过滤器下还有多少剩余结果?
|
|
218
|
+
*/
|
|
219
|
+
filters: PagefindFilterCounts;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 如果移除搜索的过滤器,每个过滤器的总结果数是多少?
|
|
223
|
+
*/
|
|
224
|
+
totalFilters: PagefindFilterCounts;
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Pagefind 执行此查询所用时间的信息
|
|
228
|
+
*/
|
|
229
|
+
timings: {
|
|
230
|
+
/** 预加载时间(毫秒) */
|
|
231
|
+
preload: number;
|
|
232
|
+
/** 搜索时间(毫秒) */
|
|
233
|
+
search: number;
|
|
234
|
+
/** 总时间(毫秒) */
|
|
235
|
+
total: number;
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Pagefind 单个搜索结果(在加载实际数据之前)
|
|
241
|
+
*/
|
|
242
|
+
export interface PagefindSearchResult {
|
|
243
|
+
/**
|
|
244
|
+
* Pagefind 内部为此页面分配的 ID,在整个站点中唯一
|
|
245
|
+
*/
|
|
246
|
+
id: string;
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Pagefind 内部对查询匹配此页面的评分,用于对这些结果进行排名
|
|
250
|
+
*/
|
|
251
|
+
score: number;
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 此页面中所有匹配单词的位置
|
|
255
|
+
*/
|
|
256
|
+
words: number[];
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* 数据加载函数
|
|
260
|
+
* @description 调用 data() 加载显示此结果所需的最终数据片段。
|
|
261
|
+
* 仅在需要显示数据时调用此函数,而不是一次性全部调用。
|
|
262
|
+
* (例如,一次一页,或在滚动监听器中)
|
|
263
|
+
* @returns 包含完整结果的 Promise
|
|
264
|
+
*/
|
|
265
|
+
data(): Promise<PagefindSearchFragment>;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Pagefind 搜索片段 - 搜索结果的有用数据
|
|
270
|
+
*/
|
|
271
|
+
export interface PagefindSearchFragment {
|
|
272
|
+
/**
|
|
273
|
+
* Pagefind 处理过的此页面的 URL。如果配置了 baseUrl,将包含 baseUrl
|
|
274
|
+
*/
|
|
275
|
+
url: string;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Pagefind 未处理的此页面的原始 URL
|
|
279
|
+
*/
|
|
280
|
+
raw_url?: string;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* 此页面的完整处理后的内容文本
|
|
284
|
+
*/
|
|
285
|
+
content: string;
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* 内部类型 - 暂时忽略
|
|
289
|
+
*/
|
|
290
|
+
raw_content?: string;
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 此结果的处理后的摘要,匹配术语用 `<mark>` 元素包裹
|
|
294
|
+
*/
|
|
295
|
+
excerpt: string;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* 页面的哪些区域匹配此搜索查询?
|
|
299
|
+
* @description 基于带有 ID 的 h1->6 标签预先计算,使用每个标签之间的文本。
|
|
300
|
+
*/
|
|
301
|
+
sub_results: PagefindSubResult[];
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* 此页面上总共有多少个单词?
|
|
305
|
+
*/
|
|
306
|
+
word_count: number;
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* 此页面中所有匹配单词的位置
|
|
310
|
+
*/
|
|
311
|
+
locations: number[];
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* 此页面中所有匹配单词的位置,
|
|
315
|
+
* 配对其权重和与此查询相关性的数据
|
|
316
|
+
*/
|
|
317
|
+
weighted_locations: PagefindWordLocation[];
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* 此页面标记的过滤器键和值
|
|
321
|
+
*/
|
|
322
|
+
filters: Record<string, string[]>;
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* 此页面标记的元数据键和值
|
|
326
|
+
*/
|
|
327
|
+
meta: Record<string, string>;
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Pagefind 用于生成 sub_results 的原始锚点数据。
|
|
331
|
+
* @description 包含页面上所有具有 ID 的元素,因此可用于使用不同的语义实现您自己的子结果计算。
|
|
332
|
+
*/
|
|
333
|
+
anchors: PagefindSearchAnchor[];
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* 页面内匹配段的数据
|
|
338
|
+
*/
|
|
339
|
+
export interface PagefindSubResult {
|
|
340
|
+
/**
|
|
341
|
+
* 此子结果的标题 - 从标题内容派生。
|
|
342
|
+
* @description 如果这是页面在任何带有 ID 的标题之前的部分的结果,这将与页面的 meta.title 值相同。
|
|
343
|
+
*/
|
|
344
|
+
title: string;
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* 此子结果的直接 URL,由页面的 URL 加上标题的哈希字符串组成。
|
|
348
|
+
* @description 如果这是页面在任何带有 ID 的标题之前的部分的结果,这将与页面 URL 相同。
|
|
349
|
+
*/
|
|
350
|
+
url: string;
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* 此段中所有匹配单词的位置
|
|
354
|
+
*/
|
|
355
|
+
locations: number[];
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* 此段中所有匹配单词的位置,
|
|
359
|
+
* 配对其权重和与此查询相关性的数据
|
|
360
|
+
*/
|
|
361
|
+
weighted_locations: PagefindWordLocation[];
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* 此段的处理后的摘要,匹配术语用 `<mark>` 元素包裹
|
|
365
|
+
*/
|
|
366
|
+
excerpt: string;
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* 与此子结果关联的锚点元素的原始数据。
|
|
370
|
+
* @description 省略此字段意味着此子结果是针对在第一个具有 ID 的标题之前页面上找到的文本。
|
|
371
|
+
*/
|
|
372
|
+
anchor?: PagefindSearchAnchor;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* 页面上匹配单词的信息
|
|
377
|
+
*/
|
|
378
|
+
export interface PagefindWordLocation {
|
|
379
|
+
/**
|
|
380
|
+
* 此单词最初标记的权重
|
|
381
|
+
*/
|
|
382
|
+
weight: number;
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Pagefind 为此单词计算的内部分数。
|
|
386
|
+
* @description 绝对值有些无意义,但该值可用于与此组搜索结果中的其他值进行比较以执行自定义排名。
|
|
387
|
+
*/
|
|
388
|
+
balanced_score: number;
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* 此单词在结果内容中的索引。
|
|
392
|
+
* @description 按空白分割 content 键并按此数字索引将产生正确的单词。
|
|
393
|
+
*/
|
|
394
|
+
location: number;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Pagefind 在索引页面时遇到的带有 ID 的元素的原始数据
|
|
399
|
+
*/
|
|
400
|
+
export interface PagefindSearchAnchor {
|
|
401
|
+
/**
|
|
402
|
+
* 此锚点是什么元素类型?例如 "h1"、"div"
|
|
403
|
+
*/
|
|
404
|
+
element: string;
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* 元素的原始 id="..." 属性内容
|
|
408
|
+
*/
|
|
409
|
+
id: string;
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* 此元素的文本内容。
|
|
413
|
+
* @description 为了防止为每个锚点重复大部分页面数据,
|
|
414
|
+
* Pagefind 将仅获取顶级文本节点,或嵌套在 <a> 和 <span> 等内联元素内的文本节点。
|
|
415
|
+
*/
|
|
416
|
+
text?: string;
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* 此锚点在结果内容中的位置。
|
|
420
|
+
* @description 按空白分割 content 键并按此数字索引将产生在此元素的 ID 之后索引的第一个单词。
|
|
421
|
+
*/
|
|
422
|
+
location: number;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type * as PageFind from '@pagefind';
|
|
2
|
+
import { $, $new, handle } from './_utils';
|
|
3
|
+
|
|
4
|
+
let pagefind: typeof PageFind;
|
|
5
|
+
let dialog: HTMLDialogElement;
|
|
6
|
+
let input: HTMLInputElement;
|
|
7
|
+
let resultList: HTMLElement;
|
|
8
|
+
|
|
9
|
+
function renderResult(
|
|
10
|
+
result: PageFind.PagefindSearchFragment,
|
|
11
|
+
node: HTMLAnchorElement,
|
|
12
|
+
) {
|
|
13
|
+
node.href = result.url;
|
|
14
|
+
const title = $new('div');
|
|
15
|
+
title.classList.add('search-title');
|
|
16
|
+
title.textContent = result.meta.title;
|
|
17
|
+
const excerpt = $new('div');
|
|
18
|
+
excerpt.innerHTML = result.excerpt;
|
|
19
|
+
excerpt.classList.add('search-excerpt');
|
|
20
|
+
node.append(title, excerpt);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function search() {
|
|
24
|
+
if (!pagefind) return;
|
|
25
|
+
const result = await pagefind.debouncedSearch(input.value);
|
|
26
|
+
if (!result) return;
|
|
27
|
+
resultList.replaceChildren(
|
|
28
|
+
...result.results.map(({ data }, i) => {
|
|
29
|
+
const node: HTMLAnchorElement = $new('a');
|
|
30
|
+
if (i === 0) node.classList.add('active');
|
|
31
|
+
data().then((result) => renderResult(result, node));
|
|
32
|
+
return node;
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function loadPagefind() {
|
|
38
|
+
if (pagefind) return;
|
|
39
|
+
pagefind = (await import('../pagefind.js' as any)) as typeof PageFind;
|
|
40
|
+
pagefind.init();
|
|
41
|
+
$('.search progress')?.remove();
|
|
42
|
+
(window as any).pagefind = pagefind;
|
|
43
|
+
search();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function openSearchDialog() {
|
|
47
|
+
dialog.showModal();
|
|
48
|
+
loadPagefind();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function navHandler(event: KeyboardEvent) {
|
|
52
|
+
let current = $<HTMLAnchorElement>('.active', resultList);
|
|
53
|
+
if (event.key === 'Enter') {
|
|
54
|
+
current?.click();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const down = event.key === 'ArrowDown';
|
|
58
|
+
if (!down && event.key !== 'ArrowUp') return;
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
if (current) current.classList.remove('active');
|
|
61
|
+
if (down) current = current?.nextSibling as HTMLAnchorElement;
|
|
62
|
+
else current = current?.previousSibling as HTMLAnchorElement;
|
|
63
|
+
if (!current) {
|
|
64
|
+
if (down) current = [...resultList.children].at(-1) as HTMLAnchorElement;
|
|
65
|
+
else current = resultList.children[0] as HTMLAnchorElement;
|
|
66
|
+
}
|
|
67
|
+
if (!current) return;
|
|
68
|
+
current.classList.add('active');
|
|
69
|
+
current.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function dialogClickHandler({ x, y }: MouseEvent) {
|
|
73
|
+
const { top, right, bottom, left } = dialog.getBoundingClientRect();
|
|
74
|
+
if (x <= right && x >= left && y <= bottom && y >= top) return;
|
|
75
|
+
dialog.close();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function initSearch() {
|
|
79
|
+
const searchBtn = $('#search')!;
|
|
80
|
+
dialog = $('.search')!;
|
|
81
|
+
input = $('#search-input')!;
|
|
82
|
+
resultList = $('.search-result')!;
|
|
83
|
+
handle($('#search-close')!, 'click', () => dialog.close());
|
|
84
|
+
handle(searchBtn, 'click', openSearchDialog);
|
|
85
|
+
handle(input, 'input', search);
|
|
86
|
+
handle(input, 'keydown', navHandler);
|
|
87
|
+
handle(dialog, 'click', dialogClickHandler);
|
|
88
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export const doc = document;
|
|
2
|
+
|
|
3
|
+
export function $<T extends Element>(
|
|
4
|
+
selector: string,
|
|
5
|
+
scope: ParentNode = doc,
|
|
6
|
+
): T | null {
|
|
7
|
+
return scope.querySelector<T>(selector);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function $$<T extends Element>(
|
|
11
|
+
selector: string,
|
|
12
|
+
scope: ParentNode = doc,
|
|
13
|
+
): NodeListOf<T> {
|
|
14
|
+
return scope.querySelectorAll<T>(selector);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function $new<
|
|
18
|
+
K extends keyof HTMLElementTagNameMap | (string & {}) =
|
|
19
|
+
| keyof HTMLElementTagNameMap
|
|
20
|
+
| (string & {}),
|
|
21
|
+
E extends HTMLElement = K extends keyof HTMLElementTagNameMap
|
|
22
|
+
? HTMLElementTagNameMap[K]
|
|
23
|
+
: HTMLElement,
|
|
24
|
+
>(tag: K): E {
|
|
25
|
+
return doc.createElement(tag) as E;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const sleep = (time = 0) =>
|
|
29
|
+
new Promise((resolve) => setTimeout(resolve, time));
|
|
30
|
+
|
|
31
|
+
export function handle<
|
|
32
|
+
T extends EventTarget,
|
|
33
|
+
K extends keyof HTMLElementEventMap | (string & {}),
|
|
34
|
+
E extends Event = K extends keyof HTMLElementEventMap
|
|
35
|
+
? HTMLElementEventMap[K]
|
|
36
|
+
: Event,
|
|
37
|
+
>(
|
|
38
|
+
target: T,
|
|
39
|
+
event: keyof HTMLElementEventMap | (string & {}),
|
|
40
|
+
handler: (this: T, event: E) => any,
|
|
41
|
+
) {
|
|
42
|
+
target.addEventListener(event, handler as any);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function offHandle<
|
|
46
|
+
T extends EventTarget,
|
|
47
|
+
K extends keyof HTMLElementEventMap | (string & {}),
|
|
48
|
+
E extends Event = K extends keyof HTMLElementEventMap
|
|
49
|
+
? HTMLElementEventMap[K]
|
|
50
|
+
: Event,
|
|
51
|
+
>(
|
|
52
|
+
target: T,
|
|
53
|
+
event: keyof HTMLElementEventMap | (string & {}),
|
|
54
|
+
handler: (this: T, event: E) => any,
|
|
55
|
+
) {
|
|
56
|
+
target.removeEventListener(event, handler as any);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function shuffle<T>(array: T[]): T[] {
|
|
60
|
+
for (let i = array.length - 1; i > 0; i--) {
|
|
61
|
+
const j = Math.trunc(Math.random() * (i + 1));
|
|
62
|
+
if (i === j) continue;
|
|
63
|
+
[array[i], array[j]] = [array[j], array[i]];
|
|
64
|
+
}
|
|
65
|
+
return array;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function debounce(fn: Function, delay: number) {
|
|
69
|
+
let timeoutId: number;
|
|
70
|
+
return (...args: any[]) => {
|
|
71
|
+
clearTimeout(timeoutId);
|
|
72
|
+
timeoutId = setTimeout(() => fn.apply(null, args), delay);
|
|
73
|
+
};
|
|
74
|
+
}
|