feat(stage5): 文档主题系统/自定义 CSS/导出 HTML/打印/导入 HTML 与 Word

This commit is contained in:
Muse
2026-11-13 22:58:35 +08:00
parent c8fc67104b
commit 83cedbc5f3
11 changed files with 1604 additions and 14 deletions

54
tests/stage5.test.ts Normal file
View File

@@ -0,0 +1,54 @@
// 阶段 5 验收:主题注册表完整性、导出 HTML 管线(结构/表格/公式/代码)、HTML 导入转换。
import { describe, it, expect } from 'vitest'
import { DOC_THEMES, docThemeById } from '../src/themes'
import { markdownToExportHtml, htmlToMarkdown, exportCss } from '../src/services/exportHtml'
describe('文档主题注册表', () => {
it('7 套主题 id 唯一且可查', () => {
expect(DOC_THEMES).toHaveLength(7)
expect(new Set(DOC_THEMES.map((t) => t.id)).size).toBe(7)
expect(docThemeById('paper').name).toBe('经典纸张')
expect(docThemeById('不存在').id).toBe('default')
for (const t of DOC_THEMES) expect(exportCss(t.id)).toContain('--f:')
})
})
describe('导出 HTML 管线', () => {
it('标题/表格/行内公式/代码块都转成对应 HTML(关高亮与 Mermaid 时走纯管线)', async () => {
const md = '# 导出标题\n\n正文含 $E = mc^2$ 公式。\n\n| A | B |\n| --- | --- |\n| 1 | 2 |\n\n```ts\nconst v = 1;\n```\n'
const html = await markdownToExportHtml(md, {
title: '导出标题', themeId: 'github', imageMode: 'relative', highlight: false, renderMermaid: false,
})
expect(html).toContain('<!DOCTYPE html>')
expect(html).toContain('<h1>导出标题</h1>')
expect(html).toContain('<table>')
expect(html).toContain('katex') // 公式经 rehype-katex 渲染(含 MathML)
expect(html).toContain('const v = 1;')
expect(html).toContain('<title>导出标题</title>')
})
it('Shiki 高亮开启时代码块带着色 span', async () => {
const html = await markdownToExportHtml('```ts\nconst v: number = 42;\n```\n', {
title: 't', themeId: 'default', imageMode: 'relative', renderMermaid: false,
})
expect(html).toContain('style="color:')
expect(html).toContain('42')
})
it('图片内嵌模式用 resolveImage 替换为 data URL', async () => {
const html = await markdownToExportHtml('![示意图](文章.assets/a.png)\n', {
title: 't', themeId: 'default', imageMode: 'embed', highlight: false, renderMermaid: false,
resolveImage: async (src) => (src.endsWith('a.png') ? 'data:image/png;base64,QUJD' : null),
})
expect(html).toContain('src="data:image/png;base64,QUJD"')
})
})
describe('导入转换', () => {
it('HTML 转 Markdown 保留标题与列表', async () => {
const md = await htmlToMarkdown('<h1>标题</h1><ul><li>第一项</li><li>第二项</li></ul><p>正文<strong>加粗</strong></p>')
expect(md).toContain('# 标题')
expect(md).toMatch(/-\s+第一项/)
expect(md).toContain('**加粗**')
})
})