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);
}
}