修复“粘贴 Markdown 无格式”的实践笔记
68a9bf5027bd2d30529618d02025/9/5约 13 分钟阅读
在自建博客中从 Typora/网页粘贴内容时,只显示成普通文字;而在 Typora 中能保留标题、列表、代码块。本文记录问题成因与最小可行修复。
背景与问题
- 根因:大多数应用复制的是 HTML(
text/html
),而textarea
只能接收纯文本。直接粘贴会丢失格式,ReactMarkdown
也就无法按 Markdown 渲染。 - 现状:我们已用
ReactMarkdown + remark-gfm
做预览,但编辑输入源没有转换步骤。
解决思路
- 接管粘贴事件:在
textarea
的onPaste
中读取剪贴板的text/html
,若存在则用 Turndown 将 HTML 转为 Markdown,再插回光标处。 - 保持预览链路不变:
content
始终保存 Markdown 字符串,预览继续走ReactMarkdown
。
关键实现
- 安装依赖
npm i turndown
npm i -D @types/turndown
- 在编辑页中初始化 Turndown,并接管粘贴
import TurndownService from 'turndown';
const turndownRef = useRef<TurndownService | null>(null);
if (!turndownRef.current) {
turndownRef.current = new TurndownService({
headingStyle: 'atx',
codeBlockStyle: 'fenced',
});
}
<textarea
ref={textareaRef}
value={content}
onChange={e => setContent(e.target.value)}
onPaste={e => {
const html = e.clipboardData?.getData('text/html');
const plain = e.clipboardData?.getData('text');
if (html) {
e.preventDefault();
const md = turndownRef.current!.turndown(html);
const ta = textareaRef.current!;
const { selectionStart: s, selectionEnd: t } = ta;
const next = content.slice(0, s) + md + content.slice(t);
setContent(next);
setTimeout(() => ta.setSelectionRange(s + md.length, s + md.length), 0);
} else if (plain?.includes('\t')) {
// 粘贴纯文本时的小优化:将 Tab 替换为两个空格,避免列表缩进错位
e.preventDefault();
const fixed = plain.replace(/\t/g, ' ');
const ta = textareaRef.current!;
const { selectionStart: s, selectionEnd: t } = ta;
const next = content.slice(0, s) + fixed + content.slice(t);
setContent(next);
setTimeout(() => ta.setSelectionRange(s + fixed.length, s + fixed.length), 0);
}
}}
/>
- 预览(保持不变)
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
{content}
</ReactMarkdown>
核心知识点
- 剪贴板 MIME 类型:浏览器粘贴事件可读多种类型,常见为
text/html
与text/plain
。优先取text/html
再降级。 textarea
限制:不支持富文本,必须“先转换后插入”。若需要更复杂排版,应使用contenteditable
或富文本编辑器。- Turndown 的作用:将 HTML 结构“降级”为 Markdown;可通过规则定制(如表格、代码块样式)。
- 光标与选区恢复:粘贴后需手动设置
setSelectionRange
,保证编辑体验流畅。 - 安全性:你启用了
rehypeRaw
,允许渲染原始 HTML。上线建议搭配rehype-sanitize
或移除rehypeRaw
,避免 XSS。 - 用户体验:保留“纯文本粘贴”路径(如 Shift+Ctrl+V),并对制表符做最小替换,降低格式错位。
小结
通过“粘贴事件拦截 + Turndown 转换”,把来自 Typora/网页的 HTML 在落入编辑器前就转为 Markdown,从而让内容源与预览渲染保持一致,最终解决了“粘贴后无格式”的问题。