md2ui 1.0.16 → 1.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +3 -55
  2. package/bin/build.js +82 -7
  3. package/bin/md2ui.js +80 -4
  4. package/package.json +23 -9
  5. package/public/docs/00-/345/277/253/351/200/237/345/274/200/345/247/213.md +48 -28
  6. package/public/docs/01-/345/212/237/350/203/275/347/211/271/346/200/247.md +55 -40
  7. package/public/docs/02-Markdown/346/270/262/346/237/223/00-/345/237/272/347/241/200/350/257/255/346/263/225.md +86 -0
  8. package/public/docs/02-Markdown/346/270/262/346/237/223/01-/344/273/243/347/240/201/345/235/227.md +91 -0
  9. package/public/docs/02-Markdown/346/270/262/346/237/223/02-/350/241/250/346/240/274.md +187 -0
  10. package/public/docs/02-Markdown/346/270/262/346/237/223/03-Mermaid/345/233/276/350/241/250.md +101 -0
  11. package/public/docs/02-Markdown/346/270/262/346/237/223/04-Frontmatter.md +32 -0
  12. package/public/docs/02-Markdown/346/270/262/346/237/223/05-/346/225/260/345/255/246/345/205/254/345/274/217.md +47 -0
  13. package/public/docs/03-/345/257/274/350/210/252/344/270/216/345/270/203/345/261/200/00-/344/270/211/346/240/217/345/270/203/345/261/200.md +33 -0
  14. package/public/docs/03-/345/257/274/350/210/252/344/270/216/345/270/203/345/261/200/01-/347/233/256/345/275/225/346/240/221/345/257/274/350/210/252.md +43 -0
  15. package/public/docs/03-/345/257/274/350/210/252/344/270/216/345/270/203/345/261/200/02-/346/226/207/346/241/243/345/244/247/347/272/262.md +51 -0
  16. package/public/docs/03-/345/257/274/350/210/252/344/270/216/345/270/203/345/261/200/03-/344/270/212/344/270/213/347/257/207/345/257/274/350/210/252.md +29 -0
  17. package/public/docs/03-/345/257/274/350/210/252/344/270/216/345/270/203/345/261/200/04-/347/253/231/345/206/205/351/223/276/346/216/245.md +39 -0
  18. package/public/docs/04-/346/220/234/347/264/242/345/212/237/350/203/275/00-/345/205/250/346/226/207/346/220/234/347/264/242.md +46 -0
  19. package/public/docs/05-/347/274/226/350/276/221/345/212/237/350/203/275/00-/347/274/226/350/276/221/345/231/250/345/237/272/347/241/200.md +65 -0
  20. package/public/docs/05-/347/274/226/350/276/221/345/212/237/350/203/275/01-/350/207/252/345/212/250/344/277/235/345/255/230.md +38 -0
  21. package/public/docs/06-/351/230/205/350/257/273/344/275/223/351/252/214/00-/351/230/205/350/257/273/350/277/233/345/272/246.md +43 -0
  22. package/public/docs/06-/351/230/205/350/257/273/344/275/223/351/252/214/01-/345/233/276/347/211/207/346/224/276/345/244/247.md +40 -0
  23. package/public/docs/06-/351/230/205/350/257/273/344/275/223/351/252/214/02-/350/277/224/345/233/236/351/241/266/351/203/250.md +38 -0
  24. package/public/docs/06-/351/230/205/350/257/273/344/275/223/351/252/214/assets/img-1777261394722.png +0 -0
  25. package/public/docs/07-/347/247/273/345/212/250/347/253/257/351/200/202/351/205/215/00-/345/223/215/345/272/224/345/274/217/345/270/203/345/261/200.md +37 -0
  26. package/public/docs/08-/346/226/207/346/241/243/347/256/241/347/220/206/00-/346/226/260/345/273/272/344/270/216/345/210/240/351/231/244.md +47 -0
  27. package/public/docs/09-/345/257/274/345/207/272/345/212/237/350/203/275/00-/345/257/274/345/207/272Word.md +77 -0
  28. package/public/docs/10-/351/203/250/347/275/262/344/270/216/351/205/215/347/275/256/00-CLI/345/267/245/345/205/267.md +52 -0
  29. package/public/docs/10-/351/203/250/347/275/262/344/270/216/351/205/215/347/275/256/01-SSG/351/235/231/346/200/201/346/236/204/345/273/272.md +44 -0
  30. package/public/docs/10-/351/203/250/347/275/262/344/270/216/351/205/215/347/275/256/02-/350/207/252/345/256/232/344/271/211/351/205/215/347/275/256.md +58 -0
  31. package/public/docs/11-/345/244/232/347/272/247/347/233/256/345/275/225/346/265/213/350/257/225/00-/344/270/200/347/272/247/346/226/207/346/241/243.md +20 -0
  32. package/public/docs/11-/345/244/232/347/272/247/347/233/256/345/275/225/346/265/213/350/257/225/01-/345/255/220/347/233/256/345/275/225/00-/344/272/214/347/272/247/346/226/207/346/241/243.md +13 -0
  33. package/public/docs/11-/345/244/232/347/272/247/347/233/256/345/275/225/346/265/213/350/257/225/01-/345/255/220/347/233/256/345/275/225/01-/346/267/261/345/261/202/345/265/214/345/245/227/00-/344/270/211/347/272/247/346/226/207/346/241/243.md +23 -0
  34. package/src/App.vue +130 -6
  35. package/src/components/AppSidebar.vue +181 -21
  36. package/src/components/CodeBlockNodeView.vue +72 -0
  37. package/src/components/DocContent.vue +25 -14
  38. package/src/components/EditorContent.vue +257 -0
  39. package/src/components/EditorToolbar.vue +264 -0
  40. package/src/components/ImageZoom.vue +199 -2
  41. package/src/components/MathBlockNodeView.vue +160 -0
  42. package/src/components/MathInlineNodeView.vue +145 -0
  43. package/src/components/MermaidNodeView.vue +149 -0
  44. package/src/components/TableBubbleMenu.vue +177 -0
  45. package/src/components/TableOfContents.vue +138 -32
  46. package/src/components/TopBar.vue +69 -4
  47. package/src/components/TreeNode.vue +232 -39
  48. package/src/components/WelcomePage.vue +2 -2
  49. package/src/composables/useDocHash.js +9 -1
  50. package/src/composables/useDocManager.js +325 -68
  51. package/src/composables/useDocTree.js +56 -1
  52. package/src/composables/useExportPdf.js +102 -0
  53. package/src/composables/useExportWord.js +73 -10
  54. package/src/composables/useFileWatcher.js +45 -0
  55. package/src/composables/useFrontmatter.js +2 -2
  56. package/src/composables/useMarkdown.js +529 -42
  57. package/src/composables/useScroll.js +47 -5
  58. package/src/config.js +1 -1
  59. package/src/extensions/CodeBlockCustom.js +113 -0
  60. package/src/extensions/MathBlock.js +107 -0
  61. package/src/extensions/MathInline.js +100 -0
  62. package/src/extensions/MermaidBlock.js +73 -0
  63. package/src/extensions/TableControls.js +670 -0
  64. package/src/services/DocService.js +184 -0
  65. package/src/style.css +2194 -39
  66. package/vite-plugin-doc-api.js +368 -0
  67. package/vite.config.js +2 -1
  68. package/public/docs/02-Mermaid/345/233/276/350/241/250.md +0 -102
  69. package/public/docs/03-/350/277/233/351/230/266/346/214/207/345/215/227/01-/347/233/256/345/275/225/347/273/223/346/236/204.md +0 -55
  70. package/public/docs/03-/350/277/233/351/230/266/346/214/207/345/215/227/02-/350/207/252/345/256/232/344/271/211/351/205/215/347/275/256.md +0 -63
  71. package/public/docs/03-/350/277/233/351/230/266/346/214/207/345/215/227/03-/351/203/250/347/275/262/346/226/271/346/241/210.md +0 -73
  72. package/public/docs/04-API/345/217/202/350/200/203/01-/347/273/204/344/273/266API.md +0 -80
  73. package/public/docs/04-API/345/217/202/350/200/203/02-Composables.md +0 -92
  74. package/src/api/docs.js +0 -106
