BlockNote 剪贴板快照深度解析:跨父子边界选择(childrenToNextParent)的 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 仓库中 tests/src/unit/core/clipboard/copy/snapshots/text/plain/childrenToNextParent.md 这一测试快照文件为切入点围绕它所属的复制copy测试体系完整讲解 BlockNote 在用户选中嵌套子块 其后的兄弟父块这一跨父子边界范围时text/plain剪贴板数据Markdown 形式是如何生成的。读完本文你将掌握 BlockNote 复制测试的完整链路测试用例定义 → 编辑器初始化 → 选区构造 → 片段序列化 → 快照断言理解嵌套块复制时 Markdown 输出每块一行、块间空行分隔的底层原理并能够依据快照文件反推、验证 BlockNote 的复制行为。快照文件是什么一个 Markdown 剪贴板输出的黄金基准打开 childrenToNextParent.md文件内容非常简短Nested Paragraph 1 Nested Paragraph 2 Nested Paragraph 3 Paragraph 2它不是一个普通文档而是BlockNote 复制测试的期望输出快照snapshot当用户在编辑器内复制一段跨越嵌套层级的选择范围时写入系统剪贴板text/plain数据应恰好等于上述文本。快照文件由 vitest 的toMatchFileSnapshot断言自动比对见 copyTestExecutors.ts任何复制逻辑的改动导致输出变化测试都会失败并提示差异从而锁定剪贴板行为不被无意破坏。与它同目录的 HTML 快照 childrenToNextParent.html 记录了同一选择范围在text/html剪贴板中的数据pNested Paragraph 1/p pNested Paragraph 2/p pNested Paragraph 3/p pParagraph 2/pHTML 与 Markdown 两种快照一一对应共同构成同一选区、多格式输出的验证矩阵。测试用例定义childrenToNextParent 的文档与选区快照对应的测试用例定义在 copyTestInstances.ts 中名为childrenToNextParent。它先用PartialBlock数组描述测试文档结构{ testCase: { name: childrenToNextParent, document: [ { type: paragraph, content: Paragraph 1, children: [ { type: paragraph, content: Nested Paragraph 1 }, { type: paragraph, content: Nested Paragraph 2 }, { type: paragraph, content: Nested Paragraph 3 }, ], }, { type: paragraph, content: Paragraph 2, }, ], getCopySelection: (doc) { const startPos getPosOfTextNode(doc, Nested Paragraph 1); const endPos getPosOfTextNode(doc, Paragraph 2, true); return TextSelection.create(doc, startPos, endPos); }, }, executeTest: testCopyHTML, }该文档对应一棵典型的 Notion 风格嵌套树顶级块Paragraph 1父块内含三个子块Nested Paragraph 1/2/3顶级块Paragraph 2父块的兄弟块。选区selection跨越了父子边界起点是第一个嵌套子块Nested Paragraph 1的文本开头终点是顶级兄弟块Paragraph 2的文本末尾。这就是测试名 childrenToNextParent子块到下一个父块的含义——它专门验证选中内容同时覆盖某父块的多个子块与下一个父块本身这一场景确保复制出的块列表结构正确、顺序正确、不丢块。选区的定位依赖测试工具函数 getPosOfTextNode默认返回目标文本节点前的位置传true时返回节点之后的位置pos node.nodeSize以此构造TextSelection.create(doc, startPos, endPos)。该函数通过 ProseMirror 的doc.descendants遍历查找文本内容完全匹配的节点。测试执行管线从文档到快照的三步链路整个复制测试由 runTests.test.ts 驱动。它对copyTestInstancesHTML与copyTestInstancesMarkdown两组用例分别运行Copy tests (HTML)与Copy tests (Markdown)两组 describe。其中 Markdown 组的关键在于copyTestInstancesMarkdown并非重新定义文档而是直接复用 HTML 组的同一批测试用例仅把执行器换成testCopyMarkdown见 copyTestInstances.ts——同一选区在两种剪贴板格式下被分别验证。testCopyMarkdown执行器copyTestExecutors.ts的调用链如下export const testCopyMarkdown async (editor, testCase) { initTestEditor(editor, testCase.document, testCase.getCopySelection); const { markdown } selectedFragmentToHTML(editor.prosemirrorView, editor); await expect(markdown).toMatchFileSnapshot( ./__snapshots__/text/plain/${testCase.name}.md, ); };三个步骤环环相扣初始化编辑器与选区initTestEditor 重置 mock 块 ID 计数器__TEST_OPTIONS.mockID 0保证快照中块 ID 稳定可复现通过editor.replaceBlocks载入测试文档再在editor.transact事务中调用getCopySelection(tr.doc)设置选区生成剪贴板数据调用核心 APIselectedFragmentToHTML取出其中markdown字段快照断言toMatchFileSnapshot将实际输出与__snapshots__/text/plain/${name}.md比对即与本文主角childrenToNextParent.md比对。底层原理Markdown 剪贴板数据如何从选区产生selectedFragmentToHTML定义于 copyExtension.ts它一次生成三种剪贴板数据返回{ clipboardHTML, externalHTML, markdown }字段用途生成方式clipboardHTML写入blocknote/htmlBlockNote 内部格式ProseMirror 默认剪贴板序列化view.serializeForClipboardexternalHTML写入text/html外部通用 HTMLfragmentToExternalHTML走外部 HTML 导出器markdown写入text/plain纯文本由externalHTML经cleanHTMLToMarkdown转换其中markdown的生成遵循两条规则普通场景cleanHTMLToMarkdown(externalHTML)—— 先由外部 HTML 导出器把选中片段序列化为块级 HTML再转换为 Markdown纯代码块场景当选区完全位于meta.code true的块内如 codeBlock时直接取doc.textBetween($from.pos, $to.pos)的原始文本避免 Markdown 围栏fences和反斜杠转义残留注释见 copyTestInstances.ts。随后实际写入剪贴板的动作发生在 copyToClipboardevent.preventDefault()阻止浏览器默认行为依次setData(blocknote/html, clipboardHTML)、setData(text/html, externalHTML)、setData(text/plain, markdown)。因此本快照文件实质上就是用户按 Ctrl/CmdC 时系统剪贴板text/plain数据的精确还原。为何输出是每块一行 空行分隔markdown的生成链路是externalHTML → cleanHTMLToMarkdown。cleanHTMLToMarkdown 做了两件事移除EMPTY_BLOCK_PLACEHOLDER——外部 HTML 导出器会用占位字符填充空的内联内容块以保证 HTML 往返不丢块但 Markdown 不需要它必须剔除否则会出现幽灵字符源码注释明确说明这一点调用htmlToMarkdown把块级 HTML 转成 Markdown。对于本例这种全部由普通段落paragraph组成的选中范围p.../p会被转换为各自独立的 Markdown 段落段落之间以空行分隔于是得到快照中的 4 行文本 3 个空行。这里没有任何缩进或列表标记因为选中块均为顶层级别的普通段落——注意Nested Paragraph 1/2/3虽是Paragraph 1的子块但在 plain/text 序列化中它们被扁平化为独立的 Markdown 段落层级信息由块结构承载而非缩进符号表达。这正是 Markdown 剪贴板输出与文档树层级之间的关键差异点。横向对照相邻用例验证嵌套行为同目录下另两个快照可作为对照进一步验证跨父边界选择的序列化行为childrenToNextParentsChildren.md 对应 childrenToNextParentsChildren 用例Paragraph 2也有自己的三个子块Nested Paragraph 4/5/6选区覆盖全部六个子块加Paragraph 2输出为 7 段连续文本同样块间空行分隔childToParent.md 对应childToParent用例选区从父块Paragraph 1到第一个子块Nested Paragraph 1验证的是反向父到子边界。三个用例共同覆盖了子→子multipleChildren、父→子childToParent、子→下一个父childrenToNextParent、子→下一个父的子childrenToNextParentsChildren这四种嵌套选区组合构成完整的嵌套复制验证矩阵。如何运行与复现该快照验证快照所属测试运行在tests/工作区中。仓库采用 pnpm workspace 管理见 pnpm-workspace.yaml核心包为blocknote/corepackages/core测试通过 vite.config.ts 配置。可执行以下命令复现# 在仓库根目录安装依赖若尚未安装 pnpm install # 运行复制相关单元测试含 HTML 与 Markdown 两组快照断言 pnpm --filter blocknote/test run test -- src/unit/core/clipboard/copy运行后 vitest 会逐条执行runTests.test.ts中的用例将selectedFragmentToHTML实际生成的 markdown 与 childrenToNextParent.md 等快照比对。若复制逻辑发生变化导致输出与快照不一致测试会失败并生成.new后缀的差异文件便于开发者审查是否符合预期后再决定是否更新快照。结语从一行快照反推整个复制子系统childrenToNextParent.md表面上只是 7 行纯文本但它浓缩了 BlockNote 剪贴板子系统的一条完整验证链测试用例copyTestInstances.ts定义文档与跨父边界选区 → 执行器copyTestExecutors.ts初始化编辑器并调用核心 API → copyExtension.ts 的selectedFragmentToHTML产出三格式数据 → markdownExporter.ts 的cleanHTMLToMarkdown完成 HTML→Markdown 转换 → 快照锁定结果。阅读这类快照时建议始终对照其 HTML 同名快照与测试用例中的document/getCopySelection即可快速还原什么样的选区产生什么样的剪贴板输出这是理解 BlockNote 数据序列化行为最高效的入口。赞分享前端富文本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 剪贴板复制测试深度解析text/plain 快照如何保证嵌套块复制后的 Markdown 输出BlockNote 剪贴板复制测试深度解析text/plain 快照如何保证嵌套块复制后的 Markdown 输出 BlockNote 在复制/剪切时会在剪贴前端富文本UI组件AI 应用BlockNote 嵌套块复制的剪贴板输出解析从 childrenToNextParentsChildren 测试快照看 text/plain Markdown 链路BlockNote 嵌套块复制的剪贴板输出解析从 childrenToNextParentsChildren 测试快照看 text/plain Markdow前端富文本UI组件AI 应用BlockNote 代码块部分选择的剪贴板复制text/plain 快照与源码级解析BlockNote 代码块部分选择的剪贴板复制text/plain 快照与源码级解析 BlockNote 是一款基于 ProseMirror 与 Tiptap前端富文本UI组件AI 应用上一篇如何快速获取抖音直播数据DouyinLiveWebFetcher完整实战指南下一篇Origami Simulator 实时折纸模拟从打开浏览器到折出立体模型创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

