GrapesJS Commands 命令系统 API 实战指南注册、生命周期、事件与状态管理【免费下载链接】grapesjsFree and Open source Web Builder Framework. Next generation tool for building templates without coding项目地址: https://gitcode.com/GitHub_Trending/gr/grapesjs导读GrapesJS 的命令系统Commands是编辑器的动作中枢几乎所有的用户交互——选中组件、拖拽移动、复制粘贴、预览、全屏、打开面板——在内部都被抽象为一条条可注册、可触发、可监听、可扩展的命令。本文以 docs/api/commands.md 为主线结合packages/core中命令模块的完整源码与测试用例系统讲解命令模块的初始化配置、事件体系、全部 API 方法与状态跟踪机制并给出可直接复制的自定义命令实战代码。读完本文你将掌握如何用editor.Commands注册自己的业务命令、在正确时机监听与中止命令执行以及利用strict、defaultOptions等配置项统一管理命令行为。命令模块的定位与初始化命令模块是 GrapesJS 编辑器的核心子系统之一模块实现位于 packages/core/src/commands/index.ts 中的CommandsModule类每个内置命令的抽象基类则在 packages/core/src/commands/view/CommandAbstract.ts。模块在编辑器初始化时自动加载默认注册了数十条内置命令如core:preview、core:component-select等同时也允许你在初始化阶段通过配置对象定制初始状态const editor grapesjs.init({ commands: { // options } })编辑器实例化完成后可以通过 API 与事件与命令系统交互。事件与 API 两条路径在文档与源码中是对应的事件用于观察API 用于驱动。// Listen to events editor.on(command:run, () { ... }); // Use the API const commands editor.Commands; commands.add(...);editor.Commands即命令模块的公开接口所有方法add、run、stop、get等均可直接调用编辑器层面还提供了便捷的editor.runCommand(id, options)与editor.stopCommand(id, options)方法对应commands.run/commands.stop。模块配置项详解命令模块的配置定义在 packages/core/src/commands/config/config.ts通过grapesjs.init({ commands: {...} })传入。四个配置项及其默认值如下配置项类型默认值说明stylePrefixstringcom-命令相关 DOM 元素的样式前缀通常无需改动defaultsRecordstring, CommandObject{}默认命令集合初始化时会通过add注册其中的每一条命令strictbooleantrue严格模式有状态命令同时定义了run与stop激活后不能重复执行run为false时允许重复执行defaultOptionsCommandsDefaultOptions{}命令的默认选项在run/stop时与调用方传入的 options 合并适合集中定义命令的公共行为defaults初始化时注入自定义命令从源码看CommandsModule构造函数会在模块加载阶段遍历config.defaults并逐一注册见 packages/core/src/commands/index.ts 构造函数部分const editor grapesjs.init({ commands: { defaults: { my-startup-command: { id: my-startup-command, run(editor, sender, options) { console.log(Command injected via config); } } } } });strict有状态命令的幂等控制strict直接影响runCommand的执行条件。源码中runCommand的逻辑为packages/core/src/commands/index.tsif (!this.isActive(id) || options.force || !config.strict) { // 执行命令 }即在严格模式下如果命令已经处于激活状态再次run会被静默忽略但你可以通过传入{ force: true }强制重新执行。stop侧同理stopCommand要求命令处于激活状态或force、或非严格模式才执行。defaultOptions集中定义命令的公共行为defaultOptions允许你为指定命令的run/stop各设置一个选项处理函数返回值会与调用时的 options 合并后再交给命令体执行。配置示例来自 config.ts 官方注释const editor grapesjs.init({ commands: { defaultOptions: { core:component-drag: { run: (options) ({ ...options, skipGuidesRender: true, addStyle({ component, styles, partial }) { component.addStyle(styles, { partial }); } }), stop: (options) ({ ...options, skipGuidesRender: true, addStyle({ component, styles, partial }) { component.addStyle(styles, { partial }); } }) } } } });该机制在测试用例 packages/core/test/specs/commands/index.ts 的Run command and check if none, custom, and default options are passed用例中得到验证run结果会依次合并默认选项与自定义选项。命令的本质run 与 stop在 GrapesJS 中一条命令就是一个具有run方法可选stop方法的对象。命令内部统一继承自CommandAbstractpackages/core/src/commands/view/CommandAbstract.ts其执行流程由callRun/callStop两个私有方法驱动这也是所有事件触发的源头。run(editor, sender, options)执行命令主体逻辑stop(editor, sender, options)撤销/结束命令可选。没有stop方法的命令不会进入激活状态源码中注册时会将无stop的命令标记为noStop true见add与create方法。CommandAbstract同时为命令实例提供了与画布交互的便利能力getCanvas()、getCanvasBody()、getCanvasTools()以及offset(el)内置命令可借此操作画布 DOM 与工具层。内置命令全景模块构造函数中通过commandsDef数组packages/core/src/commands/index.ts注册了全部内置命令每条命令同时挂载core:xxx命名空间并保留旧命令名作为别名旧名触发的事件会自动转发到新名见源码中em.on(${name}:${oldCmd}, ...)的转发逻辑。完整列表如下新命令名旧命令名别名功能core:previewpreview预览模式隐藏面板、禁止编辑实现见 Preview.tscore:resizeresize组件尺寸调整core:fullscreenfullscreen编辑器全屏core:copy—复制选中组件core:paste—粘贴组件core:canvas-move—平移画布core:canvas-clear—清空画布core:open-codeexport-template打开代码导出弹窗HTML/CSS 源码实现见 ExportTemplate.tscore:open-layersopen-layers打开图层面板core:open-stylesopen-sm打开样式管理器core:open-traitsopen-tm打开属性Traits面板core:open-blocksopen-blocks打开块管理器面板实现见 OpenBlocks.tscore:open-assetsopen-assets打开资源管理器core:component-selectselect-comp组件选择工具高亮、badge、工具栏、尺寸手柄实现见 SelectComponent.tscore:component-outlinesw-visibility显示/隐藏组件轮廓core:component-offsetshow-offset显示边距/内边距偏移core:component-movemove-comp移动组件core:component-next—选中下一个组件core:component-prev—选中上一个组件core:component-enter—进入选中组件内部core:component-exitselect-parent退出到父组件core:component-delete—删除选中组件core:component-style-clear—清除组件内联样式core:component-drag—拖拽组件此外还有core:undo/core:redo代理到UndoManager以及工具栏内部使用的tlb-delete、tlb-clone、tlb-move。测试用例Default command aliases keep their registered ids见 packages/core/test/specs/commands/index.ts确认了core:preview/preview、core:fullscreen/fullscreen、core:component-outline/sw-visibility等别名各自独立持有 id。命令生命周期事件体系命令模块通过事件将执行过程完全暴露给外部。事件名定义在 packages/core/src/commands/types.ts 的CommandsEvents枚举中触发顺序由CommandAbstract.callRun/callStop保证。所有事件回调均接收{ id, result, options }command:call系列额外携带type。执行run阶段command:run—— 任意命令执行完成后触发editor.on(command:run, ({ id, result, options }) { console.log(Command id, id, command result, result); });command:run:COMMAND-ID—— 指定命令执行完成后触发editor.on(command:run:my-command, ({ result, options }) { ... });command:run:before:COMMAND-ID—— 命令体执行之前触发这是拦截/中止命令的关键钩子editor.on(command:run:before:my-command, ({ options }) { ... });command:abort:COMMAND-ID—— 命令被中止时触发。中止方式是在 before 事件中设置options.abort trueeditor.on(command:abort:my-command, ({ options }) { ... }); // The command could be aborted during the before event editor.on(command:run:before:my-command, ({ options }) { if (someCondition) { options.abort true; } });从callRun源码可以看到options.abort被检查后若为真则直接触发command:abort:ID并提前返回命令体run不会执行。停止stop阶段command:stop—— 任意命令停止时触发editor.on(command:stop, ({ id, result, options }) { console.log(Command id, id, command result, result); });command:stop:COMMAND-ID—— 指定命令停止时触发注意原文档示例中的事件名写法为command:run:my-command实际应监听command:stop:my-commandeditor.on(command:stop:my-command, ({ result, options }) { ... });command:stop:before:COMMAND-ID——stop方法被调用之前触发editor.on(command:stop:before:my-command, ({ options }) { ... });统一调用call阶段command:call与command:call:COMMAND-ID在每次run或stop时都会触发通过type字段区分调用类型editor.on(command:call, ({ id, result, options, type }) { console.log(Command id, id, command result, result, call type, type); });editor.on(command:call:my-command, ({ result, options, type }) { ... });事件的内部触发顺序结合 CommandAbstract.ts 的callRun实现一次完整的run触发顺序为command:run:before:COMMAND-ID若options.abortcommand:abort:COMMAND-ID流程终止执行run(editor, sender, options)若无stop方法则不会写入激活表command:run:COMMAND-ID→command:call:COMMAND-ID→command:run→command:callcallStop的触发顺序为command:stop:before:COMMAND-ID→ 执行stop→ 从激活表中移除 →command:stop:COMMAND-ID→command:call:COMMAND-ID→command:stop→command:call。API 方法逐一详解模块全部公开方法均实现在 packages/core/src/commands/index.ts下文按文档顺序展开并补充源码级行为说明。add —— 注册新命令向命令集合添加命令。id为命令 IDcommand可以是对象或函数传函数等价于只有run的无状态命令。commands.add(myCommand, { run(editor, sender) { alert(Hello world!); }, stop(editor, sender) { }, }); // As a function commands.add(myCommand2, editor { ... });返回this支持链式调用。源码实现细节若传入的是CommandAbstract子类构造函数直接登记为构造函数并按原型是否含stop决定noStop若传入普通函数包装为{ run: command }若无stop方法标记noStop true该命令不会进入激活状态命令对象会通过CommandAbstract.extend(result)转换为命令类并写入id。remove —— 移除命令按 ID 从集合中移除命令。若该命令正处于激活状态会先自动调用stop再删除源码中remove首先isActive检查并调用stopCommand(command, { force: true })随后清理激活表与命令表commands.remove(myCommand);返回this。测试用例Remove active command and clean up active state验证了移除激活中的命令会先触发其stop且移除后isActive与has均返回false。get —— 获取命令按 ID 返回命令对象CommandAbstract实例。若该命令以构造函数形式注册首次get时会实例化并缓存var myCommand commands.get(myCommand); myCommand.run();若命令不存在会向编辑器写入logWarning(id command not found)并返回undefined。extend —— 扩展已有命令在已有命令须为对象形式定义基础上叠加新方法。新方法会与命令原型合并后重新注册适合对内置命令做局部增强commands.extend(old-command, { someInnerFunction() { // ... } });返回this。源码中还会检查被扩展的旧命令名别名并同步扩展见extend中对commandsDef旧名的处理保证新旧命名空间行为一致。has —— 检查命令是否存在const exists commands.has(myCommand); // true | false返回布尔值实现即!!this.commands[id]。getAll —— 获取全部命令返回包含所有已注册命令的对象键为命令 IDconst all commands.getAll();run —— 执行命令按 ID 执行命令第二个参数为透传给命令体run方法的选项对象commands.run(myCommand, { someOption: 1 });返回任意类型具体返回值由命令的run方法决定。如run返回result该值会出现在command:run/command:call事件数据中同时被记录进激活表对有状态命令而言。stop —— 停止命令按 ID 停止命令将options透传给命令体stopcommands.stop(myCommand, { someOption: 1 });返回任意类型返回值由命令的stop方法决定。注意只有带stop方法有状态的命令才可被记录为激活并成功停止。isActive —— 检查命令是否激活run激活命令、stop停用命令。未定义stop方法的命令无法被登记为激活const cId some-command; commands.run(cId); commands.isActive(cId); // - true commands.stop(cId); commands.isActive(cId); // - false实现为this.getActive().hasOwnProperty(id)。getActive —— 获取全部激活命令返回当前处于激活状态的所有命令及其最近一次run的返回值console.log(commands.getActive()); // - { someCommand: itsLastReturn, anotherOne: ... };状态跟踪的底层逻辑激活状态与run返回值绑定callRun在命令体执行后执行editor.Commands.active[id] result无noStop时callStop在停止后执行delete editor.Commands.active[id]。测试用例Run simple command and check if the state is tracked与Run command only with run method, ensure is not tracked、Run function command, ensure is not tracked分别验证了有状态命令与无状态仅run/ 函数式命令在激活表上的差异。实战注册一条带 TypeScript 类型约束的自定义命令命令模块对 TypeScript 支持完善CommandRegistryRun/CommandRegistryStop接口packages/core/src/commands/registry.ts会为runCommand/stopCommand提供按命令 ID 推导的选项与返回值类型。在测试文件 packages/core/test/specs/commands/index.ts 中展示了标准的类型增强写法interface MyCommandOptions { value: number; } interface MyCommandResult { done: boolean; } interface MyCommandStopOptions { reason: string; } declare module grapesjs { interface CommandRegistryRun { my:command: (options: MyCommandOptions) MyCommandResult; my:stateless: () number; } interface CommandRegistryStop { my:command: (options: MyCommandStopOptions) void; } } editor.Commands.add(my:command, { run(_editor, _sender, options) { options.value.toFixed(); return { done: true }; }, stop(_editor, _sender, options) { options.reason.toUpperCase(); }, }); // 类型安全的调用选项与返回值均受注册表约束 const result editor.runCommand(my:command, { value: 1 }); result.done; // true注册后即可在运行时与编辑器面板按钮、快捷键联动同时配合command:run:before:my:command事件与options.abort实现权限校验后放行的拦截式命令治理。总结GrapesJS 的命令系统用一套统一的注册—触发—监听—停止模型覆盖了编辑器全部核心交互add/remove/extend负责命令的静态管理run/stop驱动命令执行isActive/getActive维护有状态命令的激活表而command:*事件体系则把执行过程的每一个阶段before、abort、run、stop、call都开放给外部观察与干预。深入理解这套机制无论是扩展内置命令、编排复杂交互流程还是为编辑器集成方提供可配置的能力入口都能做到有的放矢。【免费下载链接】grapesjsFree and Open source Web Builder Framework. Next generation tool for building templates without coding项目地址: https://gitcode.com/GitHub_Trending/gr/grapesjs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考