CKeditor5实现图片上传

发布时间 2023-11-02 17:52:52作者: Xproer-松鼠

// 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('uploadStatus', 'complete', imageElement);
                });

                clean();
            })
            .catch(error => {
                model.enqueueChange('transparent', writer => {
                    writer.setAttribute('src', failImage, imageElement);
                });

                clean();
            })
    }

    function clean() {
        model.enqueueChange('transparent', writer => {
            writer.removeAttribute('uploadId', imageElement);
            writer.removeAttribute('uploadStatus', imageElement);
        });
    }
}

使用

Editor.create(document.querySelector('#article_body'), {
    toolbar: ['bold', 'italic', 'blockQuote', 'bulletedList', 'numberedList', 'link', 'imageUpload', 'undo', 'redo'],
});

 

// demo.html
<div id="article_body"></div>

最早的时候,我是使用的 UploadAdapter 方式 ,但是后续的版本直接可以用 ckfinder 来实现,方便了很多。

FileUploadAdapter 的核心是需要实现 upload和 abort方法 ,upload方法需要返回 Promise对象。

上面 ckfinder 接口 /upload 返回数据格式为

{
    uploaded: true,
    url: '图片地址'
}

完成。

 

参考文章:http://blog.ncmem.com/wordpress/2023/11/02/ckeditor5%e5%ae%9e%e7%8e%b0%e5%9b%be%e7%89%87%e4%b8%8a%e4%bc%a0/

欢迎入群一起讨论