CorelDRAW下载安装教程:版本选型、高版本转低版本与报错排查

CorelDRAW下载安装教程:版本选型、高版本转低版本与报错排查

1. 为什么矢量设计软件值得你花时间折腾做设计这行十几年,被问得最多的问题不是“怎么做出好看的效果”,而是“我该装哪个版本”“装完打不开怎么办”“客户发来的文件我这边显示乱码”。这些问题里,有一大半都跟矢量设计软件有关。CorelDRAW…

2026/9/25 6:11:52 阅读更多 →
DoWhy 快速上手:安装配置、四步因果效应估计与 GCM 根因分析入门指南

DoWhy 快速上手:安装配置、四步因果效应估计与 GCM 根因分析入门指南

机器学习数据分析 【免费下载链接】dowhy DoWhy is a Python library for causal inference that supports explicit modeling and testing of causal assumptions. DoWhy is based on a unified language for causal inference, combining causal graphical models and potent…

2026/9/25 6:11:52 阅读更多 →
Compose 组件 - 网格布局 Grid

Compose 组件 - 网格布局 Grid

官方页面 一、概念 Column/Row 适合一维布局,拼出二维布局即便 Compose 不怕嵌套但可读性差。LazyGrid 用于延迟加载大量的同质化条目,就好比 ScrollView(可滑动的Column) 和 RecyclerView(LazyColumn) 的区…

