TinyMCE編輯器貼上遠端圖片本地化以及圖片拖入上傳, 踩坑, 還有上傳回調增加自定義屬性

語言: CN / TW / HK

1. 首先說下拖入編輯器上傳

採坑說明

拖入上傳功能, 是自帶的, 我之前以為不行, 自己寫了幾個小時, 浪費了很多時間

上傳後的圖片不能插入到拖入的位置, 改了tinymce內部程式碼後也只能插入到拖入位置的dom節點外層, 最終放棄了, 然後繼續啃文件

最終發現了下面的配置項

啟用外掛paste

首先啟用配置paste_data_images預設是false如果需要啟用拖入上傳則需要設定為true

{
    paste_data_images: true
}

文件中寫的是: 此選項指定是否應從貼上的內容中刪除內聯圖。

但是實際上會把剪下板以及拖入的圖片, 用已經配置好的上傳介面, 或images_upload_handler重寫的上傳介面至伺服器

允許拖入的圖片

這裡需要配置images_file_types允許拖入的圖片字尾, 如果設定會提示拖入檔案不支援

{
    images_file_types: 'jpeg,jpg,png,gif,bmp,webp',
}

好了, 拖拽上傳以及剪下板上傳設定一下這些配置就好了😂

2. 遠端圖片本地化

設定初始化結束後執行的回撥引數

init_instance_callback : (editor) => {
}

首先檢測是否需要上傳以及白名單域名

// 設定白名單域名
const localDomains = ['cfyun.cc', 'cfyun.top'];

// 檢測是否需要上傳
let test = function test(url) {
    if (url.indexOf(location.host) !== -1 || /(^\.)|(^\/)/.test(url)) {
        return !0;
    }
    // 白名單
    if (localDomains) {
        for (let domain in localDomains) {
            if (localDomains.hasOwnProperty(domain) && url.indexOf(localDomains[domain]) !== -1) {
                return !0;
            }
        }
    }
    return !1;
};

貼上內容中獲取圖片

// 貼上遠端圖片本地化
editor.on('paste', (e) => {
    let remoteImages = [];
    const doc = editor.getDoc();
    const items = doc.getElementsByTagName('img');

    if(items.length) {
        for (img of items) {
            var src = img.getAttribute('_src') || img.src || '';
            if (/^(https?|ftp):/img.test(src) && !test(src)) {
                remoteImages.push(src);
            }
        }
    }

    if (remoteImages.length) {
        // 需要上傳的圖片
        CatchRemoteImage(remoteImages, {
            success: (data) => {
                // 這部分程式碼本來是直接修改編輯器內的dom就可以實現效果的, 但是那種方式無解, 只能現在這樣替換原有img的dom才能更新編輯器
                // 貌似編輯器diff驗證了dom節點是否相同, 如果相同則沒有更新編輯器的內容
                // 希望有大佬能有更好的解決方案
                // 現在的遺憾就是在上傳完成後替換為本地的圖片後, 有一段載入圖片的時間, 這時候編輯器內圖片會留白一段時間, 留白時間取決於網速
                let i, o, item, res, _src, __src, list = data.list;
                for (i = 0; item = items[i++];) {
                    _src = item.getAttribute('_src') || item.src || '';
                    for (o = 0; res = list[o++];) {
                        if (_src == res.source && res.state == 'success') {// 抓取失敗時不做替換處理
                            __src = res.url;
                            // 無奈之舉, 直接修改dom編輯器不更新內容, 但是替換dom可以更新
                            // 這裡用到了jquery, 其他框架換個替換dom的方法
                            $(`<img src="${__src}" data-aid="${res.id}" data-width="${res.width}" data-height="${res.height}">`).replaceAll($(item));
                            // 下面給出一種從官方程式碼中參考來的方式
                            // replaceImage(item, res); // 使用下面的新方法
                            break;
                        }
                    }
                }
                // 同步到textarea
                editor.save();
            },
            error: (data) => {
                // 上傳出錯回撥
            }
        });
    }
});

從tinymce原始碼中參考的替換圖片src的方式

此種方法, 不會造成圖片上傳之前的留白現象

