docnest 0.1.24 → 0.1.26
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 +16 -5
- package/dist/cli.js +1 -0
- package/dist/config.d.ts +9 -2
- package/dist/config.js +6 -0
- package/dist/server.js +26 -14
- package/docnest.config.example.mjs +4 -1
- package/package.json +1 -1
- package/server/static/css/themes.css +1 -7
- package/server/static/js/core/doc-search.js +5 -2
- package/server/static/js/diagram/diagram-viewer.js +27 -26
- package/server/views/doc.ejs +7 -7
- package/server/views/index.ejs +7 -7
- package/server/views/partials/appearance-head.ejs +1 -0
- package/server/views/partials/diagram-viewer.ejs +13 -4
- package/server/views/partials/header.ejs +3 -3
package/README.md
CHANGED
|
@@ -58,20 +58,31 @@ export default defineConfig({
|
|
|
58
58
|
|
|
59
59
|
### 文档搜索
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
整理目录时可以把文档移动到原目录下的分类文件夹。旧地址会在原目录子树中按完全相同的文件名查找,只有唯一匹配时才跳转;重名、目录不存在或读取失败时保留 404,避免打开错误的文档。直接访问现有路径始终优先。
|
|
62
|
+
|
|
63
|
+
文档搜索默认开启,与 `restrictedMode` 独立。页面顶部提供搜索入口,也可以使用 `⌘K`(macOS)或
|
|
62
64
|
`Ctrl K`(Windows/Linux)打开。搜索索引在首次打开搜索时才加载,覆盖文档标题、目录路径、标题层级、正文和代码块;中文按连续字符匹配,英文和带空格的查询按多个词匹配。结果按标题、目录和正文命中位置综合排序,并显示安全的文本摘要。
|
|
63
65
|
|
|
64
66
|
搜索完全基于宿主项目自己的 Markdown 内容,不依赖 Algolia 或其他外部服务。动态服务通过同源
|
|
65
67
|
`/search-index.json` 提供索引;静态站点需要把同一份响应保存为站点根目录的
|
|
66
68
|
`search-index.json`,并保留页面上的静态站点标记,让结果链接指向 `/doc/<path>.html`。
|
|
67
|
-
|
|
69
|
+
只有 `search.enabled: false` 才关闭搜索入口、快捷键与索引接口;省略该项时包括受限模式在内均默认开启。索引继续经过原有授权检查;当前实现将可检索的 Markdown 文本索引交给已授权浏览器处理,不索引嵌入 HTML 的正文。
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
export default defineConfig({
|
|
73
|
+
search: { enabled: true },
|
|
74
|
+
restrictedMode: true,
|
|
75
|
+
})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`search.enabled` 只接受布尔值。使用 CLI 时由配置文件解析并传递给运行时;直接嵌入 `docnest/server` 时可通过 `DOCS_SEARCH_ENABLED=true|false` 设置,缺省为开启。
|
|
68
79
|
|
|
69
80
|
搜索框为空时显示最近搜索:仅记录有结果的关键词,去重后保留最近 10 条,点击即可再次搜索。
|
|
70
81
|
历史保存在当前浏览器,按 `site.storageKeyPrefix` 隔离;不上传服务器。
|
|
71
82
|
|
|
72
83
|
多空间托管场景可设置 `DOCS_ALLOW_RAW_HTML=false`,把 Markdown 中的原始 HTML 显示为文本,避免文档内脚本取得宿主页面权限;Markdown 语法与 Mermaid 不受影响。默认保留原始 HTML 兼容行为。
|
|
73
84
|
|
|
74
|
-
|
|
85
|
+
复制、导出与页面水印由 `restrictedMode` 控制;搜索独立配置,图表放大查看始终可用:
|
|
75
86
|
|
|
76
87
|
```js
|
|
77
88
|
export default defineConfig({
|
|
@@ -80,9 +91,9 @@ export default defineConfig({
|
|
|
80
91
|
```
|
|
81
92
|
|
|
82
93
|
- `restrictedMode: false`:显示打印和 PDF 导出,允许 Mermaid 图下载与大图查看。导出 PDF 时会弹出本次水印设置,可选择是否添加并自定义文案;默认文案为 `site.title`,不会写回配置。
|
|
83
|
-
- `restrictedMode: true`:隐藏打印和 PDF 导出入口且不加载导出模块;使用 `site.title` 作为固定水印,禁止文字选择、复制、剪切和拖拽,并隐藏 Mermaid
|
|
94
|
+
- `restrictedMode: true`:隐藏打印和 PDF 导出入口且不加载导出模块;使用 `site.title` 作为固定水印,禁止文字选择、复制、剪切和拖拽,并隐藏 Mermaid 复制与下载入口。Mermaid 可正常渲染、放大、缩小、重置和全屏查看,查看器内保留水印。
|
|
84
95
|
|
|
85
|
-
|
|
96
|
+
图表查看没有启用/禁用配置项。受限模式只限制 DocNest 提供的复制和导出入口,不承诺对浏览器开发者工具或系统级截图提供绝对防护。
|
|
86
97
|
|
|
87
98
|
页面水印使用 4% 透明度,保持可辨识而不遮挡正文和图表。Mermaid 代码围栏按独立图表渲染,不继承普通代码块的等宽字体;节点内的 HTML 换行与图表字体测量保持一致。
|
|
88
99
|
|
package/dist/cli.js
CHANGED
|
@@ -63,6 +63,7 @@ function exposeConfig(config) {
|
|
|
63
63
|
process.env.DOCS_THEME_MODE_DEFAULT = config.appearance.defaultMode;
|
|
64
64
|
process.env.DOCS_THEME_ENABLED = JSON.stringify(config.appearance.enabledThemes);
|
|
65
65
|
process.env.DOCS_RESTRICTED_MODE = String(config.restrictedMode);
|
|
66
|
+
process.env.DOCS_SEARCH_ENABLED = String(config.search.enabled);
|
|
66
67
|
process.env.DOCS_HOST = config.server.host;
|
|
67
68
|
process.env.DOCS_PORT_START = String(config.server.port);
|
|
68
69
|
process.env.DOCS_WATCH_ENABLED = String(config.server.watch);
|
package/dist/config.d.ts
CHANGED
|
@@ -14,6 +14,10 @@ export type DocNestConfig = {
|
|
|
14
14
|
navigation?: {
|
|
15
15
|
rootDirectoryOrder?: string[];
|
|
16
16
|
};
|
|
17
|
+
search?: {
|
|
18
|
+
/** Search is enabled by default, independently of restrictedMode. */
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
};
|
|
17
21
|
appearance?: {
|
|
18
22
|
defaultTheme?: DocNestThemeName;
|
|
19
23
|
defaultMode?: DocNestColorMode;
|
|
@@ -38,8 +42,8 @@ export type DocNestConfig = {
|
|
|
38
42
|
sessionTtlMinutes?: number;
|
|
39
43
|
};
|
|
40
44
|
/**
|
|
41
|
-
* Restrict
|
|
42
|
-
*
|
|
45
|
+
* Restrict copying and export, and enable the fixed site-title watermark.
|
|
46
|
+
* Search and diagram viewing are reading capabilities and remain independent.
|
|
43
47
|
*/
|
|
44
48
|
restrictedMode?: boolean;
|
|
45
49
|
};
|
|
@@ -49,6 +53,9 @@ export type ResolvedDocNestConfig = Required<DocNestConfig> & {
|
|
|
49
53
|
docsDir: string;
|
|
50
54
|
site: Required<NonNullable<DocNestConfig['site']>>;
|
|
51
55
|
navigation: Required<NonNullable<DocNestConfig['navigation']>>;
|
|
56
|
+
search: {
|
|
57
|
+
enabled: boolean;
|
|
58
|
+
};
|
|
52
59
|
appearance: Required<NonNullable<DocNestConfig['appearance']>>;
|
|
53
60
|
server: Required<NonNullable<DocNestConfig['server']>>;
|
|
54
61
|
auth: {
|
package/dist/config.js
CHANGED
|
@@ -98,6 +98,11 @@ async function loadConfig(projectRoot = process.cwd()) {
|
|
|
98
98
|
const projectName = await readProjectName(projectRoot);
|
|
99
99
|
const site = userConfig.site ?? {};
|
|
100
100
|
const navigation = userConfig.navigation ?? {};
|
|
101
|
+
const search = userConfig.search ?? {};
|
|
102
|
+
if (typeof search !== 'object' || Array.isArray(search) ||
|
|
103
|
+
(search.enabled !== undefined && typeof search.enabled !== 'boolean')) {
|
|
104
|
+
throw new Error('search.enabled must be a boolean');
|
|
105
|
+
}
|
|
101
106
|
const appearance = userConfig.appearance ?? {};
|
|
102
107
|
const server = userConfig.server ?? {};
|
|
103
108
|
const auth = userConfig.auth ?? {};
|
|
@@ -117,6 +122,7 @@ async function loadConfig(projectRoot = process.cwd()) {
|
|
|
117
122
|
? navigation.rootDirectoryOrder.filter((item) => typeof item === 'string')
|
|
118
123
|
: [],
|
|
119
124
|
},
|
|
125
|
+
search: { enabled: search.enabled ?? true },
|
|
120
126
|
appearance: resolveAppearance(appearance),
|
|
121
127
|
server: {
|
|
122
128
|
host: server.host?.trim() || '127.0.0.1',
|
package/dist/server.js
CHANGED
|
@@ -55,6 +55,11 @@ const DOCS_THEME_ENABLED = (() => {
|
|
|
55
55
|
}
|
|
56
56
|
})();
|
|
57
57
|
const DOCS_RESTRICTED_MODE = process.env.DOCS_RESTRICTED_MODE === 'true';
|
|
58
|
+
if (process.env.DOCS_SEARCH_ENABLED !== undefined &&
|
|
59
|
+
!['true', 'false'].includes(process.env.DOCS_SEARCH_ENABLED)) {
|
|
60
|
+
throw new Error('DOCS_SEARCH_ENABLED must be true or false');
|
|
61
|
+
}
|
|
62
|
+
const DOCS_SEARCH_ENABLED = process.env.DOCS_SEARCH_ENABLED !== 'false';
|
|
58
63
|
const DOCS_PDF_ENABLED = !DOCS_RESTRICTED_MODE;
|
|
59
64
|
const DOCS_PAGE_WATERMARK_TEXT = DOCS_TITLE;
|
|
60
65
|
const DOCS_DIR = path.resolve(process.env.DOCS_ROOT || path.join(process.cwd(), 'docs'));
|
|
@@ -110,6 +115,7 @@ app.locals.docsThemeDefault = DOCS_THEME_DEFAULT;
|
|
|
110
115
|
app.locals.docsThemeModeDefault = DOCS_THEME_MODE_DEFAULT;
|
|
111
116
|
app.locals.docsThemeEnabled = DOCS_THEME_ENABLED;
|
|
112
117
|
app.locals.docsRestrictedMode = DOCS_RESTRICTED_MODE;
|
|
118
|
+
app.locals.docsSearchEnabled = DOCS_SEARCH_ENABLED;
|
|
113
119
|
app.locals.docsPageWatermarkText = DOCS_PAGE_WATERMARK_TEXT;
|
|
114
120
|
app.locals.docsAuthEnabled = BUILT_IN_AUTH_ENABLED;
|
|
115
121
|
app.locals.docsAuthStorageKey = AUTH_CONFIG.localStorageKey || `${DOCS_STORAGE_KEY_PREFIX}:auth-passphrase`;
|
|
@@ -526,7 +532,7 @@ function invalidateSearchIndex() {
|
|
|
526
532
|
searchIndexCache = null;
|
|
527
533
|
}
|
|
528
534
|
app.get('/search-index.json', async (req, res) => {
|
|
529
|
-
if (
|
|
535
|
+
if (!DOCS_SEARCH_ENABLED) {
|
|
530
536
|
return res.status(404).json({ ok: false, error: '搜索功能已关闭。' });
|
|
531
537
|
}
|
|
532
538
|
try {
|
|
@@ -719,7 +725,10 @@ async function resolveDocPath(requestedPath) {
|
|
|
719
725
|
if (await storageFileExists(resolvedPath)) {
|
|
720
726
|
return { resolvedPath };
|
|
721
727
|
}
|
|
722
|
-
|
|
728
|
+
// A document may have moved into a subdirectory during catalog cleanup.
|
|
729
|
+
// Resolve only an unambiguous filename inside its original directory.
|
|
730
|
+
const movedPath = await findFileInTree(resolvedDirPath, fileName);
|
|
731
|
+
return movedPath ? { resolvedPath: movedPath } : null;
|
|
723
732
|
}
|
|
724
733
|
// 如果只是文件名,尝试在整个文档目录中查找
|
|
725
734
|
try {
|
|
@@ -733,29 +742,32 @@ async function resolveDocPath(requestedPath) {
|
|
|
733
742
|
}
|
|
734
743
|
return null;
|
|
735
744
|
}
|
|
736
|
-
//
|
|
737
|
-
async function findFileInTree(dir, fileName
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
const entries = await storageReadDir(
|
|
745
|
+
// Find one exact filename; ambiguity or an unreadable subtree must not redirect.
|
|
746
|
+
async function findFileInTree(dir, fileName) {
|
|
747
|
+
const matches = [];
|
|
748
|
+
async function visit(basePath) {
|
|
749
|
+
const entries = await storageReadDir(basePath);
|
|
741
750
|
for (const entry of entries) {
|
|
742
751
|
if (entry.name.startsWith('.'))
|
|
743
752
|
continue;
|
|
744
|
-
const relativePath = joinDocPath(
|
|
753
|
+
const relativePath = joinDocPath(basePath, entry.name);
|
|
745
754
|
if (entry.isDirectory) {
|
|
746
|
-
|
|
747
|
-
if (found)
|
|
748
|
-
return found;
|
|
755
|
+
await visit(relativePath);
|
|
749
756
|
}
|
|
750
757
|
else if (entry.isFile && entry.name === fileName && entry.name.endsWith('.md')) {
|
|
751
|
-
|
|
758
|
+
matches.push(relativePath);
|
|
752
759
|
}
|
|
760
|
+
if (matches.length > 1)
|
|
761
|
+
return;
|
|
753
762
|
}
|
|
754
763
|
}
|
|
764
|
+
try {
|
|
765
|
+
await visit(dir || '');
|
|
766
|
+
}
|
|
755
767
|
catch (e) {
|
|
756
|
-
|
|
768
|
+
return null;
|
|
757
769
|
}
|
|
758
|
-
return null;
|
|
770
|
+
return matches.length === 1 ? matches[0] : null;
|
|
759
771
|
}
|
|
760
772
|
// 构建面包屑:基于文档路径
|
|
761
773
|
async function buildBreadcrumbs(docPath) {
|
|
@@ -43,7 +43,10 @@ export default defineConfig({
|
|
|
43
43
|
localStorageKey: 'my-project:auth-passphrase',
|
|
44
44
|
sessionTtlMinutes: 24 * 60,
|
|
45
45
|
},
|
|
46
|
-
//
|
|
46
|
+
// 搜索默认开启,独立于受限模式;设为 false 时同时关闭入口和索引。
|
|
47
|
+
search: { enabled: true },
|
|
48
|
+
// 受限阅读模式;默认关闭。开启后隐藏打印/PDF 和 Mermaid 复制、下载,
|
|
47
49
|
// 并使用 site.title 显示固定水印,同时禁止页面文字选择与复制。
|
|
50
|
+
// 图表放大查看始终可用,没有单独配置项。
|
|
48
51
|
restrictedMode: false,
|
|
49
52
|
})
|
package/package.json
CHANGED
|
@@ -59,13 +59,7 @@ html.docnest-page-protected body [contenteditable="true"] {
|
|
|
59
59
|
user-select: text !important;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
/*
|
|
63
|
-
* or exporting a larger copy of the diagram. */
|
|
64
|
-
html.docnest-restricted .markdown-body .mermaid {
|
|
65
|
-
cursor: default;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
html.docnest-restricted .markdown-body .mermaid::after,
|
|
62
|
+
/* Diagram viewing is always available; restricted mode only hides export. */
|
|
69
63
|
html.docnest-restricted .markdown-body .mermaid-download-btn {
|
|
70
64
|
display: none !important;
|
|
71
65
|
}
|
|
@@ -88,7 +88,10 @@
|
|
|
88
88
|
function getAssetUrl(path) {
|
|
89
89
|
var base = document.querySelector('base');
|
|
90
90
|
if (base && base.getAttribute('href')) {
|
|
91
|
-
|
|
91
|
+
var prefix = base.getAttribute('href').replace(/\/?$/, '/');
|
|
92
|
+
// A mounting proxy may have already rewritten a root-relative link.
|
|
93
|
+
if (path.indexOf(prefix) === 0) return path;
|
|
94
|
+
return prefix + path.replace(/^\//, '');
|
|
92
95
|
}
|
|
93
96
|
return path;
|
|
94
97
|
}
|
|
@@ -510,7 +513,7 @@
|
|
|
510
513
|
|
|
511
514
|
function init() {
|
|
512
515
|
var config = window.DOCNEST_CONFIG || {};
|
|
513
|
-
if (config.
|
|
516
|
+
if (config.searchEnabled === false) return;
|
|
514
517
|
var trigger = document.getElementById('doc-search-trigger');
|
|
515
518
|
if (!trigger) return;
|
|
516
519
|
|
|
@@ -3,6 +3,7 @@ let currentZoom = 1;
|
|
|
3
3
|
let defaultZoom = 1;
|
|
4
4
|
let currentSvg = null;
|
|
5
5
|
let isFullscreen = false;
|
|
6
|
+
let viewerPreviousFocus = null;
|
|
6
7
|
|
|
7
8
|
function isDocNestRestrictedMode() {
|
|
8
9
|
return !!(window.DOCNEST_CONFIG && window.DOCNEST_CONFIG.restrictedMode === true);
|
|
@@ -44,45 +45,42 @@ function getDiagramIntrinsicDimensions(svgElement) {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
function setupDiagramViewer() {
|
|
47
|
-
if (isDocNestRestrictedMode()) return;
|
|
48
48
|
const diagrams = document.querySelectorAll('.markdown-body .mermaid');
|
|
49
49
|
diagrams.forEach((diagram, index) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
downloadBtn
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
50
|
+
let downloadBtn = diagram.querySelector('.mermaid-download-btn');
|
|
51
|
+
if (isDocNestRestrictedMode()) {
|
|
52
|
+
if (downloadBtn) downloadBtn.remove();
|
|
53
|
+
downloadBtn = null;
|
|
54
|
+
} else if (!downloadBtn) {
|
|
55
|
+
downloadBtn = document.createElement('button');
|
|
56
|
+
downloadBtn.className = 'mermaid-download-btn';
|
|
57
|
+
downloadBtn.innerHTML = '⬇️ 下载';
|
|
58
|
+
downloadBtn.title = '下载图表';
|
|
59
|
+
downloadBtn.onclick = function(e) {
|
|
60
|
+
e.stopPropagation();
|
|
61
|
+
const svg = diagram.querySelector('svg');
|
|
62
|
+
if (svg && typeof showDownloadMenu === 'function') {
|
|
60
63
|
showDownloadMenu(svg.cloneNode(true), `diagram-${index + 1}`);
|
|
61
64
|
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
diagram.
|
|
68
|
-
|
|
69
|
-
if (e.target === downloadBtn || downloadBtn.contains(e.target)) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
65
|
+
};
|
|
66
|
+
diagram.appendChild(downloadBtn);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Repeated renders must not accumulate viewer handlers or download buttons.
|
|
70
|
+
diagram.onclick = function(e) {
|
|
71
|
+
if (downloadBtn && (e.target === downloadBtn || downloadBtn.contains(e.target))) return;
|
|
72
72
|
const svg = diagram.querySelector('svg');
|
|
73
|
-
if (svg)
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
});
|
|
73
|
+
if (svg) openDiagramViewer(svg.cloneNode(true));
|
|
74
|
+
};
|
|
77
75
|
});
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
function openDiagramViewer(svgElement) {
|
|
81
|
-
if (isDocNestRestrictedMode()) return;
|
|
82
79
|
const viewer = document.getElementById('diagram-viewer');
|
|
83
80
|
const container = document.getElementById('diagram-viewer-svg-container');
|
|
84
81
|
|
|
85
82
|
if (!viewer || !container) return;
|
|
83
|
+
viewerPreviousFocus = document.activeElement;
|
|
86
84
|
|
|
87
85
|
// viewBox 才是图表的实际尺寸;不能把 Mermaid 的 width="100%" 解析成 100。
|
|
88
86
|
const { width: originalWidth, height: originalHeight } = getDiagramIntrinsicDimensions(svgElement);
|
|
@@ -159,6 +157,7 @@ function openDiagramViewer(svgElement) {
|
|
|
159
157
|
|
|
160
158
|
viewer.classList.add('show');
|
|
161
159
|
document.body.style.overflow = 'hidden';
|
|
160
|
+
viewer.querySelector('.diagram-viewer-close')?.focus();
|
|
162
161
|
|
|
163
162
|
// 重置全屏状态
|
|
164
163
|
isFullscreen = false;
|
|
@@ -207,6 +206,8 @@ function closeDiagramViewer() {
|
|
|
207
206
|
if (typeof removeModal === 'function') {
|
|
208
207
|
removeModal('diagram-viewer');
|
|
209
208
|
}
|
|
209
|
+
viewerPreviousFocus?.focus?.();
|
|
210
|
+
viewerPreviousFocus = null;
|
|
210
211
|
}
|
|
211
212
|
|
|
212
213
|
function resetDiagramZoom() {
|
package/server/views/doc.ejs
CHANGED
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
</main>
|
|
40
40
|
</div>
|
|
41
41
|
</div>
|
|
42
|
+
<%- include('partials/diagram-viewer') %>
|
|
43
|
+
<!-- 按依赖顺序加载模块 -->
|
|
44
|
+
<script src="/static/js/utils/modal-stack.js?v=<%= assetVersion %>"></script>
|
|
45
|
+
<script src="/static/js/diagram/diagram-viewer.js?v=<%= assetVersion %>"></script>
|
|
46
|
+
<script src="/static/js/diagram/diagram-zoom.js?v=<%= assetVersion %>"></script>
|
|
47
|
+
<script src="/static/js/diagram/diagram-drag.js?v=<%= assetVersion %>"></script>
|
|
48
|
+
<script src="/static/js/diagram/diagram-controls.js?v=<%= assetVersion %>"></script>
|
|
42
49
|
<% if (!docsRestrictedMode) { %>
|
|
43
|
-
<%- include('partials/diagram-viewer') %>
|
|
44
|
-
<!-- 按依赖顺序加载模块 -->
|
|
45
|
-
<script src="/static/js/utils/modal-stack.js?v=<%= assetVersion %>"></script>
|
|
46
|
-
<script src="/static/js/diagram/diagram-viewer.js?v=<%= assetVersion %>"></script>
|
|
47
|
-
<script src="/static/js/diagram/diagram-zoom.js?v=<%= assetVersion %>"></script>
|
|
48
|
-
<script src="/static/js/diagram/diagram-drag.js?v=<%= assetVersion %>"></script>
|
|
49
|
-
<script src="/static/js/diagram/diagram-controls.js?v=<%= assetVersion %>"></script>
|
|
50
50
|
<script src="/static/js/diagram/diagram-download.js?v=<%= assetVersion %>"></script>
|
|
51
51
|
<% } %>
|
|
52
52
|
<script src="/static/js/utils/app.js?v=<%= assetVersion %>"></script>
|
package/server/views/index.ejs
CHANGED
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
</main>
|
|
31
31
|
</div>
|
|
32
32
|
</div>
|
|
33
|
+
<%- include('partials/diagram-viewer') %>
|
|
34
|
+
<!-- 按依赖顺序加载模块 -->
|
|
35
|
+
<script src="/static/js/utils/modal-stack.js?v=<%= assetVersion %>"></script>
|
|
36
|
+
<script src="/static/js/diagram/diagram-viewer.js?v=<%= assetVersion %>"></script>
|
|
37
|
+
<script src="/static/js/diagram/diagram-zoom.js?v=<%= assetVersion %>"></script>
|
|
38
|
+
<script src="/static/js/diagram/diagram-drag.js?v=<%= assetVersion %>"></script>
|
|
39
|
+
<script src="/static/js/diagram/diagram-controls.js?v=<%= assetVersion %>"></script>
|
|
33
40
|
<% if (!docsRestrictedMode) { %>
|
|
34
|
-
<%- include('partials/diagram-viewer') %>
|
|
35
|
-
<!-- 按依赖顺序加载模块 -->
|
|
36
|
-
<script src="/static/js/utils/modal-stack.js?v=<%= assetVersion %>"></script>
|
|
37
|
-
<script src="/static/js/diagram/diagram-viewer.js?v=<%= assetVersion %>"></script>
|
|
38
|
-
<script src="/static/js/diagram/diagram-zoom.js?v=<%= assetVersion %>"></script>
|
|
39
|
-
<script src="/static/js/diagram/diagram-drag.js?v=<%= assetVersion %>"></script>
|
|
40
|
-
<script src="/static/js/diagram/diagram-controls.js?v=<%= assetVersion %>"></script>
|
|
41
41
|
<script src="/static/js/diagram/diagram-download.js?v=<%= assetVersion %>"></script>
|
|
42
42
|
<% } %>
|
|
43
43
|
<script src="/static/js/utils/app.js?v=<%= assetVersion %>"></script>
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
authStorageKey: <%- JSON.stringify(docsAuthStorageKey) %>,
|
|
5
5
|
siteTitle: <%- JSON.stringify(docsTitle) %>,
|
|
6
6
|
restrictedMode: <%= docsRestrictedMode ? 'true' : 'false' %>,
|
|
7
|
+
searchEnabled: <%= docsSearchEnabled ? 'true' : 'false' %>,
|
|
7
8
|
storageKeyPrefix: <%- JSON.stringify(docsStorageKeyPrefix) %>,
|
|
8
9
|
appearance: {
|
|
9
10
|
defaultTheme: <%- JSON.stringify(docsThemeDefault) %>,
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
<!-- 图表查看器模态框 -->
|
|
2
|
-
<div id="diagram-viewer" class="diagram-viewer">
|
|
2
|
+
<div id="diagram-viewer" class="diagram-viewer" role="dialog" aria-modal="true" aria-label="图表查看器">
|
|
3
|
+
<% if (docsRestrictedMode) { %>
|
|
4
|
+
<div class="docnest-page-watermark" aria-hidden="true">
|
|
5
|
+
<% for (let index = 0; index < 12; index += 1) { %>
|
|
6
|
+
<span class="docnest-page-watermark__item"><%= docsPageWatermarkText %></span>
|
|
7
|
+
<% } %>
|
|
8
|
+
</div>
|
|
9
|
+
<% } %>
|
|
3
10
|
<div class="diagram-viewer-content">
|
|
4
|
-
<button class="diagram-viewer-close" onclick="closeDiagramViewer()">×</button>
|
|
11
|
+
<button class="diagram-viewer-close" type="button" aria-label="关闭图表查看器" onclick="closeDiagramViewer()">×</button>
|
|
5
12
|
<div class="diagram-viewer-controls">
|
|
6
13
|
<button onclick="zoomDiagram('out')">缩小</button>
|
|
7
14
|
<span class="zoom-info" id="zoom-info">100%</span>
|
|
8
15
|
<button onclick="zoomDiagram('in')">放大</button>
|
|
9
16
|
<button onclick="resetDiagramZoom()">重置</button>
|
|
10
17
|
<button onclick="toggleFullscreen()" class="fullscreen-btn" id="fullscreen-btn">⛶ 全屏</button>
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
<% if (!docsRestrictedMode) { %>
|
|
19
|
+
<button onclick="copyCurrentDiagram()" class="copy-btn">📋 复制</button>
|
|
20
|
+
<button onclick="downloadCurrentDiagram()" class="download-btn">⬇️ 下载</button>
|
|
21
|
+
<% } %>
|
|
13
22
|
</div>
|
|
14
23
|
<div id="diagram-viewer-svg-container" class="diagram-viewer-svg-container"></div>
|
|
15
24
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<header class="header">
|
|
2
|
-
<div class="header-content<%=
|
|
2
|
+
<div class="header-content<%= docsSearchEnabled ? ' header-content-with-search' : '' %>">
|
|
3
3
|
<div class="header-title-group">
|
|
4
4
|
<h1>
|
|
5
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><% } %>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<p class="subtitle"><%= docsSubtitle %></p>
|
|
12
12
|
<% } %>
|
|
13
13
|
</div>
|
|
14
|
-
<% if (
|
|
14
|
+
<% if (docsSearchEnabled) { %>
|
|
15
15
|
<button type="button" id="doc-search-trigger" class="header-action-btn doc-search-trigger" aria-haspopup="dialog" aria-controls="doc-search-modal" title="搜索文档(⌘K / Ctrl K)">
|
|
16
16
|
<span class="doc-search-icon" aria-hidden="true">⌕</span>
|
|
17
17
|
<span class="header-action-text">搜索文档</span>
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
</div>
|
|
66
66
|
</div>
|
|
67
67
|
</header>
|
|
68
|
-
<% if (
|
|
68
|
+
<% if (docsSearchEnabled) { %>
|
|
69
69
|
<script src="/static/js/core/doc-search.js?v=<%= typeof assetVersion !== 'undefined' ? assetVersion : 'dev' %>"></script>
|
|
70
70
|
<% } %>
|
|
71
71
|
<% if (docsAuthEnabled) { %>
|