2026/9/25 6:11:52 阅读更多 →

最新新闻

VisiData Loader 开发指南:从 open_<filetype> 到 Saver 的完整实战教程

VisiData Loader 开发指南:从 open_<filetype> 到 Saver 的完整实战教程

数据分析CLI数据可视化 【免费下载链接】visidata A terminal spreadsheet multitool for discovering and arranging data 项目地址: https://gitcode.com/gh_mirrors/vi/visidata 点击查看 免费下载 本指南以 VisiData 官方 API 文档(docs/api/loader…

2026/9/25 7:15:41 阅读更多 →
PrusaSlicer slic3r-platform 跨平台渲染运行时架构解析:AbstractRenderModule 与 AbstractRenderCanvas 设计精读

PrusaSlicer slic3r-platform 跨平台渲染运行时架构解析:AbstractRenderModule 与 AbstractRenderCanvas 设计精读

桌面应用3D渲染 【免费下载链接】PrusaSlicer G-code generator for 3D printers (RepRap, Makerbot, Ultimaker etc.) 项目地址: https://gitcode.com/gh_mirrors/pr/PrusaSlicer 点击查看 免费下载 导读:本文以 src/slic3r-platform/README.md 为骨架…

2026/9/25 7:15:41 阅读更多 →
cuDF pylibcudf.replace 模块指南:空值填充、查找替换与数值钳制(含 C++ 底层实现剖析)

