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.
- package/README.md +3 -55
- package/bin/build.js +82 -7
- package/bin/md2ui.js +80 -4
- package/package.json +23 -9
- package/public/docs/00-/345/277/253/351/200/237/345/274/200/345/247/213.md +48 -28
- package/public/docs/01-/345/212/237/350/203/275/347/211/271/346/200/247.md +55 -40
- 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
- package/public/docs/02-Markdown/346/270/262/346/237/223/01-/344/273/243/347/240/201/345/235/227.md +91 -0
- package/public/docs/02-Markdown/346/270/262/346/237/223/02-/350/241/250/346/240/274.md +187 -0
- package/public/docs/02-Markdown/346/270/262/346/237/223/03-Mermaid/345/233/276/350/241/250.md +101 -0
- package/public/docs/02-Markdown/346/270/262/346/237/223/04-Frontmatter.md +32 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/public/docs/06-/351/230/205/350/257/273/344/275/223/351/252/214/assets/img-1777261394722.png +0 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/src/App.vue +130 -6
- package/src/components/AppSidebar.vue +181 -21
- package/src/components/CodeBlockNodeView.vue +72 -0
- package/src/components/DocContent.vue +25 -14
- package/src/components/EditorContent.vue +257 -0
- package/src/components/EditorToolbar.vue +264 -0
- package/src/components/ImageZoom.vue +199 -2
- package/src/components/MathBlockNodeView.vue +160 -0
- package/src/components/MathInlineNodeView.vue +145 -0
- package/src/components/MermaidNodeView.vue +149 -0
- package/src/components/TableBubbleMenu.vue +177 -0
- package/src/components/TableOfContents.vue +138 -32
- package/src/components/TopBar.vue +69 -4
- package/src/components/TreeNode.vue +232 -39
- package/src/components/WelcomePage.vue +2 -2
- package/src/composables/useDocHash.js +9 -1
- package/src/composables/useDocManager.js +325 -68
- package/src/composables/useDocTree.js +56 -1
- package/src/composables/useExportPdf.js +102 -0
- package/src/composables/useExportWord.js +73 -10
- package/src/composables/useFileWatcher.js +45 -0
- package/src/composables/useFrontmatter.js +2 -2
- package/src/composables/useMarkdown.js +529 -42
- package/src/composables/useScroll.js +47 -5
- package/src/config.js +1 -1
- package/src/extensions/CodeBlockCustom.js +113 -0
- package/src/extensions/MathBlock.js +107 -0
- package/src/extensions/MathInline.js +100 -0
- package/src/extensions/MermaidBlock.js +73 -0
- package/src/extensions/TableControls.js +670 -0
- package/src/services/DocService.js +184 -0
- package/src/style.css +2194 -39
- package/vite-plugin-doc-api.js +368 -0
- package/vite.config.js +2 -1
- package/public/docs/02-Mermaid/345/233/276/350/241/250.md +0 -102
- 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
- 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
- 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
- package/public/docs/04-API/345/217/202/350/200/203/01-/347/273/204/344/273/266API.md +0 -80
- package/public/docs/04-API/345/217/202/350/200/203/02-Composables.md +0 -92
- package/src/api/docs.js +0 -106
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PDF 导出 — 基于浏览器 window.print() 实现
|
|
5
|
+
* 通过创建独立的打印窗口,只包含文档内容,生成干净的 PDF
|
|
6
|
+
*/
|
|
7
|
+
export function useExportPdf() {
|
|
8
|
+
const exportingPdf = ref(false)
|
|
9
|
+
|
|
10
|
+
async function exportToPdf(title = '文档') {
|
|
11
|
+
exportingPdf.value = true
|
|
12
|
+
try {
|
|
13
|
+
const contentEl = document.querySelector('.markdown-content')
|
|
14
|
+
if (!contentEl) return
|
|
15
|
+
|
|
16
|
+
// 克隆内容,移除不需要打印的元素
|
|
17
|
+
const clone = contentEl.cloneNode(true)
|
|
18
|
+
clone.querySelectorAll('.code-block-actions').forEach(el => el.remove())
|
|
19
|
+
clone.querySelectorAll('.image-copy-btn, .mermaid-copy-btn').forEach(el => el.remove())
|
|
20
|
+
clone.querySelectorAll('.table-toolbar').forEach(el => el.remove())
|
|
21
|
+
|
|
22
|
+
// 收集页面中的样式
|
|
23
|
+
const styles = []
|
|
24
|
+
for (const sheet of document.styleSheets) {
|
|
25
|
+
try {
|
|
26
|
+
for (const rule of sheet.cssRules) {
|
|
27
|
+
styles.push(rule.cssText)
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
// 跨域样式表忽略
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 创建打印窗口
|
|
35
|
+
const printWindow = window.open('', '_blank')
|
|
36
|
+
if (!printWindow) {
|
|
37
|
+
alert('请允许弹出窗口以导出 PDF')
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const safeTitle = title.replace(/[<>&"]/g, c =>
|
|
42
|
+
({ '<': '<', '>': '>', '&': '&', '"': '"' }[c]))
|
|
43
|
+
|
|
44
|
+
printWindow.document.write(`<!DOCTYPE html>
|
|
45
|
+
<html lang="zh-CN">
|
|
46
|
+
<head>
|
|
47
|
+
<meta charset="UTF-8">
|
|
48
|
+
<title>${safeTitle}</title>
|
|
49
|
+
<style>
|
|
50
|
+
${styles.join('\n')}
|
|
51
|
+
body {
|
|
52
|
+
margin: 0;
|
|
53
|
+
padding: 20px 40px;
|
|
54
|
+
background: #fff;
|
|
55
|
+
color: #1a1e1b;
|
|
56
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif;
|
|
57
|
+
font-size: 14px;
|
|
58
|
+
line-height: 1.6;
|
|
59
|
+
}
|
|
60
|
+
.markdown-content {
|
|
61
|
+
max-width: 100%;
|
|
62
|
+
padding: 0;
|
|
63
|
+
margin: 0;
|
|
64
|
+
}
|
|
65
|
+
.code-block-wrapper { break-inside: avoid; border: 1px solid #ddd; }
|
|
66
|
+
.code-block-header { background: #f5f5f5; }
|
|
67
|
+
table { break-inside: avoid; }
|
|
68
|
+
.mermaid { break-inside: avoid; }
|
|
69
|
+
.heading-anchor { display: none !important; }
|
|
70
|
+
@media print {
|
|
71
|
+
a[href^="http"]::after {
|
|
72
|
+
content: " (" attr(href) ")";
|
|
73
|
+
font-size: 0.85em;
|
|
74
|
+
color: #666;
|
|
75
|
+
word-break: break-all;
|
|
76
|
+
}
|
|
77
|
+
a[data-doc-key]::after { content: none; }
|
|
78
|
+
}
|
|
79
|
+
</style>
|
|
80
|
+
</head>
|
|
81
|
+
<body>
|
|
82
|
+
<article class="markdown-content">${clone.innerHTML}</article>
|
|
83
|
+
</body>
|
|
84
|
+
</html>`)
|
|
85
|
+
printWindow.document.close()
|
|
86
|
+
|
|
87
|
+
// 等待加载完成后触发打印
|
|
88
|
+
printWindow.onload = () => {
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
printWindow.print()
|
|
91
|
+
printWindow.onafterprint = () => printWindow.close()
|
|
92
|
+
}, 500)
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
console.error('导出 PDF 失败:', err)
|
|
96
|
+
} finally {
|
|
97
|
+
exportingPdf.value = false
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { exportingPdf, exportToPdf }
|
|
102
|
+
}
|
|
@@ -3,10 +3,26 @@ import {
|
|
|
3
3
|
Document, Packer, Paragraph, TextRun, HeadingLevel,
|
|
4
4
|
Table, TableRow, TableCell, WidthType, BorderStyle,
|
|
5
5
|
ImageRun, AlignmentType, ShadingType,
|
|
6
|
-
ExternalHyperlink,
|
|
6
|
+
ExternalHyperlink, TableLayoutType,
|
|
7
7
|
convertInchesToTwip
|
|
8
8
|
} from 'docx'
|
|
9
|
-
|
|
9
|
+
// 使用原生方式下载文件,兼容 Chrome / Safari / Firefox
|
|
10
|
+
// 使用原生方式下载文件,兼容 Chrome / Safari / Firefox
|
|
11
|
+
function downloadBlob(blob, fileName) {
|
|
12
|
+
// 将 Blob 包装为 File 对象,Chrome 会从 File.name 获取文件名
|
|
13
|
+
const file = new File([blob], fileName, { type: blob.type })
|
|
14
|
+
const url = URL.createObjectURL(file)
|
|
15
|
+
const a = document.createElement('a')
|
|
16
|
+
a.href = url
|
|
17
|
+
a.download = fileName
|
|
18
|
+
a.style.display = 'none'
|
|
19
|
+
document.body.appendChild(a)
|
|
20
|
+
a.click()
|
|
21
|
+
setTimeout(() => {
|
|
22
|
+
document.body.removeChild(a)
|
|
23
|
+
URL.revokeObjectURL(url)
|
|
24
|
+
}, 200)
|
|
25
|
+
}
|
|
10
26
|
|
|
11
27
|
// ---- SVG 转 PNG ----
|
|
12
28
|
|
|
@@ -160,16 +176,35 @@ function extractTextRuns(node, inherited = {}) {
|
|
|
160
176
|
return runs
|
|
161
177
|
}
|
|
162
178
|
|
|
163
|
-
//
|
|
179
|
+
// 解析表格(兼容 Office + WPS)
|
|
164
180
|
function parseTable(tableEl) {
|
|
181
|
+
// A4 内容区宽度(缇),左右页边距各 1.2 英寸,纸宽 8.27 英寸
|
|
182
|
+
const TABLE_WIDTH_DXA = 9024
|
|
165
183
|
const rows = []
|
|
166
184
|
const allTr = tableEl.querySelectorAll('tr')
|
|
185
|
+
|
|
186
|
+
// 1. 扫描最大列数(考虑 colspan)
|
|
187
|
+
let maxCols = 0
|
|
188
|
+
for (const tr of allTr) {
|
|
189
|
+
let colCount = 0
|
|
190
|
+
for (const td of tr.querySelectorAll('th, td')) {
|
|
191
|
+
colCount += parseInt(td.getAttribute('colspan') || '1', 10)
|
|
192
|
+
}
|
|
193
|
+
maxCols = Math.max(maxCols, colCount)
|
|
194
|
+
}
|
|
195
|
+
if (maxCols === 0) return null
|
|
196
|
+
|
|
197
|
+
// 2. 每列均分宽度
|
|
198
|
+
const colWidth = Math.floor(TABLE_WIDTH_DXA / maxCols)
|
|
199
|
+
|
|
200
|
+
// 3. 构建行(处理 colspan)
|
|
167
201
|
for (const tr of allTr) {
|
|
168
202
|
const cells = []
|
|
169
203
|
const tds = tr.querySelectorAll('th, td')
|
|
170
204
|
const isHeader = tr.querySelector('th') !== null
|
|
171
205
|
for (const td of tds) {
|
|
172
|
-
|
|
206
|
+
const colspan = parseInt(td.getAttribute('colspan') || '1', 10)
|
|
207
|
+
const cellOpts = {
|
|
173
208
|
children: [new Paragraph({
|
|
174
209
|
children: extractTextRuns(td, isHeader ? { bold: true } : {}),
|
|
175
210
|
spacing: { before: 40, after: 40 },
|
|
@@ -177,17 +212,23 @@ function parseTable(tableEl) {
|
|
|
177
212
|
shading: isHeader
|
|
178
213
|
? { type: ShadingType.CLEAR, fill: 'f0f0f0' }
|
|
179
214
|
: undefined,
|
|
180
|
-
width: { size:
|
|
181
|
-
}
|
|
215
|
+
width: { size: colWidth * colspan, type: WidthType.DXA },
|
|
216
|
+
}
|
|
217
|
+
if (colspan > 1) cellOpts.columnSpan = colspan
|
|
218
|
+
cells.push(new TableCell(cellOpts))
|
|
182
219
|
}
|
|
183
220
|
if (cells.length > 0) {
|
|
184
221
|
rows.push(new TableRow({ children: cells }))
|
|
185
222
|
}
|
|
186
223
|
}
|
|
187
224
|
if (rows.length === 0) return null
|
|
225
|
+
|
|
226
|
+
// 4. FIXED 布局 + columnWidths(tblGrid),WPS 严格依赖这两项
|
|
188
227
|
return new Table({
|
|
189
228
|
rows,
|
|
190
|
-
width: { size:
|
|
229
|
+
width: { size: TABLE_WIDTH_DXA, type: WidthType.DXA },
|
|
230
|
+
layout: TableLayoutType.FIXED,
|
|
231
|
+
columnWidths: Array(maxCols).fill(colWidth),
|
|
191
232
|
})
|
|
192
233
|
}
|
|
193
234
|
|
|
@@ -410,8 +451,8 @@ async function parseDomToDocx(contentEl) {
|
|
|
410
451
|
continue
|
|
411
452
|
}
|
|
412
453
|
|
|
413
|
-
// 表格(可能被 table-wrapper 包裹)
|
|
414
|
-
if (tag === 'TABLE' || node.classList?.contains('table-wrapper')) {
|
|
454
|
+
// 表格(可能被 table-outer 或 table-wrapper 包裹)
|
|
455
|
+
if (tag === 'TABLE' || node.classList?.contains('table-wrapper') || node.classList?.contains('table-outer')) {
|
|
415
456
|
const tableEl = tag === 'TABLE' ? node : node.querySelector('table')
|
|
416
457
|
if (tableEl) {
|
|
417
458
|
const table = parseTable(tableEl)
|
|
@@ -601,6 +642,24 @@ export function useExportWord() {
|
|
|
601
642
|
alignment: AlignmentType.LEFT,
|
|
602
643
|
},
|
|
603
644
|
},
|
|
645
|
+
heading1: {
|
|
646
|
+
run: { font: 'Microsoft YaHei', size: 36, bold: true, color: '333333' },
|
|
647
|
+
},
|
|
648
|
+
heading2: {
|
|
649
|
+
run: { font: 'Microsoft YaHei', size: 32, bold: true, color: '333333' },
|
|
650
|
+
},
|
|
651
|
+
heading3: {
|
|
652
|
+
run: { font: 'Microsoft YaHei', size: 28, bold: true, color: '333333' },
|
|
653
|
+
},
|
|
654
|
+
heading4: {
|
|
655
|
+
run: { font: 'Microsoft YaHei', size: 26, bold: true, color: '333333' },
|
|
656
|
+
},
|
|
657
|
+
heading5: {
|
|
658
|
+
run: { font: 'Microsoft YaHei', size: 24, bold: true, color: '333333' },
|
|
659
|
+
},
|
|
660
|
+
heading6: {
|
|
661
|
+
run: { font: 'Microsoft YaHei', size: 22, bold: true, color: '333333' },
|
|
662
|
+
},
|
|
604
663
|
},
|
|
605
664
|
},
|
|
606
665
|
sections: [{
|
|
@@ -619,8 +678,12 @@ export function useExportWord() {
|
|
|
619
678
|
})
|
|
620
679
|
|
|
621
680
|
const blob = await Packer.toBlob(doc)
|
|
681
|
+
// 确保 blob 的 MIME type 正确
|
|
682
|
+
const wordBlob = new Blob([blob], {
|
|
683
|
+
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
|
684
|
+
})
|
|
622
685
|
const fileName = title.replace(/[\\/:*?"<>|]/g, '_') + '.docx'
|
|
623
|
-
|
|
686
|
+
downloadBlob(wordBlob, fileName)
|
|
624
687
|
} catch (err) {
|
|
625
688
|
console.error('导出 Word 失败:', err)
|
|
626
689
|
} finally {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
|
2
|
+
import { pollDocsList, pollDocContent } from '../services/DocService.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 轮询监听文件变更
|
|
6
|
+
* 通过 DocService 统一处理模式检测和 ETag,这里只管定时调用
|
|
7
|
+
*/
|
|
8
|
+
export function useFileWatcher({ getCurrentDocPath, onDocsListChange, onDocContentChange }) {
|
|
9
|
+
const watching = ref(false)
|
|
10
|
+
let timer = null
|
|
11
|
+
let polling = false
|
|
12
|
+
|
|
13
|
+
async function poll() {
|
|
14
|
+
if (polling) return
|
|
15
|
+
polling = true
|
|
16
|
+
try {
|
|
17
|
+
// 轮询文档列表
|
|
18
|
+
const newTree = await pollDocsList()
|
|
19
|
+
if (newTree) onDocsListChange?.(newTree)
|
|
20
|
+
|
|
21
|
+
// 轮询当前文档内容
|
|
22
|
+
const docPath = getCurrentDocPath?.()
|
|
23
|
+
const content = await pollDocContent(docPath)
|
|
24
|
+
if (content !== null) onDocContentChange?.(content)
|
|
25
|
+
} finally {
|
|
26
|
+
polling = false
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function start() {
|
|
31
|
+
if (watching.value) return
|
|
32
|
+
watching.value = true
|
|
33
|
+
timer = setInterval(poll, 500)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function stop() {
|
|
37
|
+
watching.value = false
|
|
38
|
+
if (timer) { clearInterval(timer); timer = null }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
onMounted(start)
|
|
42
|
+
onBeforeUnmount(stop)
|
|
43
|
+
|
|
44
|
+
return { watching, start, stop }
|
|
45
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// 支持 title / description / order / hidden 字段
|
|
5
5
|
export function parseFrontmatter(markdown) {
|
|
6
6
|
const match = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/)
|
|
7
|
-
if (!match) return { data: {}, content: markdown }
|
|
7
|
+
if (!match) return { data: {}, content: markdown, rawYaml: '' }
|
|
8
8
|
const yamlStr = match[1]
|
|
9
9
|
const data = {}
|
|
10
10
|
for (const line of yamlStr.split('\n')) {
|
|
@@ -20,7 +20,7 @@ export function parseFrontmatter(markdown) {
|
|
|
20
20
|
else val = val.replace(/^['"]|['"]$/g, '')
|
|
21
21
|
data[m[1]] = val
|
|
22
22
|
}
|
|
23
|
-
return { data, content: markdown.slice(match[0].length) }
|
|
23
|
+
return { data, content: markdown.slice(match[0].length), rawYaml: yamlStr }
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// 计算阅读时间(中文 400 字/分钟,英文 200 词/分钟)
|