docnest 0.1.0
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/README.md +61 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +83 -0
- package/dist/config.d.ts +36 -0
- package/dist/config.js +79 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.js +814 -0
- package/package.json +60 -0
- package/server/favicon.ico +0 -0
- package/server/static/css/highlight.css +72 -0
- package/server/static/css/styles.css +5762 -0
- package/server/static/favicon.ico +0 -0
- package/server/static/img/404-deep-space-light.webp +0 -0
- package/server/static/img/404-deep-space.webp +0 -0
- package/server/static/js/core/doc-client-nav.js +260 -0
- package/server/static/js/core/doc-pdf-export.js +579 -0
- package/server/static/js/core/mermaid-init.js +36 -0
- package/server/static/js/core/sidebar.js +141 -0
- package/server/static/js/core/theme.js +138 -0
- package/server/static/js/diagram/diagram-controls.js +90 -0
- package/server/static/js/diagram/diagram-download.js +594 -0
- package/server/static/js/diagram/diagram-drag.js +157 -0
- package/server/static/js/diagram/diagram-viewer.js +405 -0
- package/server/static/js/diagram/diagram-zoom.js +67 -0
- package/server/static/js/utils/app.js +14 -0
- package/server/static/js/utils/modal-stack.js +33 -0
- package/server/views/dir.ejs +142 -0
- package/server/views/doc.ejs +483 -0
- package/server/views/index.ejs +77 -0
- package/server/views/not-found.ejs +57 -0
- package/server/views/partials/breadcrumbs.ejs +15 -0
- package/server/views/partials/diagram-viewer.ejs +16 -0
- package/server/views/partials/header.ejs +37 -0
- package/server/views/partials/sidebar.ejs +19 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文档页 SPA 式导航:点击文档链接时通过 fetch 局部更新内容,避免整页重载。
|
|
3
|
+
* 效果:无页面闪烁、无卡顿,Socket 保持连接(不再出现断开+连接)。
|
|
4
|
+
*/
|
|
5
|
+
(function() {
|
|
6
|
+
var article = null;
|
|
7
|
+
var treeNav = null;
|
|
8
|
+
var inFlight = false;
|
|
9
|
+
|
|
10
|
+
function pathNorm(p) {
|
|
11
|
+
return (p || '').trim().replace(/\.(html|md)$/i, '').replace(/\\/g, '/');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function buildBreadcrumbsFromPath(docPath) {
|
|
15
|
+
var crumbs = [];
|
|
16
|
+
var normalizedPath = (docPath || '').replace(/\\/g, '/');
|
|
17
|
+
var parts = normalizedPath.split('/').filter(Boolean);
|
|
18
|
+
for (var i = 0; i < parts.length; i++) {
|
|
19
|
+
var isLast = i === parts.length - 1;
|
|
20
|
+
var isLastFile = isLast && /\.md$/i.test(parts[i]);
|
|
21
|
+
var displayName = isLastFile ? parts[i].replace(/\.md$/i, '') : parts[i];
|
|
22
|
+
if (isLast) {
|
|
23
|
+
crumbs.push({ name: displayName, isLast: true, url: '' });
|
|
24
|
+
} else {
|
|
25
|
+
var dirPath = parts.slice(0, i + 1).join('/');
|
|
26
|
+
crumbs.push({ name: displayName, isLast: false, url: '/doc?path=' + encodeURIComponent(dirPath + '/README.md') });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return crumbs;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function renderBreadcrumbs(crumbs) {
|
|
33
|
+
var bc = document.querySelector('.breadcrumbs');
|
|
34
|
+
if (!bc) return;
|
|
35
|
+
var html = '<ol>';
|
|
36
|
+
html += '<li class="breadcrumb-item"><a href="/" class="breadcrumb-link">首页</a></li>';
|
|
37
|
+
for (var i = 0; i < crumbs.length; i++) {
|
|
38
|
+
var c = crumbs[i];
|
|
39
|
+
html += '<li class="breadcrumb-separator" aria-hidden="true">/</li>';
|
|
40
|
+
if (c.isLast) {
|
|
41
|
+
html += '<li class="breadcrumb-item breadcrumb-current" aria-current="page">' + escapeHtml(c.name) + '</li>';
|
|
42
|
+
} else if (c.url) {
|
|
43
|
+
html += '<li class="breadcrumb-item"><a href="' + c.url + '" class="breadcrumb-link">' + escapeHtml(c.name) + '</a></li>';
|
|
44
|
+
} else {
|
|
45
|
+
html += '<li class="breadcrumb-item breadcrumb-current">' + escapeHtml(c.name) + '</li>';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
html += '</ol>';
|
|
49
|
+
bc.innerHTML = html;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function escapeHtml(text) {
|
|
53
|
+
var map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
54
|
+
return String(text).replace(/[&<>"']/g, function(m) { return map[m]; });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function updateBreadcrumbs(docPath) {
|
|
58
|
+
var crumbs = buildBreadcrumbsFromPath(docPath);
|
|
59
|
+
renderBreadcrumbs(crumbs);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function pathFromHref(href) {
|
|
63
|
+
if (!href) return '';
|
|
64
|
+
try {
|
|
65
|
+
var u = new URL(href, document.baseURI || window.location.href);
|
|
66
|
+
if (u.searchParams.has('path')) return u.searchParams.get('path') || '';
|
|
67
|
+
var idx = u.pathname.indexOf('/doc/');
|
|
68
|
+
if (idx !== -1) return u.pathname.slice(idx + 5).replace(/\.html$/i, '');
|
|
69
|
+
} catch (e) {}
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function showNavError(message) {
|
|
74
|
+
console.warn('[doc-nav] ' + message);
|
|
75
|
+
var id = 'doc-nav-error-toast';
|
|
76
|
+
var old = document.getElementById(id);
|
|
77
|
+
if (old && old.parentNode) old.parentNode.removeChild(old);
|
|
78
|
+
|
|
79
|
+
var toast = document.createElement('div');
|
|
80
|
+
toast.id = id;
|
|
81
|
+
toast.textContent = message;
|
|
82
|
+
toast.style.cssText = [
|
|
83
|
+
'position:fixed',
|
|
84
|
+
'top:16px',
|
|
85
|
+
'right:16px',
|
|
86
|
+
'padding:10px 14px',
|
|
87
|
+
'background:#dc2626',
|
|
88
|
+
'color:#fff',
|
|
89
|
+
'font-size:13px',
|
|
90
|
+
'border-radius:6px',
|
|
91
|
+
'z-index:10001',
|
|
92
|
+
'box-shadow:0 4px 14px rgba(0,0,0,0.2)'
|
|
93
|
+
].join(';');
|
|
94
|
+
document.body.appendChild(toast);
|
|
95
|
+
setTimeout(function() {
|
|
96
|
+
if (toast && toast.parentNode) toast.parentNode.removeChild(toast);
|
|
97
|
+
}, 2200);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function resolveAbsoluteUrl(url) {
|
|
101
|
+
return new URL(url, document.baseURI || window.location.href).href;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function resolveDocUrlFromPath(docPath) {
|
|
105
|
+
var pathname = window.location.pathname || '';
|
|
106
|
+
var prefix = '';
|
|
107
|
+
var idx = pathname.indexOf('/doc/');
|
|
108
|
+
if (idx >= 0) {
|
|
109
|
+
prefix = pathname.slice(0, idx);
|
|
110
|
+
} else {
|
|
111
|
+
idx = pathname.indexOf('/doc');
|
|
112
|
+
if (idx >= 0) {
|
|
113
|
+
prefix = pathname.slice(0, idx);
|
|
114
|
+
} else {
|
|
115
|
+
var baseTag = document.querySelector('base');
|
|
116
|
+
if (baseTag && baseTag.getAttribute('href')) {
|
|
117
|
+
try {
|
|
118
|
+
var baseUrl = new URL(baseTag.getAttribute('href'), window.location.origin);
|
|
119
|
+
prefix = (baseUrl.pathname || '').replace(/\/$/, '');
|
|
120
|
+
} catch (e) {}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return window.location.origin + prefix + '/doc?path=' + encodeURIComponent(docPath);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function findClosestSidebarLink(eventTarget) {
|
|
128
|
+
var node = eventTarget;
|
|
129
|
+
if (!node) return null;
|
|
130
|
+
if (node.nodeType !== 1) node = node.parentElement;
|
|
131
|
+
if (!node || typeof node.closest !== 'function') return null;
|
|
132
|
+
return node.closest('.tree-nav a.file-link');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function updateActiveLink(targetPath) {
|
|
136
|
+
var nav = treeNav || document.querySelector('.tree-nav');
|
|
137
|
+
if (!nav) return;
|
|
138
|
+
var targetNorm = pathNorm(targetPath);
|
|
139
|
+
nav.querySelectorAll('.file-link').forEach(function(link) {
|
|
140
|
+
var linkPath = pathFromHref(link.getAttribute('href') || link.href);
|
|
141
|
+
link.classList.toggle('active', pathNorm(linkPath) === targetNorm);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function loadDoc(url, opts) {
|
|
146
|
+
if (!article || inFlight) return;
|
|
147
|
+
opts = opts || {};
|
|
148
|
+
var absoluteUrl = resolveAbsoluteUrl(url);
|
|
149
|
+
var targetNorm = pathNorm(pathFromHref(absoluteUrl));
|
|
150
|
+
if (pathNorm(window.currentDocPath || '') === targetNorm) return;
|
|
151
|
+
|
|
152
|
+
console.log('[doc-nav] 切换文档:', targetNorm || url);
|
|
153
|
+
inFlight = true;
|
|
154
|
+
article.style.transition = 'opacity 0.15s ease';
|
|
155
|
+
article.style.opacity = '0.6';
|
|
156
|
+
article.style.pointerEvents = 'none';
|
|
157
|
+
|
|
158
|
+
fetch(absoluteUrl, { headers: { 'X-Requested-With': 'XMLHttpRequest' } })
|
|
159
|
+
.then(function(res) {
|
|
160
|
+
if (!res.ok) {
|
|
161
|
+
throw new Error('HTTP ' + res.status);
|
|
162
|
+
}
|
|
163
|
+
return res.text();
|
|
164
|
+
})
|
|
165
|
+
.then(function(html) {
|
|
166
|
+
var parser = new DOMParser();
|
|
167
|
+
var doc = parser.parseFromString(html, 'text/html');
|
|
168
|
+
var newArticle = doc.querySelector('.markdown-body');
|
|
169
|
+
var newTitle = doc.querySelector('title');
|
|
170
|
+
var pathFromUrl = (new URL(absoluteUrl, document.baseURI || window.location.href)).searchParams.get('path') || pathFromHref(absoluteUrl);
|
|
171
|
+
|
|
172
|
+
if (!newArticle) {
|
|
173
|
+
throw new Error('返回页面不是文档页');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
article.innerHTML = newArticle.innerHTML;
|
|
177
|
+
if (newTitle) document.title = newTitle.textContent;
|
|
178
|
+
window.currentDocPath = pathFromUrl || pathFromHref(absoluteUrl);
|
|
179
|
+
updateActiveLink(window.currentDocPath);
|
|
180
|
+
updateBreadcrumbs(window.currentDocPath);
|
|
181
|
+
if (typeof window.buildDocToc === 'function') window.buildDocToc();
|
|
182
|
+
if (typeof window.scrollToDocHash === 'function') window.scrollToDocHash();
|
|
183
|
+
|
|
184
|
+
if (opts.replace) {
|
|
185
|
+
history.replaceState({ path: window.currentDocPath }, '', absoluteUrl);
|
|
186
|
+
console.log('[doc-nav] 完成 (popstate):', window.currentDocPath);
|
|
187
|
+
} else {
|
|
188
|
+
history.pushState({ path: window.currentDocPath }, '', absoluteUrl);
|
|
189
|
+
console.log('[doc-nav] 完成 (SPA):', window.currentDocPath);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (typeof mermaid !== 'undefined') {
|
|
193
|
+
mermaid.run().then(function() {
|
|
194
|
+
if (typeof setupDiagramViewer === 'function') setupDiagramViewer();
|
|
195
|
+
});
|
|
196
|
+
} else if (typeof setupDiagramViewer === 'function') {
|
|
197
|
+
setupDiagramViewer();
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
.catch(function(err) {
|
|
201
|
+
showNavError('文档加载失败,请稍后重试');
|
|
202
|
+
console.warn('SPA 加载失败,保持当前页面:', err);
|
|
203
|
+
})
|
|
204
|
+
.finally(function() {
|
|
205
|
+
inFlight = false;
|
|
206
|
+
article.style.opacity = '';
|
|
207
|
+
article.style.pointerEvents = '';
|
|
208
|
+
article.style.transition = '';
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function init() {
|
|
213
|
+
article = document.querySelector('.main-content .markdown-body');
|
|
214
|
+
treeNav = document.querySelector('.tree-nav');
|
|
215
|
+
if (!article) return;
|
|
216
|
+
|
|
217
|
+
window.currentDocPath = window.currentDocPath || (document.body.getAttribute('data-current-path') || '');
|
|
218
|
+
if (window.currentDocPath) {
|
|
219
|
+
history.replaceState({ path: window.currentDocPath }, '', window.location.href);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 委托到 body,避免部署后 treeNav 初始化时机导致未绑定(静态页/缓存等)
|
|
223
|
+
document.body.addEventListener('click', function(e) {
|
|
224
|
+
var link = findClosestSidebarLink(e.target);
|
|
225
|
+
if (!link) return;
|
|
226
|
+
e.preventDefault();
|
|
227
|
+
e.stopPropagation();
|
|
228
|
+
if (typeof window.saveTreeOpenState === 'function') window.saveTreeOpenState();
|
|
229
|
+
var url = link.getAttribute('href');
|
|
230
|
+
if (!url) return;
|
|
231
|
+
url = resolveAbsoluteUrl(url);
|
|
232
|
+
loadDoc(url);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
window.navigateToDoc = function(urlOrPath) {
|
|
236
|
+
var url = urlOrPath;
|
|
237
|
+
if (url.indexOf('/') !== 0 && url.indexOf('http') !== 0) {
|
|
238
|
+
url = resolveDocUrlFromPath(urlOrPath);
|
|
239
|
+
} else {
|
|
240
|
+
url = resolveAbsoluteUrl(url);
|
|
241
|
+
}
|
|
242
|
+
loadDoc(url);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
window.addEventListener('popstate', function() {
|
|
246
|
+
var pathname = window.location.pathname || '';
|
|
247
|
+
var isDocPage = pathname.indexOf('/doc/') !== -1 || (new URL(window.location.href)).searchParams.get('path');
|
|
248
|
+
if (isDocPage) {
|
|
249
|
+
console.log('[doc-nav] 后退/前进:', pathname);
|
|
250
|
+
loadDoc(window.location.href, { replace: true });
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (document.readyState === 'loading') {
|
|
256
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
257
|
+
} else {
|
|
258
|
+
init();
|
|
259
|
+
}
|
|
260
|
+
})();
|