新闻详情

新闻详情

首页 / 资讯中心 / 详情

BlockNote 剪贴板复制测试深度解析:text/plain 快照如何保证嵌套块复制后的 Markdown 输出

发布时间:2026/9/25 8:22:56来源:尧图网络
BlockNote 剪贴板复制测试深度解析:text/plain 快照如何保证嵌套块复制后的 Markdown 输出
前端富文本UI组件AI 应用【免费下载链接】BlockNoteA React Rich Text Editor thats block-based (Notion style) and extensible. Built on top of Prosemirror and Tiptap.项目地址https://gitcode.com/gh_mirrors/bl/BlockNote点击查看免费下载BlockNote 在复制/剪切时会在剪贴板写入三种数据格式blocknote/html内部往返格式、text/html供富文本应用粘贴以及text/plain供纯文本与 Markdown 应用粘贴。本文以tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/multipleChildren.md这份快照文件为入口拆解 BlockNote 复制嵌套子块时text/plain输出的 Markdown 语义并沿测试用例、执行器与序列化源码的调用链还原从选区到剪贴板纯文本的完整实现原理。读完你既能读懂这套快照测试的断言方式也能理解 BlockNote 复制功能的三格式剪贴板架构与 Markdown 序列化细节。快照文件的定位复制功能的 text/plain 期望输出multipleChildren.md是 BlockNote 单元测试体系中的一份文件快照file snapshot它记录的是多子块multiple children复制场景下写往剪贴板text/plain位置的预期内容。全文仅 5 行Nested Paragraph 1 Nested Paragraph 2 Nested Paragraph 3这段输出的含义是当用户复制一个父段落下的三个嵌套子段落时BlockNote 生成的纯文本是一个扁平的 Markdown 段落序列——每段文本之间以空行分隔既不保留嵌套层级缩进也不附加任何列表或缩进标记。这与text/html保留嵌套结构的行为形成鲜明对比是理解 BlockNote 剪贴板格式设计的关键细节。快照文件存放在 tests/src/unit/core/clipboard/copy/snapshots/text/plain/ 目录下与basicBlocks.md、styledText.md、tableRow.md等 20 份快照并列共同覆盖基础块、样式文本、表格、图片、代码块等复制场景的纯文本输出契约。对应测试用例multipleChildren 的文档结构与选区快照并非凭空生成它由 copyTestInstances.ts 中名为multipleChildren的用例驱动。该用例以PartialBlock树描述测试文档一个父段落paragraph内容 Paragraph 1其下嵌套三个子段落内容分别为 Nested Paragraph 1、Nested Paragraph 2、Nested Paragraph 3。选区由getCopySelection函数构造它借助getPosOfTextNode定位文本节点位置const startPos getPosOfTextNode(doc, Nested Paragraph 1); const endPos getPosOfTextNode(doc, Nested Paragraph 3, true); return TextSelection.create(doc, startPos, endPos);也就是说测试模拟的是用户从第一个嵌套段落的开头框选到第三个嵌套段落的结尾这一真实操作选区内恰好包含三个兄弟子块BlockNote 中嵌套块以blockGroup包裹它们都是父blockContainer的 children。选区起点与终点均由TextSelection描述属于纯文本选区而非节点选区。同一文档结构在 copyTestInstances.ts 中被childToParent、partialChildToParent、childrenToNextParent等用例复用分别验证跨父子层级、部分选区、跨到下一个父块等不同边界的复制行为multipleChildren专注验证纯子块序列这一最朴素的情形。执行器快照如何被断言快照比对发生在执行器 copyTestExecutors.ts 的testCopyMarkdown中export const testCopyMarkdown async B, I, S( editor: BlockNoteEditorB, I, S, testCase: CopyTestCaseB, I, S, ) { initTestEditor(editor, testCase.document, testCase.getCopySelection); const { markdown } selectedFragmentToHTML(editor.prosemirrorView, editor); await expect(markdown).toMatchFileSnapshot( ./__snapshots__/text/plain/${testCase.name}.md, ); };流程分三步initTestEditor载入用例文档并应用getCopySelection构造的选区调用selectedFragmentToHTML从 ProseMirror 视图中取出markdown字段用toMatchFileSnapshot将实际输出与__snapshots__/text/plain/multipleChildren.md逐字符比对不匹配即测试失败。值得注意的是copyTestInstancesMarkdown是通过copyTestInstancesHTML.map(...)映射生成的——即所有 HTML 用例的选区与文档完全复用只是执行器换成testCopyMarkdown从而保证两种剪贴板格式在相同选区下各自拥有一份独立契约HTML 快照在__snapshots__/text/html/纯文本快照在__snapshots__/text/plain/。快照文件以.md后缀存储表明text/plain位置的内容并非原始选区文本而是 GFM 兼容的 Markdown。底层原理selectedFragmentToHTML 的三格式产出selectedFragmentToHTML是复制功能的枢纽实现在 copyExtension.ts。它一次返回三个字段return { clipboardHTML, externalHTML, markdown };clipboardHTML使用 ProseMirror 默认的view.serializeForClipboard(...)保留完整内部结构与属性供 BlockNote 内部粘贴往返MIME 类型为blocknote/htmlexternalHTML通过fragmentToExternalHTML将选区转换为面向外部应用的语义化 HTMLMIME 类型为text/htmlmarkdown即本快照断言的目标MIME 类型为text/plain。三者的写入发生在copyToClipboard中copyExtension.tsevent.clipboardData!.setData(blocknote/html, clipboardHTML); event.clipboardData!.setData(text/html, externalHTML); event.clipboardData!.setData(text/plain, markdown);这段逻辑挂在 ProseMirror 插件的handleDOMEvents.copy/cut上并在dragstart中同样写入dataTransfer。因此用户在 BlockNote 中执行复制、剪切或拖拽时剪贴板都会带上这三种格式粘贴回 BlockNote 用blocknote/html粘贴到 Word/Google Docs 等富文本应用用text/html粘贴到纯文本编辑器、终端或笔记软件则使用text/plain的 Markdown。markdown字段的生成在 copyExtension.tsconst isPurelyInsideCodeBlock $from.sameParent($to) parentBlockSpec?.implementation.meta?.code true; const markdown isPurelyInsideCodeBlock ? view.state.doc.textBetween($from.pos, $to.pos) : cleanHTMLToMarkdown(externalHTML);即仅当选区完整落在代码块内部时text/plain直接输出原始选中文本保留换行、不转义反引号确保代码块内容可原样回贴其余一切场景都走cleanHTMLToMarkdown(externalHTML)先把选区转成 external HTML再将该 HTML 转换为 Markdown。multipleChildren用例选区落在普通段落上因此走的是HTML → Markdown转换链路。Markdown 序列化链路从 external HTML 到 GFMcleanHTMLToMarkdown定义于 markdownExporter.ts它在转换前先移除 external HTML 导出器为保住空块而注入的占位字符EMPTY_BLOCK_PLACEHOLDER避免空段落粘贴后出现残留字符然后把干净的 HTML 交给htmlToMarkdown。真正的序列化器是 htmlToMarkdown.ts它没有采用 unified/rehype-remark 管线而是基于 DOM 的手写实现先用临时div解析 HTML浏览器与 JSDOM 环境通用再对节点树做递归序列化其中p标签输出为段落文本 \n\n见serializeParagraphh1–h6输出为#–######标题serializeHeadingul/ol输出*/1.列表serializeUnorderedList/serializeOrderedListpre输出带围栏的代码块serializeCodeBlocktable输出 GFM 表格serializeTablehr输出***。序列化上下文SerializeContext携带indent与inListItem两个状态前者用于列表嵌套缩进后者用于抑制列表项内连续段落产生的多余空行避免把紧凑列表意外变成宽松列表loose list。把这份输出契约放回multipleChildren.md即可对号入座三个嵌套段落对应的三个p节点各被序列化为一行文本加一个空行最终得到三段以空行分隔的扁平段落——快照正是serializeParagraph行为的直接证据。嵌套与扁平化的取舍与其他快照的横向对照把multipleChildren.md与同目录其他快照对照能更清楚地看出text/plain的策略是内容完整、结构扁平childToParent.md 输出Paragraph 1与Nested Paragraph 1两个扁平段落说明跨层级选区同样不保留嵌套层级childrenToNextParent.md 输出 4 个扁平段落3 个嵌套段落 下一个父块跨块边界同样被压平basicBlocks.md 则展示了完整语法覆盖# Heading 1、1.有序列表、*无序列表、* [ ]任务列表、javascript 围栏代码块、GFM 表格以及***分隔线。这些快照共同构成 text/plain 的黄金样本集嵌套关系在纯文本语境下没有等价表达因此 BlockNote 选择按文档顺序扁平展开而块类型标题、列表、代码、表格则通过标准 Markdown 语法保留。这保证用户把 BlockNote 内容粘贴进 Typora、Obsidian、GitHub Issue 或任意 Markdown 编辑器时得到的都是语义正确的文本而非带缩进的原始 HTML 或丢失结构的裸文本。小结一份 5 行快照背后的完整工程multipleChildren.md虽只有 5 行但它锚定了 BlockNote 复制功能的一条关键契约嵌套块复制到纯文本剪贴板时输出为扁平、空行分隔的 Markdown 段落。支撑这一契约的是完整的三格式剪贴板架构blocknote/htmltext/htmltext/plain、selectedFragmentToHTML的统一选区处理、代码块特殊分支以及 DOM 驱动的htmlToMarkdown序列化器。对于想要扩展 BlockNote 剪贴板行为或为其新增块类型贡献快照用例的开发者copyTestInstances.ts 与 copyTestExecutors.ts 是理解测试约定的最佳起点copyExtension.ts 与 htmlToMarkdown.ts 则是深入实现细节的必读源码。赞分享前端富文本UI组件AI 应用【免费下载链接】BlockNoteA React Rich Text Editor thats block-based (Notion style) and extensible. Built on top of Prosemirror and Tiptap.项目地址https://gitcode.com/gh_mirrors/bl/BlockNote点击查看免费下载相关推荐amis 边框宽度工具类全解析border / border-t / border-r 等 24 个 Class 的用法与 SCSS 实现原理amis 边框宽度工具类全解析border / border t / border r 等 24 个 Class 的用法与 SCSS 实现原理 导读 在 am前端富文本UI组件AI 应用Data-Science-For-Beginners 数据集分类作业详解从结构、价值到来源的三维度数据判定方法Data Science For Beginners 数据集分类作业详解从结构、价值到来源的三维度数据判定方法 本文以 Data Science For Be前端富文本UI组件AI 应用在 OpenSandbox 中运行 Claude CodePython SDK 注入 CLI、Headless 调用与会话恢复的完整实践在 OpenSandbox 中运行 Claude CodePython SDK 注入 CLI、Headless 调用与会话恢复的完整实践 本文以 OpenSa前端富文本UI组件AI 应用上一篇htty错误处理指南如何快速解决常见的HTTP交互问题 下一篇5分钟上手FarPlaneTwo从安装到实现100000方块渲染距离的完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