@@ -0,0 +1,368 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import crypto from 'crypto'
4
+
5
+ /**
6
+ * Vite 插件:文档 API(统一使用 /@user-docs-* 路由前缀)
7
+ *
8
+ * 路由:
9
+ * - GET /@user-docs-list 返回文档树 + ETag
10
+ * - GET /@user-docs/xxx 返回文件内容(md / 图片等)
11
+ * - POST /@upload-image 上传图片
12
+ * - POST /api/save 保存文件内容
13
+ * - POST /api/create 创建文件或文件夹
14
+ * - POST /api/delete 删除文件或文件夹
15
+ * - POST /api/rename 重命名
16
+ * - POST /api/reorder 批量重编号
17
+ * - POST /api/move 移动文件/文件夹
18
+ */
19
+ export default function docApiPlugin(docsDir = '.') {
20
+ const resolvedDocsDir = path.resolve(docsDir)
21
+
22
+ // 计算内容的 ETag
23
+ function etag(content) {
24
+ return '"' + crypto.createHash('md5').update(content).digest('hex') + '"'
25
+ }
26
+
27
+ // 递归扫描 .md 文件,返回树结构(与 CLI 模式 scanDocs 一致)
28
+ function scanDocs(dir, basePath = '', level = 0) {
29
+ const items = []
30
+ if (!fs.existsSync(dir)) return items
31
+ const entries = fs.readdirSync(dir, { withFileTypes: true })
32
+ .filter(e => !e.name.startsWith('.') && e.name !== 'node_modules')
33
+ .sort((a, b) => a.name.localeCompare(b.name, 'zh-CN'))
34
+
35
+ for (const entry of entries) {
36
+ const fullPath = path.join(dir, entry.name)
37
+ const relativePath = basePath ? `${basePath}/${entry.name}` : entry.name
38
+
39
+ if (entry.isDirectory()) {
40
+ const children = scanDocs(fullPath, relativePath, level + 1)
41
+ if (children.length > 0) {
42
+ const match = entry.name.match(/^(\d+)-(.+)$/)
43
+ items.push({
44
+ key: relativePath,
45
+ label: match ? match[2] : entry.name,
46
+ order: match ? parseInt(match[1]) : 999,
47
+ type: 'folder',
48
+ level,
49
+ expanded: false,
50
+ children
51
+ })
52
+ }
53
+ } else if (entry.name.endsWith('.md')) {
54
+ const match = entry.name.match(/^(\d+)-(.+)\.md$/)
55
+ const label = match ? match[2] : entry.name.replace(/\.md$/, '')
56
+ items.push({
57
+ key: relativePath.replace(/\.md$/, ''),
58
+ label,
59
+ order: match ? parseInt(match[1]) : 999,
60
+ type: 'file',
61
+ level,
62
+ path: `/@user-docs/${relativePath}`
63
+ })
64
+ }
65
+ }
66
+
67
+ items.sort((a, b) => a.order - b.order)
68
+ return items
69
+ }
70
+
71
+ // MIME 类型映射
72
+ const mimeMap = {
73
+ md: 'text/plain; charset=utf-8',
74
+ png: 'image/png',
75
+ jpg: 'image/jpeg',
76
+ jpeg: 'image/jpeg',
77
+ gif: 'image/gif',
78
+ webp: 'image/webp',
79
+ svg: 'image/svg+xml',
80
+ bmp: 'image/bmp',
81
+ }
82
+
83
+ return {
84
+ name: 'vite-plugin-doc-api',
85
+ configureServer(server) {
86
+ // 统一中间件
87
+ server.middlewares.use((req, res, next) => {
88
+ // GET /@user-docs-list — 返回文档树 + ETag
89
+ if (req.url === '/@user-docs-list' && req.method === 'GET') {
90
+ const docs = scanDocs(resolvedDocsDir)
91
+ const body = JSON.stringify(docs)
92
+ const tag = etag(body)
93
+ if (req.headers['if-none-match'] === tag) {
94
+ res.statusCode = 304; res.end(); return
95
+ }
96
+ res.setHeader('Content-Type', 'application/json; charset=utf-8')
97
+ res.setHeader('ETag', tag)
98
+ res.end(body)
99
+ return
100
+ }
101
+
102
+ // GET /@user-docs/xxx — 返回文件内容(md / 图片等)
103
+ if (req.url?.startsWith('/@user-docs/') && req.method === 'GET') {
104
+ const filePath = path.resolve(resolvedDocsDir, decodeURIComponent(req.url.replace('/@user-docs/', '')))
105
+ if (!filePath.startsWith(resolvedDocsDir)) {
106
+ res.statusCode = 403; res.end('禁止访问'); return
107
+ }
108
+ if (!fs.existsSync(filePath)) {
109
+ res.statusCode = 404; res.end('文件不存在'); return
110
+ }
111
+ const extName = filePath.split('.').pop().toLowerCase()
112
+ const contentType = mimeMap[extName] || 'application/octet-stream'
113
+ res.setHeader('Content-Type', contentType)
114
+ // 附带最后修改时间
115
+ const stat = fs.statSync(filePath)
116
+ res.setHeader('X-Last-Modified', stat.mtime.toISOString())
117
+ // 文本文件支持 ETag
118
+ if (contentType.startsWith('text/')) {
119
+ const content = fs.readFileSync(filePath, 'utf-8')
120
+ const tag = etag(content)
121
+ if (req.headers['if-none-match'] === tag) {
122
+ res.statusCode = 304; res.end(); return
123
+ }
124
+ res.setHeader('ETag', tag)
125
+ res.end(content)
126
+ } else {
127
+ res.end(fs.readFileSync(filePath))
128
+ }
129
+ return
130
+ }
131
+
132
+ // POST /@upload-image — 上传图片
133
+ if (req.url === '/@upload-image' && req.method === 'POST') {
134
+ const chunks = []
135
+ req.on('data', chunk => chunks.push(chunk))
136
+ req.on('end', () => {
137
+ try {
138
+ const body = Buffer.concat(chunks)
139
+ const docPath = decodeURIComponent(req.headers['x-doc-path'] || '')
140
+ const fileName = decodeURIComponent(req.headers['x-file-name'] || `img-${Date.now()}.png`)
141
+
142
+ if (!docPath) {
143
+ res.statusCode = 400; res.end('缺少文档路径'); return
144
+ }
145
+
146
+ const docDir = path.dirname(path.resolve(resolvedDocsDir, docPath))
147
+ if (!docDir.startsWith(resolvedDocsDir)) {
148
+ res.statusCode = 403; res.end('禁止访问'); return
149
+ }
150
+
151
+ const assetsDir = path.join(docDir, 'assets')
152
+ fs.mkdirSync(assetsDir, { recursive: true })
153
+
154
+ const ext = path.extname(fileName) || '.png'
155
+ const baseName = `img-${Date.now()}`
156
+ const targetName = `${baseName}${ext}`
157
+ const targetPath = path.join(assetsDir, targetName)
158
+
159
+ fs.writeFileSync(targetPath, body)
160
+
161
+ // 返回 /@user-docs/ 前缀的路径
162
+ const docDirRel = path.dirname(docPath)
163
+ const encodedDir = docDirRel && docDirRel !== '.'
164
+ ? docDirRel.split('/').map(encodeURIComponent).join('/')
165
+ : ''
166
+ const imageUrl = encodedDir
167
+ ? `/@user-docs/${encodedDir}/assets/${targetName}`
168
+ : `/@user-docs/assets/${targetName}`
169
+
170
+ res.setHeader('Content-Type', 'application/json; charset=utf-8')
171
+ res.end(JSON.stringify({ url: imageUrl }))
172
+ } catch (e) {
173
+ res.statusCode = 500; res.end(e.message)
174
+ }
175
+ })
176
+ return
177
+ }
178
+
179
+ next()
180
+ })
181
+
182
+ // POST /api/create — 创建文件或文件夹
183
+ server.middlewares.use('/api/create', (req, res) => {
184
+ if (req.method !== 'POST') { res.statusCode = 405; res.end(); return }
185
+ let body = ''
186
+ req.on('data', chunk => { body += chunk })
187
+ req.on('end', () => {
188
+ try {
189
+ const { path: filePath, type } = JSON.parse(body)
190
+ if (!filePath) {
191
+ res.statusCode = 400; res.end('缺少 path'); return
192
+ }
193
+ const fullPath = path.resolve(resolvedDocsDir, filePath)
194
+ if (!fullPath.startsWith(resolvedDocsDir)) {
195
+ res.statusCode = 403; res.end('禁止访问'); return
196
+ }
197
+ if (fs.existsSync(fullPath)) {
198
+ res.statusCode = 409; res.end('已存在同名文件或文件夹'); return
199
+ }
200
+ if (type === 'folder') {
201
+ fs.mkdirSync(fullPath, { recursive: true })
202
+ } else {
203
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true })
204
+ fs.writeFileSync(fullPath, '', 'utf-8')
205
+ }
206
+ res.setHeader('Content-Type', 'application/json')
207
+ res.end(JSON.stringify({ ok: true }))
208
+ } catch (e) {
209
+ res.statusCode = 500; res.end(e.message)
210
+ }
211
+ })
212
+ })
213
+
214
+ // POST /api/delete — 删除文件或文件夹
215
+ server.middlewares.use('/api/delete', (req, res) => {
216
+ if (req.method !== 'POST') { res.statusCode = 405; res.end(); return }
217
+ let body = ''
218
+ req.on('data', chunk => { body += chunk })
219
+ req.on('end', () => {
220
+ try {
221
+ const { path: filePath } = JSON.parse(body)
222
+ if (!filePath) {
223
+ res.statusCode = 400; res.end('缺少 path'); return
224
+ }
225
+ const fullPath = path.resolve(resolvedDocsDir, filePath)
226
+ if (!fullPath.startsWith(resolvedDocsDir)) {
227
+ res.statusCode = 403; res.end('禁止访问'); return
228
+ }
229
+ if (!fs.existsSync(fullPath)) {
230
+ res.statusCode = 404; res.end('文件不存在'); return
231
+ }
232
+ fs.rmSync(fullPath, { recursive: true, force: true })
233
+ res.setHeader('Content-Type', 'application/json')
234
+ res.end(JSON.stringify({ ok: true }))
235
+ } catch (e) {
236
+ res.statusCode = 500; res.end(e.message)
237
+ }
238
+ })
239
+ })
240
+
241
+ // POST /api/rename — 重命名文件或文件夹
242
+ server.middlewares.use('/api/rename', (req, res) => {
243
+ if (req.method !== 'POST') { res.statusCode = 405; res.end(); return }
244
+ let body = ''
245
+ req.on('data', chunk => { body += chunk })
246
+ req.on('end', () => {
247
+ try {
248
+ const { oldPath, newPath } = JSON.parse(body)
249
+ if (!oldPath || !newPath) {
250
+ res.statusCode = 400; res.end('缺少 oldPath 或 newPath'); return
251
+ }
252
+ const fullOld = path.resolve(resolvedDocsDir, oldPath)
253
+ const fullNew = path.resolve(resolvedDocsDir, newPath)
254
+ if (!fullOld.startsWith(resolvedDocsDir) || !fullNew.startsWith(resolvedDocsDir)) {
255
+ res.statusCode = 403; res.end('禁止访问'); return
256
+ }
257
+ if (!fs.existsSync(fullOld)) {
258
+ res.statusCode = 404; res.end('源文件不存在'); return
259
+ }
260
+ if (fs.existsSync(fullNew)) {
261
+ res.statusCode = 409; res.end('目标名称已存在'); return
262
+ }
263
+ fs.mkdirSync(path.dirname(fullNew), { recursive: true })
264
+ fs.renameSync(fullOld, fullNew)
265
+ res.setHeader('Content-Type', 'application/json')
266
+ res.end(JSON.stringify({ ok: true }))
267
+ } catch (e) {
268
+ res.statusCode = 500; res.end(e.message)
269
+ }
270
+ })
271
+ })
272
+
273
+ // POST /api/reorder — 批量重编号
274
+ server.middlewares.use('/api/reorder', (req, res) => {
275
+ if (req.method !== 'POST') { res.statusCode = 405; res.end(); return }
276
+ let body = ''
277
+ req.on('data', chunk => { body += chunk })
278
+ req.on('end', () => {
279
+ try {
280
+ const { items } = JSON.parse(body)
281
+ if (!Array.isArray(items) || items.length === 0) {
282
+ res.statusCode = 400; res.end('缺少 items'); return
283
+ }
284
+ for (const { oldPath, newPath } of items) {
285
+ const fullOld = path.resolve(resolvedDocsDir, oldPath)
286
+ const fullNew = path.resolve(resolvedDocsDir, newPath)
287
+ if (!fullOld.startsWith(resolvedDocsDir) || !fullNew.startsWith(resolvedDocsDir)) {
288
+ res.statusCode = 403; res.end('禁止访问'); return
289
+ }
290
+ }
291
+ const tempMap = []
292
+ for (let i = 0; i < items.length; i++) {
293
+ const { oldPath } = items[i]
294
+ const fullOld = path.resolve(resolvedDocsDir, oldPath)
295
+ if (!fs.existsSync(fullOld)) continue
296
+ const tempName = fullOld + `.__reorder_tmp_${i}__`
297
+ fs.renameSync(fullOld, tempName)
298
+ tempMap.push({ temp: tempName, newPath: items[i].newPath })
299
+ }
300
+ for (const { temp, newPath: np } of tempMap) {
301
+ const fullNew = path.resolve(resolvedDocsDir, np)
302
+ fs.mkdirSync(path.dirname(fullNew), { recursive: true })
303
+ fs.renameSync(temp, fullNew)
304
+ }
305
+ res.setHeader('Content-Type', 'application/json')
306
+ res.end(JSON.stringify({ ok: true }))
307
+ } catch (e) {
308
+ res.statusCode = 500; res.end(e.message)
309
+ }
310
+ })
311
+ })
312
+
313
+ // POST /api/move — 移动文件/文件夹
314
+ server.middlewares.use('/api/move', (req, res) => {
315
+ if (req.method !== 'POST') { res.statusCode = 405; res.end(); return }
316
+ let body = ''
317
+ req.on('data', chunk => { body += chunk })
318
+ req.on('end', () => {
319
+ try {
320
+ const { oldPath, newPath } = JSON.parse(body)
321
+ if (!oldPath || !newPath) {
322
+ res.statusCode = 400; res.end('缺少 oldPath 或 newPath'); return
323
+ }
324
+ const fullOld = path.resolve(resolvedDocsDir, oldPath)
325
+ const fullNew = path.resolve(resolvedDocsDir, newPath)
326
+ if (!fullOld.startsWith(resolvedDocsDir) || !fullNew.startsWith(resolvedDocsDir)) {
327
+ res.statusCode = 403; res.end('禁止访问'); return
328
+ }
329
+ if (!fs.existsSync(fullOld)) {
330
+ res.statusCode = 404; res.end('源文件不存在'); return
331
+ }
332
+ fs.mkdirSync(path.dirname(fullNew), { recursive: true })
333
+ fs.renameSync(fullOld, fullNew)
334
+ res.setHeader('Content-Type', 'application/json')
335
+ res.end(JSON.stringify({ ok: true }))
336
+ } catch (e) {
337
+ res.statusCode = 500; res.end(e.message)
338
+ }
339
+ })
340
+ })
341
+
342
+ // POST /api/save — 保存文件内容
343
+ server.middlewares.use('/api/save', (req, res) => {
344
+ if (req.method !== 'POST') { res.statusCode = 405; res.end(); return }
345
+ let body = ''
346
+ req.on('data', chunk => { body += chunk })
347
+ req.on('end', () => {
348
+ try {
349
+ const { path: filePath, content } = JSON.parse(body)
350
+ if (!filePath || content == null) {
351
+ res.statusCode = 400; res.end('缺少 path 或 content'); return
352
+ }
353
+ const fullPath = path.resolve(resolvedDocsDir, filePath)
354
+ if (!fullPath.startsWith(resolvedDocsDir)) {
355
+ res.statusCode = 403; res.end('禁止访问'); return
356
+ }
357
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true })
358
+ fs.writeFileSync(fullPath, content, 'utf-8')
359
+ res.setHeader('Content-Type', 'application/json')
360
+ res.end(JSON.stringify({ ok: true }))
361
+ } catch (e) {
362
+ res.statusCode = 500; res.end(e.message)
363
+ }
364
+ })
365
+ })
366
+ }
367
+ }
368
+ }
package/vite.config.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { defineConfig } from 'vite'
2
2
  import vue from '@vitejs/plugin-vue'