const replaceImage = (image, data) => {
    const each = function (xs, f) {
        for (var i = 0, len = xs.length; i < len; i++) {
            var x = xs[i];
            f(x, i);
        }
    };
    const map = function (xs, f) {
        var len = xs.length;
        var r = new Array(len);
        for (var i = 0; i < len; i++) {
            var x = xs[i];
            r[i] = f(x, i);
        }
        return r;
    };
    const replaceString = function (content, search, replace) {
        let index = 0;
        do {
            index = content.indexOf(search, index);
            if (index !== -1) {
                content = content.substring(0, index) + replace + content.substr(index + search.length);
                index += replace.length - search.length + 1;
            }
        } while (index !== -1);
        return content;
    };
    const replaceImageUrl = function (content, targetUrl, replacementUrl) {
        let replacementString = 'src="' + replacementUrl + '"' + (replacementUrl === 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' ? ' data-placeholder="1"' : '');
        content = replaceString(content, 'src="' + targetUrl + '"', replacementString);
        return content;
    };
    const replaceUrlInUndoStack = function (targetUrl, replacementUrl) {
        each(editor.undoManager.data, function (level) {
            if (level.type === 'fragmented') {
                level.fragments = map(level.fragments, function (fragment) {
                    return replaceImageUrl(fragment, targetUrl, replacementUrl);
                });
            } else {
                level.content = replaceImageUrl(level.content, targetUrl, replacementUrl);
            }
        });
    };
    let src = editor.convertURL(data.url, 'src');
    let attr = {
        'src': data.url,
        'data-app': data.app,
        'data-aid': data.aid
    };
    if(data.db) {
        attr['data-db'] = data.db;
    } else {
        attr['data-width'] = data.width;
        attr['data-height'] = data.height;
    }
    replaceUrlInUndoStack(image.src, data.url);
    editor.$(image).attr(attr).removeAttr('alt').removeAttr('data-mce-src');
}

上傳回調方法

/**
 * 遠端圖片本地化
 */
let CatchRemoteImage = (images, callback)  => {
    const _this = this;

    let data = new FormData();

    // 經過FormData處理後提交給後端的images陣列是以,分割的字串, 這個需要後端自己轉為陣列再處理
    data.append('urls', images);

    // Append add Form Data
    // formData是需要提交給後端的引數
    $.each(formData, function(aKey, aVal) {
        if(typeof aVal == 'object') { // 如果是object則用jquery的val方法取得內容
            aVal = aVal.val();
        }
        data.append(aKey, aVal);
    });
    // jquery的ajax上傳可以替換為其他ajax方法, 或者使用原生XMLHttpRequest方法
    $.ajax({
        url: options.uploadUrl, // 接收遠端圖片抓去的後端地址
        method: 'post',
        data: data,
        cache: !1,
        contentType: !1,
        processData: !1,
        forceSync: !1,
        beforeSend: function(jqXHR, settings) {
            jqXHR.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
        }
    }).then((res) => {
        if(res.errno == 0) {
            callback.success(res.data);
            return !1;
        }
        callback.error(res.data);
    }).catch((error) => {
        callback.error('error');
    });
};

上傳成功後的json資料結構

{
    "errno":0,
    "data":{
        "list":[
            {
                "state":"success",
                "url":"//img.cfyun.cc/apps/news/remote/Mon_2103/1616069544468119.png",
                "_url":"//img.cfyun.cc/apps/news/remote/Mon_2103/1616069544468119.png",
                "path":"apps/news/remote/Mon_2103/1616069544468119.png",
                "name":"1616069544468119.png",
                "original":"excalidraw.png",
                "size":84931,
                "ext":".png",
                "is_thumb":0,
                "is_image":1,
                "app":"news",
                "width":1280,
                "height":669,
                "db":52.265625,
                "aid":190,
                "source":"https://gitee.com/kevwan/static/raw/master/doc/images/excalidraw.png"
            }
        ]
    },
    "type":"right",
    "message":"success",
    "referer":"",
    "refresh":false
}

3. 上傳圖片增加而外屬性(需改原始碼)

官方的成功回撥方法可接受引數只有圖片連結, 達不到預期效果, 因為要給圖片增加一些而外屬性, 用於前端

本來打算參考tinymce原始碼裡的回撥函式重寫一個, 但是發現這部分涉及的程式碼太多, 不利於重寫這個功能

萬般無奈下只能修改原始碼, 下面給出修改的部分, 希望有需要的同學可以參考

首先在搜尋handlerSuccess

handlerFailure函式上面

var handlerSuccess = function (blobInfo, url, attr) { // 新增attr
  return {
    url: url,
    blobInfo: blobInfo,
    status: true,
    // 新增attr
    attr: attr
  };
};

繼續向下搜尋handlerSuccess有2處,在success函式內, 將他改為

var success = function (url, attr) { // 新增attr
  closeNotification_1();
  uploadStatus.markUploaded(blobInfo.blobUri(), url);
  // 這裡修改增加了attr
  resolvePending(blobInfo.blobUri(), handlerSuccess(blobInfo, url, attr));
  resolve(handlerSuccess(blobInfo, url, attr));
};

搜尋replaceImageUriInView第一個函式修改如下

var replaceImageUriInView = function (image, resultUri, _attr) { // 新增_attr
  var src = editor.convertURL(resultUri, 'src');
  replaceUrlInUndoStack(image.src, resultUri);
  var attr = {
      'src': shouldReuseFileName(editor) ? cacheInvalidator(resultUri) : resultUri,
      'data-mce-src': src,
  };
  // 這裡修改來原來的程式碼增加了屬性新增
  if(typeof attr == 'object') {
    attr = Tools.extend(attr, _attr);
  }
  editor.$(image).attr(attr);
};

搜尋第二個replaceImageUriInView改為

replaceImageUriInView(image, uploadInfo.url, uploadInfo.attr);

好了, 如果你沒有改錯程式碼,

在自定義上傳回調函式內更改

success增加第二個引數attr如下

success(res.url, {
    'data-id': res.id,
    'data-width': res.width,
    'data-height': res.height
});

修改完畢👏

現在可以測試一下, 拖入上傳以及正常選擇上傳是否會在img標籤增加你而外配置的屬性

修改原始碼後壓縮請看另一篇文章😸

使用UglifyJS壓縮混淆js

至此貼上遠端圖片本地化就完成了, 希望本文能幫到你, 讓你避免長時間的浪費在這個功能上🧲

分享到: