plate 项目 Mark 插件功能车道:Highlight / Subscript / Superscript 的审计、文档对齐与实现现状
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
本篇指南以仓库中的功能车道计划文档 docs/plans/2026-04-04-mark-plugin-feature-lane.md 为骨架,系统梳理 plate 富文本编辑器中高亮(highlight)、下标(subscript)、上标(superscript)三类 leaf mark 插件的审计目标、编辑器行为文档对齐方式、源码实现细节与测试覆盖现状。读完本篇,你将理解这三个 mark 插件在 plate 中的完整落地面:从 base 插件定义、HTML 反序列化、选区亲和性,到 markdown 输入规则与用例验证,以及该功能车道当前尚未关闭的收尾工作。
一、功能车道概览:目标与阶段划分
该计划文档定义了一条以"审计 + 文档 + 测试/运行时"三件套驱动的功能车道,目标明确:
将 highlight、subscript、superscript 与 editor-behavior 文档族进行对齐审计,并按需推动后续的功能车道(tests / docs / runtime)。
计划将工作拆分为四个阶段,并给出了当前勾选状态:
| 阶段 | 内容 | 状态 |
|---|---|---|
| Phase 1 | 审计现有 highlight/subscript/superscript 插件表面与 editor-behavior 文档 | ✅ 已完成 |
| Phase 2 | 为受支持的 mark 插件新增或更新 protocol / parity / spec 覆盖 | ✅ 已完成 |
| Phase 3 | 实现缺失的 markdown / 编辑器行为测试或运行时变更 | ⬜ 待办 |
| Phase 4 | 验证并总结下一功能车道状态 | ⬜ 待办 |
从状态可以看出,这条车道当前处于"文档与审计先行、实现收尾待续"的中间态:插件表面审计与协议/一致性文档覆盖已经落袋,而测试补齐与运行时变更、最终验收总结仍在计划中。这与 plate 团队"law → readable law → gate coverage"的文档驱动工作法一致,下文会展开说明。
二、审计基准:editor-behavior 文档族
计划文档中反复强调的"editor-behavior docs"并非单一文件,而是一套以 markdown 编辑行为规范为核心的文档体系,存放于 docs/editor-behavior。其中与 mark 插件审计直接相关的核心文件包括:
- markdown-standards.md:方法论与权威模型(authority model),定义"谁说了算";
- markdown-editing-spec.md:规范层面(normative)的逐族"法律";
- markdown-parity-matrix.md:按功能族的发布门禁(release-gate)覆盖矩阵;
- editor-protocol-matrix.md:汇总上述三者的协议矩阵总览。
从 editor-protocol-matrix.md 可以看到这套体系的层级关系:standards 定义方法论与权威模型,spec 定义逐族规范,parity matrix 则按族记录门禁覆盖状态。对于 mark 插件而言,"soft mark / hard mark" 被归类为 leaf mark 节点模型(见该矩阵的 Node Model 章节),highlight、subscript、superscript 正属于其中的 leaf mark 家族。
因此,Phase 1 的"审计"本质上是将三个插件的公开表面(base 插件、react 插件、markdown 输入规则)与上述规范逐条对照;Phase 2 则把审计结论沉淀回 protocol / parity / spec 三类文档,形成可追踪的记录。
三、Base 插件表面:leaf mark 的完整实现形态
三个 mark 插件在basic-nodes包中均有对应的 base 实现,遵循同一套createSlatePlugin声明式范式,适合作为编写自定义 mark 插件的参照模板。
3.1 Highlight:以<mark>为语义载体
BaseHighlightPlugin.ts 的完整定义如下:
import { createSlatePlugin, KEYS } from 'platejs'; export const BaseHighlightPlugin = createSlatePlugin({ key: KEYS.highlight, node: { isLeaf: true }, parsers: { html: { deserializer: { rules: [ { validNodeName: ['MARK'], }, ], }, }, }, render: { as: 'mark' }, rules: { selection: { affinity: 'directional' } }, }).extendTransforms(({ editor, type }) => ({ toggle: () => { editor.tf.toggleMark(type); }, }));逐项拆解其含义:
key: KEYS.highlight:插件注册键,KEYS由platejs统一导出,highlight键对应的 mark 类型在文档数据中表现为<htext highlight>...</htext>;node: { isLeaf: true }:声明这是 leaf(叶子)级节点,即附着在文本节点上的格式标记,而非独立 block/inline 节点;parsers.html.deserializer.rules:HTML 反序列化规则,将粘贴/导入的<mark>标签映射为该 mark。这意味着从网页或富文本源复制高亮内容可以无损进入 plate 文档模型;render: { as: 'mark' }:渲染为<mark>元素,浏览器默认给出荧光笔背景样式;rules.selection.affinity: 'directional':设置选区亲和性为 directional。结合 editor-protocol-matrix.md 中 "soft mark → leaf mark → directional" 的归类,这是 leaf mark 的典型亲和性配置;extendTransforms:为插件扩展toggle变换,内部调用editor.tf.toggleMark(type)完成选中文本的开关切换。
3.2 Subscript / Superscript:互斥切换的经典实现
BaseSubscriptPlugin.ts 与 BaseSuperscriptPlugin.ts 结构对称,差异集中在反序列化规则与 toggle 的互斥逻辑:
// BaseSubscriptPlugin 关键片段 export const BaseSubscriptPlugin = createSlatePlugin({ key: KEYS.sub, node: { isLeaf: true }, parsers: { html: { deserializer: { rules: [ { validNodeName: ['SUB'] }, { validStyle: { verticalAlign: 'sub' } }, // 兼容 CSS 直写 vertical-align 的源 ], }, }, }, render: { as: 'sub' }, rules: { selection: { affinity: 'directional' } }, }).extendTransforms(({ editor, type }) => ({ toggle: () => { editor.tf.toggleMark(type, { remove: editor.getType(KEYS.sup), // 开启下标时移除上标 }); }, }));两个值得注意的工程细节:
- 双重反序列化规则:下标/上标不仅匹配语义化标签
<sub>/<sup>,还匹配内联样式vertical-align: sub/vertical-align: super。这覆盖了 Word、Google Docs 等以 CSS 表达上下标的来源,是 HTML 导入保真度的关键; - 互斥 toggle:切换下标时通过
remove: editor.getType(KEYS.sup)显式移除上标,反之亦然。因为同一段文本不可能同时是上标又是下标,这种"开启即互斥"的行为避免了语义冲突。
3.3 组合插件:BasicMarks 家族
三者与 Bold、Italic、Code、Strikethrough、Underline 一起被聚合进 BaseBasicMarksPlugin.ts,开发者只需引入一个组合插件即可获得全套基础 mark 能力:
export const BaseBasicMarksPlugin = createSlatePlugin({ plugins: [ BaseBoldPlugin, BaseCodePlugin, BaseItalicPlugin, BaseStrikethroughPlugin, BaseSubscriptPlugin, BaseSuperscriptPlugin, BaseUnderlinePlugin, ], });四、React 插件表面:与运行时环境解耦
与 plate 的"headless core + react adapter"分层一致,三个插件各自还有 React 包装层,位于basic-nodes/src/react:
- HighlightPlugin.tsx
- SubscriptPlugin.tsx
- SuperscriptPlugin.tsx
三者实现完全同构,均为一句话转发:
import { toPlatePlugin } from 'platejs/react'; import { BaseHighlightPlugin } from '../lib/BaseHighlightPlugin'; export const HighlightPlugin = toPlatePlugin(BaseHighlightPlugin);toPlatePlugin在 base 插件之上叠加 React 运行时所需的能力(渲染上下文、react 侧装饰等)。也就是说:逻辑与数据层全部沉淀在 base 插件中,react 层只做适配转发,这是 plate 插件体系"平台无关核心"设计的直接体现,也让这三个 mark 插件可以在非 React 环境中复用核心逻辑。
五、Markdown 输入规则:==、~、^ 的分隔符语义
Phase 1 审计的另一块表面是 markdown 输入规则。全部规则集中在 BasicMarkRules.ts,其中与本文主题相关的三个规则如下:
export const SubscriptRules = { markdown: createRuleFactory({ type: 'mark', start: '~', trigger: '~', }), }; export const SuperscriptRules = { markdown: createRuleFactory({ type: 'mark', start: '^', trigger: '^', }), }; export const HighlightRules = { markdown: createRuleFactory<{}, { variant: '==' | '≡' }>({ type: 'mark', variant: '==', end: ({ variant }) => (variant === '≡' ? undefined : '='), start: ({ variant }) => (variant === '≡' ? '≡' : '=='), trigger: ({ variant }) => (variant === '≡' ? '≡' : '='), }), };语义解读:
- Subscript:单
~触发,~hello输入后按规则格式化为下标;注意这与删除线 Strikethrough 的~~(双波浪线成对)不同——单波浪线归下标,双波浪线归删除线,二者通过触发字符长度天然区分; - Superscript:单
^触发,^hello格式化为上标; - Highlight:支持两种变体——主流 markdown 方言的
==hello==(v1 双等号),以及面向特殊输入习惯的≡(数学恒等符号)单字符变体。≡变体由于end返回undefined,表现为只依赖起始触发、无需闭合分隔符的格式。
重要前提:这些规则默认不启用。从 BaseMarkInputRules.spec.tsx 的第一个用例可以看出,仅引入BaseBoldPlugin而不显式配置 input rules 时,**hello*会保持字面量输出——"stays literal until markdown groups are explicitly enabled"。启用方式是在插件配置中显式挂载规则,例如:
BaseHighlightPlugin.configure({ inputRules: [HighlightRules.markdown({ variant: '==' })], });这也解释了为什么 Phase 3"实现缺失的 markdown/编辑器行为测试或运行时变更"仍是待办:输入规则与插件配置的挂载组合还有进一步测试与运行时验证的空间。
六、测试证据:输入规则用例如何验证行为
BaseMarkInputRules.spec.tsx 是 Phase 2 文档覆盖之外最重要的行为证据,它以"输入 → 格式化 → 断言"的表格化用例验证三个 mark 的 markdown 输入规则:
| 用例标题 | 输入文本 | 期望输出 | 配置 |
|---|---|---|---|
| formats highlight delimiters | ==hello= | <htext highlight>hello</htext> | BaseHighlightPlugin.configure({ inputRules: [HighlightRules.markdown({ variant: '==' })] }) |
| formats subscript delimiters | ~hello | <htext sub>hello</htext> | BaseSubscriptPlugin.configure({ inputRules: [SubscriptRules.markdown()] }) |
| formats superscript delimiters | ^hello | <htext sup>hello</htext> | BaseSuperscriptPlugin.configure({ inputRules: [SuperscriptRules.markdown()] }) |
用例通过@platejs/test-utils提供的jsxt语法构造文档树,调用editor.tf.insertText触发输入,最终断言input.children与output.children深度相等。这种"结构快照"式断言精确到 leaf mark 层(highlight/sub/sup属性挂在文本节点上),与 BaseHighlightPlugin.ts 的isLeaf: true声明互相印证。
此外,basic-nodes的入口 index.ts 统一导出 Base 插件、React 插件与全部规则(BoldRules、ItalicRules、HighlightRules、SubscriptRules、SuperscriptRules等),调用方一条 import 即可取用。
七、车道现状与下一步
综合仓库证据,当前功能车道状态可归纳为:
- 已完成(文档侧):三个 mark 插件的表面审计(Phase 1)与 protocol / parity / spec 覆盖(Phase 2)均已勾选,相关结论沉淀在 docs/editor-behavior 文档族中;
- 待办(实现侧):Phase 3(补齐缺失的 markdown/编辑器行为测试或运行时变更)与 Phase 4(验证并总结下一功能车道)尚未完成——从源码看,输入规则与插件配置组合的边界测试(如 highlight 的
≡变体、sub/sup 互斥 toggle 的运行时校验)是潜在的补齐方向。
对于希望复用这套能力的开发者,直接照抄三个 base 插件的最小结构即可:createSlatePlugin({ key, node: { isLeaf: true }, parsers, render, rules })加上extendTransforms提供toggle,再以toPlatePlugin包装出 React 版本,最后按需用configure({ inputRules })挂载 markdown 规则。这一模式正是 plate mark 插件功能车道沉淀出的可复制范式。
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考