cuDF pylibcudf.replace 模块指南:空值填充、查找替换与数值钳制(含 C++ 底层实现剖析)

数据分析数据工程机器学习 【免费下载链接】cudf cuDF - GPU DataFrame Library 项目地址: https://gitcode.com/gh_mirrors/cu/cudf 点击查看 免费下载 pylibcudf.replace 是 cuDF GPU DataFrame 库(RAPIDS 生态)中负责列内数值与空值替换…

2026/9/25 7:15:41 阅读更多 →
Qt+MySQL教务系统毕业设计:从数据库设计到驱动避坑全指南

Qt+MySQL教务系统毕业设计:从数据库设计到驱动避坑全指南

简介:这是一套基于Qt框架与MySQL数据库的教务系统完整源码,包含学生、教师、管理员三种身份模块,覆盖课程管理、成绩录入与查询、用户权限区分等典型业务场景,面向计算机相关专业学生开展课程设计、毕业设计或项目初期演示使用&am…

2026/9/25 7:15:41 阅读更多 →
MCP不是协议,而是工具能力调用的统一接口规范

MCP不是协议,而是工具能力调用的统一接口规范

1. 先别急着查文档:MCP不是新协议,而是“能力调度员”的代号你搜“MCP”时,页面上跳出来的全是碎片:蓝湖MCP、Figma MCP、Playwright MCP、BurpSuite MCP、Workbuddy MCP……还有人问“手机怎么获取MCP服务”“Chrome扩展里启用MC…

2026/9/25 7:15:41 阅读更多 →
微信小程序省市县三级联动:数据驱动组件化实现

微信小程序省市县三级联动:数据驱动组件化实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 7:14:40 阅读更多 →

日新闻

AI元人文:从工具使用到思维重构的深度探索

AI元人文:从工具使用到思维重构的深度探索

最近半年我一直在琢磨一件事:AI元人文到底是什么?说白了,就是“用元视角重新审视人与AI的关系”,也在“探索AI如何反向逼着我们发现自己的思考边界”。标题里的“元探索”,在我看就是一层套一层的追问——当你用AI解决…

2026/9/25 0:00:41 阅读更多 →
Python+CNN车牌识别实战:从数据预处理到模型训练与部署

Python+CNN车牌识别实战:从数据预处理到模型训练与部署

简介:基于Python与卷积神经网络的车牌识别项目,面向计算机视觉初学者及智能交通开发者,目标是帮助用户掌握从数据预处理、模型构建到实际部署的完整流程。压缩包共25个文件,包含jpg/png图像样本、py训练脚本、md说明文档、dat数据…

2026/9/25 0:00:41 阅读更多 →
Vim基础操作全攻略:保存退出、模式切换与高频命令实战

Vim基础操作全攻略:保存退出、模式切换与高频命令实战

1. 项目概述1.1 核心需求解析今天聊聊Vim。写这个题目的原因是:几乎每个后端开发者、运维人员、数据工程师某天都会遇到一个场景——深夜加班,服务器登录界面只有黑底白字,编辑器只有vi/vim,你必须在五分钟内完成一次配置修改并保…

2026/9/25 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/24 14:34:13 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/24 14:33:56 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/24 14:33:48 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →