☰
在线Markdown编辑工具全链路:从选型到导出Word的工程实践
2026/9/30 5:36:27 网站建设 项目流程

简介:这是一套基于 Python、Flask 与 Editor.md 构建的在线 Markdown 编辑工具源码,面向具备 Flask 基础、希望学习或直接搭建在线编辑平台的开发者。项目整合了 Flask-SQLAlchemy、Flask-Login 与 sm.ms 图床,实现登录注册、文章编辑与文章列表三个页面,并支持自动保存、图片上传至图床等功能,适合作为 Web 开发练手项目或轻量级写作工具直接部署。压缩包共 559 个文件,约 15.07MB,以 208 个 js、170 个 html、60 个 css 等前端资源为主,另有 14 个 py 后端文件、8 个 md 说明文档及图片、字体、配置等辅助文件,并附有 readme.md 教程。目前已有 447 人学习。读者可从中获取完整的项目目录结构、前后端交互逻辑与图床集成思路,便于理解 Flask 项目的组织方式并在此基础上二次开发。

1. 在线 Markdown 编辑工具:从「打开就能写」到「写完能交付」的完整链路

很多人第一次接触在线 Markdown 编辑工具,是因为临时要写一份技术文档,本地没装 Typora,或者公司电脑不让随便装软件。打开浏览器,找个在线编辑器,左边写# 标题,右边实时渲染,感觉挺顺手。但真正把它用进日常工作流之后,问题就来了:换行怎么不生效?表格怎么复制到 Excel?图片路径为什么在别人电脑上打不开?数学公式渲染出来是乱码?这些坑,几乎每个长期用 Markdown 的人都会踩一遍。

在线 Markdown 编辑工具的核心价值,不是「能写 Markdown」——本地编辑器也能写。它的真正优势在于:跨设备、免安装、可协作、能直接对接发布流程。你可以在公司 Windows 上写一半,回家用 Mac 继续改;可以把链接甩给同事,对方不用装任何东西就能看渲染效果;可以直接导出 Word、PDF,或者复制到公众号后台。这篇文章面向的是需要把 Markdown 用进实际交付流程的工程师和文档写作者,不是只想知道「Markdown 是什么」的纯新手。我会从选型、核心功能实现、避坑、进阶技巧四个层面,把在线 Markdown 编辑工具这条链路讲透。

2. 在线 Markdown 编辑工具的技术选型:为什么不是随便找一个就行

2.1 渲染引擎决定了下限:marked、markdown-it 还是 remark

在线编辑器的第一层是渲染引擎。你看到的「左边写、右边变」效果,背后是某个 Markdown 解析库在干活。常见的有三类:

  • marked:轻量、快,GitHub 早期也在用。适合对性能敏感、不需要复杂扩展的场景。但它对表格、脚注、数学公式的原生支持较弱,需要自己挂插件。
  • markdown-it:插件生态最丰富,支持表格、任务列表、脚注、自定义容器、数学公式(通过 markdown-it-katex 或 markdown-it-mathjax3)。大多数在线编辑器选它,因为「什么都能加」。
  • remark / rehype:基于 AST,适合需要深度定制输出结构的场景,比如你要把 Markdown 转成特定 JSON 给前端渲染,或者做 lint 规则。学习曲线比前两个陡。

选型建议很直接:如果你只是做一个「能写能预览」的工具,markdown-it 是默认答案。如果你要做「Markdown 转 Word 并保留序号自动编号」这种深度转换,remark 的 AST 操作会更可控。

2.2 编辑器内核:CodeMirror、Monaco 还是 textarea

渲染引擎管「怎么解析」,编辑器内核管「怎么写」。三种常见方案:

内核优势劣势适用场景
textarea零依赖、极简无语法高亮、无快捷键扩展极简工具、嵌入页面
CodeMirror 6轻量、移动端友好、扩展性好生态比 Monaco 小大多数在线编辑器
MonacoVSCode 同款、功能最强体积大、移动端体验差桌面端优先的复杂编辑器

