import { marked, type MarkedOptions } from 'marked'; /** * Markdown 转 HTML 转换结果 */ export interface MarkdownToHtmlResult { /** 转换后的 HTML 字符串 */ html: string; /** 原始 Markdown 文本长度 */ originalLength: number; /** 生成的 HTML 长度 */ htmlLength: number; /** 是否包含错误 */ hasError: boolean; /** 错误信息(如果有) */ error?: string; } /** * 默认的 Markdown 渲染选项配置 */ const defaultMarkedOptions: MarkedOptions = { gfm: true, breaks: true, }; /** * 将 Markdown 文本转换为 HTML * * @param markdown - Markdown 源文本 * @returns MarkdownToHtmlResult 转换结果 */ export function markdownToHtml(markdown: string): MarkdownToHtmlResult { try { const trimmed = markdown.trim(); if (!trimmed) { return { html: '', originalLength: 0, htmlLength: 0, hasError: false, }; } const html = marked.parse(trimmed, { ...defaultMarkedOptions, async: false }); return { html: html.trim(), originalLength: markdown.length, htmlLength: html.length, hasError: false, }; } catch (error) { return { html: '', originalLength: markdown.length, htmlLength: 0, hasError: true, error: error instanceof Error ? error.message : 'Markdown 解析失败', }; } } /** * 为 HTML 内容包装完整的文档结构(用于打印/下载) * * @param html - 主体 HTML 内容 * @param title - 文档标题 * @returns 完整的 HTML 文档字符串 */ export function wrapHtmlDocument(html: string, title: string = 'Markdown Export'): string { return ` ${escapeHtml(title)} ${html} `; } /** * 转义 HTML 特殊字符 */ function escapeHtml(text: string): string { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } /** * 下载 HTML 文件 * * @param content - 文件内容 * @param filename - 下载文件名 */ export function downloadHtmlFile(content: string, filename: string = 'export.html'): void { const blob = new Blob([content], { type: 'text/html;charset=utf-8' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); } /** * 打印 HTML 内容 * * @param html - 要打印的 HTML 内容 * @param title - 打印窗口标题 */ export function printHtml(html: string, title: string = 'Markdown Preview'): void { const doc = wrapHtmlDocument(html, title); const blob = new Blob([doc], { type: 'text/html;charset=utf-8' }); const url = URL.createObjectURL(blob); const printWindow = window.open(url, '_blank', 'width=800,height=600'); if (!printWindow) { URL.revokeObjectURL(url); console.error('无法打开打印窗口,请检查浏览器弹窗拦截设置'); return; } // 等待样式加载完成后打印 let printed = false; printWindow.onload = () => { if (!printed) { printed = true; printWindow.print(); } }; // 部分浏览器 onload 不触发,使用延迟回退 setTimeout(() => { if (!printed) { printed = true; printWindow.print(); } }, 500); // 打印完成后释放 Blob URL(浏览器标签页关闭后也会自动回收) setTimeout(() => { URL.revokeObjectURL(url); }, 60000); } /** * 示例 Markdown 文本(用于占位提示) */ export const SAMPLE_MARKDOWN = `# 欢迎使用 Markdown 转 HTML 这是一个 **Markdown** 编辑器,支持实时预览。 ## 基础语法 ### 标题 使用 \`#\` 符号表示不同级别的标题。 ### 列表 - 无序列表项 1 - 无序列表项 2 - 嵌套列表项 1. 有序列表项 1 2. 有序列表项 2 ### 文本样式 - **粗体文本** - *斜体文本* - ~~删除线文本~~ - \`行内代码\` ### 链接与图片 [访问 GitHub](https://github.com) ### 代码块 \`\`\`javascript function hello() { console.log('Hello, World!'); } \`\`\` ## 扩展语法 ### 表格 | 名称 | 类型 | 描述 | |------|------|------| | name | string | 用户名 | | age | number | 年龄 | ### 任务列表 - [x] 已完成任务 - [ ] 待办任务 ### 引用 > 这是一段引用文本。 > 可以有多行。 ### 分割线 --- *开始编辑你的 Markdown 内容吧!*`;