
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()加上markInputRule、textblockTypeInputRule、nodeInputRule、wrappingInputRule、textInputRule等类型化构建助手Lexical 把显式的 transformer 子集传给MarkdownShortcutPluginshadcn 模式则证明拷贝到本地的代码对希望检视并修改的产品糖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 ceremonialplatejs/typography是为产品糖做的包膨胀package sprawl规则构建器逻辑在多个包之间重复BaseHeadingPlugin拥有标题快捷语法是糟糕的 DX因为用户通常配置的是H1Plugin到H6Plugin而不是聚合插件shouldAutoLinkPaste与按命名规则覆盖的能力重复link 包的automd目录是一个僵尸表面zombie surfacegetTextFromBlockStart作为独立导出是一种笨拙的工具函数泄漏defineInputRule对主构建器而言太原始raw但又太小、不足以帮助开发者发现更好的路径。结论运行时值得保留公开 API 还需要再做一次硬性的清理one more hard cleanup pass。三、十一项核心决策决策 1删除公开的 inputRuleGroupsinputRuleGroups不应作为公开配置存活。它准确但笨重——它强迫每个使用者为一个概念哪些快捷方式开着思考两个字段。计划要求的公开配置形态变为单字段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{ ... }nullpreset 条目启用该 preset—不适用删除该 preset规则条目直接启用该规则配置该规则删除该规则约束同一个插件内preset 名与规则名不得冲突。决策 3把分隔符变体拆成独立的公开规则名这是文档认定的真实失误如果开发者真正关心*和_的差别emphasis这个名字太粗糙。公开规则名应描述真正的覆盖单元emphasisAsterisk/emphasisUnderscorestrongAsterisk/strongUnderscoreboldItalicAsterisk/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 暴露matchDelimitedText、matchBlockStart、matchTerminalBlock、matchTextSubstitution。它们用于构建自定义规则或支撑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.ts、packages/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.ts、BaseSymbolsPlugin.ts、autoformat-kit.tsx 及相关 registry kit 文件。任务删除已发布的 typography/symbols 包方向把已发布的替换规则移入 registry 本地的拷贝代码把 kit 文件重命名为它实际的东西。Phase 6清理与文档任务让InputRulesPlugin变为 edit-only文档只教inputRules停止教inputRuleGroupsregistry 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。七、验收标准公开配置只使用inputRulespreset 激活与逐规则覆盖共享同一个显而易见的配置面不存在已发布的文本替换包共享 matcher 逻辑不再在各功能包间重复标题快捷语法不再依赖HeadingPlugin聚合插件shouldAutoLinkPaste消失packages/link/src/lib/automd消失InputRulesPlugin为 edit-only文档与 registry 示例与新形态一致。八、结合仓库源码的实现印证以下事实来自对当前仓库代码的核实可帮助读者判断该方案在仓库中的落地程度运行时已 edit-only。从源码结构看internal/InputRulesPlugin.ts 中InputRulesPlugin以createTSlatePlugin({ editOnly: true, key: inputRules })创建对应决策 10 已经落实。该插件overrideEditor重写了insertBreak/insertData/insertText三个 transform遍历editor.meta.inputRules中对应目标下的规则enabled返回false或resolve返回undefined时跳过apply返回非false即视为已处理并break否则回落到原始 transform见 InputRulesPlugin.ts。insertText分支通过editor.meta.inputRules.insertText.byTrigger[text]做触发字符索引只评估当前输入字符相关的规则——这正是文档评估中肯定的运行时索引方向。规则元模型与解析。types.ts 定义了InputRuleTargetinsertBreak | 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 导出createInputRules、createRuleFactory、defineInputRule、types四个模块。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 5packages/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——用inputRules内的 preset 激活取代公开inputRuleGroups加入createInputRule把getTextFromBlockStart迁移到 editor API实现为textFromBlockStart();端到端迁移一个完整功能切片ItalicPlugin、H1Plugin、CodeBlockPlugin、link 的 paste autolink 覆盖。这个切片足以在推平整个仓库之前证明该 DX 方案成立。从当前仓库状态看上述四点中的大部分方向edit-only 运行时、core 构建器层、link 清理、typography/automd 移除已有对应代码落地说明该计划已从Proposed进入执行期。附适用前提与阅读建议本文讨论的是 Plate v2platejs/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),仅供参考