我一般会选 CodeMirror 6。它在移动端能正常输入,体积可控,而且有现成的 Markdown 语言包和快捷键扩展。Monaco 虽然功能强,但在线工具如果面向「打开就能写」的场景,加载一个几 MB 的编辑器内核会让首屏体验变差。

2.3 存储与同步:localStorage、IndexedDB 还是后端

在线编辑器最怕的是「写了一半,刷新没了」。存储方案分三档:

  • localStorage:简单,但容量只有 5MB 左右,且同步阻塞。适合存草稿,不适合存大量文档。
  • IndexedDB:容量大、异步,适合存多篇文档和图片 blob。但 API 复杂,通常用 idb 或 Dexie.js 封装。
  • 后端存储:适合协作场景,但需要处理用户体系、权限、冲突合并。

一个务实的做法是:本地用 IndexedDB 做自动保存,后端只存「用户主动保存」的版本。这样即使断网,也不会丢内容。

2.4 最小可运行版本:用 markdown-it + CodeMirror 6 搭一个在线编辑器

下面是一个可以直接跑起来的最小实现。用 Vite 起项目,装两个核心依赖:

npm create vite@latest online-md-editor -- --template vanilla cd online-md-editor npm install markdown-it codemirror @codemirror/lang-markdown @codemirror/view @codemirror/state

然后写主逻辑:

// main.js import MarkdownIt from 'markdown-it'; import { EditorView, basicSetup } from 'codemirror'; import { markdown } from '@codemirror/lang-markdown'; // 初始化 markdown-it,开启表格和换行支持 const md = new MarkdownIt({ html: false, // 不渲染原始 HTML,防止 XSS linkify: true, // 自动识别链接 breaks: true, // 单个换行转 <br>,解决 markdown换行 问题 typographer: true, // 智能标点 }); // 左侧编辑器 const editor = new EditorView({ doc: '# 标题\n\n开始写...', extensions: [basicSetup, markdown()], parent: document.querySelector('#editor'), }); // 右侧预览:监听编辑器变化,实时渲染 editor.dispatch({ changes: { from: 0, insert: '' }, }); // 简单轮询同步(生产环境应使用 updateListener) setInterval(() => { const content = editor.state.doc.toString(); document.querySelector('#preview').innerHTML = md.render(content); }, 300);

这段代码的逻辑说明:

  • breaks: true是解决「markdown换行」问题的关键。默认 Markdown 规则里,单个换行会被合并成空格,必须空一行才换段。开启后,单换行直接转<br>,符合大多数人在线写作的直觉。
  • html: false是安全底线。在线编辑器如果允许渲染原始 HTML,别人可以注入脚本。除非你完全信任输入来源,否则不要开。
  • 轮询同步只是演示。实际项目里应该用 CodeMirror 的EditorView.updateListener扩展,在文档变化时触发渲染,避免不必要的重绘。

参数怎么改:

  • 如果要支持数学公式,加markdown-it-katex插件,并在页面引入 KaTeX 的 CSS。
  • 如果要支持 Mermaid 图表,加markdown-it-mermaid或自己写一个 fence 规则,把```mermaid块转成<div class="mermaid">。
  • 如果要支持表格复制到 Excel,需要在渲染后的<table>上挂一个复制按钮,把表格转成 TSV 格式写入剪贴板。

3. 在线 Markdown 编辑工具的核心功能实现:表格、图片、公式、导出

3.1 Markdown 表格转 Excel:复制粘贴背后的 TSV 转换

「markdown表格复制」是高频需求。很多人写完表格,想直接粘到 Excel 或飞书表格里,结果粘过去是一坨文本。原因是剪贴板里放的是 HTML 或纯文本,Excel 不认。

正确做法是:在渲染后的表格上加一个「复制为表格」按钮,点击时把<table>转成 TSV(Tab 分隔值),然后写入剪贴板。

function tableToTSV(table) { const rows = table.querySelectorAll('tr'); const lines = []; rows.forEach(row => { const cells = row.querySelectorAll('th, td'); const values = Array.from(cells).map(cell => { // 去掉单元格内的换行和多余空格,避免破坏 TSV 结构 return cell.innerText.replace(/\n/g, ' ').trim(); }); lines.push(values.join('\t')); }); return lines.join('\n'); } async function copyTable(btn) { const table = btn.closest('table'); const tsv = tableToTSV(table); await navigator.clipboard.writeText(tsv); btn.innerText = '已复制'; setTimeout(() => btn.innerText = '复制为表格', 1500); }

逻辑说明:TSV 是 Excel 和大多数表格软件都能识别的纯文本格式。用\t分隔列,\n分隔行。关键点是单元格内的换行必须替换成空格,否则粘贴到 Excel 会错行。

参数注意:如果表格里有合并单元格,TSV 无法表达,需要降级为 HTML 格式写入剪贴板。但大多数 Markdown 表格没有合并单元格,TSV 足够。

3.2 图片路径的三种处理方式:相对路径、Base64、图床

「markdown图片路径」是在线编辑器最容易翻车的地方。你在本地写![图](./images/a.png),本地预览正常,但把 Markdown 发给别人,图片全挂。

三种方案对比:

方案写法优点缺点
相对路径![图](./images/a.png)简单、可版本管理换设备就挂
Base64 内嵌![图](data:image/png;base64,...)单文件自包含文件体积暴涨、编辑器卡顿
图床 URL![图](https://图床/xxx.png)跨设备可用依赖外部服务、可能失效

在线编辑器的常见做法是:粘贴图片时自动上传到图床,然后把 Markdown 里的路径替换成返回的 URL。如果不想依赖图床,可以在导出时把图片转成 Base64 内嵌,但只建议对小图这么做。

// 粘贴图片时读取文件并转 Base64 插入 editor.dom.addEventListener('paste', async (e) => { const items = e.clipboardData.items; for (const item of items) { if (item.type.startsWith('image/')) { const file = item.getAsFile(); const reader = new FileReader(); reader.onload = () => { const base64 = reader.result; const pos = editor.state.selection.main.head; editor.dispatch({ changes: { from: pos, insert: `![图片](${base64})` } }); }; reader.readAsDataURL(file); } } });

注意:Base64 图片会让 Markdown 文件变得很大,一篇带十几张图的文档可能超过 10MB。在线编辑器如果自动保存到 IndexedDB,大文件会导致保存变慢。建议超过 200KB 的图片走上传流程,不内嵌。

3.3 数学公式与 Mermaid:插件接入的两种方式

「markdown数学公式插件」和「markdown preview mermaid support」是在线编辑器拉开差距的地方。

数学公式用 KaTeX 比 MathJax 快,渲染质量也够。接入方式:

import markdownItKatex from 'markdown-it-katex'; md.use(markdownItKatex);

然后在页面引入 KaTeX 的 CSS:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">

Mermaid 的接入稍微麻烦一点,因为 Mermaid 是异步渲染的。思路是:在 markdown-it 里把```mermaid块渲染成<div class="mermaid">,然后在预览更新后调用mermaid.run()。

import mermaid from 'mermaid'; mermaid.initialize({ startOnLoad: false }); // 自定义 fence 规则 const defaultFence = md.renderer.rules.fence; md.renderer.rules.fence = (tokens, idx, options, env, self) => { const token = tokens[idx]; if (token.info.trim() === 'mermaid') { return `<div class="mermaid">${token.content}</div>`; } return defaultFence(tokens, idx, options, env, self); }; // 预览更新后重新渲染 Mermaid async function renderPreview(content) { document.querySelector('#preview').innerHTML = md.render(content); await mermaid.run({ querySelector: '.mermaid' }); }

参数说明:mermaid.initialize里的startOnLoad: false必须设,否则 Mermaid 会在页面加载时自动扫描,和我们的手动调用冲突。mermaid.run每次都会重新渲染所有.mermaid元素,文档很大时会有性能问题,可以只渲染新增的节点。

3.4 导出 Word 与 PDF:序号自动编号的坑

「markdown转word工作流」是很多人的最终交付需求。在线编辑器如果只能预览不能导出,价值少一半。

导出 Word 的常见做法是:把 Markdown 转成 HTML,再用html-docx-js或docx库生成.docx。但这里有一个大坑:Word 的自动编号和 Markdown 的有序列表是两套逻辑。

Markdown 里写:

1. 第一步 2. 第二步 3. 第三步

转成 HTML 是<ol><li>第一步</li>...</ol>。如果直接把这个 HTML 塞进 Word,Word 会把它当成普通段落,序号是纯文本,不会自动编号。如果你在 Word 里删掉中间一项,后面的序号不会自动更新。

要保留 Word 的自动编号,需要在生成 docx 时使用 Word 的 numbering 配置。用docx库的话,需要定义numbering配置:

import { Document, Paragraph, TextRun, Numbering } from 'docx'; const numbering = new Numbering({ config: [{ reference: 'my-numbering', levels: [{ level: 0, format: 'decimal', text: '%1.', alignment: 'start', }], }], }); const doc = new Document({ numbering, sections: [{ children: [ new Paragraph({ text: '第一步', numbering: { reference: 'my-numbering', level: 0 }, }), new Paragraph({ text: '第二步', numbering: { reference: 'my-numbering', level: 0 }, }), ], }], });

这样生成的 Word 文档,序号是真正的自动编号,删掉一项后面的会自动更新。代价是代码复杂度上升,需要把 Markdown 的列表结构解析成对应的 Paragraph 数组。

如果只是偶尔导出,不想写这么复杂,可以用 Pandoc 做服务端转换。Pandoc 对 Markdown 到 docx 的序号处理已经比较成熟,但需要后端环境。

4. 在线 Markdown 编辑工具避坑:5 个血泪教训

4.1 换行不生效:breaks 参数没开

现象:在编辑器里写了两行,预览时变成一行。

原因:CommonMark 规范里,单个换行是「软换行」,渲染成空格。必须空一行才是新段落。

解决:markdown-it 初始化时设breaks: true。但要注意,开了之后所有单换行都变<br>,如果你写的是英文段落,可能会觉得行距太密。折中方案是:只在中文场景开,或者提供开关让用户自己选。

4.2 表格粘贴到 Excel 错行:单元格里有换行

现象:复制 Markdown 表格到 Excel,本来 3 列的数据变成了 6 列。

原因:某个单元格里写了多行文本,TSV 转换时没有把换行替换掉,Excel 把换行当成了新行。

解决:在tableToTSV里对每个单元格做replace(/\n/g, ' ')。如果单元格内容必须保留换行,那就不能用 TSV,改用 HTML 格式写剪贴板。

4.3 图片路径在别人电脑上打不开:用了本地绝对路径

现象:自己电脑上预览正常,发给同事后图片全是裂图。

原因:Markdown 里写的是![图](C:\Users\xxx\images\a.png)或./images/a.png,对方没有这个文件。

解决:在线编辑器应该默认把粘贴的图片转成 Base64 或上传图床。如果用户手动写路径,在导出时提示「检测到本地路径,是否转为内嵌图片」。

4.4 Mermaid 渲染后代码块还在:没有替换原始内容

现象:预览区同时出现了 Mermaid 图表和它的源码。

原因:自定义 fence 规则时,只返回了<div class="mermaid">,但 markdown-it 可能还保留了原始 token 的渲染。

解决:确保md.renderer.rules.fence里对 mermaid 分支直接return,不要调用self.renderToken。另外,Mermaid 渲染是异步的,如果预览更新频繁,可能会出现「旧图还没渲染完,新内容已经替换」的情况。加一个防抖,或者用mermaid.run的 Promise 做队列。

