ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Plate 节点模型与亲和性(Node Model Affinity)规范:从文档修复到运行时落地的完整实践

Plate 节点模型与亲和性(Node Model  Affinity)规范:从文档修复到运行时落地的完整实践 Plate 节点模型与亲和性Node Model Affinity规范从文档修复到运行时落地的完整实践【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate导读本文基于 Plate 仓库中 editor-behavior 文档体系的一次专项规范整理2026-04-04-node-model-affinity-spec-pass.md系统讲解 Plate 如何把节点原子性atomicity、voidness 与亲和性affinity从模糊的文字描述升级为每类功能家族必须显式声明的规范字段。你将掌握Plate 的 7 类节点模型与 4 类亲和性分类体系、文档与运行时源码交叉审计的方法、以及一次真实的规范先说、实现后追案例footnote 引用从非 void 文本变为真正的 inline void atom。文章同时结合 markdown-standards.md、markdown-editing-spec.md、editor-protocol-matrix.md 与packages/*下的插件源码给出可在当前仓库中逐条验证的实践路径。一、这次 Spec Pass 要解决什么问题1.1 背景文档在手挥编辑器模型在本次专项之前Plate 的 editor-behavior 文档已经在谈论atoms原子和 affinity但没有建立强制约束每个功能家族都必须声明一个显式的模型字段。结果就是规范文档在真正关键的地方手挥hand-waving——用自然语言描述节点的原子性却不落到可测试、可交叉验证的模型声明上。1.2 Goal 原文本次 pass 的目标Goal非常明确Make node atomicity, voidness, and affinity explicit across the editor-behavior docs so the spec stops hand-waving over the actual editor model.即让节点原子性、voidness 与亲和性在全部 editor-behavior 文档中显式化使规范不再回避真实编辑器模型。1.3 六阶段执行计划原文档把工作拆成六个阶段Phase全部标记为已完成阶段内容交付物1复查当前规范在 footnote 引用、inline atoms 与 affinity 策略上的缺口缺口清单2交叉检查当前各包表面真实的isVoid、isInline、mark-affinity 声明运行时表面审计3更新 standards 文档要求显式的节点模型与 affinity 声明markdown-standards.md4更新 readable spec补充节点模型分类与修正后的家族法则markdown-editing-spec.md5更新 parity/protocol 文档使每个功能家族都声明自己的模型与 affinity 类markdown-parity-matrix.md、editor-protocol-matrix.md6验证文档一致性记录新暴露的过时声明一致性核查结果这套先审计、后改文档、再验证的顺序本身就是值得复用的方法规范改动不是拍脑袋而是先对源码做地毯式扫描再反向约束文档。二、核心成果一节点模型Node Model七分类2.1 规范定义standards 文档markdown-standards.md与 readable specmarkdown-editing-spec.md中节点模型被统一为以下 7 类模型类含义典型实体block non-void可编辑的块级/容器内容段落、标题、块引用、列表项、代码块、表格、脚注定义block void atom富文本模式下内部无光标的原子块表面TOC、分隔线thematic break、媒体嵌入、图片、绘图、块级公式inline non-void span可编辑的内联内容如链接链接、autolink 字面量inline void atom无富文本正文的原子内联表面mention、date、脚注引用、内联公式leaf mark由 leaf 携带的文本标记而非独立内联元素加粗、斜体、删除线、高亮、上/下标、样式类 marktext token保留语法的文本行为如解析后的硬换行硬换行、emoji 短代码解析后的文本overlay / no node不拥有文档节点的编辑器 chrome生成的 TOC 条目、Yjs 远程光标、讨论锚点2.2 四条强制规则规范同步立下了四条不允许绕过的规则不要从 UI chrome 推断原子性——界面上看起来像一个整体不代表节点是原子的不要从 DOM 的contentEditable{false}推断 voidness——渲染层的小技巧不能替代编辑器节点契约必须使用编辑器节点契约editor node contract而非渲染 DOM 技巧如果一个特性是非 void 且参与内联输入规范必须声明它使用哪一类 affinity而 inline void atoms 不依赖 link/mark affinity它们作为原子自己拥有导航与边界删除行为。这套规则直接堵死了实现与文档各说各话的漏洞也为此后所有功能家族建立了统一的语言。三、核心成果二Affinity亲和性四分类3.1 分类定义当内联输入可能跨越某个边界时规范要求声明亲和性类Affinity 类行为适用对象directional从已格式化一侧输入会扩展该格式从纯文本一侧输入则保持在格式之外加粗/斜体等软 mark、链接 spanhard边界输入保持在格式之外不扩展格式化 span内联代码、kbd 等偏源码的内联节点outward元数据范围偏向避免意外增长评论comment、建议suggestion等协作元数据 marknone / n-a不拥有内联亲和性块级节点、void atoms、text tokens、overlays3.2 为什么需要 affinityreadable spec 中有一句非常关键的话markdown-editing-spec.mdAffinity belongs here because cursor behavior changes the meaning of later typing and deletion.光标停留在边界时的行为会改变后续输入与删除的语义。典型规则见EDIT-AFF-MARK-001**bold|**text输入x后得到**boldx**text即 directional、EDIT-AFF-LINK-001链接侧进入扩展链接纯文本侧进入则留在外面、EDIT-AFF-HARD-001code|text输入后得到codextext即 hard。3.3 源码中的 affinity 实现在 packages/core/src/lib/plugins/affinity/AffinityPlugin.ts 中affinity 被实现为一个独立的 core 插件affinity值为backward | forward用于描述删除操作后光标应该贴向哪一侧删除跨过 mark 边界时若删除的是右侧字符则 affinity 为forward删除的是左侧 mark 字符则为backward插件通过rules.selection?.affinity读取每个节点类型声明的亲和性AffinityPlugin.ts。而isNodeAffinity查询packages/core/src/lib/plugins/affinity/queries/isNodeAffinity.ts把规范中的三类值directional | hard | outward与插件规则直接对接形成了文档分类 ↔ 源码类型的一一映射。四、运行时表面审计每类功能家族的模型归属本次 pass 最重要的发现之一是运行时表面是混合的Runtime surfaces are mixed。原文档 Findings 明确列出了当时的真实状态linksinline non-void spandirectionalaffinitymention / date / inline equationinline void atomTOC、thematic break、media embed、file/audio/video、image、drawing、block equationblock void atom许多格式化 marks 已经在插件规则中暴露 selection affinitydirectional、hard或outward。protocol matrix 中有一张实体模型映射表Entity Model Map是这次审计沉淀下来的权威清单editor-protocol-matrix.md摘录关键行家族实体节点模型亲和性/边界策略markdown-native段落 / 标题 / 块引用 / 列表项block non-voidn/amarkdown-native链接inline non-void spandirectionalmarkdown-native图片block void media atomn/amarkdown-native软 mark / 硬 markleaf markdirectional/hardmarkdown-native代码块block non-void ownern/amarkdown-native分隔线block void atomn/amarkdown-extension内联公式 / 块级公式inline void atom / block void atomn/amarkdown-extensionautolink 字面量inline non-void link spandirectionalmarkdown-extension脚注引用 / 脚注定义inline void atom / block non-void containern/ablock-editor-nativemention / dateinline void atomn/ablock-editor-nativecallout / toggle / 列block non-void containern/ablock-editor-nativeTOC / 媒体嵌入 / code drawing / excalidrawblock void atomn/acollaborationcomment / suggestionleaf metadata markoutwardcollaborationdiscussion / Yjs 光标overlay / no noden/a这张表的意义在于任何新增功能家族都必须在这张表中占一行否则协议层不承认它有模型。4.1 源码交叉验证用插件源码验证上述表格链接是inline non-void span在 packages/link/src/lib/BaseLinkPlugin.ts 中只声明了isInline: true没有isVoid——这正是内联但可编辑的节点契约mention 是inline void atom在 packages/mention/src/lib/BaseMentionPlugin.ts 中声明为node: { isElement: true, isInline: true, isVoid: true }三者齐备脚注引用修复后同样变为isInline: true, isVoid: true见下一节。这印证了 standards 文档的规则voidness 由编辑器节点契约决定而不是由渲染 DOM 决定。五、案例研究footnote 引用的规范谎言与运行时修复5.1 发现的问题原文档 Notes 部分记录了一个非常典型的问题The spec drifted into a real lie on footnotes:footnoteReferencewas treated like an atom in prose while the runtime node was still non-void.即文档散文把footnoteReference当作 atom 来描述但运行时节点仍然是非 void 的。规范与实现出现了真实的分叉。这次 pass 最初只是纯文档工作docs-only却在审计中暴露了这个真实的运行时 mismatch从而触发了后续的执行修复2026-04-04-footnote-inline-void-fix.md。5.2 修复目标与症状修复文档明确列出了要消灭的浏览器症状引用后的 Backspace 会编辑可见的标识符[^1]中的数字被当作可编辑文本回链backlink导航触发的是通用编辑 chrome而不是干净的导航。5.3 修复结果修复完成后footnoteReference成为带空子节点哨兵empty child sentinel的 inline void atom——在 packages/footnote/src/lib/BaseFootnoteReferencePlugin.ts 中可以看到最终声明node: { isElement: true, isInline: true, isVoid: true }且render: { as: sup }渲染为上标回链聚焦落在引用旁最近的稳定兄弟文本点nearest stable sibling text point而不是节点范围选择浏览器验证/docs/footnote页面显示回链跳转落在[1]后的.上、屏幕上只有一个 toolbar、一次 Backspace 删除整个引用而不是单个数字。配套的测试也同步固化在 packages/footnote/src/lib/BaseFootnotePlugins.spec.ts 中同时断言了isInline: true, isVoid: true的模型声明以及非 void 场景的反例。5.4 这一案例的方法论价值这是规范驱动实现spec-driven implementation的教科书案例规范先立规矩footnote 引用 inline void atom审计发现运行时不符合仍是非 void 文本用红色测试锁定期望行为实现修复并把模型声明改到与规范一致浏览器级验证收尾。它同时说明文档审计不是文案工作它能反向暴露真实的产品 bug。六、文档体系中的落地与关联6.1 规范的三层结构本次 pass 更新了三个层面的文档它们分工不同文档角色关键内容markdown-standards.md方法论与权威模型参考池Typora/Obsidian/Notion/Google Docs/GitHub/Milkdown、权威顺序、节点模型与 affinity 要求、偏差政策、Spec ID 方案markdown-editing-spec.md规范性家族法则readable law全局不变量、节点模型与 affinity 类、所有权顺序、每个家族的EDIT-*规则editor-protocol-matrix.md穷尽式场景矩阵Row Schema、实体模型映射表、按家族的协议行、状态机seeded/specified/tested/partial/deferredmarkdown-parity-matrix.md发布门槛按家族的语法支持与 round-trip 状态protocol matrix 的 Practical Use 一节给出了工作流闭环先在 protocol matrix 中加穷尽场景行 → 在 markdown-editing-spec.md 中锁定行为 → 在 markdown-parity-matrix.md 中跟踪家族级充分性。6.2 Spec ID 与测试映射standards 文档还确立了稳定的 Spec ID 方案EDIT编辑行为、PARITY解析/序列化/往返、STREAM流式 markdown、DEV有意偏差。目标是从文档直接驱动 TDD——每个锁定的规则都应映射到测试、所属包与行为 profile。协议矩阵中大量行已经标注了tested状态及对应的证据文件如AffinityPlugin.spec.tsx、withBreakRules.spec.tsx、withTable.spec.tsx等。七、实践建议与延伸阅读7.1 如果你想为 Plate 新增一个功能家族按本次 pass 沉淀的流程走在 editor-protocol-matrix.md 的实体模型映射表中为新实体占一行声明节点模型与 affinity 类在 markdown-editing-spec.md 中为该家族补充EDIT-*规则与 canonical 示例在插件源码的node配置中如实声明isElement/isInline/isVoid参考 BaseMentionPlugin.ts 或 BaseFootnoteReferencePlugin.ts为边界行为编写 red testsreference 现有AffinityPlugin.spec.tsx等测试在 markdown-parity-matrix.md 中更新家族级覆盖状态。7.2 三条可复用的核心经验文档必须显式声明模型任何功能家族如果没有声明节点模型与 affinity 类就视为未规范以编辑器节点契约为准不以渲染 DOM 为准contentEditable{false}不是 void 的证明文档审计是 bug 探测器规范与实现不一致的地方往往是真实产品缺陷footnote 引用就是证据。如需继续深入建议顺序阅读markdown-standards.md方法论→ markdown-editing-spec.md家族法则→ editor-protocol-matrix.md场景矩阵→ 2026-04-04-footnote-inline-void-fix.md修复案例并结合packages/core/src/lib/plugins/affinity/下的实现逐条对照。【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表