docnest 0.1.1 → 0.1.3
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 +35 -1
- package/dist/cli.js +3 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +36 -0
- package/dist/server.js +26 -2
- package/package.json +1 -1
- package/server/static/css/themes.css +1210 -0
- package/server/static/js/core/doc-pdf-export.js +61 -2
- package/server/static/js/core/theme.js +305 -108
- package/server/views/dir.ejs +2 -0
- package/server/views/doc.ejs +2 -6
- package/server/views/index.ejs +2 -6
- package/server/views/not-found.ejs +2 -0
- package/server/views/partials/appearance-head.ejs +12 -0
- package/server/views/partials/header.ejs +28 -5
|
@@ -488,6 +488,65 @@
|
|
|
488
488
|
});
|
|
489
489
|
}
|
|
490
490
|
|
|
491
|
+
function showPdfExportError(message) {
|
|
492
|
+
var modalId = 'pdf-export-error';
|
|
493
|
+
var existing = document.querySelector('.pdf-export-error');
|
|
494
|
+
if (existing && existing.parentNode) {
|
|
495
|
+
existing.parentNode.removeChild(existing);
|
|
496
|
+
}
|
|
497
|
+
if (typeof removeModal === 'function') {
|
|
498
|
+
removeModal(modalId);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
var overlay = document.createElement('div');
|
|
502
|
+
overlay.className = 'pdf-export-modal pdf-export-error';
|
|
503
|
+
overlay.innerHTML = [
|
|
504
|
+
'<div class="pdf-export-modal__panel" role="alertdialog" aria-modal="true" aria-labelledby="pdf-export-error-title" aria-describedby="pdf-export-error-desc">',
|
|
505
|
+
'<div class="pdf-export-modal__title" id="pdf-export-error-title">导出 PDF 失败</div>',
|
|
506
|
+
'<div class="pdf-export-modal__desc" id="pdf-export-error-desc"></div>',
|
|
507
|
+
'<div class="pdf-export-modal__actions">',
|
|
508
|
+
'<button type="button" class="pdf-export-modal__btn pdf-export-modal__btn--primary" data-action="close">知道了</button>',
|
|
509
|
+
'</div>',
|
|
510
|
+
'</div>'
|
|
511
|
+
].join('');
|
|
512
|
+
|
|
513
|
+
var description = overlay.querySelector('#pdf-export-error-desc');
|
|
514
|
+
if (description) {
|
|
515
|
+
description.textContent = message || '暂时无法生成 PDF,请稍后重试。';
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
var closed = false;
|
|
519
|
+
function close() {
|
|
520
|
+
if (closed) return;
|
|
521
|
+
closed = true;
|
|
522
|
+
if (typeof removeModal === 'function') {
|
|
523
|
+
removeModal(modalId);
|
|
524
|
+
}
|
|
525
|
+
if (overlay.parentNode) {
|
|
526
|
+
overlay.parentNode.removeChild(overlay);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
overlay.addEventListener('click', function(event) {
|
|
531
|
+
if (event.target === overlay) {
|
|
532
|
+
close();
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
var closeButton = overlay.querySelector('[data-action="close"]');
|
|
537
|
+
if (closeButton) {
|
|
538
|
+
closeButton.addEventListener('click', close);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
document.body.appendChild(overlay);
|
|
542
|
+
if (typeof pushModal === 'function') {
|
|
543
|
+
pushModal(modalId, close);
|
|
544
|
+
}
|
|
545
|
+
if (closeButton) {
|
|
546
|
+
closeButton.focus();
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
491
550
|
function setButtonLoading(button, loading) {
|
|
492
551
|
if (!button) return;
|
|
493
552
|
if (loading) {
|
|
@@ -512,7 +571,7 @@
|
|
|
512
571
|
function exportCurrentDoc(button, options) {
|
|
513
572
|
var article = document.querySelector('.main-content .markdown-body');
|
|
514
573
|
if (!article) {
|
|
515
|
-
|
|
574
|
+
showPdfExportError('未找到当前文档内容,无法导出 PDF。');
|
|
516
575
|
return;
|
|
517
576
|
}
|
|
518
577
|
|
|
@@ -552,7 +611,7 @@
|
|
|
552
611
|
})
|
|
553
612
|
.catch(function(error) {
|
|
554
613
|
console.error('导出 PDF 失败:', error);
|
|
555
|
-
|
|
614
|
+
showPdfExportError('暂时无法生成 PDF,请稍后重试。');
|
|
556
615
|
})
|
|
557
616
|
.finally(function() {
|
|
558
617
|
setButtonLoading(button, false);
|
|
@@ -1,138 +1,335 @@
|
|
|
1
|
-
//
|
|
1
|
+
// DocNest 外观系统:主题风格与明暗模式相互独立,并按宿主项目持久化。
|
|
2
2
|
(function() {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
var THEME_PROFILES = {
|
|
6
|
+
'slate-modern': {
|
|
7
|
+
label: 'Slate Modern',
|
|
8
|
+
description: '清爽、克制的现代文档界面',
|
|
9
|
+
colors: ['#ffffff', '#111111', '#5e6ad2']
|
|
10
|
+
},
|
|
11
|
+
'editorial-atlas': {
|
|
12
|
+
label: 'Editorial Atlas',
|
|
13
|
+
description: '安静、舒展的技术出版物',
|
|
14
|
+
colors: ['#f3f0e7', '#171815', '#1f5b57']
|
|
15
|
+
},
|
|
16
|
+
'precision-index': {
|
|
17
|
+
label: 'Precision Index',
|
|
18
|
+
description: '高密度、可追溯的工程索引',
|
|
19
|
+
colors: ['#eef2f3', '#14242b', '#007486']
|
|
17
20
|
},
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
'archive-room': {
|
|
22
|
+
label: 'Archive Room',
|
|
23
|
+
description: '温和、有秩序的档案阅览室',
|
|
24
|
+
colors: ['#f3efe4', '#262821', '#a5523d']
|
|
22
25
|
},
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
'swiss-manual': {
|
|
27
|
+
label: 'Swiss Manual',
|
|
28
|
+
description: '鲜明、直接的瑞士工程手册',
|
|
29
|
+
colors: ['#f4f3ef', '#111111', '#e33b27']
|
|
27
30
|
}
|
|
28
31
|
};
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
var COLOR_MODES = {
|
|
34
|
+
auto: { label: '跟随系统', shortLabel: 'A' },
|
|
35
|
+
light: { label: '浅色', shortLabel: 'L' },
|
|
36
|
+
dark: { label: '深色', shortLabel: 'D' }
|
|
37
|
+
};
|
|
38
|
+
var MODE_CYCLE = ['auto', 'light', 'dark'];
|
|
39
|
+
var config = window.DOCNEST_CONFIG || {};
|
|
40
|
+
var appearance = config.appearance || {};
|
|
41
|
+
var storagePrefix = String(config.storageKeyPrefix || 'docnest');
|
|
42
|
+
var themeStorageKey = storagePrefix + ':theme-style';
|
|
43
|
+
var modeStorageKey = storagePrefix + ':theme-mode';
|
|
44
|
+
|
|
45
|
+
function readStorage(key) {
|
|
46
|
+
try {
|
|
47
|
+
return window.localStorage.getItem(key);
|
|
48
|
+
} catch (_) {
|
|
49
|
+
return null;
|
|
36
50
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
function
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function writeStorage(key, value) {
|
|
54
|
+
try {
|
|
55
|
+
window.localStorage.setItem(key, value);
|
|
56
|
+
} catch (_) {}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function normalizeThemeName(value) {
|
|
60
|
+
return value === 'current-docs' ? 'slate-modern' : value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function uniqueKnownThemes(values) {
|
|
64
|
+
var seen = new Set();
|
|
65
|
+
return (Array.isArray(values) ? values : [])
|
|
66
|
+
.map(normalizeThemeName)
|
|
67
|
+
.filter(function(value) {
|
|
68
|
+
if (!THEME_PROFILES[value] || seen.has(value)) return false;
|
|
69
|
+
seen.add(value);
|
|
70
|
+
return true;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
var enabledThemes = uniqueKnownThemes(appearance.enabledThemes);
|
|
75
|
+
if (enabledThemes.length === 0) enabledThemes = Object.keys(THEME_PROFILES);
|
|
76
|
+
|
|
77
|
+
var configuredThemeName = normalizeThemeName(appearance.defaultTheme);
|
|
78
|
+
var configuredTheme = THEME_PROFILES[configuredThemeName]
|
|
79
|
+
? configuredThemeName
|
|
80
|
+
: 'slate-modern';
|
|
81
|
+
var defaultTheme = enabledThemes.indexOf(configuredTheme) >= 0
|
|
82
|
+
? configuredTheme
|
|
83
|
+
: enabledThemes[0];
|
|
84
|
+
var defaultMode = COLOR_MODES[appearance.defaultMode] ? appearance.defaultMode : 'auto';
|
|
85
|
+
var storedThemeValue = readStorage(themeStorageKey);
|
|
86
|
+
var storedTheme = normalizeThemeName(storedThemeValue);
|
|
87
|
+
var storedMode = readStorage(modeStorageKey);
|
|
88
|
+
var migratedLegacyMode = false;
|
|
89
|
+
|
|
90
|
+
// 兼容 0.1.x 的明暗模式存储键;只迁移有效值,不覆盖新的项目级偏好。
|
|
91
|
+
if (!storedMode) {
|
|
92
|
+
var legacyMode = readStorage('markdown-theme');
|
|
93
|
+
if (COLOR_MODES[legacyMode]) {
|
|
94
|
+
storedMode = legacyMode;
|
|
95
|
+
migratedLegacyMode = true;
|
|
62
96
|
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
var activeTheme = enabledThemes.indexOf(storedTheme) >= 0 ? storedTheme : defaultTheme;
|
|
100
|
+
var activeMode = COLOR_MODES[storedMode] ? storedMode : defaultMode;
|
|
101
|
+
if (storedThemeValue === 'current-docs') writeStorage(themeStorageKey, activeTheme);
|
|
102
|
+
if (migratedLegacyMode) writeStorage(modeStorageKey, activeMode);
|
|
103
|
+
|
|
104
|
+
function getSystemMode() {
|
|
105
|
+
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
106
|
+
? 'dark'
|
|
107
|
+
: 'light';
|
|
108
|
+
}
|
|
63
109
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
110
|
+
function getEffectiveMode(mode) {
|
|
111
|
+
return mode === 'auto' ? getSystemMode() : mode;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function setAppearanceAttributes(theme, mode) {
|
|
115
|
+
var effectiveMode = getEffectiveMode(mode);
|
|
116
|
+
var root = document.documentElement;
|
|
117
|
+
root.setAttribute('data-doc-theme', theme);
|
|
118
|
+
root.setAttribute('data-theme', effectiveMode);
|
|
119
|
+
root.setAttribute('data-theme-mode', mode);
|
|
120
|
+
root.style.colorScheme = effectiveMode;
|
|
121
|
+
if (document.body) {
|
|
122
|
+
document.body.setAttribute('data-doc-theme', theme);
|
|
123
|
+
document.body.setAttribute('data-theme', effectiveMode);
|
|
124
|
+
document.body.setAttribute('data-theme-mode', mode);
|
|
69
125
|
}
|
|
126
|
+
return effectiveMode;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 在 DOM 完成前先写入 html,避免主题闪烁。
|
|
130
|
+
setAppearanceAttributes(activeTheme, activeMode);
|
|
131
|
+
|
|
132
|
+
function resolveThemeCss(assetPath) {
|
|
133
|
+
var base = document.querySelector('base');
|
|
134
|
+
if (base && base.getAttribute('href')) {
|
|
135
|
+
return base.getAttribute('href').replace(/\/?$/, '/') + assetPath.replace(/^\//, '');
|
|
136
|
+
}
|
|
137
|
+
return assetPath.charAt(0) === '/' ? assetPath : '/' + assetPath;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function applyMarkdownTheme(effectiveMode) {
|
|
141
|
+
var link = document.getElementById('markdown-theme');
|
|
142
|
+
if (!link) return;
|
|
143
|
+
link.href = resolveThemeCss(
|
|
144
|
+
effectiveMode === 'dark'
|
|
145
|
+
? 'vendor/github-markdown-css/github-markdown-dark.css'
|
|
146
|
+
: 'vendor/github-markdown-css/github-markdown-light.css'
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function updateControls() {
|
|
151
|
+
var profile = THEME_PROFILES[activeTheme];
|
|
152
|
+
var styleText = document.querySelector('.theme-style-text');
|
|
153
|
+
var modeText = document.querySelector('.theme-text');
|
|
154
|
+
var modeIcon = document.querySelector('.theme-icon');
|
|
155
|
+
if (styleText) styleText.textContent = profile.label;
|
|
156
|
+
if (modeText) modeText.textContent = COLOR_MODES[activeMode].label;
|
|
157
|
+
if (modeIcon) modeIcon.textContent = COLOR_MODES[activeMode].shortLabel;
|
|
70
158
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
159
|
+
document.querySelectorAll('[data-theme-option]').forEach(function(button) {
|
|
160
|
+
var checked = button.getAttribute('data-theme-option') === activeTheme;
|
|
161
|
+
button.setAttribute('aria-checked', String(checked));
|
|
162
|
+
button.classList.toggle('is-active', checked);
|
|
163
|
+
});
|
|
164
|
+
document.querySelectorAll('[data-mode-option]').forEach(function(button) {
|
|
165
|
+
var checked = button.getAttribute('data-mode-option') === activeMode;
|
|
166
|
+
button.setAttribute('aria-checked', String(checked));
|
|
167
|
+
button.classList.toggle('is-active', checked);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
76
170
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
171
|
+
function refreshMermaid(effectiveMode) {
|
|
172
|
+
if (typeof window.mermaid === 'undefined') return;
|
|
173
|
+
window.mermaid.initialize({
|
|
174
|
+
startOnLoad: false,
|
|
175
|
+
theme: effectiveMode === 'dark' ? 'dark' : 'default',
|
|
176
|
+
securityLevel: 'loose',
|
|
177
|
+
flowchart: { useMaxWidth: true, htmlLabels: true }
|
|
178
|
+
});
|
|
179
|
+
}
|
|
80
180
|
|
|
81
|
-
|
|
181
|
+
function applyAppearance(theme, mode, options) {
|
|
182
|
+
var nextTheme = enabledThemes.indexOf(theme) >= 0 ? theme : defaultTheme;
|
|
183
|
+
var nextMode = COLOR_MODES[mode] ? mode : defaultMode;
|
|
184
|
+
activeTheme = nextTheme;
|
|
185
|
+
activeMode = nextMode;
|
|
186
|
+
var effectiveMode = setAppearanceAttributes(activeTheme, activeMode);
|
|
187
|
+
applyMarkdownTheme(effectiveMode);
|
|
188
|
+
updateControls();
|
|
189
|
+
refreshMermaid(effectiveMode);
|
|
82
190
|
|
|
83
|
-
|
|
84
|
-
|
|
191
|
+
if (!options || options.persist !== false) {
|
|
192
|
+
writeStorage(themeStorageKey, activeTheme);
|
|
193
|
+
writeStorage(modeStorageKey, activeMode);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
window.dispatchEvent(new CustomEvent('docnest:appearance-change', {
|
|
197
|
+
detail: {
|
|
198
|
+
theme: activeTheme,
|
|
199
|
+
mode: activeMode,
|
|
200
|
+
effectiveMode: effectiveMode
|
|
85
201
|
}
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function buildThemeOptions(container) {
|
|
206
|
+
enabledThemes.forEach(function(themeName) {
|
|
207
|
+
var profile = THEME_PROFILES[themeName];
|
|
208
|
+
var button = document.createElement('button');
|
|
209
|
+
button.type = 'button';
|
|
210
|
+
button.className = 'theme-option';
|
|
211
|
+
button.setAttribute('role', 'radio');
|
|
212
|
+
button.setAttribute('data-theme-option', themeName);
|
|
213
|
+
button.setAttribute('aria-checked', 'false');
|
|
86
214
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
215
|
+
var palette = document.createElement('span');
|
|
216
|
+
palette.className = 'theme-option-palette';
|
|
217
|
+
palette.setAttribute('aria-hidden', 'true');
|
|
218
|
+
profile.colors.forEach(function(color) {
|
|
219
|
+
var chip = document.createElement('i');
|
|
220
|
+
chip.style.backgroundColor = color;
|
|
221
|
+
palette.appendChild(chip);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
var copy = document.createElement('span');
|
|
225
|
+
copy.className = 'theme-option-copy';
|
|
226
|
+
var title = document.createElement('strong');
|
|
227
|
+
title.textContent = profile.label;
|
|
228
|
+
var description = document.createElement('small');
|
|
229
|
+
description.textContent = profile.description;
|
|
230
|
+
copy.appendChild(title);
|
|
231
|
+
copy.appendChild(description);
|
|
232
|
+
|
|
233
|
+
var check = document.createElement('span');
|
|
234
|
+
check.className = 'theme-option-check';
|
|
235
|
+
check.setAttribute('aria-hidden', 'true');
|
|
236
|
+
check.textContent = '✓';
|
|
237
|
+
|
|
238
|
+
button.appendChild(palette);
|
|
239
|
+
button.appendChild(copy);
|
|
240
|
+
button.appendChild(check);
|
|
241
|
+
button.addEventListener('click', function() {
|
|
242
|
+
applyAppearance(themeName, activeMode);
|
|
243
|
+
});
|
|
244
|
+
container.appendChild(button);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function buildModeOptions(container) {
|
|
249
|
+
MODE_CYCLE.forEach(function(mode) {
|
|
250
|
+
var button = document.createElement('button');
|
|
251
|
+
button.type = 'button';
|
|
252
|
+
button.className = 'theme-mode-option';
|
|
253
|
+
button.setAttribute('role', 'radio');
|
|
254
|
+
button.setAttribute('data-mode-option', mode);
|
|
255
|
+
button.setAttribute('aria-checked', 'false');
|
|
256
|
+
button.innerHTML = '<span aria-hidden="true">' + COLOR_MODES[mode].shortLabel + '</span>' +
|
|
257
|
+
'<strong>' + COLOR_MODES[mode].label + '</strong>';
|
|
258
|
+
button.addEventListener('click', function() {
|
|
259
|
+
applyAppearance(activeTheme, mode);
|
|
260
|
+
});
|
|
261
|
+
container.appendChild(button);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function initAppearanceControls() {
|
|
266
|
+
setAppearanceAttributes(activeTheme, activeMode);
|
|
267
|
+
var styleToggle = document.getElementById('theme-style-toggle');
|
|
268
|
+
var modeToggle = document.getElementById('theme-toggle');
|
|
269
|
+
var popover = document.getElementById('theme-popover');
|
|
270
|
+
var themeOptions = document.getElementById('theme-options');
|
|
271
|
+
var modeOptions = document.getElementById('theme-mode-options');
|
|
272
|
+
|
|
273
|
+
if (themeOptions) buildThemeOptions(themeOptions);
|
|
274
|
+
if (modeOptions) buildModeOptions(modeOptions);
|
|
275
|
+
|
|
276
|
+
function setPopoverOpen(open) {
|
|
277
|
+
if (!styleToggle || !popover) return;
|
|
278
|
+
popover.hidden = !open;
|
|
279
|
+
styleToggle.setAttribute('aria-expanded', String(open));
|
|
280
|
+
if (open) {
|
|
281
|
+
var activeOption = popover.querySelector('.theme-option.is-active');
|
|
282
|
+
if (activeOption) activeOption.focus();
|
|
102
283
|
}
|
|
103
284
|
}
|
|
104
285
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
286
|
+
if (styleToggle && popover) {
|
|
287
|
+
styleToggle.addEventListener('click', function(event) {
|
|
288
|
+
event.stopPropagation();
|
|
289
|
+
setPopoverOpen(popover.hidden);
|
|
290
|
+
});
|
|
291
|
+
popover.addEventListener('click', function(event) {
|
|
292
|
+
event.stopPropagation();
|
|
293
|
+
});
|
|
294
|
+
document.addEventListener('click', function() { setPopoverOpen(false); });
|
|
295
|
+
document.addEventListener('keydown', function(event) {
|
|
296
|
+
if (event.key === 'Escape') {
|
|
297
|
+
setPopoverOpen(false);
|
|
298
|
+
styleToggle.focus();
|
|
299
|
+
}
|
|
300
|
+
});
|
|
111
301
|
}
|
|
112
302
|
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (currentMode === 'auto') {
|
|
118
|
-
applyTheme('auto', true);
|
|
119
|
-
}
|
|
303
|
+
if (modeToggle) {
|
|
304
|
+
modeToggle.addEventListener('click', function() {
|
|
305
|
+
var index = MODE_CYCLE.indexOf(activeMode);
|
|
306
|
+
applyAppearance(activeTheme, MODE_CYCLE[(index + 1) % MODE_CYCLE.length]);
|
|
120
307
|
});
|
|
121
|
-
window._themeMediaQueryBound = true;
|
|
122
308
|
}
|
|
123
309
|
|
|
124
|
-
|
|
310
|
+
if (window.matchMedia && !window.__docnestThemeMediaQueryBound) {
|
|
311
|
+
var query = window.matchMedia('(prefers-color-scheme: dark)');
|
|
312
|
+
query.addEventListener('change', function() {
|
|
313
|
+
if (activeMode === 'auto') applyAppearance(activeTheme, 'auto', { persist: false });
|
|
314
|
+
});
|
|
315
|
+
window.__docnestThemeMediaQueryBound = true;
|
|
316
|
+
}
|
|
125
317
|
|
|
126
|
-
|
|
127
|
-
e.stopPropagation();
|
|
128
|
-
cycleTheme();
|
|
129
|
-
});
|
|
318
|
+
applyAppearance(activeTheme, activeMode, { persist: false });
|
|
130
319
|
}
|
|
131
|
-
|
|
132
|
-
|
|
320
|
+
|
|
321
|
+
window.DocNestAppearance = {
|
|
322
|
+
get: function() {
|
|
323
|
+
return { theme: activeTheme, mode: activeMode, effectiveMode: getEffectiveMode(activeMode) };
|
|
324
|
+
},
|
|
325
|
+
setTheme: function(theme) { applyAppearance(theme, activeMode); },
|
|
326
|
+
setMode: function(mode) { applyAppearance(activeTheme, mode); },
|
|
327
|
+
themes: Object.keys(THEME_PROFILES)
|
|
328
|
+
};
|
|
329
|
+
|
|
133
330
|
if (document.readyState === 'loading') {
|
|
134
|
-
document.addEventListener('DOMContentLoaded',
|
|
331
|
+
document.addEventListener('DOMContentLoaded', initAppearanceControls);
|
|
135
332
|
} else {
|
|
136
|
-
|
|
333
|
+
initAppearanceControls();
|
|
137
334
|
}
|
|
138
335
|
})();
|
package/server/views/dir.ejs
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
<link id="markdown-theme" rel="stylesheet" href="/vendor/github-markdown-css/github-markdown-light.css?v=<%= assetVersion %>">
|
|
9
9
|
<link rel="stylesheet" href="/static/css/styles.css?v=<%= assetVersion %>">
|
|
10
10
|
<link rel="stylesheet" href="/static/css/highlight.css?v=<%= assetVersion %>">
|
|
11
|
+
<link rel="stylesheet" href="/static/css/themes.css?v=<%= assetVersion %>">
|
|
11
12
|
<script src="/mermaid/mermaid.min.js?v=<%= assetVersion %>"></script>
|
|
13
|
+
<%- include('partials/appearance-head') %>
|
|
12
14
|
<script src="/static/js/core/theme.js?v=<%= assetVersion %>"></script>
|
|
13
15
|
<script src="/static/js/core/sidebar.js?v=<%= assetVersion %>"></script>
|
|
14
16
|
</head>
|
package/server/views/doc.ejs
CHANGED
|
@@ -8,13 +8,9 @@
|
|
|
8
8
|
<link id="markdown-theme" rel="stylesheet" href="/vendor/github-markdown-css/github-markdown-light.css?v=<%= assetVersion %>">
|
|
9
9
|
<link rel="stylesheet" href="/static/css/styles.css?v=<%= assetVersion %>">
|
|
10
10
|
<link rel="stylesheet" href="/static/css/highlight.css?v=<%= assetVersion %>">
|
|
11
|
+
<link rel="stylesheet" href="/static/css/themes.css?v=<%= assetVersion %>">
|
|
11
12
|
<script src="/mermaid/mermaid.min.js?v=<%= assetVersion %>"></script>
|
|
12
|
-
|
|
13
|
-
window.DOCNEST_CONFIG = {
|
|
14
|
-
watermarkEnabled: <%= docsWatermarkEnabled ? 'true' : 'false' %>,
|
|
15
|
-
watermarkText: <%- JSON.stringify(docsWatermarkText) %>
|
|
16
|
-
};
|
|
17
|
-
</script>
|
|
13
|
+
<%- include('partials/appearance-head') %>
|
|
18
14
|
<!-- 基础功能模块 -->
|
|
19
15
|
<script src="/static/js/core/theme.js?v=<%= assetVersion %>"></script>
|
|
20
16
|
<script src="/static/js/core/mermaid-init.js?v=<%= assetVersion %>"></script>
|
package/server/views/index.ejs
CHANGED
|
@@ -8,13 +8,9 @@
|
|
|
8
8
|
<link id="markdown-theme" rel="stylesheet" href="/vendor/github-markdown-css/github-markdown-light.css?v=<%= assetVersion %>">
|
|
9
9
|
<link rel="stylesheet" href="/static/css/styles.css?v=<%= assetVersion %>">
|
|
10
10
|
<link rel="stylesheet" href="/static/css/highlight.css?v=<%= assetVersion %>">
|
|
11
|
+
<link rel="stylesheet" href="/static/css/themes.css?v=<%= assetVersion %>">
|
|
11
12
|
<script src="/mermaid/mermaid.min.js?v=<%= assetVersion %>"></script>
|
|
12
|
-
|
|
13
|
-
window.DOCNEST_CONFIG = {
|
|
14
|
-
watermarkEnabled: <%= docsWatermarkEnabled ? 'true' : 'false' %>,
|
|
15
|
-
watermarkText: <%- JSON.stringify(docsWatermarkText) %>
|
|
16
|
-
};
|
|
17
|
-
</script>
|
|
13
|
+
<%- include('partials/appearance-head') %>
|
|
18
14
|
<!-- 基础功能模块 -->
|
|
19
15
|
<script src="/static/js/core/theme.js?v=<%= assetVersion %>"></script>
|
|
20
16
|
<script src="/static/js/core/mermaid-init.js?v=<%= assetVersion %>"></script>
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
<link id="markdown-theme" rel="stylesheet" href="/vendor/github-markdown-css/github-markdown-light.css?v=<%= assetVersion %>">
|
|
9
9
|
<link rel="stylesheet" href="/static/css/styles.css?v=<%= assetVersion %>">
|
|
10
10
|
<link rel="stylesheet" href="/static/css/highlight.css?v=<%= assetVersion %>">
|
|
11
|
+
<link rel="stylesheet" href="/static/css/themes.css?v=<%= assetVersion %>">
|
|
12
|
+
<%- include('partials/appearance-head') %>
|
|
11
13
|
<script src="/static/js/core/theme.js?v=<%= assetVersion %>"></script>
|
|
12
14
|
<script src="/static/js/core/sidebar.js?v=<%= assetVersion %>"></script>
|
|
13
15
|
</head>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
window.DOCNEST_CONFIG = {
|
|
3
|
+
watermarkEnabled: <%= docsWatermarkEnabled ? 'true' : 'false' %>,
|
|
4
|
+
watermarkText: <%- JSON.stringify(docsWatermarkText) %>,
|
|
5
|
+
storageKeyPrefix: <%- JSON.stringify(docsStorageKeyPrefix) %>,
|
|
6
|
+
appearance: {
|
|
7
|
+
defaultTheme: <%- JSON.stringify(docsThemeDefault) %>,
|
|
8
|
+
defaultMode: <%- JSON.stringify(docsThemeModeDefault) %>,
|
|
9
|
+
enabledThemes: <%- JSON.stringify(docsThemeEnabled) %>
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
</script>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="header-content">
|
|
3
3
|
<div class="header-title-group">
|
|
4
4
|
<h1>
|
|
5
|
-
<% if (docsLogo) { %><img class="brand-logo" src="<%= docsLogo %>" alt=""><% } else {
|
|
5
|
+
<% if (docsLogo) { %><img class="brand-logo" src="<%= docsLogo %>" alt=""><% } else { %><span class="brand-fallback" aria-hidden="true"><span class="brand-fallback-emoji">📚</span><span class="brand-fallback-shape"></span></span><% } %>
|
|
6
6
|
<%= docsTitle %>
|
|
7
7
|
</h1>
|
|
8
8
|
<% if (typeof showBackLink !== 'undefined' && showBackLink) { %>
|
|
@@ -25,12 +25,35 @@
|
|
|
25
25
|
<span class="header-action-text">下载 PDF</span>
|
|
26
26
|
</button>
|
|
27
27
|
<% } %>
|
|
28
|
-
<div class="theme-selector">
|
|
29
|
-
<button
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
<div class="theme-selector" id="theme-selector">
|
|
29
|
+
<button
|
|
30
|
+
id="theme-style-toggle"
|
|
31
|
+
class="header-action-btn theme-style-toggle"
|
|
32
|
+
type="button"
|
|
33
|
+
aria-haspopup="dialog"
|
|
34
|
+
aria-expanded="false"
|
|
35
|
+
aria-controls="theme-popover"
|
|
36
|
+
title="选择文档中心主题"
|
|
37
|
+
>
|
|
38
|
+
<span class="theme-swatch" aria-hidden="true"></span>
|
|
39
|
+
<span class="theme-style-text">Slate Modern</span>
|
|
40
|
+
<span class="theme-chevron" aria-hidden="true">⌄</span>
|
|
32
41
|
</button>
|
|
42
|
+
<div class="theme-popover" id="theme-popover" role="dialog" aria-label="外观设置" hidden>
|
|
43
|
+
<div class="theme-popover-section">
|
|
44
|
+
<p class="theme-popover-label">主题风格</p>
|
|
45
|
+
<div class="theme-options" id="theme-options" role="radiogroup" aria-label="主题风格"></div>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="theme-popover-section theme-mode-section">
|
|
48
|
+
<p class="theme-popover-label">明暗模式</p>
|
|
49
|
+
<div class="theme-mode-options" id="theme-mode-options" role="radiogroup" aria-label="明暗模式"></div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
33
52
|
</div>
|
|
53
|
+
<button id="theme-toggle" class="header-action-btn theme-toggle" type="button" title="切换明暗模式">
|
|
54
|
+
<span class="theme-icon" aria-hidden="true">A</span>
|
|
55
|
+
<span class="theme-text">跟随系统</span>
|
|
56
|
+
</button>
|
|
34
57
|
</div>
|
|
35
58
|
</div>
|
|
36
59
|
</div>
|