ESP32-S3桌面AI机器人实战:全双工语音与视觉多模态交互全解析 2026/9/25 8:51:52

ESP32-S3桌面AI机器人实战:全双工语音与视觉多模态交互全解析

EchoEar喵伴这个项目,实际做下来我最大的感受是:它表面上看是个桌面小玩具,本质上却是一道特别扎手的嵌入式工程题。要在ESP32-S3这颗MCU上同时搞定全双工语音交互、摄像头视觉采集、云端大模型对话,还要保证用户能随时打断机器人…

阅读更多 →
树莓派5 GPIO 5V引脚供电实操:方案选型、压力测试与避坑指南 2026/9/25 8:51:45

树莓派5 GPIO 5V引脚供电实操:方案选型、压力测试与避坑指南

这段时间身边好几个玩树莓派5的朋友都跑来问我同一个问题:能不能直接通过GPIO的5V引脚给板子供电?有的想把树莓派5塞进无人机或者小车里,不想带着原装Type-C电源线;有的是想省一个插座,从稳压模块直接拉电;…

阅读更多 →
ng-zorro-antd Affix(固钉)组件完全指南:从 API 配置到源码级实现原理 2026/9/25 8:51:39

ng-zorro-antd Affix(固钉)组件完全指南:从 API 配置到源码级实现原理

UI组件前端 【免费下载链接】ng-zorro-antd Angular UI Component Library based on Ant Design 项目地址: https://gitcode.com/gh_mirrors/ng/ng-zorro-antd 点击查看 免费下载 Affix(固钉)是 ng-zorro-antd 提供的页面固定组件&#xff0…

阅读更多 →
黏菌算法SMA优化SVM/SVR/LSSVM参数:回归预测调参实战 2026/9/25 8:51:39

黏菌算法SMA优化SVM/SVR/LSSVM参数:回归预测调参实战

玩SVM的朋友都知道,模型性能的下限靠数据,上限靠调参。尤其做回归预测时,惩罚参数c和核函数参数这两个参数一旦选不好,特征工程做得再漂亮也是白搭。我这边用的方案是黏菌算法SMA去自动搜索SVM、SVR还有LSSVM的惩罚参数c和核函数参…

阅读更多 →
制造经理的高效:从救火队长到系统管理员 2026/9/25 8:51:39

制造经理的高效:从救火队长到系统管理员

做了十几年制造经理,我对“高效”这个词越来越警惕。面试的时候老板爱问“你怎么理解效率”,很多人脱口就是“结果导向、执行力强、按计划走”,听着都对,但放到现场基本没用。真正的高效不是你自己一天处理了多少件事,…

阅读更多 →
天津法士特配件总成哪家好 瑞纳铂汽车配件省心之选 2026/9/25 8:51:13

天津法士特配件总成哪家好 瑞纳铂汽车配件省心之选

法士特配件总成选购核心逻辑:从原理到落地的避坑指南很多卡友、物流车队管理者在遇到变速箱、离合器总成故障时,最先头疼的就是法士特配件总成怎么选。作为商用车核心传动部件的关键耗材,法士特配件的品质直接决定了车辆出勤率和运维成本&…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