aifastdb-devplan 1.6.1 → 1.6.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.
Files changed (97) hide show
  1. package/dist/dev-plan-document-store.d.ts +13 -1
  2. package/dist/dev-plan-document-store.d.ts.map +1 -1
  3. package/dist/dev-plan-document-store.js +119 -0
  4. package/dist/dev-plan-document-store.js.map +1 -1
  5. package/dist/dev-plan-factory.d.ts.map +1 -1
  6. package/dist/dev-plan-factory.js +3 -1
  7. package/dist/dev-plan-factory.js.map +1 -1
  8. package/dist/dev-plan-graph-store.d.ts +341 -9
  9. package/dist/dev-plan-graph-store.d.ts.map +1 -1
  10. package/dist/dev-plan-graph-store.js +2414 -210
  11. package/dist/dev-plan-graph-store.js.map +1 -1
  12. package/dist/dev-plan-interface.d.ts +119 -1
  13. package/dist/dev-plan-interface.d.ts.map +1 -1
  14. package/dist/dev-plan-migrate.d.ts +1 -0
  15. package/dist/dev-plan-migrate.d.ts.map +1 -1
  16. package/dist/dev-plan-migrate.js +28 -2
  17. package/dist/dev-plan-migrate.js.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/mcp-server/index.js +652 -0
  22. package/dist/mcp-server/index.js.map +1 -1
  23. package/dist/shard-config.d.ts +64 -0
  24. package/dist/shard-config.d.ts.map +1 -0
  25. package/dist/shard-config.js +109 -0
  26. package/dist/shard-config.js.map +1 -0
  27. package/dist/types.d.ts +305 -2
  28. package/dist/types.d.ts.map +1 -1
  29. package/dist/types.js.map +1 -1
  30. package/dist/visualize/graph-canvas/api-compat.d.ts.map +1 -1
  31. package/dist/visualize/graph-canvas/api-compat.js +22 -12
  32. package/dist/visualize/graph-canvas/api-compat.js.map +1 -1
  33. package/dist/visualize/graph-canvas/core.d.ts.map +1 -1
  34. package/dist/visualize/graph-canvas/core.js +296 -4
  35. package/dist/visualize/graph-canvas/core.js.map +1 -1
  36. package/dist/visualize/graph-canvas/interaction.d.ts.map +1 -1
  37. package/dist/visualize/graph-canvas/interaction.js +11 -0
  38. package/dist/visualize/graph-canvas/interaction.js.map +1 -1
  39. package/dist/visualize/graph-canvas/layout-worker.d.ts.map +1 -1
  40. package/dist/visualize/graph-canvas/layout-worker.js +45 -9
  41. package/dist/visualize/graph-canvas/layout-worker.js.map +1 -1
  42. package/dist/visualize/graph-canvas/renderer.d.ts.map +1 -1
  43. package/dist/visualize/graph-canvas/renderer.js +164 -33
  44. package/dist/visualize/graph-canvas/renderer.js.map +1 -1
  45. package/dist/visualize/graph-canvas/styles.d.ts.map +1 -1
  46. package/dist/visualize/graph-canvas/styles.js +146 -121
  47. package/dist/visualize/graph-canvas/styles.js.map +1 -1
  48. package/dist/visualize/graph-canvas/viewport.d.ts.map +1 -1
  49. package/dist/visualize/graph-canvas/viewport.js +10 -0
  50. package/dist/visualize/graph-canvas/viewport.js.map +1 -1
  51. package/dist/visualize/server.js +371 -32
  52. package/dist/visualize/server.js.map +1 -1
  53. package/dist/visualize/template-core.d.ts +9 -0
  54. package/dist/visualize/template-core.d.ts.map +1 -0
  55. package/dist/visualize/template-core.js +721 -0
  56. package/dist/visualize/template-core.js.map +1 -0
  57. package/dist/visualize/template-data-loading.d.ts +7 -0
  58. package/dist/visualize/template-data-loading.d.ts.map +1 -0
  59. package/dist/visualize/template-data-loading.js +677 -0
  60. package/dist/visualize/template-data-loading.js.map +1 -0
  61. package/dist/visualize/template-detail-panel.d.ts +14 -0
  62. package/dist/visualize/template-detail-panel.d.ts.map +1 -0
  63. package/dist/visualize/template-detail-panel.js +624 -0
  64. package/dist/visualize/template-detail-panel.js.map +1 -0
  65. package/dist/visualize/template-graph-3d.d.ts +7 -0
  66. package/dist/visualize/template-graph-3d.d.ts.map +1 -0
  67. package/dist/visualize/template-graph-3d.js +1114 -0
  68. package/dist/visualize/template-graph-3d.js.map +1 -0
  69. package/dist/visualize/template-graph-vis.d.ts +8 -0
  70. package/dist/visualize/template-graph-vis.d.ts.map +1 -0
  71. package/dist/visualize/template-graph-vis.js +1215 -0
  72. package/dist/visualize/template-graph-vis.js.map +1 -0
  73. package/dist/visualize/template-html.d.ts +9 -0
  74. package/dist/visualize/template-html.d.ts.map +1 -0
  75. package/dist/visualize/template-html.js +635 -0
  76. package/dist/visualize/template-html.js.map +1 -0
  77. package/dist/visualize/template-md-viewer.d.ts +11 -0
  78. package/dist/visualize/template-md-viewer.d.ts.map +1 -0
  79. package/dist/visualize/template-md-viewer.js +806 -0
  80. package/dist/visualize/template-md-viewer.js.map +1 -0
  81. package/dist/visualize/template-pages.d.ts +7 -0
  82. package/dist/visualize/template-pages.d.ts.map +1 -0
  83. package/dist/visualize/template-pages.js +1892 -0
  84. package/dist/visualize/template-pages.js.map +1 -0
  85. package/dist/visualize/template-stats-modal.d.ts +7 -0
  86. package/dist/visualize/template-stats-modal.d.ts.map +1 -0
  87. package/dist/visualize/template-stats-modal.js +466 -0
  88. package/dist/visualize/template-stats-modal.js.map +1 -0
  89. package/dist/visualize/template-styles.d.ts +9 -0
  90. package/dist/visualize/template-styles.d.ts.map +1 -0
  91. package/dist/visualize/template-styles.js +623 -0
  92. package/dist/visualize/template-styles.js.map +1 -0
  93. package/dist/visualize/template.d.ts +15 -3
  94. package/dist/visualize/template.d.ts.map +1 -1
  95. package/dist/visualize/template.js +44 -3475
  96. package/dist/visualize/template.js.map +1 -1
  97. package/package.json +2 -2