4.5 数学公式显示为源码:KaTeX CSS 没加载

现象:$E=mc^2$渲染出来还是$E=mc^2$,没有变成公式。

原因:markdown-it-katex 只负责生成 HTML 结构,真正的排版靠 KaTeX 的 CSS 和字体文件。如果 CSS 没引入,或者 CDN 被墙,公式就是一堆乱码。

解决:确保<link rel="stylesheet" href="...katex.min.css">在页面里,并且字体文件路径正确。如果面向国内用户,建议把 KaTeX 的 CSS 和字体下载到本地,不要依赖 CDN。

5. 进阶:把在线编辑器变成「写完就能发」的交付工具

5.1 公众号格式化:从 Markdown 到微信后台的一键复制

「公众号文章markdown格式化」是一个很实际的需求。微信公众号后台不认 Markdown,只认富文本。如果你用在线编辑器写完,直接复制预览区的 HTML 到公众号后台,样式会丢。

一个可行的做法是:在预览区渲染时,给所有元素加上内联样式。因为公众号后台会过滤<style>标签,但保留style属性。

function inlineStyles(html) { const div = document.createElement('div'); div.innerHTML = html; div.querySelectorAll('h1, h2, h3, p, li, blockquote, code, pre').forEach(el => { const tag = el.tagName.toLowerCase(); const styles = { h1: 'font-size: 24px; font-weight: bold; margin: 20px 0 10px;', h2: 'font-size: 20px; font-weight: bold; margin: 18px 0 8px;', p: 'font-size: 16px; line-height: 1.8; margin: 10px 0;', code: 'background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-family: monospace;', pre: 'background: #f5f5f5; padding: 12px; border-radius: 6px; overflow-x: auto;', blockquote: 'border-left: 4px solid #ddd; padding-left: 12px; color: #666; margin: 10px 0;', }; if (styles[tag]) el.setAttribute('style', styles[tag]); }); return div.innerHTML; }

逻辑说明:公众号后台会保留style属性,但会过滤掉<style>标签和 class。所以必须把样式内联到每个元素上。代码块还要注意,公众号不支持<pre>里的语法高亮,只能保留纯文本。

5.2 验证导出效果:三个必须检查的点

导出功能写完,怎么验证它真的能用?我一般会检查三个点:

  1. 序号是否自动更新:在 Word 里删掉中间一个列表项,看后面的序号有没有自动变。如果没变,说明用的是纯文本序号,需要改 numbering 配置。
  2. 图片是否内嵌:把导出的 docx 发给另一台电脑,看图片能不能显示。如果裂图,说明图片还是外链。
  3. 表格是否可编辑:在 Word 里点表格,看是不是真正的表格对象。如果是图片或纯文本,说明转换时丢了结构。

5.3 一个我常用的习惯:导出前先跑一遍 lint

Markdown 写多了,难免有语法错误。比如表格分隔行少了一列、链接括号没闭合、代码块没写语言。这些小问题在预览时可能看不出来,但导出后就会暴露。

我的习惯是在导出前跑一遍markdownlint。在线编辑器可以集成markdownlint的浏览器版本,在预览区上方显示警告。这样用户在导出前就能发现「表格列数不一致」「标题级别跳跃」这类问题,减少返工。

import markdownlint from 'markdownlint'; import markdownlintRuleHelpers from 'markdownlint-rule-helpers'; const result = markdownlint.sync({ strings: { content: editor.state.doc.toString() }, config: { default: true, MD013: false, // 关闭行长度限制,在线编辑器不适用 }, }); // result.content 是警告数组,渲染到预览区上方

这个习惯帮我省了很多「导出后才发现格式乱」的后悔药。在线编辑器如果能在用户点「导出」之前就把问题指出来,体验会好很多。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询