likes
comments
collection
share

让UEditor编辑器可以直接WORD图文粘贴

作者站长头像
站长
· 阅读数 11

前言

UE编辑器本身就集成了WORD图文粘贴,可惜由于年代久远,官方放弃了维护且现代浏览器不在需要毒瘤flash了,所以UE编辑器就用不了word图文粘贴。

浏览器技术也在不断改进,要解决word图文粘贴有几种做法:

  1. 浏览器控件(扩展)。

让用户的浏览器安装控件,直接读取word保存在硬盘的临时图片。但是这个体验不够好。

  1. 先将word文件上传到服务器后端,后端解析word文件后,再回调到前端编辑器。

比较常见的实现方法,但是对于纯前端来说,我觉得这种做法通用性低,仅适用公司自用。

  1. 纯前端解决。本次实现的方式。

解决思路

首先UE本身已经实现WORD的图文粘贴,只是图片转换没有做解决方案,会用一张临时图片顶替。因此本次任务就是去解决前人遗留的问题。

众所周知,复制整份图文word文档,在浏览器中粘贴,你通过监听剪切板内容,会发现图片标签都是以 file:/// 开头的。 由于浏览器安全机制,他会阻止你加载本地资源。所以摆在我眼前的问题就成了:如何将file:/// 开头的图片,转换为base64。 找到了目标后,就很好办了。

错误的思路

尽管找到了目标,但是要将本地图片转换为base64不是一个容易的事情。不过这些小困难想难倒一个拥有10多年面向搜索引擎开发经验的程序员来说,只是一个易事。

皇天不负有心人,我想到了用过JS FileReader来转换base64 。思路:

  1. 读取剪切板所有本地图片地址。
  2. 手动追加 <input type="file"> 并将本地图片地址进行手动模拟。

我就把时间花在如何将图片地址添加到input file中,且是模式用户的。 几小时过去,我认为这个思路是错误的。

站在巨人之上去解决问题

正当我一筹莫展的时候,我想起了知名的富文本编辑器——CK5。CK5已经实现了word图文粘贴,遂到官网一试,看看源码,CK5成功将图片转换为base64了。果断下载CK5源码到本地一探究竟。

在CK5源码的packages库中,可以找到一个名为ckeditor5-paste-from-office目录。大概看了一下里面的源码,然而我也看不懂。

通过站在巨人之上,整个解决方案就出来了。

实现方案

由于篇幅和时间限制,如何整合到UE的思路我就不叙述了,直接来实现的方案:打开UE的核心文件ueditor.all.js

首先,在核心文件开头附近,声明一个数组,用于存放转换好的图片。大概30行附近左右,添加

var wordImg = [];

找到_initEvents: function () {,大概7454行,这里大概就是UE初始化的代码,在这里可以监听到当前UE编辑器的DOM。 接下来将整个_initEvents覆盖如下代码即可,如果你有对比器,可以根据我这部分获取差异。

/**
 * 初始化UE事件及部分事件代理
 * @method _initEvents
 * @private
 */
_initEvents: function () {
    var me = this,
        doc = me.document,
        win = me.window;

    //监听word复制粘贴功能
    doc.addEventListener("paste", function (e) {

        if (!(e.clipboardData && e.clipboardData.items)) {
            return;
        }

        const clipboardData = e.clipboardData;

        //粘贴板中的HTML文本
        let copyStr = clipboardData.getData('text/html');

        //粘贴板中的RTF数据
        let rtf = clipboardData.getData('text/rtf');

        //获取粘贴板中图片的数量
        let imgs = me.findAllImageElementsWithLocalSource(copyStr);

        me.replaceImagesFileSourceWithInlineRepresentation(imgs, me.extractImageDataFromRtf(rtf))

    })

    me._proxyDomEvent = utils.bind(me._proxyDomEvent, me);
    domUtils.on(doc, ['click', 'contextmenu', 'mousedown', 'keydown', 'keyup', 'keypress', 'mouseup', 'mouseover', 'mouseout', 'selectstart'], me._proxyDomEvent);
    domUtils.on(win, ['focus', 'blur'], me._proxyDomEvent);
    domUtils.on(me.body,'drop',function(e){
        //阻止ff下默认的弹出新页面打开图片
        if(browser.gecko && e.stopPropagation) { e.stopPropagation(); }
        me.fireEvent('contentchange')
    });
    domUtils.on(doc, ['mouseup', 'keydown'], function (evt) {
        //特殊键不触发selectionchange
        if (evt.type == 'keydown' && (evt.ctrlKey || evt.metaKey || evt.shiftKey || evt.altKey)) {
            return;
        }
        if (evt.button == 2)return;
        me._selectionChange(250, evt);
    });
},

接着在后面添加如下代码

//获取粘贴板中图片数量
findAllImageElementsWithLocalSource:function(copyStr){
    let imgs = $(copyStr).find('img');
    return imgs;
},

//处理图片信息
extractImageDataFromRtf:function(rtfData){
    if (!rtfData) {
        return [];
    }

    const regexPictureHeader = /{\pict[\s\S]+?\bliptag-?\d+(\blipupi-?\d+)?({\*\blipuid\s?[\da-fA-F]+)?[\s}]*?/;
    const regexPicture = new RegExp('(?:(' + regexPictureHeader.source + '))([\da-fA-F\s]+)\}', 'g');
    const images = rtfData.match(regexPicture);
    const result = [];

    if (images) {
        for (const image of images) {
            let imageType = false;

            if (image.includes('\pngblip')) {
                imageType = 'image/png';
            } else if (image.includes('\jpegblip')) {
                imageType = 'image/jpeg';
            }

            if (imageType) {
                result.push({
                    hex: image.replace(regexPictureHeader, '').replace(/[^\da-fA-F]/g, ''),
                    type: imageType
                });
            }
        }
    }

    return result;
},

//16进制转换为base64
_convertHexToBase64:function(hexString){
    return btoa(hexString.match(/\w{2}/g).map(char => {
        return String.fromCharCode(parseInt(char, 16));
    }).join(''));
},

//存储图片资源
replaceImagesFileSourceWithInlineRepresentation:function(imageElements, imagesHexSources){
    // Assume there is an equal amount of image elements and images HEX sources so they can be matched accordingly based on existing order.
    if (imageElements.length === imagesHexSources.length) {
        for (let i = 0; i < imageElements.length; i++) {
            const newSrc = `data:${imagesHexSources[i].type};base64,${this._convertHexToBase64(imagesHexSources[i].hex)}`;

            wordImg.push(newSrc);

            // writer.setAttribute('src', newSrc, imageElements[i]);
        }
    }

    console.dir(wordImg)

},

上述代码作用是监控粘贴板,并将转换好的图片保存在wordImg数组中。

接下来在文件搜索//todo base64暂时去掉,后边做远程图片上传后,干掉这个 将下方的代码去除。最终样子大概如下:

    case 'img':
        //todo base64暂时去掉,后边做远程图片上传后,干掉这个
        if (val = node.getAttr('src')) {

        }
        node.setAttr('_src', node.getAttr('src'));
        break;

最后,定位到 UE.plugin.register('wordimage',function(){ 大概13990行。直接将inputRule : function (root) { 覆盖替换如下代码

inputRule : function (root) {
    utils.each(root.getNodesByTagName('img'), function (img, key) {
        var attrs = img.attrs,
            flag = parseInt(attrs.width) < 128 || parseInt(attrs.height) < 43,
            opt = me.options,
            src = opt.UEDITOR_HOME_URL + 'themes/default/images/spacer.gif';
        if (attrs['src'] && /^(?:(file:\/+))/.test(attrs['src'])) {
            img.setAttr({
                width:attrs.width,
                height:attrs.height,
                alt:attrs.alt,
                src:wordImg[key],
                'style':'background:url(' + ( flag ? opt.themePath + opt.theme + '/images/word.gif' : opt.langPath + opt.lang + '/images/localimage.png') + ') no-repeat center center;border:1px solid #ddd'
            })
        }
    })
    //清空调用记录
    wordImg = [];

}

至此,整个UE编辑器可以支持word图文粘贴了。需要注意的是:本次修改没有将图片上传到后端服务器。具体的逻辑请大家自行修改了。x

效果图:

让UEditor编辑器可以直接WORD图文粘贴

对了,RTF目前还是实验性功能,可能随着浏览器的升级,这次修改又双叒叕失效。

整合代码

考虑到上面代码有些分散,可以直接查看我的GITEE 本次代码的提交:gitee.com/fallBirds/P…

打个小广告,如果大家对文档编辑有需求,可以使用我编写的文档管理:PESCMS DOC gitee.com/fallBirds/P…