// 2019-3-27 更新,最初发在这里只是记录一下 CKeditor5 的发布,当时的版本还是 1.0.0-alpha.1,截止今日,已经更新为 v12.0.0,之前的一些方法已经不太兼容,特更新如下,希望能帮助到大家。

CKeditor5 相比 CKeditor4 更轻量级,可以按需定制很灵活,CKeditor5 是彻头彻尾的重写,支持移动端、支持按需定制,灵活的插件机制等。CKeditor5 和 CKeditor4 是完全不兼容的,使用方式也有许多不同。

在我们的项目中,使用 CKeditor5 实现了文章发布、图片上传、图片拖拽上传、剪贴板粘贴上传、以及复制图文内容实现远程图片自动本地化。

首先安装 CKeditor5 和 axios 库(上传需要)

yarn add @ckeditor/ckeditor5-build-classic
yarn add axios

相关代码

// Editor.js
import ClassicEditor from '@ckeditor/ckeditor5-build-classic'
import '@ckeditor/ckeditor5-build-classic/build/translations/zh-cn.js'
import CatchRemoteImage from './CatchRemoteImage'

export default class Editor {

    constructor(element, config) {
        let defaultConfig = {
            toolbar: ['heading', '|', 'bold', 'italic', 'blockQuote', 'bulletedList', 'numberedList', 'link', 'imageUpload', 'undo', 'redo'],
            language: 'zh-cn',
            ckfinder: {
                uploadUrl: '/upload'
            }
        };

        this.element = element;
        this.config = Object.assign(defaultConfig, config)
    }

    static create(element, config) {
        const editor = new this(element, config);

        ClassicEditor
            .create(editor.element, editor.config)
            .then(editor => {
                const doc = editor.model.document;

                // 远程图片上传
                // 参考 https://github.com/ckeditor/ckeditor5-image/blob/master/src/imageupload/imageuploadediting.js#L78
                editor.model.document.on('change', () => {
                    const changes = doc.differ.getChanges();

                    for (const entry of changes) {
                        if (entry.type === 'insert' && entry.name === 'image') {
                            const item = entry.position.nodeAfter;

                            // Check if the image element still has upload id.
                            const uploadId = item.getAttribute('uploadId');
                            const uploadStatus = item.getAttribute('uploadStatus');

                            if (!uploadId && !uploadStatus) {
                                CatchRemoteImage(editor, item);
                            }
                        }
                    }
                });
            })
            .catch(error => {
                console.log(error)
            })
    }
};

图文混合内容粘贴后,图片本地化方法脚本:

// CatchRemoteImage.js
import axios from "axios";

export default function (editor, imageElement) {
    const uploadingImage = 'https://www.cshome.com/build/images/uploading.gif';
    const failImage = 'https://www.cshome.com/build/images/upload-fail.jpg';
    const imageUrl = imageElement.getAttribute('src');
    const localDomains = ['cshome.com'];

    const model = editor.model;

    // 检测是否需要上传
    function test(url) {
        if (url.indexOf(location.host) !== -1 || /(^\.)|(^\/)/.test(url)) {
            return true;
        }

        if (localDomains) {
            for (let domain in localDomains) {
                if (localDomains.hasOwnProperty(domain) && url.indexOf(localDomains[domain]) !== -1) {
                    return true;
                }
            }
        }

        return false;
    }

    // 图片上传
    function upload(url) {
        let data = new FormData();
        data.append('url', url);

        return axios.post(
            '/upload-by-url',
            data,
            {
                headers: {
                    'content-type': 'multipart/form-data'
                }
            })
    }

    if (/^https?:/i.test(imageUrl) && !test(imageUrl)) {
        model.enqueueChange('transparent', writer => {
            writer.setAttribute('src', uploadingImage, imageElement);
            writer.setAttribute('uploadStatus', 'uploading', imageElement);
        });

        upload(imageUrl)
            .then(response => {
                model.enqueueChange('transparent', writer => {
                    writer.setAttribute('src', response.data.url, imageElement);
                    writer.setAttribute