如何为MooEditable编写Action扩展:快捷键、状态到对话框完整指南

原创2026-10-15 16:10:271,409 阅读

如何为MooEditable编写Action扩展:快捷键、状态到对话框完整指南

MooEditable 是一款基于 MooTools 的轻量级 Web WYSIWYG 所见即所得编辑器,它的每一个工具栏按钮、快捷键、按钮高亮状态乃至弹出对话框,背后都是一个注册在 MooEditable.Actions 对象里的 Action 扩展。本文以最短路径带你吃透 Action 的 6 个成员(title / options / states / command / dialogs / events),学会从"一句话按钮"到"带对话框+快捷键+状态高亮"的完整 Action 扩展写法,并附 4 个真实源码案例与排错清单,让你快速掌握 MooEditable 自定义按钮扩展的方法。

1. 先搞懂:MooEditable 的 Action 机制

在 MooEditable 里,"Action"= 工具栏上的一枚按钮 + 一串可被触发的行为。所有 Action 都注册在全局对象 MooEditable.Actions 上,核心定义位于 MooEditable.js。

编辑器初始化时会自动完成三件事:

  • 遍历 actions 选项中的每个名字,到 MooEditable.Actions 里查找对应对象,并在工具栏上渲染按钮;
  • 收集每个 Action 的 options.shortcut,注册成 Ctrl/Cmd + 字母 快捷键;
  • 绑定每个 Action 声明的 dialogs 与 events。

这段"自动绑定"逻辑就在构造函数里,源码见 MooEditable.js:

this.actions.each(function(action){
    var act = MooEditable.Actions[action];
    if (!act) return;
    if (act.options){
        var key = act.options.shortcut;
        if (key) this.keys[key] = action;
    }
    if (act.dialogs){
        Object.each(act.dialogs, function(dialog, name){
            dialog = dialog.attempt(this);
            dialog.name = action + ':' + name;
            // ... 自动注入对话框并绑定 open/close
        }, this);
    }
    if (act.events){
        Object.each(act.events, function(fn, event){
            this.addEvent(event, fn);
        }, this);
    }
}.bind(this));

💡 关键结论:注册 Action 的 JS 必须放在 mooEditable() 初始化之前加载,否则找不到按钮定义。

Action 对象的 6 个成员速览

成员 作用 示例
title 按钮标题(悬停提示),通常用 MooEditable.Locale.get() 取多语言文本 title: MooEditable.Locale.get('bold')
options.shortcut 注册快捷键(mac 上是 Cmd + 字母) options: { shortcut: 'b' }
states 按钮状态高亮判定(tags / css,或自定义函数) states: { tags: ['b', 'strong'] }
command 自定义执行函数,优先于浏览器 execCommand command: function(){ ... }
dialogs 该 Action 可打开的对话框(打开时自动保存选区) dialogs: { prompt: function(editor){...} }
events 监听编辑器事件(attach、beforeToggleView 等) 见 pagebreak 案例

2. 给 Action 加快捷键:一行 options 就够

以核心源码中的加粗按钮为例(MooEditable.js):

bold: {
    title: MooEditable.Locale.get('bold'),
    options: {
        shortcut: 'b'
    },
    states: {
        tags: ['b', 'strong'],
        css: {'font-weight': 'bold'}
    }
}

快捷键的生效链路非常简洁——initialize 中把 shortcut 存入 this.keys,按键时由 keyListener 触发对应工具栏按钮(MooEditable.js):

keyListener: function(e){
    var key = (Browser.Platform.mac) ? e.meta : e.control;
    if (!key || !this.keys[e.key]) return;
    e.preventDefault();
    var item = this.toolbar.getItem(this.keys[e.key]);
    item.action(e);
}

也就是说:你不需要写任何 keydown 监听代码,声明 shortcut 即可,且按钮悬停提示里会自动显示 Ctrl + B 之类的字样。

⚠️ 注意:同一个字母只能属于一个 Action,声明顺序以 MooEditable.Actions 中先出现的为准。

3. 状态高亮原理:states 的三种写法