3
3
  import { config } from './src/config.js'
4
+ import docApiPlugin from './vite-plugin-doc-api.js'
4
5
 
5
6
  export default defineConfig({
6
- plugins: [vue()],
7
+ plugins: [vue(), docApiPlugin('public/docs')],
7
8
  server: {
8
9
  port: config.defaultPort
9
10
  },
@@ -1,102 +0,0 @@
1
- # Mermaid 图表
2
-
3
- md2ui 内置了 Mermaid 支持,可以直接在 Markdown 中绘制各类图表。
4
-
5
- ## 流程图
6
-
7
- ```mermaid
8
- flowchart TD
9
- A[开始] --> B{是否有文档?}
10
- B -->|是| C[解析 Markdown]
11
- B -->|否| D[显示空状态]
12
- C --> E[渲染 HTML]
13
- E --> F[展示页面]
14
- D --> F
15
- F --> G[结束]
16
- ```
17
-
18
- ## 时序图
19
-
20
- ```mermaid
21
- sequenceDiagram
22
- participant U as 用户
23
- participant B as 浏览器
24
- participant S as 服务器
25
-
26
- U->>B: 点击文档链接
27
- B->>S: 请求 Markdown 文件
28
- S-->>B: 返回文件内容
29
- B->>B: 解析并渲染
30
- B-->>U: 显示文档页面
31
- ```
32
-
33
- ## 类图
34
-
35
- ```mermaid
36
- classDiagram
37
- class Document {
38
- +String title
39
- +String content
40
- +Date createdAt
41
- +render()
42
- }
43
-
44
- class Folder {
45
- +String name
46
- +Document[] documents
47
- +Folder[] subfolders
48
- +expand()
49
- +collapse()
50
- }
51
-
52
- class Navigator {
53
- +Folder root
54
- +Document current
55
- +navigate(path)
56
- }
57
-
58
- Folder "1" *-- "*" Document
59
- Folder "1" *-- "*" Folder
60
- Navigator --> Folder
61
- Navigator --> Document
62
- ```
63
-
64
- ## 状态图
65
-
66
- ```mermaid
67
- stateDiagram-v2
68
- [*] --> 空闲
69
- 空闲 --> 加载中: 请求文档
70
- 加载中 --> 已加载: 加载成功
71
- 加载中 --> 错误: 加载失败
72
- 已加载 --> 空闲: 切换文档
73
- 错误 --> 空闲: 重试
74
- ```
75
-
76
- ## 甘特图
77
-
78
- ```mermaid
79
- gantt
80
- title 项目开发计划
81
- dateFormat YYYY-MM-DD
82
- section 基础功能
83
- Markdown 渲染 :done, a1, 2024-01-01, 7d
84
- 目录导航 :done, a2, after a1, 5d
85
- section 增强功能
86
- Mermaid 支持 :done, b1, after a2, 3d
87
- 代码高亮 :done, b2, after b1, 2d
88
- section 优化
89
- 性能优化 :active, c1, after b2, 5d
90
- 响应式适配 :c2, after c1, 3d
91
- ```
92
-
93
- ## 饼图
94
-
95
- ```mermaid
96
- pie title 技术栈占比
97
- "Vue 3" : 40
98
- "Vite" : 20
99
- "Marked" : 15
100
- "Mermaid" : 15
101
- "其他" : 10
102
- ```
@@ -1,55 +0,0 @@
1
- # 目录结构
2
-
3
- 了解 md2ui 的项目结构,有助于你进行定制开发。
4
-
5
- ## 项目结构
6
-
7
- ```
8
- md2ui/
9
- ├── public/ # 静态资源
10
- │ ├── README.md # 首页内容
11
- │ ├── logo.svg # 网站图标
12
- │ └── docs/ # 文档目录
13
- ├── src/
14
- │ ├── api/ # API 接口
15
- │ │ └── docs.js # 文档列表获取
16
- │ ├── components/ # Vue 组件
17
- │ │ ├── Logo.vue # Logo 组件
18
- │ │ ├── TreeNode.vue # 树形节点
19
- │ │ ├── TableOfContents.vue # 目录组件
20
- │ │ └── ImageZoom.vue # 图片放大
21
- │ ├── composables/ # 组合式函数
22
- │ │ ├── useMarkdown.js # Markdown 渲染
23
- │ │ ├── useScroll.js # 滚动处理
24
- │ │ └── useResize.js # 拖拽调整
25
- │ ├── App.vue # 主组件
26
- │ ├── main.js # 入口文件
27
- │ └── style.css # 全局样式
28
- ├── index.html # HTML 模板
29
- ├── vite.config.js # Vite 配置
30
- └── package.json # 项目配置
31
- ```
32
-
33
- ## 核心模块说明
34
-
35
- ### api/docs.js
36
-
37
- 负责扫描 `public/docs/` 目录,构建文档树结构。
38
-
39
- ### composables/useMarkdown.js
40
-
41
- 封装 Markdown 解析逻辑,包括:
42
- - 使用 marked 解析 Markdown
43
- - 提取标题生成目录
44
- - 处理 Mermaid 代码块
45
-
46
- ### composables/useScroll.js
47
-
48
- 处理滚动相关逻辑:
49
- - 计算阅读进度
50
- - 高亮当前章节
51
- - 返回顶部功能
52
-
53
- ### composables/useResize.js
54
-
55
- 实现拖拽调整宽度功能。
@@ -1,63 +0,0 @@
1
- # 自定义配置
2
-
3
- md2ui 支持多种自定义配置,满足不同场景需求。
4
-
5
- ## 修改网站标题
6
-
7
- 编辑 `index.html`:
8
-
9
- ```html
10
- <title>你的文档站点</title>
11
- ```
12
-
13
- ## 修改 Logo
14
-
15
- 编辑 `src/components/Logo.vue`:
16
-
17
- ```vue
18
- <span class="logo-text">你的项目名</span>
19
- ```
20
-
21
- 或替换 `public/logo.svg` 图标文件。
22
-
23
- ## 修改首页
24
-
25
- 编辑 `public/README.md` 文件,这是点击 Logo 后显示的首页内容。
26
-
27
- ## 修改端口
28
-
29
- 编辑 `vite.config.js`:
30
-
31
- ```javascript
32
- export default defineConfig({
33
- server: {
34
- port: 3000
35
- }
36
- })
37
- ```
38
-
39
- ## 样式定制
40
-
41
- 全局样式在 `src/style.css` 中,主要变量:
42
-
43
- ```css
44
- /* 颜色 */
45
- --primary-color: #3b82f6;
46
- --text-color: #1f2937;
47
- --bg-color: #ffffff;
48
- --border-color: #e5e7eb;
49
-
50
- /* 尺寸 */
51
- --sidebar-width: 280px;
52
- --toc-width: 240px;
53
- ```
54
-
55
- ## 文档排序规则
56
-
57
- 文件名格式:`序号-名称.md`
58
-
59
- - `00-快速开始.md` 排在最前
60
- - `01-使用指南.md` 排在其后
61
- - 没有序号的文件按字母排序
62
-
63
- 文件夹同样支持序号前缀。
@@ -1,73 +0,0 @@
1
- # 部署方案
2
-
3
- md2ui 构建后是纯静态文件,可部署到任意静态服务器。
4
-
5
- ## 构建
6
-
7
- ```bash
8
- pnpm build
9
- ```
10
-
11
- 构建产物在 `dist/` 目录。
12
-
13
- ## Nginx 部署
14
-
15
- ```nginx
16
- server {
17
- listen 80;
18
- server_name docs.example.com;
19
- root /var/www/md2ui/dist;
20
- index index.html;
21
-
22
- # 支持 HTML5 History 模式
23
- location / {
24
- try_files $uri $uri/ /index.html;
25
- }
26
-
27
- # 静态资源缓存
28
- location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
29
- expires 30d;
30
- add_header Cache-Control "public, immutable";
31
- }
32
- }
33
- ```
34
-
35
- ## Docker 部署
36
-
37
- 项目已包含 Dockerfile:
38
-
39
- ```bash
40
- # 构建镜像
41
- docker build -t md2ui .
42
-
43
- # 运行容器
44
- docker run -d -p 3000:3000 md2ui
45
- ```
46
-
47
- ## 部署流程
48
-
49
- ```mermaid
50
- flowchart LR
51
- A[代码提交] --> B[CI 构建]
52
- B --> C[生成 dist]
53
- C --> D{部署目标}
54
- D --> E[Nginx]
55
- D --> F[Docker]
56
- D --> G[CDN]
57
- ```
58
-
59
- ## GitHub Pages
60
-
61
- 1. 修改 `vite.config.js` 设置 base 路径:
62
-
63
- ```javascript
64
- export default defineConfig({
65
- base: '/your-repo-name/'
66
- })
67
- ```
68
-
69
- 2. 构建并推送到 gh-pages 分支
70
-
71
- ## Vercel / Netlify
72
-
73
- 直接连接 Git 仓库,自动构建部署,无需额外配置。