news 2026/9/16 18:11:57

Plate Input Rules DX 修订方案:用单一 inputRules 配置面取代 inputRuleGroups 的插件 API 重构计划

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Plate Input Rules DX 修订方案:用单一 inputRules 配置面取代 inputRuleGroups 的插件 API 重构计划

Plate Input Rules DX 修订方案:用单一 inputRules 配置面取代 inputRuleGroups 的插件 API 重构计划

【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate

本文基于 Plate 仓库内的修订计划文档 2026-04-12-input-rules-dx-revision-plan.md(状态:Proposed)展开,系统讲解 Plate 输入规则(Input Rules)子系统的公开 API 重构方向:为什么保留核心输入规则运行时、为什么删除公开的inputRuleGroups配置、如何设计createInputRule主构建器与 preset 激活语义,以及配套的六阶段迁移计划、测试计划与验收标准。读完本文,你可以完整掌握 Plate 中*italic*> quote# heading这类 Markdown 快捷输入的规则注册、解析与归属模型,并了解该方案在当前仓库代码中的落地情况。

一、计划的目标与背景

该计划的核心立场是:保留核心的 input-rules 运行时,但重写公开 API 与归属模型(ownership model),让 DX(开发者体验)真正达到可发布的水准。目标可归纳为五条:

  • 不保留公开的inputRuleGroups配置;
  • 只有一个显而易见的Plugin.configure(...)入口;
  • 共享的规则构建器(rule builders)放在 core 包,而不是在各功能包里重复本地 matcher 代码;
  • 不为通用文本替换(smart quotes、箭头、分数等)单独发布 npm 包;
  • 对类似快捷键的行为提供 shadcn 式的透明性(代码拷入项目、可直接检视和修改)。

计划文档中列出的"当前代码证据"(as-of 文档撰写时)包括:

  • 运行时:InputRulesPlugin.ts、types.ts、resolvePlugin.ts、resolvePlugins.ts;
  • 分散在各功能包、存在重复的规则构建逻辑:basic-nodes 的markdownInputRules.ts、list/inputRules.ts、math 与 link 包各自的inputRules.ts(其中 link 的当前实现位于 LinkRules.ts);
  • 归属不合理的问题插件:BaseHeadingPlugin.ts、BaseCodeBlockPlugin.ts、link 包的BaseLinkPlugin.ts,以及注册表侧的 autoformat-kit.tsx。

外部参照系来自本地克隆的对比结论:Tiptap 用 feature 自持的addInputRules()加上markInputRuletextblockTypeInputRulenodeInputRulewrappingInputRuletextInputRule等类型化构建助手;Lexical 把显式的 transformer 子集传给MarkdownShortcutPlugin;shadcn 模式则证明"拷贝到本地的代码"对希望检视并修改的产品糖(product sugar)更有吸引力。此外,计划的上游依据还引用了 editor 架构候选对比文档 editor-architecture-candidates.md。

二、严厉评估:哪些已经做对了,哪些仍然不对

2.1 已经做对的部分

  • 由 core 持有 input-rules 分发逻辑(core-owned dispatch)是正确的选择;
  • 功能插件各自持有功能行为(feature plugins owning feature behavior)是正确的选择;
  • 同名规则覆盖(same-name override)加运行时索引(runtime indexing)是正确的方向。

2.2 仍然不对的部分

  • inputRuleGroups对普通使用场景来说过于仪式感(too ceremonial);
  • @platejs/typography是"为产品糖做的包膨胀"(package sprawl);
  • 规则构建器逻辑在多个包之间重复;
  • BaseHeadingPlugin拥有标题快捷语法是糟糕的 DX,因为用户通常配置的是H1PluginH6Plugin,而不是聚合插件;
  • shouldAutoLinkPaste与"按命名规则覆盖"的能力重复;
  • link 包的automd目录是一个僵尸表面(zombie surface);
  • getTextFromBlockStart作为独立导出是一种笨拙的工具函数泄漏;
  • defineInputRule对主构建器而言太原始(raw),但又太小、不足以帮助开发者发现更好的路径。

结论:运行时值得保留;公开 API 还需要再做一次硬性的清理(one more hard cleanup pass)。

三、十一项核心决策

决策 1:删除公开的 inputRuleGroups

inputRuleGroups不应作为公开配置存活。它准确但笨重——它强迫每个使用者为一个概念("哪些快捷方式开着?")思考两个字段。计划要求的公开配置形态变为单字段:

ItalicPlugin.configure({ inputRules: { markdown: true, emphasisUnderscore: null, }, });

这是最常见路径的三个优点:一个字段;显式的 preset 激活;同一个对象里逐规则覆盖/删除。

决策 2:保留 preset 捆绑,但降为内部定义细节

捆绑语义仍然需要,但不需要第二个公开配置字段来承载。插件定义侧应变为:

ItalicPlugin.extend({ inputRulePresets: { markdown: ["emphasisAsterisk", "emphasisUnderscore"], }, inputRules: { emphasisAsterisk: createInputRule({ type: "delimitedMark", mark: KEYS.italic, pattern: { start: "*", end: "*", trigger: "*" }, }), emphasisUnderscore: createInputRule({ type: "delimitedMark", mark: KEYS.italic, pattern: { start: "_", end: "_", trigger: "_" }, }), }, });

公开配置(inputRules)同时接受 preset 名与规则名,配置语义如下:

条目类型true{ ... }null
preset 条目启用该 preset—(不适用)删除该 preset
规则条目直接启用该规则配置该规则删除该规则

约束:同一个插件内,preset 名与规则名不得冲突。

决策 3:把分隔符变体拆成独立的公开规则名

这是文档认定的"真实失误":如果开发者真正关心*_的差别,emphasis这个名字太粗糙。公开规则名应描述真正的覆盖单元:

  • emphasisAsterisk/emphasisUnderscore
  • strongAsterisk/strongUnderscore
  • boldItalicAsterisk/boldItalicUnderscore

链接与数学公式同理:暴露开发者真正想单独开关的行为单元,不要把实质不同的触发器藏在一个粗糙的规则名之下。

决策 4:不发布 @platejs/text-substitutions 包

smart quotes、箭头、分数、法律符号等通用替换属于"产品糖",不属于持久的编辑器语义,应移出packages/*。最佳归属:

  • 通用构建器留在 core;
  • 实际的替换规则集作为 registry kits / 拷贝代码放到apps/www/src/registry/**
  • 用户可以像 shadcn 安装的代码一样检视和修改它们。

这样做的收益:避免包膨胀、让 Plate 包聚焦文档语义、保留 shadcn 透明性、"我只想要其中三条规则"变得极简单。被否决的归属方案有三个:留在@platejs/autoformat(归属错误、方向已死);留在@platejs/utils(太隐蔽、语义模糊);留在@platejs/typography(比 autoformat 好,但对"可拷贝的糖"仍是过大的发布表面)。

决策 5:只加一个可发现的构建器 createInputRule

不要让开发者在一堆助手名里猜。保留defineInputRule作为低层逃生舱,新增createInputRule作为主 DX 表面。计划给出的四种类型化变体形态:

// 分隔 Mark 规则 createInputRule({ type: "delimitedMark", mark: KEYS.italic, pattern: { start: "*", end: "*", trigger: "*" }, }); // 块首匹配规则 createInputRule({ type: "blockStart", trigger: " ", match: ">", apply: ({ editor }) => { editor.tf.toggleBlock(KEYS.blockquote); }, }); // 终止符块规则(如 $$ 块级公式) createInputRule({ type: "terminalBlock", target: KEYS.p, terminal: "$$", onMatch: ({ editor, path }) => { // ... }, }); // 文本替换规则 createInputRule({ type: "textSubstitution", match: "...", format: "…", });

设计规则:一个可发现的公开构建器;按type区分类型化变体;低层defineInputRule继续可用,承载自定义逻辑。

决策 6:保留组合式助手,但降为主构建器之下的次级 API

仓库确实需要共享的 matcher 逻辑,只是它不该是用户学习的第一样东西。core 只把这些高级组合助手作为次级 API 暴露:matchDelimitedTextmatchBlockStartmatchTerminalBlockmatchTextSubstitution。它们用于构建自定义规则或支撑createInputRule,不是主要营销面。

决策 7:把共享构建器从包内部提升到 core

文档撰写时packages/basic-nodes内部的markdownInputRules.ts已经证明"共享层缺失"。归属应当是:core 拥有主构建器、高级 matcher/组合助手、共享的 input-rule 类型;功能包只拥有功能特定的规则定义。

决策 8:标题规则下沉到 H1Plugin 到 H6Plugin

标题快捷语法不应强迫用户只为获得#而安装/配置聚合的HeadingPlugin。最佳归属:BaseH1Plugin拥有h1规则、BaseH2Plugin拥有h2规则……BaseHeadingPlugin仅保留为便利聚合器。这带来更好的 kit DX 与包直觉。

决策 9:用 editor API 助手取代 getTextFromBlockStart

不要用"魔法边界选项"去重载editor.api.string(...)——那会让最基本的文本 getter 变得怪异。最佳动作:删除独立的getTextFromBlockStart.ts导出,新增editor.api.textFromBlockStart()。理由:名字直观、与实际反复出现的调用点匹配、string上不藏选项汤。若将来出现更多边界助手,再扩展为家族,现在不做过度泛化。

决策 10:让 InputRulesPlugin 变为 edit-only

输入规则运行时没有理由在非编辑表面运行。这个改动小而明显。

决策 11:硬切断 link 包的遗留 API 重复

三件事一起做:删除公开的packages/link/src/lib/automd;从BaseLinkPlugin.ts中移除shouldAutoLinkPaste配置;让pasteAutolink的命名规则覆盖/配置成为唯一的定制路径。理由:命名规则覆盖是更好的 API,同时保留两个表面等于行为控制的重复。

四、计划中的最终推荐 API

插件定义侧

ItalicPlugin.extend({ inputRulePresets: { markdown: ["emphasisAsterisk", "emphasisUnderscore"], }, inputRules: { emphasisAsterisk: createInputRule({ type: "delimitedMark", mark: KEYS.italic, pattern: { start: "*", end: "*", trigger: "*" }, }), emphasisUnderscore: createInputRule({ type: "delimitedMark", mark: KEYS.italic, pattern: { start: "_", end: "_", trigger: "_" }, }), }, });

插件配置侧

ItalicPlugin.configure({ inputRules: { markdown: true, emphasisUnderscore: null, }, });

应用本地拷贝的"产品糖"(registry 风格)

export const TypographyShortcutsKit = [ createSlatePlugin({ key: "typographyShortcuts", inputRulePresets: { defaults: ["smartQuotes", "ellipsis", "mdash"], }, inputRules: { smartQuotes: createInputRule({ type: "textSubstitution", format: ["“", "”"], match: '"', }), ellipsis: createInputRule({ type: "textSubstitution", format: "…", match: "...", }), mdash: createInputRule({ type: "textSubstitution", format: "—", match: "--", }), }, }).configure({ inputRules: { defaults: true, }, }), ];

被拒绝的备选方案

备选结论理由
保留inputRuleGroups并在旁边加糖拒绝常见场景就算被糖衣覆盖,公开 API 仍然是分裂的
发布多个顶层助手名拒绝可发现性债务;一个主构建器加一个低层逃生舱更干净
文本替换保留在发布包中拒绝为"大多数人应当本地检视的行为"做框架表面膨胀
editor.api.string加 block-start 标志拒绝为省一个助手名让简单 API 变怪异

五、六阶段迁移计划

Phase 1:重塑 core 类型

涉及文件:types.ts、packages/core/src/lib/plugin/SlatePlugin.tspackages/core/src/react/plugin/PlatePlugin.ts、resolvePlugin.ts、resolvePlugins.ts。任务:用inputRules内的 preset 激活取代公开inputRuleGroups配置;把内部定义存储从 groups 改名为 presets;更新editor.meta.inputRules.plugins[*]暴露presets而非groups;保留"同名覆盖 → 优先级 → 确定性顺序"的解析链。

Phase 2:加入真正的构建器层

涉及文件:defineInputRule.ts 与packages/core/src/lib/plugins/input-rules/下的新构建器文件。任务:保持defineInputRule最小化;新增createInputRule;加入类型化变体所用的内部 matcher 助手;把 mark/block/text-substitution 的共享匹配逻辑从功能包移出。

Phase 3:提升共享 editor 助手

任务:把独立的getTextFromBlockStart导出替换为editor.api.textFromBlockStart();更新 code-block、list、basic-nodes 及其他规则家族改用该助手。

Phase 4:修复功能归属

涉及文件:BaseHeadingPlugin.ts 及同文件的标题叶插件、BaseCodeBlockPlugin.ts、BaseLinkPlugin.ts、link/list/math 三个包的inputRules.ts。任务:把标题规则归属移到叶插件;把粗糙的公开规则名改名为真实覆盖单元;重写功能规则改用 core 构建器而非本地 matcher 拷贝;移除shouldAutoLinkPaste;删除packages/link/src/lib/automd

Phase 5:硬切断文本替换出包

涉及文件:原 typography 包的BaseTypographyPlugin.tsBaseSymbolsPlugin.ts、autoformat-kit.tsx 及相关 registry kit 文件。任务:删除已发布的 typography/symbols 包方向;把已发布的替换规则移入 registry 本地的拷贝代码;把 kit 文件重命名为它实际的东西。

Phase 6:清理与文档

任务:让InputRulesPlugin变为 edit-only;文档只教inputRules;停止教inputRuleGroups;registry kits 以"显式的拷贝快捷代码"呈现,而不是隐藏包魔法。

六、测试计划

  • Core:通过inputRules的 preset 激活;同名覆盖;preset 加逐规则删除;不经 preset 直接启用单规则;不同规则的优先级排序;运行时 meta 暴露 presets 与 rules。
  • BuilderscreateInputRule({ type: 'delimitedMark' }){ type: 'blockStart' }{ type: 'terminalBlock' }{ type: 'textSubstitution' }四种变体;低层defineInputRule仍支持自定义规则。
  • Features:标题叶插件归属;emphasisAsterisk/emphasisUnderscore分离;代码围栏与块级公式仍能正确转换;链接 autolink 用命名规则覆盖而非shouldAutoLinkPaste;列表与引用块继续尊重 code-block 守卫。
  • Registry:拷贝的文本替换 kit 保持显式可编辑;registry 元数据真实地暴露激活的 presets/rules。

七、验收标准

  1. 公开配置只使用inputRules
  2. preset 激活与逐规则覆盖共享同一个显而易见的配置面;
  3. 不存在已发布的文本替换包;
  4. 共享 matcher 逻辑不再在各功能包间重复;
  5. 标题快捷语法不再依赖HeadingPlugin聚合插件;
  6. shouldAutoLinkPaste消失;
  7. packages/link/src/lib/automd消失;
  8. InputRulesPlugin为 edit-only;
  9. 文档与 registry 示例与新形态一致。

八、结合仓库源码的实现印证

以下事实来自对当前仓库代码的核实,可帮助读者判断该方案在仓库中的落地程度:

运行时已 edit-only。从源码结构看,internal/InputRulesPlugin.ts 中InputRulesPlugincreateTSlatePlugin({ editOnly: true, key: 'inputRules' })创建,对应决策 10 已经落实。该插件overrideEditor重写了insertBreak/insertData/insertText三个 transform:遍历editor.meta.inputRules中对应目标下的规则,enabled返回falseresolve返回undefined时跳过,apply返回非false即视为"已处理"并break,否则回落到原始 transform(见 InputRulesPlugin.ts)。insertText分支通过editor.meta.inputRules.insertText.byTrigger[text]做触发字符索引,只评估当前输入字符相关的规则——这正是文档评估中肯定的"运行时索引"方向。

规则元模型与解析。types.ts 定义了InputRuleTarget'insertBreak' | 'insertData' | 'insertText')、带getBlockStartRange/getBlockStartText/getCharBefore/getCharAfter等惰性缓存 getter 的SelectionInputRuleContext,以及ResolvedInputRulesMeta(按目标分组、insertText按触发字符再索引、并记录id/pluginKey/priority/ruleIndex/pluginIndex)。resolvePlugins.ts 负责把各插件的inputRules定义(支持数组或(ctx) => 数组工厂形式,见 types.ts 的InputRulesDefinition)解析进editor.meta.inputRules,并在没有任何规则注册时给出 "Enable inputRules on the feature plugins you use instead." 的提示——印证了"core 分发 + 功能插件自持规则"的归属模型。

构建器层已在 core。index.ts 导出createInputRulescreateRuleFactorydefineInputRuletypes四个模块。createInputRules.ts 导出类型化的构建函数与次级 matcher 助手:createMarkInputRule(第 88 行)、matchBlockStart(第 166 行)、createBlockStartInputRule(第 233 行)、matchBlockFence(第 275 行)、createBlockFenceInputRule(第 312 行)、matchDelimitedInline(第 354 行)、createTextSubstitutionInputRule(第 615 行)。可以推断,实现上把文档"决策 6"命名的matchDelimitedText/matchTerminalBlock/matchTextSubstitution落成了matchDelimitedInline/matchBlockFence等具体名字,语义一一对应。createRuleFactory.ts 则提供了带默认值的工厂形态:createRuleFactory(config)支持type: 'mark' | 'blockStart' | 'blockFence' | 'insertText' | 'insertBreak' | 'insertData' | 'textSubstitution'七种类型(见 createRuleFactory.ts 的AnyRuleFactoryConfig),各字段可传静态值或(input) => 值的函数,priority/enabled可在工厂调用时覆盖。defineInputRule.ts 本身是一个带重载签名的恒等函数,保留了文档所说的"低层逃生舱"角色。

遗留表面已清除。在当前仓库中:packages/typography目录已不存在(对应决策 4 与 Phase 5);packages/link/src/lib/automd已删除;全仓库已搜不到shouldAutoLinkPaste公开配置(LinkRules.ts 中仅保留内部函数shouldAutoLinkPasteByDefault,作为粘贴自动链接的默认行为判定);独立的getTextFromBlockStart.ts文件不存在,取而代之的是运行时上下文里的getBlockStartText()getter——它通过editor.api.range('start', selection)取块首到选区的 range,再经editor.api.string(range)取文本(见 InputRulesPlugin.ts),与文档决策 9"不重载string的边界选项、避免工具函数泄漏"的精神一致(实现选择了上下文 getter 而非新增editor.api.textFromBlockStart()方法,属于对方案细节的等价演进)。

标题归属与功能规则。BaseHeadingPlugin.ts 与 BaseCodeBlockPlugin.ts 等文件按迁移计划 Phase 4 的指向继续存在,功能侧规则如 link 的粘贴 autolink 规则集中在 LinkRules.ts(第 244 行可见shouldLink: shouldAutoLinkPasteByDefault(...)的调用点),注册表侧的替换规则集继续以 autoformat-kit.tsx 这类 registry 组件形式提供,符合"拷贝代码可检视"的 shadcn 式透明性目标。

九、下一步:core-first 验证切片

文档给出的收尾建议是:若该方向获批,先做一个小的 core-first spike——

  1. inputRules内的 preset 激活取代公开inputRuleGroups
  2. 加入createInputRule
  3. getTextFromBlockStart迁移到 editor API(实现为textFromBlockStart());
  4. 端到端迁移一个完整功能切片:ItalicPluginH1PluginCodeBlockPlugin、link 的 paste autolink 覆盖。

这个切片足以在推平整个仓库之前证明该 DX 方案成立。从当前仓库状态看,上述四点中的大部分方向(edit-only 运行时、core 构建器层、link 清理、typography/automd 移除)已有对应代码落地,说明该计划已从"Proposed"进入执行期。

附:适用前提与阅读建议

  • 本文讨论的是 Plate v2(@platejs/core插件模型)下的 input-rules 子系统,代码依据均以当前仓库packages/core/src/lib/plugins/input-rules/目录为准;
  • 计划文档中引用的.omx/specs/.omx/plans/下的深访规格与 PRD 属于内部工作文档,未在仓库中随附,本文不对其内容作引用;
  • 如需进一步验证,可按以下路径深入:核心类型 types.ts、主构建器 createRuleFactory.ts、具体构建函数 createInputRules.ts、运行时 internal/InputRulesPlugin.ts、插件解析 resolvePlugins.ts。

【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 18:10:58

TileLang 容器化环境搭建:Docker 镜像构建与 GPU 容器运行全解

TileLang 容器化环境搭建:Docker 镜像构建与 GPU 容器运行全解 【免费下载链接】tilelang Domain-specific language designed to streamline the development of high-performance GPU/CPU/Accelerators kernels 项目地址: https://gitcode.com/GitHub_Trending…

作者头像 李华
网站建设 2026/9/16 18:09:44

惠普笔记本加装固态硬盘与重装系统实操指南

前两天帮朋友收拾一台惠普笔记本,拆机加装固态硬盘、重装系统,前后折腾了一下午。机器原本是一块机械硬盘,开机两分钟起步,进系统后硬盘占用率还经常飙到100%,基本没法用。加装一块M.2固态并重装Win10之后,…

作者头像 李华
网站建设 2026/9/16 18:09:17

FPGA局部动态重配:Vivado DFX原理、工程实践与避坑指南

1. 项目背景:从一次业务中断说起去年做软件无线电板卡的时候遇到一个很头疼的需求:系统需要在线切换通信波形,但客户明确要求切换期间其他通道的业务不能中断。当时最朴素的做法是停数据、拉高PROG_B、重新加载整颗FPGA的比特流、恢复配置&am…

作者头像 李华
网站建设 2026/9/16 18:07:16

AI搜索演进前瞻:GEO技术驱动下的内容生态重构

当AI搜索从信息检索工具演变为答案生成引擎,内容生态的底层逻辑正在被重写。好客搜公司自2016年成立以来,从搜索类产品起步,到2020年布局短视频系统开发,再到2025年推出智搜GEO产品,其技术路径恰好映射了搜索技术从关键…

作者头像 李华
网站建设 2026/9/16 18:07:05

从刷榜到实战:高效利用GitHub日榜挖掘优秀开源项目

每天上班前,我习惯先把GitHub热榜的日榜刷一遍,这个习惯保持了差不多五年。GitHub日榜上的项目,是过去24小时里全球开发者“用脚投票”的结果——star涨得快、讨论多、被四处转载的项目都会冒出来。2026-09-14这一天的榜单,我粗略…

作者头像 李华