光标停留在某段加粗文字上时,加粗按钮会亮起——这就是 states 的工作。状态检测发生在每次鼠标抬起/按键之后,由 checkStates() 统一调度(MooEditable.js),判定顺序是:

  1. 优先尝试浏览器的 queryCommandState(action);
  2. 失败后向上查找祖先节点,匹配 states.tags(标签名列表);
  3. 再匹配 states.css(行内样式,如 text-align: center)。

三种写法对照:

// 写法1:标签匹配(bold 按钮)
states: { tags: ['b', 'strong'] }

// 写法2:CSS 匹配(justifycenter 按钮)
states: { tags: ['center'], css: {'text-align': 'center'} }

// 写法3:自定义函数(完全掌控判定逻辑)
states: function(element, item){
    if (element.get('tag') == 'custom-tag') item.activate();
}

写法 1/2 来自 MooEditable.js 与 MooEditable.Extras.js;写法 3 由 checkStates 中的 typeOf(states) == 'function' 分支支持。

4. command:让 Action 执行自定义逻辑

按钮被点击(或快捷键被按下)时,核心 action() 方法会这样分发(MooEditable.js):

action: function(command, args){
    var action = MooEditable.Actions[command];
    if (action.command && typeOf(action.command) == 'function'){
        action.command.apply(this, args);
    } else {
        this.focus();
        this.execute(command, false, args);   // 走浏览器 execCommand
        if (this.mode == 'iframe') this.checkStates();
    }
}

由此得到两条规则:

  • 没有 command:按钮名必须是一个原生 execCommand 命令(如 bold、undo、createlink),编辑器自动执行;
  • 有 command:函数体内的 this 就是编辑器实例,可自由调用 this.execute()、this.selection、this.dialogs 等 API。

经典例子——"插入水平线",不依赖 execCommand,直接往选区插入 HTML(MooEditable.Extras.js):

insertHorizontalRule: {
    title: MooEditable.Locale.get('insertHorizontalRule'),
    states: { tags: ['hr'] },
    command: function(){
        this.selection.insertContent('<hr>');
    }
}

带参数的场景:下拉菜单按钮(type: 'menu-list')点击后,选中项会作为第 2 个参数传入(MooEditable.Extras.js):

formatBlock: {
    title: MooEditable.Locale.get('blockFormatting'),
    type: 'menu-list',
    options: {
        list: [
            {text: MooEditable.Locale.get('paragraph'), value: 'p'},
            {text: MooEditable.Locale.get('heading1'), value: 'h1', style: 'font-size:24px; font-weight:bold;'}
        ]
    },
    command: function(menulist, name){   // name 即选中的 value
        this.focus();
        this.execute('formatBlock', false, '<' + name + '>');
    }
}

💡 this 上下文是 MooTools 惯用设计:command 里直接写 this.execute(...)、this.selection.insertContent(...) 即可,无需手动传 editor 对象。

5. 对话框扩展:dialogs 让按钮弹出输入框

给 Action 声明 dialogs 后,初始化时会自动实例化并注入页面,且框架替你处理了繁琐细节:打开对话框时自动保存选区、禁用工具栏,关闭时恢复选区(MooEditable.js)。

最简示例——内置的 urlimage(按图片 URL 插入图片,MooEditable.js):

urlimage: {
    title: MooEditable.Locale.get('addImage'),
    options: { shortcut: 'm' },
    dialogs: {
        prompt: function(editor){
            return MooEditable.UI.PromptDialog(MooEditable.Locale.get('enterImageURL'), 'http://', function(url){
                editor.execute('insertimage', false, url.trim());
            });
        }
    },
    command: function(){
        this.dialogs.urlimage.prompt.open();
    }
}

两个常用对话框工厂函数:

  • MooEditable.UI.PromptDialog(question, answer, fn) —— 带输入框的对话框,点击 OK 后回调 fn(answer);
  • MooEditable.UI.AlertDialog(text) —— 纯提示,用于校验拦截(如未选中文字时提示"请先选中文本")。

更复杂的自定义对话框(表单、图片属性面板)可参考 MooEditable.Image.js 中的 MooEditable.UI.ImageDialog:自己构建 MooEditable.UI.Dialog 的 HTML 与事件,再挂到 dialogs 上即可,打开逻辑同样是一句 this.dialogs.image.prompt.open()。

6. events:监听编辑器生命周期

Action 还可以通过 events 挂接编辑器事件,最实用的两个是:

  • attach:编辑器挂载完成时执行(可注入额外 CSS、监听特殊 DOM 事件);
  • beforeToggleView:源码/所见即所得视图切换前执行(可替换内容里的可视化占位符)。

核心源码里的分页符插件就是完整范本(MooEditable.Pagebreak.js):

MooEditable.Actions.pagebreak = {
    title: MooEditable.Locale.get('pageBreak'),
    command: function(){
        this.selection.insertContent('<img class="mooeditable-visual-aid mooeditable-pagebreak">');
    },
    events: {
        // 切回源码视图时,把可视化占位图片替换成 HTML 注释
        beforeToggleView: function(){
            if (this.mode == 'iframe'){
                var s = this.getContent().replace(
                    /<img([^>]*)class="mooeditable-visual-aid mooeditable-pagebreak"([^>]*)>/gi,
                    '<!-- page break -->');
                this.setContent(s);
            }
        },
        // 初始化时注入专属 CSS
        render: function(){
            this.options.extraCSS = 'img.mooeditable-pagebreak { ... }' + this.options.extraCSS;
        }
    }
};

💡 插件里还有 MooEditable.Actions.Settings 这一约定俗成的配置对象(如分页符图片路径),便于用户不改源码就修改行为,值得在自己插件里沿用。

7. 从零写一个 Action:插入水平线实战

把前 5 节组合起来,30 秒写出一个完整 Action:

// 第 1 步:在 MooEditable.js 加载之后注册 Action
MooEditable.Actions.hline = {
    title: '插入水平线',
    options: { shortcut: 'h' },
    states:  { tags: ['hr'] },
    command: function(){
        this.selection.insertContent('<hr>');
    }
};
// 第 2 步:初始化时把它放进 actions 字符串
$('textarea-1').mooEditable({
    actions: 'bold italic | hline | toggleview'
});

actions 字符串就是工具栏的"排版代码":空格分隔按钮,| 是分组分隔线,/ 是换行分隔。更多自定义工具栏组合可参考示例页 MooEditable.custom-toolbarbuttons.html。

页面多实例场景下,MooEditable.Group 用一个工具栏控制多个编辑器,自定义 Action 只需注册一次、在所有实例中共享,同样通过 actions 选项声明。

常见 Action 写法速查表

案例 位置 用到的成员
bold MooEditable.js title / options / states / events
createlink MooEditable.js 对话框 + 选区校验 + 自定义 command
insertHorizontalRule MooEditable.Extras.js 纯 command 插入 HTML
forecolor MooEditable.Forecolor.js button-overlay 覆盖层按钮
pagebreak MooEditable.Pagebreak.js command + events + Settings
image MooEditable.Image.js 完整自定义 Dialog

8. 排错清单与总结

扩展开发翻车的 5 个高频原因:

  1. 加载顺序错误:注册 MooEditable.Actions.xxx 的脚本晚于 mooEditable() 调用,按钮直接消失(addItem 找不到 Action 会静默跳过);
  2. 快捷键冲突:shortcut 使用了已被占用的字母(如 b、m),后注册者不生效;
  3. 按钮不高亮:忘记写 states,或标签名与清理后的输出不一致(注意 semantics 选项开启后 <b> 会变成 <strong>,tags 里两者都要写);
  4. command 里 this 丢失:用箭头/回调包装函数后 this 不再是编辑器实例,执行命令前记得 this.focus();
  5. 对话框内丢失选区:自己 new MooEditable.UI.Dialog 却绕过 dialogs 注册,失去了框架的选区自动保存/恢复能力。

📌 一句话总结:写 MooEditable Action = 往 MooEditable.Actions 挂一个对象(title + shortcut + states + command/dialogs/events),再把它写进 actions 字符串。从 MooEditable.js 的基础按钮、MooEditable.Extras.js 的菜单按钮,到 MooEditable.Pagebreak.js 的完整插件,把这三个文件通读一遍,你就具备了扩展 MooEditable 的全部能力。更多接口细节可查阅 Docs/MooEditable/MooEditable.md。

登录后查看全文
mooeditable