editormd 解決 圖片上傳的問題的方式有點繞, 他的圖片是通過表單方式提交,但是表單提交成功后會刷新頁面,為了解決頁面刷新的問題,editormd將表單提交的響應提交給了一個子 iframe標簽,然后通過找到該iframe標簽,獲取該標簽的body,得到內容,轉換后復制給父窗口的相關標簽.
這樣做通常是沒問題的,同域上傳不會出現問題,但是跨域上傳的時候就會報錯,如標題.
因為瀏覽器有個同源策略,iframe本質上相當於一個新的頁面,有自己的url地址,當父窗口要訪問獲取子窗口的內部數據時,如果兩個窗口的域名不一致(域名,IP,端口任一不同),那么就會報出該安全錯誤.
editormd提供了一個跨域上傳的方案,就是配置時提供一個回傳地址,我考慮的意思是,editormd將結果放在該會傳地址中,該回調地址的域名與父窗口一致,那么就可以訪問該iframe內部的數據了.
但是可能因為我哪里沒弄對,依然報錯如上.
所以我決定修改editormd的插件代碼.
原文件中相關代碼如下: 構建代碼部分
1 var action = settings.imageUploadURL + (settings.imageUploadURL.indexOf("?") >= 0 ? "&" : "?") + "guid=" + guid; 2 3 if (settings.crossDomainUpload) 4 { 5 action += "&callback=" + settings.uploadCallbackURL + "&dialog_id=editormd-image-dialog-" + guid; 6 } 7 8 var dialogContent = ( (settings.imageUpload) ? "<form action=\"" + action +"\" target=\"" + iframeName + "\" method=\"post\" enctype=\"multipart/form-data\" class=\"" + classPrefix + "form\">" : "<div class=\"" + classPrefix + "form\">" ) + 9 ( (settings.imageUpload) ? "<iframe name=\"" + iframeName + "\" id=\"" + iframeName + "\" guid=\"" + guid + "\"></iframe>" : "" ) + 10 "<label>" + imageLang.url + "</label>" + 11 "<input type=\"text\" data-url />" + (function(){ 12 return (settings.imageUpload) ? "<div class=\"" + classPrefix + "file-input\">" + 13 "<input type=\"file\" name=\"" + classPrefix + "image-file\" accept=\"image/*\" />" + 14 "<input type=\"submit\" value=\"" + imageLang.uploadButton + "\" />" + 15 "</div>" : ""; 16 })() + 17 "<br/>" + 18 "<label>" + imageLang.alt + "</label>" + 19 "<input type=\"text\" value=\"" + selection + "\" data-alt />" + 20 "<br/>" + 21 "<label>" + imageLang.link + "</label>" + 22 "<input type=\"text\" value=\"http://\" data-link />" + 23 "<br/>" + 24 ( (settings.imageUpload) ? "</form>" : "</div>");
8到15行為上傳圖片的代碼,這部分代碼首先出現的問題是csrftoken的問題, 表單提交需要在請求頭或表單內添加csrftoken字段,這部分 editormd沒有做好,除此之外其他部分可以看到傳圖的原理:
<form action=\"" + action +"\" target=\"" + iframeName + "\" method=\"post\" 可以看到是把新頁面指向了子iframe(后面加上的),
然后就是獲取iframe內容的部分:
1 var submitHandler = function() { 2 3 var uploadIframe = document.getElementById(iframeName); 4 5 uploadIframe.onload = function() { 6 7 loading(false); 8 9 var body = (uploadIframe.contentWindow ? uploadIframe.contentWindow : uploadIframe.contentDocument).document.body; 10 var json = (body.innerText) ? body.innerText : ( (body.textContent) ? body.textContent : null); 11 12 json = (typeof JSON.parse !== "undefined") ? JSON.parse(json) : eval("(" + json + ")"); 13 14 if(!settings.crossDomainUpload) 15 { 16 if (json.success === 1) 17 { 18 dialog.find("[data-url]").val(json.url); 19 } 20 else 21 { 22 alert(json.message); 23 } 24 } 25 26 return false; 27 }; 28 };
這部分代碼先獲取iframe元素,然后得到內部文本,轉換為object對象,判斷響應狀態,賦值給Input標簽.
問題就出在獲取子iframe元素內容上,基於同源策略,瀏覽器不讓獲取.
解決方法也很簡單,但是同樣花費了我一晚上加一上午的時間(主要是方向錯了,前期主要精力放在解決iframe跨域的問題,發現不好解決,然后換了個思路,決定改變插件的傳圖方式為ajax)
下面是修改后的代碼(紅色部分為修改的部分):
1 var CallBackJS = "<script>" + 2 "function mdUpload(file) {" + 3 "upload_file(file, function(pic, url) { json = {success:1,url:url,message:'OK'};document.getElementById('json-body-ht').innerText = JSON.stringify(json) })" + 4 " }" + 5 "</script>"; 6 7 var dialogContent = ( (settings.imageUpload) ? "<form action=\"" + action +"\" target=\"" + iframeName + "\" method=\"post\" enctype=\"multipart/form-data\" class=\"" + classPrefix + "form\">" : "<div class=\"" + classPrefix + "form\">" ) + 8 ( (settings.imageUpload) ? "<iframe name=\"" + iframeName + "\" id=\"" + iframeName + "\" guid=\"" + guid + "\"></iframe>" : "" ) + 9 "<label>" + imageLang.url + "</label>" + 10 "<input type=\"text\" data-url />" + (function(){ 11 return (settings.imageUpload) ? "<div class=\"" + classPrefix + "file-input\">" + 12 "<pre id='json-body-ht' style='display: none'></pre>" + 13 "<input type='hidden' name='csrf_token' value='" + CSRFTOKEN + "' />" + 14 "<input onchange='mdUpload(this.files[0])' type=\"file\" name=\"" + classPrefix + "image-file\" accept=\"image/*\" />" + 15 "<input type=\"submit\" value=\"" + imageLang.uploadButton + "\" />" + 16 "</div>" + CallBackJS : ""; 17 })() + 18 "<br/>" + 19 "<label>" + imageLang.alt + "</label>" + 20 "<input type=\"text\" value=\"" + selection + "\" data-alt />" + 21 "<br/>" + 22 "<label>" + imageLang.link + "</label>" + 23 "<input type=\"text\" value=\"http://\" data-link />" + 24 "<br/>" + 25 ( (settings.imageUpload) ? "</form>" : "</div>");
1 var submitHandler = function() { 2 3 var uploadIframe = document.getElementById(iframeName); 4 5 uploadIframe.onload = function() { 6 7 loading(false); 8 9 // var body = (uploadIframe.contentWindow ? uploadIframe.contentWindow : uploadIframe.contentDocument).document.body; 10 var body = document.getElementById('json-body-ht'); 11 var json = (body.innerText) ? body.innerText : ( (body.textContent) ? body.textContent : null); 12 13 console.log(json); 14 json = (typeof JSON.parse !== "undefined") ? JSON.parse(json) : eval("(" + json + ")"); 15 console.log(json.success, json.success === 1); 16 if(!settings.crossDomainUpload) 17 { 18 if (json.success === 1) 19 { 20 dialog.find("[data-url]").val(json.url); 21 } 22 else 23 { 24 alert(json.message); 25 } 26 } 27 28 return false; 29 }; 30 };
修改的部分主要四個地方:
- 添加csrftoken 這部分后面極可能會多余,臨時只是解決插件自己的表單提交, 添加接受數據的元素
- 添加監聽事件
- 添加回調函數
- 修改元素獲取方式
upload_file是我定義的全局上傳圖片方法,通過axios方式上傳,參數為文件對象和回調,具體內部代碼 略.
主要操作是按照插件之前的規則將相應數據填充給自定義元素,自定義元素在父窗口中,直接調用不會出現跨域的問題
將插件源代碼中獲取數據的方式從 iframe 獲取 改為 自定義元素獲取.
我一個后端對前端的東西不是很懂,經驗不足,前期花費大量時間在解決 iframe跨域上,所以有時候 方向 也很重要.