ueditor粘贴不能粘贴word中的图片是一个很头疼的问题,在我们的业务场景中客户要求必须使用ueditor并且支持word的图片粘贴,因为这个需求头疼了半个月,因为前端方面因为安全的原因是不允许访问本地文件的。
首先说一下,ueditor粘贴word图片的问题已经解决,但是不是纯web方法解决的,在纯浏览器的条件下是否能够解决也不确定中,但是ckeditor是可以支持word图片的富文本粘贴的
接下来就是调试思路和解决方法(所有的代码都是调试ueditor源码的ueditor.all.js文件)
1、首先是分析问题,
这块就不上代码了,大家可以自己去调试,很清晰的就是因为是本地文件所以不能上传
function filter(div) {} //大概是14611行的这个函数会把本地的图片路径过滤成一个 //编辑器默认的占位图var root = UE.htmlparser(html); //罪魁祸首就是这行代码
但是尝试在罪魁祸首那行代码前将地址强制替换成网络地址就会发现,所替换的地址就会正常显示,因此找到了解决方案,就是将富文本中的img标签的src想办法替换成网络地址,
于是第一条解决思路出来了,在执行 罪魁祸首代码 之前,将本地的地址过滤出来,上传到服务器然后用服务器的地址进行替换,
2、解决问题
想到解决法案之后就去寻找解决方法,拿到图片的本地地址简单,
var imgReg = /<img.*?(?:>|\/>)/gi;var imgArr = html.match(imgReg); // arr 为包含所有img标签的数组
利用正则加一些技术基础的处理,就能把所有的图片地址过滤成为一个数组,
我们(我和客户端的大神)的解决思路就是模仿input 的type=file的方式进行上传;
接下来是怎么拿到本地地址的文件,怎么将本地地址搞成文件,我查阅了好久好久的资料,还请教了各路大神(骚骚的三水,和传说中的周皇),得到一个结论,单纯前端不可能通过本地地址拿到文件(这估计就是ueditor为什么不能粘贴word图片的根本原因),因为我们的页面也在内嵌到客户端内与客户端有数据交流,因此就委托我们的客户端大佬帮我抓到了文件对象
function GetLocalFileObject(szPath){
var objFile = null;
try{
objFile = new File([], szPath, {type:"mb/bin"});
if (objFile != null && objFile.size == 0)
objFile = null;
}
catch(err){
objFile = null;
}
return objFile;
}
接下来的处理就简单了,就是利用前端的各种基础知识,上传图片,替换路径,
在这儿本来以为要苦逼的自己写一个上ajax,但是突然发现了一个ueditor的函数sendMyAndInsertFile 这是ueditor用来上传拖拽图片的一个函数
UE.plugin.register('autoupload', function (){} //这下面的第一个函数sendAndInsertFile(){}
但是因为与粘贴在不同的作用域内因此需要将它copy一份到我们粘贴代码这块的作用域内,
当然为了严谨,我修改了一下方法名字,并且为了利于后面的html替换增加了一个回调函数
// 上传文件的函数 function sendMyAndInsertFile(file, editor,callback) {
var me = editor;
//模拟数据 var fieldName, urlPrefix, maxSize, allowFiles, actionUrl,
loadingHtml, errorHandler, successHandler,
下 filetype = /image\/\w+/i.test(file.type) ? 'image':'file',
loadingId = 'loading_' + (+new Date()).toString(36);
fieldName = me.getOpt(filetype + 'FieldName');
urlPrefix = me.getOpt(filetype + 'UrlPrefix');
maxSize = me.getOpt(filetype + 'MaxSize');
allowFiles = me.getOpt(filetype + 'AllowFiles');
actionUrl = me.getActionUrl(me.getOpt(filetype + 'ActionName'));
errorHandler = function(title) {
var loader = me.document.getElementById(loadingId);
loader && domUtils.remove(loader);
me.fireEvent('showmessage', {
'id': loadingId,
'content': title,
'type': 'error',
'timeout': 4000
});
};
if (filetype == 'image') {
loadingHtml = '<img class="loadingclass" id="' + loadingId + '" src="' +
me.options.themePath + me.options.theme +
'/images/spacer.gif" title="' + (me.getLang('autoupload.loading') || '') + '" >';
successHandler = function(data) {
var link = urlPrefix + data.url,
loader = me.document.getElementById(loadingId);
if (loader) {
loader.setAttribute('src', link);
loader.setAttribute('_src', link);
loader.setAttribute('title', data.title || '');
loader.setAttribute('alt', data.original || '');
loader.removeAttribute('id');
domUtils.removeClasses(loader, 'loadingclass');
}
};
} else {
loadingHtml = '<p>' +
'<img class="loadingclass" id="' + loadingId + '" src="' +
me.options.themePath + me.options.theme +
'/images/spacer.gif" title="' + (me.getLang('autoupload.loading') || '') + '" >' +
'</p>';
successHandler = function(data) {
var link = urlPrefix + data.url,
loader = me.document.getElementById(loadingId);
var rng = me.selection.getRange(),
bk = rng.createBookmark();
rng.selectNode(loader).select();
me.execCommand('insertfile', {'url': link});
rng.moveToBookmark(bk).select();
};
}
/* 插入loading的占位符 */
// me.execCommand('inserthtml', loadingHtml);
/* 判断后端配置是否没有加载成功 */
if (!me.getOpt(filetype + 'ActionName')) {
errorHandler(me.getLang('autoupload.errorLoadConfig'));
return;
}
/* 判断文件大小是否超出限制 */
if(file.size > maxSize) {
errorHandler(me.getLang('autoupload.exceedSizeError'));
return;
}
/* 判断文件格式是否超出允许 */
var fileext = file.name ? file.name.substr(file.name.lastIndexOf('.')):'';
if ((fileext && filetype != 'image') || (allowFiles && (allowFiles.join('') + '.').indexOf(fileext.toLowerCase() + '.') == -1)) {
errorHandler(me.getLang('autoupload.exceedTypeError'));
return;
}
/* 创建Ajax并提交 */
var xhr = new XMLHttpRequest(),
fd = new FormData(),
params = utils.serializeParam(me.queryCommandValue('serverparam')) || '',
url = utils.formatUrl(actionUrl + (actionUrl.indexOf('?') == -1 ? '?':'&') + params);
fd.append(fieldName, file, file.name || ('blob.' + file.type.substr('image/'.length)));
fd.append('type', 'ajax');
xhr.open("post", url, true);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.addEventListener('load', function (e) {
try{
var json = (new Function("return " + utils.trim(e.target.response)))();
if (json.state == 'SUCCESS' && json.url) {
// 将上传的文件返回 callback(json)
} else {
errorHandler(json.state);
}
}catch(er){
errorHandler(me.getLang('autoupload.loadError'));
}
});
xhr.send(fd);
}
ss
其中还有一些需要处理的,网络图片,粘贴时没有图片怎么办,以及多张图片我就不废话了 直接将整个filter代码粘贴在下方
function filter(div) {
var html;
if (div.firstChild) {
//去掉cut中添加的边界值 var nodes = domUtils.getElementsByTagName(div, 'span');
for (var i = 0, ni; ni = nodes[i++];) {
if (ni.id == '_baidu_cut_start' || ni.id == '_baidu_cut_end') {
domUtils.remove(ni);
}
}
if (browser.webkit) {
var brs = div.querySelectorAll('div br');
for (var i = 0, bi; bi = brs[i++];) {
var pN = bi.parentNode;
if (pN.tagName == 'DIV' && pN.childNodes.length == 1) {
pN.innerHTML = '<p><br/></p>';
domUtils.remove(pN);
}
}
var divs = div.querySelectorAll('#baidu_pastebin');
for (var i = 0, di; di = divs[i++];) {
var tmpP = me.document.createElement('p');
di.parentNode.insertBefore(tmpP, di);
while (di.firstChild) {
tmpP.appendChild(di.firstChild);
}
domUtils.remove(di);
}
var metas = div.querySelectorAll('meta');
for (var i = 0, ci; ci = metas[i++];) {
domUtils.remove(ci);
}
var brs = div.querySelectorAll('br');
for (i = 0; ci = brs[i++];) {
if (/^apple-/i.test(ci.className)) {
domUtils.remove(ci);
}
}
}
if (browser.gecko) {
var dirtyNodes = div.querySelectorAll('[_moz_dirty]');
for (i = 0; ci = dirtyNodes[i++];) {
ci.removeAttribute('_moz_dirty');
}
}
if (!browser.ie) {
var spans = div.querySelectorAll('span.Apple-style-span');
for (var i = 0, ci; ci = spans[i++];) {
domUtils.remove(ci, true);
}
}
//ie下使用innerHTML会产生多余的\r\n字符,也会产生 这里过滤掉 html = div.innerHTML;//.replace(/>(?:(\s| )*?)</g,'><'); //过滤word粘贴过来的冗余属性 html = UE.filterWord(html);
/** * @处理word粘贴进来的图片 * @此处尝试将本地图片的路径分离并上传到服务器并拿到地址替换掉字符串中的img */
//待修改之: 仅在20191221版mblink客户端支持;wzh function GetLocalFileObject(szPath){
var objFile = null;
try{
objFile = new File([], szPath, {type:"mb/bin"});
if (objFile != null && objFile.size == 0)
objFile = null;
}
catch(err){
objFile = null;
}
return objFile;
}
var imgReg = /<img.*?(?:>|\/>)/gi;
var imgArr = html.match(imgReg); // arr 为包含所有img标签的数组 var srcReg = /src=[\'\"]?([^\'\"]*)[\'\"]?/i;
if(imgArr){
html= html.replace(/\\/g,"\/") //将富文本中的\与/替换 方便下面替换成网络路径 var freauency = 0 //定义一个变量记录处理了几张图片 for(var i=0,len=imgArr.length;i<len;i++){
var strsrc = imgArr[i].match(srcReg)[1].slice(8)
var a = GetLocalFileObject(strsrc)
if(!a){ //有网络图片时的处理 console.log(999)
delHtml()
continue}
// 上传文件的函数 function sendMyAndInsertFile(file, editor,callback) {
var me = editor;
//模拟数据 console.log(file.type)
var fieldName, urlPrefix, maxSize, allowFiles, actionUrl,
loadingHtml, errorHandler, successHandler,
filetype = /image\/\w+/i.test(file.type) ? 'image':'file',
loadingId = 'loading_' + (+new Date()).toString(36);
fieldName = me.getOpt(filetype + 'FieldName');
urlPrefix = me.getOpt(filetype + 'UrlPrefix');
maxSize = me.getOpt(filetype + 'MaxSize');
allowFiles = me.getOpt(filetype + 'AllowFiles');
actionUrl = me.getActionUrl(me.getOpt(filetype + 'ActionName'));
errorHandler = function(title) {
var loader = me.document.getElementById(loadingId);
loader && domUtils.remove(loader);
me.fireEvent('showmessage', {
'id': loadingId,
'content': title,
'type': 'error',
'timeout': 4000
});
};
if (filetype == 'image') {
loadingHtml = '<img class="loadingclass" id="' + loadingId + '" src="' +
me.options.themePath + me.options.theme +
'/images/spacer.gif" title="' + (me.getLang('autoupload.loading') || '') + '" >';
successHandler = function(data) {
var link = urlPrefix + data.url,
loader = me.document.getElementById(loadingId);
if (loader) {
loader.setAttribute('src', link);
loader.setAttribute('_src', link);
loader.setAttribute('title', data.title || '');
loader.setAttribute('alt', data.original || '');
loader.removeAttribute('id');
domUtils.removeClasses(loader, 'loadingclass');
}
};
} else {
loadingHtml = '<p>' +
'<img class="loadingclass" id="' + loadingId + '" src="' +
me.options.themePath + me.options.theme +
'/images/spacer.gif" title="' + (me.getLang('autoupload.loading') || '') + '" >' +
'</p>';
successHandler = function(data) {
var link = urlPrefix + data.url,
loader = me.document.getElementById(loadingId);
var rng = me.selection.getRange(),
bk = rng.createBookmark();
rng.selectNode(loader).select();
me.execCommand('insertfile', {'url': link});
rng.moveToBookmark(bk).select();
};
}
/* 插入loading的占位符 */
// me.execCommand('inserthtml', loadingHtml);
/* 判断后端配置是否没有加载成功 */
if (!me.getOpt(filetype + 'ActionName')) {
errorHandler(me.getLang('autoupload.errorLoadConfig'));
return;
}
/* 判断文件大小是否超出限制 */
if(file.size > maxSize) {
errorHandler(me.getLang('autoupload.exceedSizeError'));
return;
}
/* 判断文件格式是否超出允许 */
var fileext = file.name ? file.name.substr(file.name.lastIndexOf('.')):'';
if ((fileext && filetype != 'image') || (allowFiles && (allowFiles.join('') + '.').indexOf(fileext.toLowerCase() + '.') == -1)) {
errorHandler(me.getLang('autoupload.exceedTypeError'));
return;
}
/* 创建Ajax并提交 */
var xhr = new XMLHttpRequest(),
fd = new FormData(),
params = utils.serializeParam(me.queryCommandValue('serverparam')) || '',
url = utils.formatUrl(actionUrl + (actionUrl.indexOf('?') == -1 ? '?':'&') + params);
fd.append(fieldName, file, file.name || ('blob.' + file.type.substr('image/'.length)));
fd.append('type', 'ajax');
xhr.open("post", url, true);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.addEventListener('load', function (e) {
try{
var json = (new Function("return " + utils.trim(e.target.response)))();
if (json.state == 'SUCCESS' && json.url) {
// 将上传的文件返回 callback(json)
} else {
errorHandler(json.state);
}
}catch(er){
errorHandler(me.getLang('autoupload.loadError'));
}
});
xhr.send(fd);
}
// 上传文件函数的回调进行图片路径替换和渲染html sendMyAndInsertFile(a,me,function (res){
freauency++
html = html.replace(res.original,res.url)
if(freauency!=imgArr.length) {return} //多张图片粘贴时等到最后一张上传完在渲染html delHtml()
})
// }) }
}else {
delHtml()
}
/** * @将之前处理富文本html的代码封装成一个函数便于在有图片和没图片的两张粘贴情况进行处理 * */
function delHtml (){
//取消了忽略空白的第二个参数,粘贴过来的有些是有空白的,会被套上相关的标签 var root = UE.htmlparser(html); //此处过滤会把file图片过滤掉 //如果给了过滤规则就先进行过滤 if (me.options.filterRules) {
UE.filterNode(root, me.options.filterRules);
}
//执行默认的处理 me.filterInputRule(root);
//针对chrome的处理 if (browser.webkit) {
var br = root.lastChild();
if (br && br.type == 'element' && br.tagName == 'br') {
root.removeChild(br)
}
utils.each(me.body.querySelectorAll('div'), function (node) {
if (domUtils.isEmptyBlock(node)) {
//domUtils.remove(node,true) }
})
}
html = {'html': root.toHtml()};
me.fireEvent('beforepaste', html, root);
//抢了默认的粘贴,那后边的内容就不执行了,比如表格粘贴 if(!html.html){
return;
}
root = UE.htmlparser(html.html,true);
//如果开启了纯文本模式 if (me.queryCommandState('pasteplain') === 1) {
me.execCommand('insertHtml', UE.filterNode(root, me.options.filterTxtRules).toHtml(), true);
} else {
//文本模式 UE.filterNode(root, me.options.filterTxtRules);
txtContent = root.toHtml();
//完全模式 htmlContent = html.html;
address = me.selection.getRange().createAddress(true);
me.execCommand('insertHtml', me.getOpt('retainOnlyLabelPasted') === true ? getPureHtml(htmlContent) : htmlContent, true);
}
me.fireEvent("afterpaste", html);
}
}
}
更多详细资料可以参考这篇文章:
技术交流可以入群一起讨论:223813913
示例下载,下载前选择合适的语言版本:
https://gitee.com/xproer/wordpaster-php-ckeditor4x
https://gitee.com/xproer/wordpaster-jsp-ckeditor4x
https://gitee.com/xproer/wordpaster-asp.net-ckeditor4x
https://gitee.com/xproer/wordpaster-asp-ckeditor4x
https://gitee.com/xproer/wordpaster-vue-ckeditor5