AI 消息“复制纯文本”:复制时剥掉 Markdown 标记
页面上的“复制”按钮今天点下去,粘贴出来的东西带着一身 Markdown 标记:**加粗**、# 标题、反引号、链接的 [文字](地址),全都原样跟过来了。用户想要的其实很简单:复制一段能直接贴进微信、备忘录、文档里的干净文字。这篇写的是待做方案,不是已上线功能的复盘,先把这句话说在前面——v3 里这个功能还没做。
现状:复制的是原文,不是看到的文字
先看现在页面上的真实状态。AI 消息下面的“复制”走的是 copyMessage:
copyMessage(e) {
const idx = Number(e.currentTarget.dataset.index);
const m = this.data.messages[idx];
if (!m || !m.text || m.typing) return;
wx.setClipboardData({ data: m.text });
}
它复制的是 m.text,也就是服务端返回的 Markdown 原文。页面上渲染出来是漂亮的加粗标题和列表,剪贴板里却是另一副样子:
## 今日安排
- **重点**:先做 `checkAlive` 的续接
- 参考 [成长中心](https://developer.huaweicloud.com/grow)
贴到微信里,对方看到的就是这堆符号。更别扭的是,这个复制还没有任何反馈——同样是复制,代码块的 copyCode 和链接的 openLink 复制完都会弹一句“代码已复制”“链接已复制”,copyMessage 复制完什么都不说,用户点完只能自己去别处粘贴验证。
顺带说一句,页面上另外两个复制不是这个问题:copyPrompt 复制的是提示词卡片的正文,copyCode 复制的是整段代码——代码本来就该原样复制,标记就是内容。需要剥标记的,只有 AI 消息正文这一种。
第一难:别用正则去剥 Markdown
最直觉的做法是写几行正则,把 **、#、反引号替换掉。但 Markdown 的标记不是平铺的符号,它是嵌套结构,正则一遇到下面这几种情况就翻车:
- 嵌套格式:
**加粗里带代码**,先剥哪个都会留一半; - 链接:
[文字](地址),想要的是“文字”,正则容易把地址也留下来,或者把括号吃错; - 代码围栏:围栏里的
**不是标记,是代码本身,剥了就改了代码; - 表格:
| a | b |剥掉竖线之后,两列会粘成一串,分不清谁是谁。
这些坑,md.js 在解析的时候其实已经一个个处理过了:围栏信息串宽松匹配、U+2028/2029 当换行、转义占位符、解析循环兜底前进。它输出的是一棵现成的结构——parseMarkdown 返回 blocks[],块分标题、段落、代码、表格、列表,块里再分 parseInlines 的行内片段。剥标记最稳的做法不是再写一套正则,而是沿着这棵已经解析好的结构,把每一段的文字取出来。
方案:顺着 blocks 走一遍,只取文字
思路是给 md.js 再加一个导出函数,比如 stripMarkdown(text),内部先调已有的 parseMarkdown,再按块类型取文本:
- 标题、段落:把行内片段的文字拼起来,标记自然就没了;
- 代码块:原样保留代码内容——代码里的符号不是标记,不能动;
- 列表:每项取文字,项之间换行,序号或圆点可以保留一个简单的
-; - 表格:每行各单元格的文字用空格或制表符隔开,行与行换行,竖线不要了;
- 链接:只取链接文字,地址丢掉;图片:取 alt 文字,地址丢掉;
- 行内代码:取反引号里的原文,不加反引号。
这样剥出来的文本,和页面上看到的文字是一致的,因为它们来自同一棵解析树。解析器以后修了什么边界情况,剥标记跟着受益,不用两头维护。
调用点只需要改一行:copyMessage 里把 m.text 换成剥完的结果:
wx.setClipboardData({ data: stripMarkdown(m.text) });
再顺手补上 copyCode 同款的成功提示,复制完弹一句“已复制”,用户就不用再跑到别处验证。失败和生成中的守卫不用动:m.typing 时本来就不给复制,失败消息没有正文可复制。
一个要先定下来的选择:覆盖“复制”,还是另加一个入口
这里有个产品选择,得先定下来再动手:
- 方案 A:把现在的“复制”直接改成复制纯文本。入口不增加,用户不用学新东西;代价是想复制 Markdown 原文的人(比如要贴回编辑器继续改)没路了。
- 方案 B:保留“复制”(原文),旁边再加一个“复制纯文本”。两条路都有,代价是消息下面的操作区多一个按钮,本来就已经有复制和重试了,再挤会显得乱。
我的倾向是 A。理由有两个:第一,大多数用户点“复制”的时候,想要的就是贴出去能直接读的文字,原文标记对他们是噪音;第二,代码块已经有单独的“复制代码”,真需要原文的人,大段内容基本都在代码块里,损失很小。如果以后真有人要 Markdown 原文,再加长按菜单也不迟。
落地清单:一个函数、一个调用点
md.js新增stripMarkdown:复用parseMarkdown的 blocks 结构,按块类型取文本,链接只取文字、代码原样保留;copyMessage改传剥完的文本,补成功 Toast;- 兜底:剥完为空(比如消息只剩一张图)时回退到原文,别让用户复制到一个空剪贴板;
- 自测三组:嵌套格式加链接的段落、带代码围栏的回答、带表格的回答,粘贴到微信和备忘录里看一眼是不是干净文本。
满打满算就是一个函数加一个调用点,难的不是代码量,是想清楚“剥标记”这件事该由谁来做——答案是已经把 Markdown 吃透了的解析器,而不是新写的一堆正则。
- 点赞
- 收藏
- 关注作者
评论(0)