SiteFactory支援Word貼上
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);
}
}
}
更多詳細資料可以參考這篇文章:
線上演示:
示例下載: