让UEditor编辑器可以直接WORD图文粘贴
前言
UE编辑器本身就集成了WORD图文粘贴,可惜由于年代久远,官方放弃了维护且现代浏览器不在需要毒瘤flash了,所以UE编辑器就用不了word图文粘贴。
浏览器技术也在不断改进,要解决word图文粘贴有几种做法:
- 浏览器控件(扩展)。
让用户的浏览器安装控件,直接读取word保存在硬盘的临时图片。但是这个体验不够好。
- 先将word文件上传到服务器后端,后端解析word文件后,再回调到前端编辑器。
比较常见的实现方法,但是对于纯前端来说,我觉得这种做法通用性低,仅适用公司自用。
- 纯前端解决。本次实现的方式。
解决思路
首先UE本身已经实现WORD的图文粘贴,只是图片转换没有做解决方案,会用一张临时图片顶替。因此本次任务就是去解决前人遗留的问题。
众所周知,复制整份图文word文档,在浏览器中粘贴,你通过监听剪切板内容,会发现图片标签都是以 file:///
开头的。 由于浏览器安全机制,他会阻止你加载本地资源。所以摆在我眼前的问题就成了:如何将file:///
开头的图片,转换为base64。 找到了目标后,就很好办了。
错误的思路
尽管找到了目标,但是要将本地图片转换为base64不是一个容易的事情。不过这些小困难想难倒一个拥有10多年面向搜索引擎开发经验的程序员来说,只是一个易事。
皇天不负有心人,我想到了用过JS FileReader来转换base64 。思路:
- 读取剪切板所有本地图片地址。
- 手动追加
<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
效果图:
对了,RTF目前还是实验性功能,可能随着浏览器的升级,这次修改又双叒叕失效。
整合代码
考虑到上面代码有些分散,可以直接查看我的GITEE 本次代码的提交:gitee.com/fallBirds/P…
打个小广告,如果大家对文档编辑有需求,可以使用我编写的文档管理:PESCMS DOC gitee.com/fallBirds/P…
转载自:https://juejin.cn/post/7096322277641289735