docnest 0.1.17 → 0.1.19
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/README.md
CHANGED
|
@@ -66,6 +66,9 @@ export default defineConfig({
|
|
|
66
66
|
`search-index.json`,并保留页面上的静态站点标记,让结果链接指向 `/doc/<path>.html`。
|
|
67
67
|
受限模式不生成搜索索引,也不显示搜索入口,避免摘要绕过阅读限制。
|
|
68
68
|
|
|
69
|
+
搜索框为空时显示最近搜索:仅记录有结果的关键词,去重后保留最近 10 条,点击即可再次搜索。
|
|
70
|
+
历史保存在当前浏览器,按 `site.storageKeyPrefix` 隔离;不上传服务器。
|
|
71
|
+
|
|
69
72
|
PDF、页面水印和图表外带能力由一个开关统一控制,默认使用普通阅读模式:
|
|
70
73
|
|
|
71
74
|
```js
|
package/package.json
CHANGED
|
@@ -2306,8 +2306,9 @@ body[data-theme="dark"] .tree-nav::-webkit-scrollbar-thumb:hover {
|
|
|
2306
2306
|
}
|
|
2307
2307
|
|
|
2308
2308
|
.doc-search-status {
|
|
2309
|
+
flex-shrink: 0;
|
|
2309
2310
|
min-height: 1.25rem;
|
|
2310
|
-
padding:
|
|
2311
|
+
padding: 1rem 1.25rem 0.875rem;
|
|
2311
2312
|
color: var(--linear-text-muted);
|
|
2312
2313
|
font-size: 0.75rem;
|
|
2313
2314
|
}
|
|
@@ -2319,6 +2320,44 @@ body[data-theme="dark"] .tree-nav::-webkit-scrollbar-thumb:hover {
|
|
|
2319
2320
|
overscroll-behavior: contain;
|
|
2320
2321
|
}
|
|
2321
2322
|
|
|
2323
|
+
.doc-search-history {
|
|
2324
|
+
min-height: 0;
|
|
2325
|
+
overflow-y: auto;
|
|
2326
|
+
padding: 0.25rem 1.25rem 1.5rem;
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
.doc-search-history__title {
|
|
2330
|
+
margin-bottom: 0.75rem;
|
|
2331
|
+
color: var(--linear-text-muted);
|
|
2332
|
+
font-size: 0.75rem;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
.doc-search-history__items {
|
|
2336
|
+
display: flex;
|
|
2337
|
+
flex-wrap: wrap;
|
|
2338
|
+
gap: 0.625rem;
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
.doc-search-history__item {
|
|
2342
|
+
max-width: 100%;
|
|
2343
|
+
padding: 0.4rem 0.75rem;
|
|
2344
|
+
border: 1px solid var(--linear-border);
|
|
2345
|
+
border-radius: 8px;
|
|
2346
|
+
background: var(--linear-bg-subtle);
|
|
2347
|
+
color: var(--linear-text);
|
|
2348
|
+
font: inherit;
|
|
2349
|
+
font-size: 0.8125rem;
|
|
2350
|
+
overflow-wrap: anywhere;
|
|
2351
|
+
text-align: left;
|
|
2352
|
+
cursor: pointer;
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
.doc-search-history__item:hover,
|
|
2356
|
+
.doc-search-history__item:focus-visible {
|
|
2357
|
+
border-color: var(--linear-accent);
|
|
2358
|
+
background: var(--doc-accent-soft);
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2322
2361
|
.doc-search-result {
|
|
2323
2362
|
position: relative;
|
|
2324
2363
|
display: block;
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
crumbs.push({ name: displayName, isLast: true, url: '' });
|
|
24
24
|
} else {
|
|
25
25
|
var dirPath = parts.slice(0, i + 1).join('/');
|
|
26
|
-
|
|
26
|
+
// 服务端会根据目录是否存在 README.md 决定跳转到文档或目录列表。
|
|
27
|
+
crumbs.push({ name: displayName, isLast: false, url: '/dir?path=' + encodeURIComponent(dirPath) });
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
return crumbs;
|
|
@@ -54,7 +55,17 @@
|
|
|
54
55
|
return String(text).replace(/[&<>"']/g, function(m) { return map[m]; });
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
function updateBreadcrumbs(docPath) {
|
|
58
|
+
function updateBreadcrumbs(docPath, loadedDocument) {
|
|
59
|
+
var currentBreadcrumbs = document.querySelector('.breadcrumbs');
|
|
60
|
+
if (!currentBreadcrumbs) return;
|
|
61
|
+
|
|
62
|
+
// 服务端已经掌握真实目录结构,优先复用它生成的面包屑,避免客户端猜测 README.md。
|
|
63
|
+
var serverBreadcrumbs = loadedDocument && loadedDocument.querySelector('.breadcrumbs');
|
|
64
|
+
if (serverBreadcrumbs) {
|
|
65
|
+
currentBreadcrumbs.innerHTML = serverBreadcrumbs.innerHTML;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
58
69
|
var crumbs = buildBreadcrumbsFromPath(docPath);
|
|
59
70
|
renderBreadcrumbs(crumbs);
|
|
60
71
|
}
|
|
@@ -194,7 +205,7 @@
|
|
|
194
205
|
if (newTitle) document.title = newTitle.textContent;
|
|
195
206
|
window.currentDocPath = pathFromUrl || pathFromHref(absoluteUrl);
|
|
196
207
|
updateActiveLink(window.currentDocPath);
|
|
197
|
-
updateBreadcrumbs(window.currentDocPath);
|
|
208
|
+
updateBreadcrumbs(window.currentDocPath, doc);
|
|
198
209
|
if (typeof window.buildDocToc === 'function') window.buildDocToc();
|
|
199
210
|
if (typeof window.scrollToDocHash === 'function') window.scrollToDocHash();
|
|
200
211
|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var MAX_RESULTS = 12;
|
|
5
|
+
var MAX_HISTORY = 10;
|
|
6
|
+
var HISTORY_DELAY = 800;
|
|
5
7
|
var SNIPPET_LENGTH = 160;
|
|
6
8
|
var SEARCH_INDEX_PATH = '/search-index.json';
|
|
7
9
|
var state = {
|
|
@@ -12,8 +14,77 @@
|
|
|
12
14
|
lastFocus: null,
|
|
13
15
|
results: [],
|
|
14
16
|
selectedIndex: -1,
|
|
17
|
+
history: null,
|
|
18
|
+
pendingQuery: '',
|
|
19
|
+
historyTimer: null,
|
|
20
|
+
composing: false,
|
|
15
21
|
};
|
|
16
22
|
|
|
23
|
+
function historyKey() {
|
|
24
|
+
return String((window.DOCNEST_CONFIG || {}).storageKeyPrefix || 'docnest') + ':search-history';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readHistory() {
|
|
28
|
+
if (state.history) return state.history;
|
|
29
|
+
var saved;
|
|
30
|
+
try {
|
|
31
|
+
saved = JSON.parse(window.localStorage.getItem(historyKey()));
|
|
32
|
+
} catch (_) {}
|
|
33
|
+
var seen = new Set();
|
|
34
|
+
state.history = (Array.isArray(saved) ? saved : []).filter(function(query) {
|
|
35
|
+
if (typeof query !== 'string' || !query.trim()) return false;
|
|
36
|
+
var key = getQueryTerms(query).join(' ');
|
|
37
|
+
if (seen.has(key)) return false;
|
|
38
|
+
seen.add(key);
|
|
39
|
+
return true;
|
|
40
|
+
}).map(function(query) { return query.trim(); }).slice(0, MAX_HISTORY);
|
|
41
|
+
return state.history;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function savePendingQuery() {
|
|
45
|
+
window.clearTimeout(state.historyTimer);
|
|
46
|
+
state.historyTimer = null;
|
|
47
|
+
var query = state.pendingQuery;
|
|
48
|
+
state.pendingQuery = '';
|
|
49
|
+
if (!query || state.composing || searchDocuments(query).length === 0) return;
|
|
50
|
+
var key = getQueryTerms(query).join(' ');
|
|
51
|
+
state.history = [query].concat(readHistory().filter(function(item) {
|
|
52
|
+
return getQueryTerms(item).join(' ') !== key;
|
|
53
|
+
})).slice(0, MAX_HISTORY);
|
|
54
|
+
try {
|
|
55
|
+
window.localStorage.setItem(historyKey(), JSON.stringify(state.history));
|
|
56
|
+
} catch (_) {}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function renderHistory() {
|
|
60
|
+
state.modal.history.textContent = '';
|
|
61
|
+
var queries = readHistory().filter(function(query) { return searchDocuments(query).length > 0; });
|
|
62
|
+
state.modal.history.hidden = queries.length === 0;
|
|
63
|
+
if (!queries.length) return false;
|
|
64
|
+
|
|
65
|
+
var heading = document.createElement('div');
|
|
66
|
+
heading.className = 'doc-search-history__title';
|
|
67
|
+
heading.textContent = '最近搜索';
|
|
68
|
+
var items = document.createElement('div');
|
|
69
|
+
items.className = 'doc-search-history__items';
|
|
70
|
+
queries.forEach(function(query) {
|
|
71
|
+
var button = document.createElement('button');
|
|
72
|
+
button.type = 'button';
|
|
73
|
+
button.className = 'doc-search-history__item';
|
|
74
|
+
button.textContent = query;
|
|
75
|
+
button.addEventListener('click', function() {
|
|
76
|
+
state.modal.input.value = query;
|
|
77
|
+
renderResults();
|
|
78
|
+
savePendingQuery();
|
|
79
|
+
state.modal.input.focus();
|
|
80
|
+
});
|
|
81
|
+
items.appendChild(button);
|
|
82
|
+
});
|
|
83
|
+
state.modal.history.appendChild(heading);
|
|
84
|
+
state.modal.history.appendChild(items);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
17
88
|
function getAssetUrl(path) {
|
|
18
89
|
var base = document.querySelector('base');
|
|
19
90
|
if (base && base.getAttribute('href')) {
|
|
@@ -241,12 +312,22 @@
|
|
|
241
312
|
function renderResults() {
|
|
242
313
|
if (!state.modal) return;
|
|
243
314
|
var query = state.modal.input.value.trim();
|
|
315
|
+
window.clearTimeout(state.historyTimer);
|
|
316
|
+
if (!query) savePendingQuery();
|
|
317
|
+
state.pendingQuery = '';
|
|
244
318
|
state.modal.results.textContent = '';
|
|
319
|
+
state.modal.results.hidden = false;
|
|
320
|
+
state.modal.history.hidden = true;
|
|
321
|
+
state.modal.input.removeAttribute('aria-activedescendant');
|
|
245
322
|
state.results = [];
|
|
246
323
|
state.selectedIndex = -1;
|
|
247
324
|
|
|
248
325
|
if (!query) {
|
|
249
326
|
setStatus('输入标题、目录或正文关键词');
|
|
327
|
+
if (renderHistory()) {
|
|
328
|
+
state.modal.results.hidden = true;
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
250
331
|
state.modal.results.appendChild(createEmptyState('支持中文关键词、英文词组和文档路径搜索'));
|
|
251
332
|
return;
|
|
252
333
|
}
|
|
@@ -260,6 +341,10 @@
|
|
|
260
341
|
}
|
|
261
342
|
|
|
262
343
|
state.results = matches.slice(0, MAX_RESULTS);
|
|
344
|
+
if (!state.composing) {
|
|
345
|
+
state.pendingQuery = query;
|
|
346
|
+
state.historyTimer = window.setTimeout(savePendingQuery, HISTORY_DELAY);
|
|
347
|
+
}
|
|
263
348
|
setStatus(matches.length > MAX_RESULTS
|
|
264
349
|
? '找到 ' + matches.length + ' 篇文档,显示最相关的 ' + MAX_RESULTS + ' 篇'
|
|
265
350
|
: '找到 ' + matches.length + ' 篇文档');
|
|
@@ -287,6 +372,7 @@
|
|
|
287
372
|
'<kbd class="doc-search-input-key">Esc</kbd>',
|
|
288
373
|
'</div>',
|
|
289
374
|
'<div class="doc-search-status" id="doc-search-status" role="status" aria-live="polite"></div>',
|
|
375
|
+
'<div class="doc-search-history" role="group" aria-label="最近搜索" hidden></div>',
|
|
290
376
|
'<div class="doc-search-results" id="doc-search-results" role="listbox" aria-label="搜索结果"></div>',
|
|
291
377
|
'<div class="doc-search-hint"><span>↑↓ 选择</span><span>Enter 打开</span><span>Esc 关闭</span></div>',
|
|
292
378
|
'</div>',
|
|
@@ -297,9 +383,19 @@
|
|
|
297
383
|
input: overlay.querySelector('.doc-search-input'),
|
|
298
384
|
status: overlay.querySelector('.doc-search-status'),
|
|
299
385
|
results: overlay.querySelector('.doc-search-results'),
|
|
386
|
+
history: overlay.querySelector('.doc-search-history'),
|
|
300
387
|
close: overlay.querySelector('.doc-search-modal__close'),
|
|
301
388
|
};
|
|
302
389
|
modal.input.addEventListener('input', renderResults);
|
|
390
|
+
modal.input.addEventListener('compositionstart', function() {
|
|
391
|
+
state.composing = true;
|
|
392
|
+
window.clearTimeout(state.historyTimer);
|
|
393
|
+
state.pendingQuery = '';
|
|
394
|
+
});
|
|
395
|
+
modal.input.addEventListener('compositionend', function() {
|
|
396
|
+
state.composing = false;
|
|
397
|
+
renderResults();
|
|
398
|
+
});
|
|
303
399
|
modal.close.addEventListener('click', closeSearch);
|
|
304
400
|
overlay.addEventListener('click', function(event) {
|
|
305
401
|
if (event.target === overlay) closeSearch();
|
|
@@ -336,6 +432,10 @@
|
|
|
336
432
|
|
|
337
433
|
function openSearch() {
|
|
338
434
|
if (!state.modal) createModal();
|
|
435
|
+
if (!state.modal.overlay.hidden) {
|
|
436
|
+
state.modal.input.focus();
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
339
439
|
state.lastFocus = document.activeElement;
|
|
340
440
|
state.modal.overlay.hidden = false;
|
|
341
441
|
state.modal.overlay.setAttribute('aria-hidden', 'false');
|
|
@@ -343,6 +443,8 @@
|
|
|
343
443
|
state.modal.input.disabled = true;
|
|
344
444
|
state.modal.input.value = '';
|
|
345
445
|
state.modal.results.textContent = '';
|
|
446
|
+
state.modal.results.hidden = false;
|
|
447
|
+
state.modal.history.hidden = true;
|
|
346
448
|
setStatus('正在加载文档索引…');
|
|
347
449
|
window.requestAnimationFrame(function() { state.modal.input.focus(); });
|
|
348
450
|
|
|
@@ -361,6 +463,8 @@
|
|
|
361
463
|
|
|
362
464
|
function closeSearch() {
|
|
363
465
|
if (!state.modal || state.modal.overlay.hidden) return;
|
|
466
|
+
savePendingQuery();
|
|
467
|
+
state.composing = false;
|
|
364
468
|
state.modal.overlay.hidden = true;
|
|
365
469
|
state.modal.overlay.setAttribute('aria-hidden', 'true');
|
|
366
470
|
document.body.classList.remove('doc-search-open');
|
|
@@ -383,6 +487,8 @@
|
|
|
383
487
|
if (event.key === 'Escape') {
|
|
384
488
|
event.preventDefault();
|
|
385
489
|
closeSearch();
|
|
490
|
+
} else if (event.target !== state.modal.input) {
|
|
491
|
+
return;
|
|
386
492
|
} else if (event.key === 'ArrowDown') {
|
|
387
493
|
event.preventDefault();
|
|
388
494
|
moveSelection(1);
|
|
@@ -414,6 +520,7 @@
|
|
|
414
520
|
var isMac = /Mac|iPhone|iPad|iPod/i.test((window.navigator && window.navigator.platform) || '');
|
|
415
521
|
if (shortcut) shortcut.textContent = isMac ? '⌘ K' : 'Ctrl K';
|
|
416
522
|
document.addEventListener('keydown', onKeyDown);
|
|
523
|
+
window.addEventListener('pagehide', savePendingQuery);
|
|
417
524
|
}
|
|
418
525
|
|
|
419
526
|
if (document.readyState === 'loading') {
|