@@ -0,0 +1,806 @@
1
+ "use strict";
2
+ /**
3
+ * DevPlan 图可视化 — Markdown 预览器模块
4
+ *
5
+ * 内置 .md 文件预览基础能力。
6
+ * 支持: 文件拖放/选择、直接粘贴输入、Markdown 渲染(marked.js CDN)、
7
+ * 代码语法高亮(highlight.js CDN)、目录导航、文档统计、搜索、打印。
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.getMdViewerStyles = getMdViewerStyles;
11
+ exports.getMdViewerPageHTML = getMdViewerPageHTML;
12
+ exports.getMdViewerScript = getMdViewerScript;
13
+ function getMdViewerStyles() {
14
+ return `
15
+ /* ========== MD Viewer Page ========== */
16
+ .mdv-page { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; }
17
+ .mdv-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 20px; background: #0f172a; border-bottom: 1px solid #1e293b; flex-shrink: 0; z-index: 10; }
18
+ .mdv-toolbar-left { display: flex; align-items: center; gap: 12px; min-width: 0; flex: 1; }
19
+ .mdv-toolbar-right { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
20
+ .mdv-logo { font-size: 15px; font-weight: 700; color: #e2e8f0; white-space: nowrap; }
21
+ .mdv-file-name { font-size: 13px; color: #8b949e; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
22
+ .mdv-file-name strong { color: #e6edf3; font-weight: 600; }
23
+
24
+ .mdv-btn { display: inline-flex; align-items: center; gap: 5px; padding: 6px 12px; font-size: 12px; font-weight: 500; color: #e2e8f0; background: #1e293b; border: 1px solid #334155; border-radius: 6px; cursor: pointer; transition: all 0.2s; white-space: nowrap; }
25
+ .mdv-btn:hover { background: #334155; border-color: #475569; }
26
+ .mdv-btn-primary { background: #4f46e5; border-color: #4f46e5; }
27
+ .mdv-btn-primary:hover { background: #6366f1; }
28
+ .mdv-btn-success { background: #16a34a; border-color: #16a34a; }
29
+ .mdv-btn-success:hover { background: #22c55e; }
30
+
31
+ .mdv-search-box { position: relative; width: 200px; }
32
+ .mdv-search-box input { width: 100%; padding: 6px 10px 6px 30px; font-size: 12px; color: #e2e8f0; background: #111827; border: 1px solid #334155; border-radius: 6px; outline: none; transition: border-color 0.2s; }
33
+ .mdv-search-box input:focus { border-color: #6366f1; box-shadow: 0 0 0 2px rgba(99,102,241,0.15); }
34
+ .mdv-search-box input::placeholder { color: #6b7280; }
35
+ .mdv-search-box svg { position: absolute; left: 9px; top: 50%; transform: translateY(-50%); width: 13px; height: 13px; color: #6b7280; pointer-events: none; }
36
+
37
+ .mdv-content-wrap { display: flex; flex: 1; min-height: 0; overflow: hidden; }
38
+ .mdv-toc-panel { width: 260px; flex-shrink: 0; background: #0f172a; border-left: 1px solid #1e293b; overflow-y: auto; padding: 16px 0; }
39
+ .mdv-toc-title { padding: 0 16px 12px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: #6b7280; }
40
+ .mdv-toc-list { list-style: none; padding: 0; margin: 0; }
41
+ .mdv-toc-list li a { display: block; padding: 5px 16px; font-size: 12px; color: #9ca3af; text-decoration: none; border-left: 2px solid transparent; transition: all 0.15s; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
42
+ .mdv-toc-list li a:hover { color: #e2e8f0; background: rgba(99,102,241,0.06); }
43
+ .mdv-toc-list li a.active { color: #818cf8; border-left-color: #818cf8; background: rgba(99,102,241,0.1); }
44
+ .mdv-toc-list li a.indent-1 { padding-left: 30px; }
45
+ .mdv-toc-list li a.indent-2 { padding-left: 44px; }
46
+ .mdv-toc-list li a.indent-3 { padding-left: 58px; }
47
+ .mdv-toc-list li a.indent-4 { padding-left: 72px; }
48
+
49
+ .mdv-scroll-area { flex: 1; overflow-y: auto; overflow-x: hidden; min-width: 0; scroll-behavior: smooth; }
50
+ .mdv-scroll-area::-webkit-scrollbar { width: 8px; }
51
+ .mdv-scroll-area::-webkit-scrollbar-track { background: #111827; }
52
+ .mdv-scroll-area::-webkit-scrollbar-thumb { background: #334155; border-radius: 4px; }
53
+ .mdv-scroll-area::-webkit-scrollbar-thumb:hover { background: #475569; }
54
+ .mdv-inner { max-width: 920px; margin: 0 auto; padding: 32px 48px 80px; }
55
+
56
+ /* Home Page */
57
+ .mdv-home { display: flex; flex-direction: column; gap: 0; }
58
+ .mdv-drop-area { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; padding: 40px 32px; border: 2px dashed #334155; border-radius: 12px; text-align: center; cursor: pointer; transition: all 0.25s; }
59
+ .mdv-drop-area:hover, .mdv-drop-area.drag-over { border-color: #6366f1; background: rgba(99,102,241,0.04); }
60
+ .mdv-drop-area .drop-icon { width: 48px; height: 48px; color: #6b7280; transition: transform 0.25s; }
61
+ .mdv-drop-area.drag-over .drop-icon { transform: scale(1.1); color: #818cf8; }
62
+ .mdv-drop-area h3 { font-size: 16px; font-weight: 600; color: #e2e8f0; }
63
+ .mdv-drop-area p { font-size: 13px; color: #9ca3af; }
64
+ .mdv-drop-area kbd { padding: 1px 5px; font-size: 11px; background: #1e293b; border: 1px solid #334155; border-radius: 3px; color: #9ca3af; font-family: inherit; }
65
+
66
+ .mdv-divider { display: flex; align-items: center; gap: 16px; padding: 20px 0; color: #6b7280; font-size: 13px; font-weight: 500; }
67
+ .mdv-divider::before, .mdv-divider::after { content: ''; flex: 1; height: 1px; background: #1e293b; }
68
+
69
+ .mdv-paste-section { display: flex; flex-direction: column; gap: 10px; }
70
+ .mdv-paste-header { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px; }
71
+ .mdv-paste-label { font-size: 14px; font-weight: 600; color: #e2e8f0; display: flex; align-items: center; gap: 8px; }
72
+ .mdv-paste-shortcuts { font-size: 12px; color: #6b7280; }
73
+ .mdv-paste-shortcuts kbd { display: inline-block; padding: 1px 5px; font-size: 11px; background: #1e293b; border: 1px solid #334155; border-radius: 3px; color: #9ca3af; font-family: inherit; }
74
+ .mdv-paste-textarea { min-height: 220px; padding: 14px 16px; font-size: 13px; font-family: 'Cascadia Code','Fira Code','JetBrains Mono',Consolas,monospace; line-height: 1.55; color: #e6edf3; background: #0d1117; border: 1px solid #334155; border-radius: 8px; resize: vertical; outline: none; tab-size: 4; transition: border-color 0.2s, box-shadow 0.2s; width: 100%; }
75
+ .mdv-paste-textarea:focus { border-color: #6366f1; box-shadow: 0 0 0 2px rgba(99,102,241,0.15); }
76
+ .mdv-paste-textarea::placeholder { color: #4b5563; }
77
+ .mdv-paste-footer { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
78
+ .mdv-paste-footer .char-count { font-size: 12px; color: #6b7280; }
79
+ .mdv-paste-footer .actions { display: flex; gap: 8px; }
80
+
81
+ /* Result */
82
+ .mdv-stats { display: flex; gap: 16px; margin-bottom: 24px; padding: 12px 16px; background: #1e293b; border-radius: 8px; border: 1px solid #334155; font-size: 12px; color: #9ca3af; flex-wrap: wrap; }
83
+ .mdv-stats span { display: flex; align-items: center; gap: 5px; }
84
+ .mdv-stats strong { color: #e2e8f0; font-weight: 600; }
85
+
86
+ /* Markdown Body */
87
+ .mdv-body { line-height: 1.7; font-size: 15px; color: #e6edf3; }
88
+ .mdv-body h1,.mdv-body h2,.mdv-body h3,.mdv-body h4,.mdv-body h5,.mdv-body h6 { margin-top: 1.5em; margin-bottom: 0.5em; font-weight: 600; line-height: 1.3; color: #e6edf3; scroll-margin-top: 20px; }
89
+ .mdv-body h1 { font-size: 2em; padding-bottom: 0.3em; border-bottom: 1px solid #1e293b; }
90
+ .mdv-body h2 { font-size: 1.5em; padding-bottom: 0.25em; border-bottom: 1px solid rgba(30,41,59,0.6); }
91
+ .mdv-body h3 { font-size: 1.25em; }
92
+ .mdv-body h4 { font-size: 1.05em; }
93
+ .mdv-body h5 { font-size: 0.95em; color: #9ca3af; }
94
+ .mdv-body h6 { font-size: 0.9em; color: #6b7280; }
95
+ .mdv-body p { margin: 0 0 16px; }
96
+ .mdv-body a { color: #818cf8; text-decoration: none; border-bottom: 1px solid transparent; transition: border-color 0.2s; }
97
+ .mdv-body a:hover { border-bottom-color: #818cf8; }
98
+ .mdv-body strong { font-weight: 600; color: #e6edf3; }
99
+ .mdv-body em { font-style: italic; }
100
+ .mdv-body img { max-width: 100%; height: auto; border-radius: 8px; margin: 8px 0; border: 1px solid #334155; }
101
+ .mdv-body ul,.mdv-body ol { margin: 0 0 16px; padding-left: 2em; }
102
+ .mdv-body li { margin: 4px 0; }
103
+ .mdv-body li>ul,.mdv-body li>ol { margin-bottom: 0; }
104
+ .mdv-body input[type="checkbox"] { margin-right: 8px; accent-color: #6366f1; }
105
+ .mdv-body blockquote { margin: 0 0 16px; padding: 8px 16px; border-left: 4px solid #6366f1; background: rgba(99,102,241,0.06); border-radius: 0 8px 8px 0; color: #9ca3af; }
106
+ .mdv-body blockquote>:last-child { margin-bottom: 0; }
107
+ .mdv-body code:not(pre code) { padding: 2px 6px; font-size: 0.88em; font-family: 'Cascadia Code','Fira Code','JetBrains Mono',Consolas,monospace; background: rgba(99,102,241,0.12); border-radius: 4px; color: #fbbf24; }
108
+ .mdv-body pre { position: relative; margin: 0 0 16px; border-radius: 8px; overflow: hidden; border: 1px solid #334155; }
109
+ .mdv-body pre code { display: block; padding: 16px; font-size: 13px; font-family: 'Cascadia Code','Fira Code','JetBrains Mono',Consolas,monospace; line-height: 1.55; overflow-x: auto; background: #0d1117; color: #e6edf3; }
110
+ .mdv-code-header { display: flex; align-items: center; justify-content: space-between; padding: 6px 12px; background: #161b22; border-bottom: 1px solid #334155; font-size: 11px; color: #6b7280; }
111
+ .mdv-copy-btn { padding: 3px 8px; font-size: 11px; color: #6b7280; background: #1e293b; border: 1px solid #334155; border-radius: 4px; cursor: pointer; transition: all 0.2s; }
112
+ .mdv-copy-btn:hover { color: #e2e8f0; border-color: #475569; }
113
+ .mdv-copy-btn.copied { color: #22c55e; border-color: #22c55e; }
114
+ .mdv-body table { width: 100%; margin: 0 0 16px; border-collapse: collapse; font-size: 14px; }
115
+ .mdv-body thead { background: #1e293b; }
116
+ .mdv-body th,.mdv-body td { padding: 8px 14px; border: 1px solid #334155; text-align: left; }
117
+ .mdv-body th { font-weight: 600; color: #e6edf3; }
118
+ .mdv-body tbody tr:nth-child(even) { background: rgba(99,102,241,0.03); }
119
+ .mdv-body tbody tr:hover { background: rgba(99,102,241,0.06); }
120
+ .mdv-body hr { margin: 24px 0; border: none; border-top: 1px solid #1e293b; }
121
+ .mdv-table-wrap { overflow-x: auto; margin-bottom: 16px; }
122
+ .mdv-body mark { background: rgba(251,191,36,0.25); color: inherit; border-radius: 2px; padding: 1px 2px; }
123
+
124
+ /* Progress */
125
+ .mdv-progress { position: absolute; bottom: 0; left: 0; right: 0; height: 2px; z-index: 5; pointer-events: none; }
126
+ .mdv-progress-bar { height: 100%; width: 0%; background: linear-gradient(90deg, #6366f1, #a78bfa); transition: width 0.15s ease-out; }
127
+
128
+ /* Back to top */
129
+ .mdv-back-to-top { position: absolute; bottom: 24px; right: 24px; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; background: #1e293b; border: 1px solid #334155; border-radius: 50%; color: #9ca3af; cursor: pointer; opacity: 0; transform: translateY(12px); transition: all 0.25s; z-index: 5; font-size: 16px; }
130
+ .mdv-back-to-top.visible { opacity: 1; transform: translateY(0); }
131
+ .mdv-back-to-top:hover { color: #818cf8; border-color: #818cf8; }
132
+
133
+ /* CDN loading indicator */
134
+ .mdv-cdn-status { font-size: 11px; color: #6b7280; padding: 0 4px; }
135
+ .mdv-cdn-status.loaded { color: #22c55e; }
136
+ .mdv-cdn-status.failed { color: #f59e0b; }
137
+
138
+ /* Highlight.js minimal dark theme */
139
+ .mdv-body .hljs { background: #0d1117; color: #e6edf3; }
140
+ .mdv-body .hljs-keyword,.mdv-body .hljs-selector-tag,.mdv-body .hljs-type { color: #ff7b72; }
141
+ .mdv-body .hljs-string,.mdv-body .hljs-attr { color: #a5d6ff; }
142
+ .mdv-body .hljs-number,.mdv-body .hljs-literal { color: #79c0ff; }
143
+ .mdv-body .hljs-comment,.mdv-body .hljs-meta { color: #8b949e; font-style: italic; }
144
+ .mdv-body .hljs-function,.mdv-body .hljs-title { color: #d2a8ff; }
145
+ .mdv-body .hljs-built_in,.mdv-body .hljs-variable,.mdv-body .hljs-template-variable { color: #ffa657; }
146
+ .mdv-body .hljs-params { color: #e6edf3; }
147
+ .mdv-body .hljs-name,.mdv-body .hljs-selector-class { color: #7ee787; }
148
+ .mdv-body .hljs-attribute { color: #79c0ff; }
149
+ .mdv-body .hljs-symbol { color: #79c0ff; }
150
+ .mdv-body .hljs-addition { color: #aff5b4; background: rgba(63,185,80,0.15); }
151
+ .mdv-body .hljs-deletion { color: #ffdcd7; background: rgba(248,81,73,0.15); }
152
+ .mdv-body .hljs-section { color: #79c0ff; font-weight: bold; }
153
+ .mdv-body .hljs-tag { color: #7ee787; }
154
+ .mdv-body .hljs-regexp { color: #a5d6ff; }
155
+ .mdv-body .hljs-bullet { color: #ffa657; }
156
+ .mdv-body .hljs-link { color: #a5d6ff; text-decoration: underline; }
157
+
158
+ /* mdv-body 在紧凑上下文中的适配(详情面板、文档库) */
159
+ .panel-body .mdv-body { font-size: 13px; line-height: 1.65; }
160
+ .panel-body .mdv-body h1 { font-size: 1.5em; }
161
+ .panel-body .mdv-body h2 { font-size: 1.3em; }
162
+ .panel-body .mdv-body h3 { font-size: 1.15em; }
163
+ .panel-body .mdv-body pre code { font-size: 12px; }
164
+ /* 文档库阅读布局 */
165
+ .docs-reader-wrap { display: flex; flex: 1; min-height: 0; overflow: hidden; }
166
+ .docs-reader-wrap .docs-content-body { flex: 1; overflow-y: auto; padding: 24px 32px 60px; scrollbar-width: thin; scrollbar-color: #374151 transparent; }
167
+ .docs-reader-wrap .docs-content-body::-webkit-scrollbar { width: 6px; }
168
+ .docs-reader-wrap .docs-content-body::-webkit-scrollbar-track { background: transparent; }
169
+ .docs-reader-wrap .docs-content-body::-webkit-scrollbar-thumb { background: #374151; border-radius: 3px; }
170
+ .docs-reader-inner { max-width: 900px; margin: 0 auto; }
171
+ .docs-toc-panel { position: sticky; top: 0; max-height: 100%; }
172
+ .docs-content-body .mdv-body { font-size: 14.5px; line-height: 1.75; }
173
+ .docs-content-body .mdv-body h1 { font-size: 1.75em; }
174
+ .docs-content-body .mdv-body h2 { font-size: 1.35em; }
175
+ .docs-content-body .mdv-body h3 { font-size: 1.15em; }
176
+ .docs-content-body .mdv-body pre code { font-size: 12.5px; }
177
+ @media (max-width: 1100px) {
178
+ .docs-toc-panel { display: none !important; }
179
+ }
180
+
181
+ /* Print */
182
+ @media print {
183
+ .mdv-toolbar,.mdv-toc-panel,.mdv-back-to-top,.mdv-home,.mdv-progress,.mdv-stats { display: none !important; }
184
+ .mdv-page { overflow: visible !important; }
185
+ .mdv-content-wrap { overflow: visible !important; }
186
+ .mdv-scroll-area { overflow: visible !important; }
187
+ .mdv-inner { max-width: 100%; padding: 20px; }
188
+ .mdv-body { color: #1a1a1a; }
189
+ .mdv-body h1,.mdv-body h2 { border-color: #ddd; }
190
+ .mdv-body pre code { background: #f6f8fa; color: #24292f; }
191
+ .mdv-body a { color: #0366d6; }
192
+ .mdv-body code:not(pre code) { background: #eff1f3; color: #d63384; }
193
+ .mdv-body th,.mdv-body td { border-color: #d0d7de; }
194
+ .mdv-body thead { background: #f6f8fa; }
195
+ }
196
+
197
+ @media (max-width: 768px) {
198
+ .mdv-inner { padding: 20px 16px 60px; }
199
+ .mdv-toolbar { padding: 8px 12px; gap: 6px; }
200
+ .mdv-search-box { width: 140px; }
201
+ .mdv-toc-panel { width: 220px; }
202
+ .mdv-paste-textarea { min-height: 160px; }
203
+ }
204
+ `;
205
+ }
206
+ function getMdViewerPageHTML() {
207
+ return `
208
+ <!-- ===== PAGE: MD Viewer ===== -->
209
+ <div class="page-view" id="pageMdViewer">
210
+ <div class="mdv-page">
211
+ <!-- Toolbar -->
212
+ <div class="mdv-toolbar">
213
+ <div class="mdv-toolbar-left">
214
+ <span class="mdv-logo">📝 Markdown 预览</span>
215
+ <span class="mdv-file-name" id="mdvFileName"></span>
216
+ <span class="mdv-cdn-status" id="mdvCdnStatus"></span>
217
+ </div>
218
+ <div class="mdv-toolbar-right">
219
+ <div class="mdv-search-box" id="mdvSearchBox" style="display:none;">
220
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
221
+ <input type="text" id="mdvSearchInput" placeholder="搜索内容..." />
222
+ </div>
223
+ <button class="mdv-btn" id="mdvTocToggle" style="display:none;" onclick="mdvToggleToc()">📑 目录</button>
224
+ <button class="mdv-btn" id="mdvPrintBtn" style="display:none;" onclick="window.print()">🖨️ 打印</button>
225
+ <button class="mdv-btn" id="mdvBackBtn" style="display:none;" onclick="mdvGoHome()">← 返回</button>
226
+ <button class="mdv-btn mdv-btn-primary" onclick="document.getElementById('mdvFileInput').click()">📂 打开</button>
227
+ </div>
228
+ </div>
229
+
230
+ <!-- Content -->
231
+ <div class="mdv-content-wrap">
232
+ <div class="mdv-scroll-area" id="mdvScrollArea">
233
+ <div class="mdv-inner">
234
+ <!-- Home -->
235
+ <div class="mdv-home" id="mdvHome">
236
+ <div class="mdv-drop-area" id="mdvDropArea" onclick="document.getElementById('mdvFileInput').click()">
237
+ <svg class="drop-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14,2 14,8 20,8"/><line x1="12" y1="18" x2="12" y2="12"/><polyline points="9,15 12,12 15,15"/></svg>
238
+ <h3>拖放 .md 文件到此处,或点击选择文件</h3>
239
+ <p>支持 .md / .markdown / .txt · 快捷键 <kbd>Ctrl+O</kbd></p>
240
+ </div>
241
+
242
+ <div class="mdv-divider">或直接在下方输入 / 粘贴 Markdown 内容</div>
243
+
244
+ <div class="mdv-paste-section">
245
+ <div class="mdv-paste-header">
246
+ <div class="mdv-paste-label">
247
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4h2a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2h2"/><rect x="8" y="2" width="8" height="4" rx="1" ry="1"/></svg>
248
+ Markdown 输入框
249
+ </div>
250
+ <div class="mdv-paste-shortcuts"><kbd>Ctrl+Enter</kbd> 渲染 · <kbd>Tab</kbd> 缩进</div>
251
+ </div>
252
+ <textarea class="mdv-paste-textarea" id="mdvTextarea" placeholder="# 在此输入或粘贴 Markdown 内容&#10;&#10;## 二级标题&#10;&#10;正文内容,支持 **粗体**、*斜体*、\`行内代码\`&#10;&#10;- 列表项 1&#10;- 列表项 2&#10;&#10;> 引用文本&#10;&#10;| 列1 | 列2 |&#10;|-----|-----|&#10;| A | B |" spellcheck="false"></textarea>
253
+ <div class="mdv-paste-footer">
254
+ <span class="char-count" id="mdvCharCount">0 字符 · 0 行</span>
255
+ <div class="actions">
256
+ <button class="mdv-btn" onclick="mdvClear()">🗑️ 清空</button>
257
+ <button class="mdv-btn mdv-btn-success" onclick="mdvRenderFromPaste()">▶ 渲染预览</button>
258
+ </div>
259
+ </div>
260
+ </div>
261
+ </div>
262
+
263
+ <!-- Result -->
264
+ <div class="mdv-result" id="mdvResult" style="display:none;">
265
+ <div class="mdv-stats" id="mdvStats"></div>
266
+ <article class="mdv-body" id="mdvBody"></article>
267
+ </div>
268
+ </div>
269
+ </div>
270
+ <nav class="mdv-toc-panel" id="mdvTocPanel" style="display:none;">
271
+ <div class="mdv-toc-title">📑 目录导航</div>
272
+ <ul class="mdv-toc-list" id="mdvTocList"></ul>
273
+ </nav>
274
+ </div>
275
+
276
+ <!-- Progress bar -->
277
+ <div class="mdv-progress"><div class="mdv-progress-bar" id="mdvProgressBar"></div></div>
278
+
279
+ <!-- Back to top -->
280
+ <button class="mdv-back-to-top" id="mdvBackToTop" onclick="mdvScrollToTop()">↑</button>
281
+
282
+ <!-- Hidden file input -->
283
+ <input type="file" id="mdvFileInput" accept=".md,.markdown,.txt,.mdown,.mkd" style="display:none" />
284
+ </div>
285
+ </div>
286
+ `;
287
+ }
288
+ function getMdViewerScript() {
289
+ return `
290
+ // ========== MD Viewer ==========
291
+ var mdvCdnLoaded = false;
292
+ var mdvCdnLoading = false;
293
+ var mdvTocVisible = false;
294
+ var mdvSearchTimeout = null;
295
+ var mdvInited = false;
296
+
297
+ /** 页面入口 — 由 navTo('md-viewer') 调用 */
298
+ function loadMdViewerPage() {
299
+ if (!mdvInited) {
300
+ mdvInitEvents();
301
+ mdvInited = true;
302
+ }
303
+ mdvLoadCDN();
304
+ }
305
+
306
+ // ===== CDN Loading =====
307
+ var MDV_MARKED_URLS = [
308
+ 'https://cdn.jsdelivr.net/npm/marked@12.0.1/marked.min.js',
309
+ 'https://unpkg.com/marked@12.0.1/marked.min.js'
310
+ ];
311
+ var MDV_HLJS_URLS = [
312
+ 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js',
313
+ 'https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/highlight.min.js'
314
+ ];
315
+
316
+ function mdvLoadCDN() {
317
+ if (mdvCdnLoaded || mdvCdnLoading) return;
318
+ mdvCdnLoading = true;
319
+ var statusEl = document.getElementById('mdvCdnStatus');
320
+ if (statusEl) statusEl.textContent = '⏳ 加载渲染引擎...';
321
+
322
+ mdvLoadScript(MDV_MARKED_URLS, 0, function(markedOk) {
323
+ if (markedOk && typeof marked !== 'undefined') {
324
+ marked.setOptions({ gfm: true, breaks: false });
325
+ }
326
+ mdvLoadScript(MDV_HLJS_URLS, 0, function(hljsOk) {
327
+ mdvCdnLoaded = true;
328
+ mdvCdnLoading = false;
329
+ if (statusEl) {
330
+ if (markedOk) {
331
+ statusEl.textContent = '✅ marked' + (hljsOk ? ' + hljs' : '');
332
+ statusEl.className = 'mdv-cdn-status loaded';
333
+ } else {
334
+ statusEl.textContent = '⚠️ CDN 加载失败,使用简易渲染';
335
+ statusEl.className = 'mdv-cdn-status failed';
336
+ }
337
+ }
338
+ });
339
+ });
340
+ }
341
+
342
+ function mdvLoadScript(urls, index, callback) {
343
+ if (index >= urls.length) { callback(false); return; }
344
+ var s = document.createElement('script');
345
+ s.src = urls[index];
346
+ s.onload = function() { callback(true); };
347
+ s.onerror = function() { mdvLoadScript(urls, index + 1, callback); };
348
+ document.head.appendChild(s);
349
+ }
350
+
351
+ // ===== Markdown Parsing =====
352
+ function mdvParseMd(text) {
353
+ if (typeof marked !== 'undefined') {
354
+ try { return marked.parse(text); } catch(e) { console.error('marked error:', e); }
355
+ }
356
+ // fallback: use existing simple renderMarkdown if available
357
+ if (typeof renderMarkdown === 'function') return renderMarkdown(text);
358
+ return '<pre style="white-space:pre-wrap;word-break:break-word;">' + mdvEscHtml(text) + '</pre>';
359
+ }
360
+
361
+ function mdvEscHtml(s) {
362
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
363
+ }
364
+
365
+ // ===== 共享: Markdown 内容后处理(代码高亮、复制按钮、锚点、表格包裹) =====
366
+ function mdEnhanceContent(container) {
367
+ if (!container) return;
368
+
369
+ // 1. Code highlighting + language label + copy button
370
+ var blocks = container.querySelectorAll('pre code');
371
+ for (var i = 0; i < blocks.length; i++) {
372
+ var block = blocks[i];
373
+ if (typeof hljs !== 'undefined') {
374
+ try { hljs.highlightElement(block); } catch(e) {}
375
+ }
376
+ var pre = block.parentElement;
377
+ if (pre.querySelector('.mdv-code-header')) continue;
378
+ var cls = null;
379
+ for (var j = 0; j < block.classList.length; j++) {
380
+ if (block.classList[j].indexOf('language-') === 0) { cls = block.classList[j]; break; }
381
+ }
382
+ var lang = cls ? cls.replace('language-', '') : 'text';
383
+ var header = document.createElement('div');
384
+ header.className = 'mdv-code-header';
385
+ var langSpan = document.createElement('span');
386
+ langSpan.textContent = lang;
387
+ var copyBtn = document.createElement('button');
388
+ copyBtn.className = 'mdv-copy-btn';
389
+ copyBtn.textContent = '复制';
390
+ copyBtn.onclick = (function(b, c) {
391
+ return function() {
392
+ navigator.clipboard.writeText(b.textContent).then(function() {
393
+ c.textContent = '已复制 ✓'; c.classList.add('copied');
394
+ setTimeout(function() { c.textContent = '复制'; c.classList.remove('copied'); }, 2000);
395
+ });
396
+ };
397
+ })(block, copyBtn);
398
+ header.appendChild(langSpan);
399
+ header.appendChild(copyBtn);
400
+ pre.insertBefore(header, pre.firstChild);
401
+ }
402
+
403
+ // 2. Heading anchors
404
+ var counter = 0;
405
+ var headings = container.querySelectorAll('h1,h2,h3,h4,h5,h6');
406
+ for (var i = 0; i < headings.length; i++) {
407
+ var h = headings[i];
408
+ if (!h.id) {
409
+ var slug = h.textContent.trim().toLowerCase()
410
+ .replace(/[^\\w\\u4e00-\\u9fff]+/g, '-')
411
+ .replace(/(^-|-$)/g, '') || ('heading-' + (++counter));
412
+ h.id = slug;
413
+ }
414
+ }
415
+
416
+ // 3. Table wrapping
417
+ var tables = container.querySelectorAll('table');
418
+ for (var i = 0; i < tables.length; i++) {
419
+ var table = tables[i];
420
+ if (table.parentElement.classList.contains('mdv-table-wrap')) continue;
421
+ var wrapper = document.createElement('div');
422
+ wrapper.className = 'mdv-table-wrap';
423
+ table.parentNode.insertBefore(wrapper, table);
424
+ wrapper.appendChild(table);
425
+ }
426
+ }
427
+
428
+ /** MD Viewer 页面专用后处理(调用共享函数) */
429
+ function mdvPostProcess() {
430
+ mdEnhanceContent(document.getElementById('mdvBody'));
431
+ }
432
+
433
+ // ===== Render =====
434
+ function mdvRender(md, name) {
435
+ var home = document.getElementById('mdvHome');
436
+ var result = document.getElementById('mdvResult');
437
+ var body = document.getElementById('mdvBody');
438
+ var fileNameEl = document.getElementById('mdvFileName');
439
+ if (!home || !result || !body) return;
440
+
441
+ home.style.display = 'none';
442
+ result.style.display = 'block';
443
+ body.innerHTML = mdvParseMd(md);
444
+ mdvPostProcess();
445
+
446
+ if (fileNameEl) fileNameEl.innerHTML = '<strong>' + mdvEscHtml(name) + '</strong>';
447
+
448
+ mdvShowStats(md);
449
+ mdvGenerateTOC();
450
+
451
+ // Show toolbar buttons
452
+ var els = ['mdvTocToggle', 'mdvPrintBtn', 'mdvSearchBox', 'mdvBackBtn'];
453
+ for (var i = 0; i < els.length; i++) {
454
+ var el = document.getElementById(els[i]);
455
+ if (el) el.style.display = '';
456
+ }
457
+
458
+ // Show TOC if headings exist
459
+ var tocList = document.getElementById('mdvTocList');
460
+ if (tocList && tocList.children.length > 0) {
461
+ mdvShowToc(true);
462
+ }
463
+
464
+ // Scroll to top
465
+ var scrollArea = document.getElementById('mdvScrollArea');
466
+ if (scrollArea) scrollArea.scrollTop = 0;
467
+ }
468
+
469
+ // ===== Render from paste =====
470
+ function mdvRenderFromPaste() {
471
+ var ta = document.getElementById('mdvTextarea');
472
+ if (!ta) return;
473
+ var text = ta.value.trim();
474
+ if (!text) { ta.focus(); return; }
475
+ var heading = text.match(/^#\\s+(.+)/m);
476
+ mdvRender(text, heading ? heading[1].trim() : '粘贴的文档');
477
+ }
478
+
479
+ // ===== Read file =====
480
+ function mdvReadFile(file) {
481
+ if (!file) return;
482
+ var reader = new FileReader();
483
+ reader.onload = function(e) { mdvRender(e.target.result, file.name); };
484
+ reader.readAsText(file, 'UTF-8');
485
+ }
486
+
487
+ // ===== Go Home =====
488
+ function mdvGoHome() {
489
+ var home = document.getElementById('mdvHome');
490
+ var result = document.getElementById('mdvResult');
491
+ var body = document.getElementById('mdvBody');
492
+ var fileNameEl = document.getElementById('mdvFileName');
493
+ var stats = document.getElementById('mdvStats');
494
+ var progressBar = document.getElementById('mdvProgressBar');
495
+
496
+ if (result) result.style.display = 'none';
497
+ if (home) home.style.display = '';
498
+ if (body) body.innerHTML = '';
499
+ if (stats) stats.innerHTML = '';
500
+ if (fileNameEl) fileNameEl.textContent = '';
501
+ if (progressBar) progressBar.style.width = '0%';
502
+
503
+ mdvShowToc(false);
504
+
505
+ var els = ['mdvTocToggle', 'mdvPrintBtn', 'mdvSearchBox', 'mdvBackBtn'];
506
+ for (var i = 0; i < els.length; i++) {
507
+ var el = document.getElementById(els[i]);
508
+ if (el) el.style.display = 'none';
509
+ }
510
+
511
+ var scrollArea = document.getElementById('mdvScrollArea');
512
+ if (scrollArea) scrollArea.scrollTop = 0;
513
+ }
514
+
515
+ // ===== Statistics =====
516
+ function mdvShowStats(md) {
517
+ var el = document.getElementById('mdvStats');
518
+ if (!el) return;
519
+ var lines = md.split('\\n').length;
520
+ var chars = md.length;
521
+ var words = md.replace(/[^\\w\\u4e00-\\u9fff]+/g, ' ').trim().split(/\\s+/).length;
522
+ var codeMatch = md.match(/\\\`\\\`\\\`/g);
523
+ var codeBlocks = codeMatch ? Math.floor(codeMatch.length / 2) : 0;
524
+ var tableMatch = md.match(/^\\|[-:| ]+\\|$/gm);
525
+ var tables = tableMatch ? tableMatch.length : 0;
526
+ var readTime = Math.max(1, Math.ceil(words / 250));
527
+ el.innerHTML =
528
+ '<span>📄 <strong>' + lines.toLocaleString() + '</strong> 行</span>' +
529
+ '<span>📝 <strong>' + chars.toLocaleString() + '</strong> 字符</span>' +
530
+ '<span>💬 <strong>' + words.toLocaleString() + '</strong> 词</span>' +
531
+ '<span>🧩 <strong>' + codeBlocks + '</strong> 代码块</span>' +
532
+ '<span>📊 <strong>' + tables + '</strong> 表格</span>' +
533
+ '<span>⏱ 约 <strong>' + readTime + '</strong> 分钟</span>';
534
+ }
535
+
536
+ // ===== TOC =====
537
+ function mdvGenerateTOC() {
538
+ var tocList = document.getElementById('mdvTocList');
539
+ var body = document.getElementById('mdvBody');
540
+ if (!tocList || !body) return;
541
+ tocList.innerHTML = '';
542
+
543
+ var headings = body.querySelectorAll('h1,h2,h3,h4,h5,h6');
544
+ if (!headings.length) return;
545
+
546
+ var minLv = 6;
547
+ for (var i = 0; i < headings.length; i++) {
548
+ var lv = parseInt(headings[i].tagName[1]);
549
+ if (lv < minLv) minLv = lv;
550
+ }
551
+
552
+ for (var i = 0; i < headings.length; i++) {
553
+ var h = headings[i];
554
+ var indent = parseInt(h.tagName[1]) - minLv;
555
+ var li = document.createElement('li');
556
+ var a = document.createElement('a');
557
+ a.href = '#' + h.id;
558
+ a.textContent = h.textContent;
559
+ a.dataset.tid = h.id;
560
+ if (indent > 0) a.className = 'indent-' + Math.min(indent, 4);
561
+ a.onclick = (function(target) {
562
+ return function(e) {
563
+ e.preventDefault();
564
+ target.scrollIntoView({ behavior: 'smooth', block: 'start' });
565
+ };
566
+ })(h);
567
+ li.appendChild(a);
568
+ tocList.appendChild(li);
569
+ }
570
+
571
+ mdvSetupScrollSpy(headings);
572
+ }
573
+
574
+ function mdvSetupScrollSpy(headings) {
575
+ var scrollArea = document.getElementById('mdvScrollArea');
576
+ var tocList = document.getElementById('mdvTocList');
577
+ var progressBar = document.getElementById('mdvProgressBar');
578
+ var backToTop = document.getElementById('mdvBackToTop');
579
+ if (!scrollArea || !tocList) return;
580
+
581
+ var links = tocList.querySelectorAll('a');
582
+ var ticking = false;
583
+
584
+ function onScroll() {
585
+ if (ticking) return;
586
+ ticking = true;
587
+ requestAnimationFrame(function() {
588
+ var st = scrollArea.scrollTop;
589
+ var sh = scrollArea.scrollHeight - scrollArea.clientHeight;
590
+ if (progressBar) progressBar.style.width = (sh > 0 ? st / sh * 100 : 0) + '%';
591
+ if (backToTop) backToTop.classList.toggle('visible', st > 300);
592
+
593
+ var cur = '';
594
+ for (var i = 0; i < headings.length; i++) {
595
+ var rect = headings[i].getBoundingClientRect();
596
+ // Offset relative to scroll area top
597
+ if (rect.top <= 100) cur = headings[i].id;
598
+ }
599
+ for (var i = 0; i < links.length; i++) {
600
+ links[i].classList.toggle('active', links[i].dataset.tid === cur);
601
+ }
602
+ ticking = false;
603
+ });
604
+ }
605
+
606
+ scrollArea.addEventListener('scroll', onScroll, { passive: true });
607
+ onScroll();
608
+ }
609
+
610
+ function mdvShowToc(show) {
611
+ var panel = document.getElementById('mdvTocPanel');
612
+ if (!panel) return;
613
+ mdvTocVisible = show;
614
+ panel.style.display = show ? '' : 'none';
615
+ }
616
+
617
+ function mdvToggleToc() {
618
+ mdvShowToc(!mdvTocVisible);
619
+ }
620
+
621
+ function mdvScrollToTop() {
622
+ var scrollArea = document.getElementById('mdvScrollArea');
623
+ if (scrollArea) scrollArea.scrollTop = 0;
624
+ }
625
+
626
+ // ===== Search =====
627
+ function mdvSearch(q) {
628
+ var body = document.getElementById('mdvBody');
629
+ if (!body) return;
630
+
631
+ // Remove existing marks
632
+ var marks = body.querySelectorAll('mark[data-mdvs]');
633
+ for (var i = 0; i < marks.length; i++) {
634
+ var m = marks[i];
635
+ m.parentNode.replaceChild(document.createTextNode(m.textContent), m);
636
+ m.parentNode.normalize();
637
+ }
638
+
639
+ if (!q || q.length < 2) return;
640
+
641
+ var w = document.createTreeWalker(body, NodeFilter.SHOW_TEXT);
642
+ var matches = [];
643
+ var lq = q.toLowerCase();
644
+ while (w.nextNode()) {
645
+ var n = w.currentNode;
646
+ var tag = n.parentNode.tagName;
647
+ if (tag !== 'SCRIPT' && tag !== 'STYLE' && tag !== 'CODE' && tag !== 'PRE') {
648
+ if (n.textContent.toLowerCase().indexOf(lq) >= 0) matches.push(n);
649
+ }
650
+ }
651
+
652
+ var escaped = q.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&');
653
+ var re = new RegExp(escaped, 'gi');
654
+ for (var i = 0; i < matches.length; i++) {
655
+ var n = matches[i];
656
+ var txt = n.textContent;
657
+ var p = n.parentNode;
658
+ var f = document.createDocumentFragment();
659
+ var last = 0;
660
+ var m;
661
+ while ((m = re.exec(txt)) !== null) {
662
+ f.appendChild(document.createTextNode(txt.slice(last, m.index)));
663
+ var mk = document.createElement('mark');
664
+ mk.dataset.mdvs = '1';
665
+ mk.textContent = m[0];
666
+ f.appendChild(mk);
667
+ last = re.lastIndex;
668
+ }
669
+ f.appendChild(document.createTextNode(txt.slice(last)));
670
+ p.replaceChild(f, n);
671
+ }
672
+
673
+ var first = body.querySelector('mark[data-mdvs]');
674
+ if (first) first.scrollIntoView({ behavior: 'smooth', block: 'center' });
675
+ }
676
+
677
+ // ===== Clear =====
678
+ function mdvClear() {
679
+ var ta = document.getElementById('mdvTextarea');
680
+ if (ta) { ta.value = ''; ta.focus(); }
681
+ mdvUpdateCharCount();
682
+ }
683
+
684
+ // ===== Char Count =====
685
+ function mdvUpdateCharCount() {
686
+ var ta = document.getElementById('mdvTextarea');
687
+ var el = document.getElementById('mdvCharCount');
688
+ if (!ta || !el) return;
689
+ var v = ta.value;
690
+ el.textContent = v.length.toLocaleString() + ' 字符 · ' + (v ? v.split('\\n').length : 0).toLocaleString() + ' 行';
691
+ }
692
+
693
+ // ===== Init Events (called once) =====
694
+ function mdvInitEvents() {
695
+ var ta = document.getElementById('mdvTextarea');
696
+ var fileInput = document.getElementById('mdvFileInput');
697
+ var dropArea = document.getElementById('mdvDropArea');
698
+ var searchInput = document.getElementById('mdvSearchInput');
699
+ var scrollArea = document.getElementById('mdvScrollArea');
700
+
701
+ // Textarea events
702
+ if (ta) {
703
+ ta.addEventListener('input', mdvUpdateCharCount);
704
+ ta.addEventListener('keydown', function(e) {
705
+ if (e.key === 'Tab') {
706
+ e.preventDefault();
707
+ var s = ta.selectionStart;
708
+ ta.value = ta.value.substring(0, s) + ' ' + ta.value.substring(ta.selectionEnd);
709
+ ta.selectionStart = ta.selectionEnd = s + 2;
710
+ mdvUpdateCharCount();
711
+ }
712
+ if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
713
+ e.preventDefault();
714
+ mdvRenderFromPaste();
715
+ }
716
+ });
717
+ }
718
+
719
+ // File input
720
+ if (fileInput) {
721
+ fileInput.addEventListener('change', function(e) {
722
+ if (e.target.files && e.target.files[0]) mdvReadFile(e.target.files[0]);
723
+ e.target.value = '';
724
+ });
725
+ }
726
+
727
+ // Drag and drop on drop area
728
+ if (dropArea) {
729
+ dropArea.addEventListener('dragenter', function(e) { e.preventDefault(); dropArea.classList.add('drag-over'); });
730
+ dropArea.addEventListener('dragover', function(e) { e.preventDefault(); dropArea.classList.add('drag-over'); });
731
+ dropArea.addEventListener('dragleave', function(e) { e.preventDefault(); dropArea.classList.remove('drag-over'); });
732
+ dropArea.addEventListener('drop', function(e) {
733
+ e.preventDefault();
734
+ e.stopPropagation();
735
+ dropArea.classList.remove('drag-over');
736
+ if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0]) {
737
+ mdvReadFile(e.dataTransfer.files[0]);
738
+ }
739
+ });
740
+ }
741
+
742
+ // Also handle global drag/drop when MD viewer is active
743
+ document.addEventListener('dragover', function(e) {
744
+ if (currentPage === 'md-viewer') e.preventDefault();
745
+ });
746
+ document.addEventListener('drop', function(e) {
747
+ if (currentPage !== 'md-viewer') return;
748
+ e.preventDefault();
749
+ if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0]) {
750
+ mdvReadFile(e.dataTransfer.files[0]);
751
+ }
752
+ });
753
+
754
+ // Search
755
+ if (searchInput) {
756
+ searchInput.addEventListener('input', function(e) {
757
+ clearTimeout(mdvSearchTimeout);
758
+ mdvSearchTimeout = setTimeout(function() { mdvSearch(e.target.value); }, 300);
759
+ });
760
+ }
761
+
762
+ // Global paste when on MD viewer page
763
+ document.addEventListener('paste', function(e) {
764
+ if (currentPage !== 'md-viewer') return;
765
+ var a = document.activeElement;
766
+ if (a && (a.tagName === 'TEXTAREA' || a.tagName === 'INPUT')) return;
767
+ var text = (e.clipboardData || window.clipboardData).getData('text');
768
+ if (!text || !text.trim()) return;
769
+ e.preventDefault();
770
+ var home = document.getElementById('mdvHome');
771
+ if (home && home.style.display !== 'none') {
772
+ var ta2 = document.getElementById('mdvTextarea');
773
+ if (ta2) { ta2.value = text; mdvUpdateCharCount(); }
774
+ mdvRenderFromPaste();
775
+ } else {
776
+ var h = text.match(/^#\\s+(.+)/m);
777
+ mdvRender(text, h ? h[1].trim() : '粘贴的文档');
778
+ }
779
+ });
780
+
781
+ // Keyboard shortcuts (only when MD viewer is active)
782
+ document.addEventListener('keydown', function(e) {
783
+ if (currentPage !== 'md-viewer') return;
784
+ if ((e.ctrlKey || e.metaKey) && e.key === 'o') {
785
+ e.preventDefault();
786
+ var fi = document.getElementById('mdvFileInput');
787
+ if (fi) fi.click();
788
+ }
789
+ if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
790
+ var searchBox = document.getElementById('mdvSearchBox');
791
+ var si = document.getElementById('mdvSearchInput');
792
+ if (searchBox && searchBox.style.display !== 'none' && si) {
793
+ e.preventDefault();
794
+ si.focus();
795
+ si.select();
796
+ }
797
+ }
798
+ if (e.key === 'Escape') {
799
+ var si2 = document.getElementById('mdvSearchInput');
800
+ if (si2) { si2.value = ''; mdvSearch(''); si2.blur(); }
801
+ }
802
+ });
803
+ }
804
+ `;
805
+ }
806
+ //# sourceMappingURL=template-md-viewer.js.map