前端开发 大前端 W3Cbest

一个专注 WEB 开发的技术博客

0%

ckeditor 从入门到放弃

CKEDITOR网页编辑器还是挺复杂的,有一个 CKEDITOR 的全局空间,有一个 CKEDITOR.instances的全局实例引用,有 Classic 编辑和 Inline 编辑两种模式,有 Plugin 也有 Widget,有自成一体的编译打包工具,与 AMD\CMD\UMD社区不兼容。

加载

生成编辑器

经典编辑(Classic Editing)

内嵌编辑(Inline Editing)

按钮面板定制(Toolbar)

一组一组定义

config.toolbarGroups = [
{ name: ‘clipboard’, groups: [ ‘clipboard’, ‘undo’ ] },
{ name: ‘editing’, groups: [ ‘find’, ‘selection’, ‘spellchecker’ ] },
{ name: ‘links’ },
{ name: ‘insert’ },
{ name: ‘forms’ },
{ name: ‘tools’ },
{ name: ‘document’, groups: [ ‘mode’, ‘document’, ‘doctools’ ] },
{ name: ‘others’ },
‘/‘,
{ name: ‘basicstyles’, groups: [ ‘basicstyles’, ‘cleanup’ ] },
{ name: ‘paragraph’, groups: [ ‘list’, ‘indent’, ‘blocks’, ‘align’, ‘bidi’ ] },
{ name: ‘styles’ },
{ name: ‘colors’ },
{ name: ‘about’ }
];

一个一个定义

config.toolbar = [
{ name: ‘document’, items: [ ‘Source’, ‘-‘, ‘NewPage’, ‘Preview’, ‘-‘, ‘Templates’ ] },
{ name: ‘clipboard’, items: [ ‘Cut’, ‘Copy’, ‘Paste’, ‘PasteText’, ‘PasteFromWord’, ‘-‘, ‘Undo’, ‘Redo’ ] },
‘/‘,
{ name: ‘basicstyles’, items: [ ‘Bold’, ‘Italic’ ] }
];

插件机制

假如我们开发一个插入当前时间戳的插件

插件目录结构

  • ckeditor root/
    • plugins/
      • timestamp/
        • icons/
          • timestamp.png
        • plugin.js

插件代码

CKEDITOR.plugins.add(‘timestamp’, {
icons: ‘timestamp’,
init: function(editor) {
editor.addCommand(‘insertTimestamp’, {
exec: function(editor) {
var now = new Date();
editor.insertHtml(‘The current date and time is: ‘ + now.toString() + ‘‘);
}
});
editor.ui.addButton(‘Timestamp’, {
label: ‘Insert Timestamp’,
command: ‘insertTimestamp’,
toolbar: ‘insert’
});
}
});

通过 CKEDITOR.plugins.add方法添加插件,第一个参数为插件名,后面为参数列表。 通过editor.addCommand方法添加一个 insertTimestamp 的命令 通过editor.ui.addButton方法添加一个按钮控件,并绑定其执行的 command 通过 editor.insertHtml方法往编辑内容区域追加内容

加载插件

通过配置文件来开启插件

config.extraPlugins = ‘timestamp’;

如果这是一个会出现在 Toolbar 的插件,且 Toolbar 被定制过,则需要显性配置 toolbar让其显示

config.toolbar = {
{name: ‘insert’, [‘Timestamp’]}
}

挂件(Widget)

挂件是由一组 html 元素组成的特殊富文本单元,类似于模板机制 与插件的区别 挂件有 template 字段,插件没有 挂件目录结构 与插件一致

挂件代码

CKEDITOR.plugins.add( ‘simplebox’, {

// 表明这是一个 widget
requires: ‘widget’,

icons: 'simplebox',

init: function( editor ) {
    CKEDITOR.dialog.add( 'simplebox', this.path + 'dialogs/simplebox.js' );

    editor.widgets.add( 'simplebox', {

   // 鼠标 hover 在 toolbar 上出现的提示
        button: 'Create a simple box',

        // 挂件模板
        template:
            '<div class="simplebox">' +
                '<h2 class="simplebox-title">Title</h2>' +
                '<div class="simplebox-content"><p>Content...</p></div>' +
            '</div>',

        // 定义挂件中可编辑的部分
        editables: {
            title: {
                selector: '.simplebox-title',
                allowedContent: 'br strong em'
            },
            content: {
                selector: '.simplebox-content',
                allowedContent: 'p br ul ol li strong em'
            }
        },

        // 挂件内允许出现的组合
        allowedContent:
            'div(!simplebox,align-left,align-right,align-center){width};' +
            'div(!simplebox-content); h2(!simplebox-title)',

        // 挂件最小组合,如果这个 div 被删除,则自动清除该挂件
        requiredContent: 'div(simplebox)',

        dialog: 'simplebox',

        upcast: function( element ) {
            return element.name \== 'div' && element.hasClass( 'simplebox' );
        },

        init: function() {
            var width \= this.element.getStyle( 'width' );
            if ( width )
                this.setData( 'width', width );
            if ( this.element.hasClass( 'align-left' ) )
                this.setData( 'align', 'left' );
            if ( this.element.hasClass( 'align-right' ) )
                this.setData( 'align', 'right' );
            if ( this.element.hasClass( 'align-center' ) )
                this.setData( 'align', 'center' );
        },

        data: function() {

            if ( this.data.width \== '' )
                this.element.removeStyle( 'width' );
            else
                this.element.setStyle( 'width', this.data.width );

            this.element.removeClass( 'align-left' );
            this.element.removeClass( 'align-right' );
            this.element.removeClass( 'align-center' );
            if ( this.data.align )
                this.element.addClass( 'align-' + this.data.align );
        }
    } );
}

} );

ACF

CKEditor 的高级内容过滤器,当用户在源码输入模式、editor.setData输入、直接粘贴 html 代码等输入时候,将不希望出现的内容给过滤掉。

自动模式(Automatic Mode)

config.allowedContent 没有设置的时候,ACF 就会进入自动模式。 自动模式通过config.removePluginsconfig.removeButtonsconfig.format_tag 来做过滤微调

config.removePlugins = ‘image,table,tabletools,horizontalrule’;
config.removeButtons = ‘Anchor,Underline,Strike,Subscript,Superscript’;
config.format_tags = ‘p;h1;h2;pre’;

自定义模式(Custom Mode)

通过 config.allowedContent 来进入自定义模式

config.allowedContent =
‘h1 h2 h3 p blockquote strong em;’ +
‘a[!href];’ +
‘img(left,right)[!src,alt,width,height];’;

ACF语法

elements [attributes]{styles}(classes)

例如我们需要保留这样的富文本内容,规则为span(mod_fillblank),其 attributes 对 class 无效。

实战建议

  • 能用 CKEditor 社区插件解决的问题,用插件解决
  • 插件解决不了的问题,业务自己写plugin 或者 widget 解决
  • 业务自己写的部分,尽量不要用 CKEditor 自带的 CKEDITOR.dialog ,他们的实现是用 JS 去码DOM 结构,太复杂了。随便一个 Dialog 控件都能用得很舒服
  • 不要用CKEditor 的 jQuery Adapter,他家的 Adapter 对于同一个 DOM 的进行实例化、销毁等操作有 bug,时不时给你冒一个错误。自己包裹一个 Adapter 则肯定没有 bug
  • 工程化的时候,构建工具做依赖分析的时候,记得排除掉 CKEditor 目录,否则他家一堆的插件,会严重拖慢依赖分析那步
坚持技术创作分享,您的支持将鼓励我继续创作!