CKeditor5系列二

2020/04/09 editor

CKeditor5第二篇,创建一个小插件

1、使用原生插件

首先我们先来说一下怎么用原生插件,CKEditor把原生的各种插件也都抽离出来了,减少了包的体积但是当代码用的多的时候可能要加载特别多的包,这个可以去使用他在线打包的那个工具,前面一篇文章也有说,其次就自定义安装加载了,需要什么加载什么的,我这边用NPM安装其他插件。

这里使用horizontal-line插件示例,这里假定你已经安装并成功运行了一个最简单的CKEditor项目。

首先安装它npm install --save @ckeditor/ckeditor5-horizontal-lin

然后在app.js里加载import HorizontalLine from '@ckeditor/ckeditor5-horizontal-line/src/horizontalline';

加载完之后注册到plugins里面

ClassicEditor
  .create(document.querySelector('#editor'), {
    // .... 其他配置
    plugins: [HorizontalLine],
    toolbar: ['HorizontalLine']
  })
  // 创建成功的回调
  .then(editor => {
    // to do sth..
  })
  .catch(error => {
    console.error(error.stack);
  });

2、创建插件

创建一个很简单的插件作为实例,官网也有对应的实例:https://ckeditor.com/docs/ckeditor5/latest/framework/guides/creating-simple-plugin.html

我这里就不用这个了。

插件基本上就是生成下面的结构

<div class="testEdit">
  <span class="edit">编辑</span><span class="delete">删除</span>
</div>

2.1 创建插件的基础

CKEditor创建插件要求尽量低耦合,然后文件管理可以一个文件也可以多个模块组合成一个插件,我这里就用多个模块来处理了。

具体代码的意义我会在注释里加上

在根目录创建一个block文件夹,一般来说需要4个模块,第一个是入口文件。

// block/block.js
import blockedit from './blockedit'
import blockui from './blockui'
// 插件类,创建插件必须基于这个类扩展
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
// 实例化一个类,然后导出
export default class Block extends Plugin{
  static get requires() {
    return [blocked, blockui];
  }
}

第二个是用来定义架构的文件,CKEditor创建元素需要先在代码里注册好架构模型,然后调用命令去生成元素,而不是直接插入一段HTML。

// block/blockedit.js
// 插件类,需要基于这个扩展
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
// 在这个文件里面创建命令
import command from './blockcommad.js';

export default class Blockedit extends Plugin {
  init() {

    // 定义架构的方法  规定元素的属性,这个属性不是HTML的属性,是CKEditor内使用的
    this._defineSchema();
    // 定义转换器  转换器是生成HTML元素的地方  将定义的模型生成HTML结构
    this._defineConverters();

    // 绑定一个事件 block
    this.editor.commands.add('block', new command(this.editor));
  }

  // 具体参数看文档: https://ckeditor.com/docs/ckeditor5/latest/api/module_engine_model_schema-Schema.html
  _defineSchema(){
    const schema = this.editor.model.schema;
    // 创建一个模型,名字是 testEdit
    schema.register('testEdit', {
      // 是否为一个完整的对象,不可被回车拆分,意思是回车等行为都是在它自身容器内进行
      isObject: true,
      // 要求字符串或数组字符串,可以从其他地方继承
      allowWhere: '$block',
    })

    schema.register('edit', {
      isLimit: true,
      // 在哪个地方使用
      allowIn: 'testEdit',
      // 设置改节点是块还是根,根可以回车
      allowContentOf: '$block'
    });

    schema.register('delete', {
      isLimit: true,
      allowIn: 'testEdit', 
      // 设置改节点是块还是根,根可以回车
      allowContentOf: '$block'
    });
  }

  // 文档:https://ckeditor.com/docs/ckeditor5/latest/api/module_editor-classic_classiceditor-ClassicEditor.html#member-conversion
  _defineConverters(){
    const conversion = this.editor.conversion;

    // 定义转换器  testEdit模型转换成视图  -> <div class="testEdit"></div>
    conversion.elementToElement({
      model: 'testEdit',
      view: {
        name: 'div',
        classes: 'testEdit'
      }
    });
    // 这些同理
    conversion.elementToElement({
      model: 'edit',
      view: {
        name: 'button',
        classes: 'edit'
      }
    });
    conversion.elementToElement({
      model: 'delete',
      view: {
        name: 'button',
        classes: 'delete'
      }
    });
  }
}

关于CSS,CSS是需要额外挂载到HTML上的,这里定义类名。也可以在block/blockedit.js里面import './css.css'

第三个文件UI文件,也是创建按钮的文件

import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';

// 这个模块是用来监听 click的
import ClickObserver from '@ckeditor/ckeditor5-engine/src/view/observer/clickobserver';
export default class BoxUI extends Plugin {
  init() {
    console.log('UI#init() got called');

    const editor = this.editor;
    const t = editor.t;

    // “block”按钮必须在编辑器的UI组件中注册
    // 将显示在工具栏中。
    editor.ui.componentFactory.add('block', locale => {
      const view = editor.editing.view;
      const command = editor.commands.get('block');

      // 使用模块
      view.addObserver(ClickObserver);

      const buttonView = new ButtonView(locale);
      // 文档:https://ckeditor.com/docs/ckeditor5/latest/api/module_ui_button_buttonview-ButtonView.html
      buttonView.set({
        // 返回一段文字,用作label,当没有ICON的时候显示Babel
        label: t('insert block'),
        withText: true,
        tooltip: true
      });
      // 将按钮的状态绑定到命令。
      buttonView.bind('isOn', 'isEnabled').to(command, 'value', 'isEnabled');
      // 单击(执行)按钮时执行命令。
      this.listenTo(buttonView, 'execute', () => editor.execute('block'));

      return buttonView;
    });
  }
}

第四个文件就是注册命令的文件了

// block/blockcommad.js
import Command from '@ckeditor/ckeditor5-core/src/command';
import Command from '@ckeditor/ckeditor5-core/src/command';

export default class InsertSimpleBoxCommand extends Command {
  // 执行命令会调这个函数
  execute() {
    this.editor.model.change(writer => {
      // 模型里面插入
      // 文档:https://ckeditor.com/docs/ckeditor5/latest/api/module_engine_model_model-Model.html
      this.editor.model.insertContent(createSimpleBox(writer));
    });
  }
  // 关于这个事件和上面事件或更多,查看文档:https://ckeditor.com/docs/ckeditor5/latest/api/module_engine_model_documentselection-DocumentSelection.html
  refresh() {
    const model = this.editor.model;
    const selection = model.document.selection;
    const allowedIn = model.schema.findAllowedParent(selection.getFirstPosition(), 'testEdit');

    this.isEnabled = allowedIn !== null;
  }
}

function createSimpleBox(writer) {
  const testEdit = writer.createElement('testEdit');
  const edit = writer.createElement('edit');
  writer.insertText('编辑', writer.createPositionAt(edit, 0));
  const deleteEl = writer.createElement('delete');
  writer.insertText('删除', writer.createPositionAt(deleteEl, 0));
  writer.append(edit, testEdit);
  writer.append(deleteEl, testEdit);

  return testEdit;
}

2.2 使用

文件都创建好我们去使用它

// app.js

// ... 假装这里有很多其他的引用

import Block  from './block/block.js'

ClassicEditor
  .create(document.querySelector('#editor'), {
    plugins: [ HorizontalLine, Block],
    toolbar: ['HorizontalLine', 'Block']
  })
  // 创建成功的回调
  .then(editor => {
    // ...
    // 监听模块的点击事件
    editor.editing.view.document.on('click', (evt, data) => {
      if (data.domTarget.className == 'edit'){
        // to do something
      }
    });
  })
  .catch(error => {
    console.error(error.stack);
  });

3、在外部调用命令

如果想在外面加一个按钮执行上面的命令,插入该组件的话可以定义一个全局变量存放CKEditor实例

// app.js
let myeditor = null;

ClassicEditor
  .create(document.querySelector('#editor'), {
  })
  // 创建成功的回调
  .then(editor => {
    myeditor = editor;
  })
  .catch(error => {
    console.error(error.stack);
  });

// 在其他地方使用
document.getElementById('insertBlock').addEventListener('click', (evt, data) => {
  myeditor.execute('block');
})

Search

    Table of Contents