秀米集成到UEditor的配置过程

秀米集成到UEditor的配置过程

虽然秀米官方有提供文档说明如何将秀米插件集成至UEditor里(秀米官方文档),但是该文档说明模糊不清,关键配置一句话带过,即使配置成功后还有坑留在里面。所以我重新整理了这个配置过程,回顾一下过程的同时也给以后需要用到的小伙伴提供便利。

一、将秀米图标集成至工具栏,并且成功弹出秀米弹框

  1. 首先我们需要一个秀米的html文件,这个html里主要就是弹框里的内容,里面有一个iframe指向秀米的网址。
    取名xiumi-ue-dialog-v5.html,放入ueditor1_4_3_3文件夹(UE的资源文件夹)下或者ueditor1_4_3_3下的dialogs文件夹里,这个只是涉及到后面引入它的路径,不是很重要,我是放在ueditor1_4_3_3文件夹下的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    <!-- ueditor1\_4\_3\_3/xiumi-ue-dialog-v5.html -->
    <!DOCTYPE html>
    <!-- saved from url=(0049)http://hgs.xiumi.us/uedit/xiumi-ue-dialog-v5.html -->
    <html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <title>XIUMI connect</title>
    <style>
    html, body {
    padding: 0;
    margin: 0;
    }

    #xiumi {
    position: absolute;
    width: 100%;
    height: 100%;
    border: none;
    box-sizing: border-box;
    }
    </style>
    </head>
    <body style="">
    <iframe id="xiumi" src="//xiumi.us/studio/v5#/paper">
    </iframe>
    <script type="text/javascript" src="./dialogs/internal.js"></script>
    <script>
    var xiumi = document.getElementById('xiumi');
    var xiumi_url = window.location.protocol + "//xiumi.us";
    xiumi.onload = function () {
    xiumi.contentWindow.postMessage('ready', xiumi_url);
    };
    document.addEventListener("mousewheel", function (event) {
    event.preventDefault();
    event.stopPropagation();
    });
    window.addEventListener('message', function (event) {
    if (event.origin == xiumi_url) {
    editor.execCommand('insertHtml', event.data);
    // 这个方法用于触发UE的图片转存接口
    editor.fireEvent('afterpaste');
    dialog.close();
    }
    }, false);
    </script>
    </body></html>
  2. 秀米的文档里使用的是UE.registerUI(‘dialog’,fn)的方式动态在UE里添加一个秀米按钮并且在按钮里添加弹出弹框事件,但是这么做可能会影响到系统其他使用UE里的地方同样出现秀米图标,无法按需加载。所以我将此改进成在UE的前端配置中的toolbars数组里添加一个”xiumi”字符串即可展示出秀米的方式。步骤如下:

    • 添加一个秀米的图标并添加样式。在ueditor1_4_3_3/themes/default/images文件夹里放入一个秀米图标;

    • 在ueditor1_4_3_3/themes/default/css/ueditor.css 添加俩句样式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      /* xiumi-dialog */
      .edui-default .edui-for-xiumi .edui-dialog-content {
      width: calc(100vw - 60px) !important;
      height: 90vh !important;
      overflow: hidden;
      }

      .edui-default .edui-for-xiumi .edui-icon {
      background-image: url("../images/xiumi-connect-icon.png") !important;
      background-size: contain;
      }
    • 在ueditor.all.js修改代码。在iframeUrlMap对象里添加键值对’xiumi’: ‘~/xiumi-ue-dialog-v5.html’,这个和刚刚那个html存放的地方有关。在btnCmds数组里添加一个’xiumi’字符串,在dialogBtns对象中的ok数组里添加’xiumi’字符串。

    • 最后在UE的前端配置里toolbars数组里添加’xiumi’,即可成功展示出秀米图标与秀米的弹框。

效果如下

  1. 在ueditor.config.js的xss过滤白名单whitList配置里,修改section参数。同时将设置是否抓取远程图片catchRemoteImageEnable设置为true。

    1
    section:['class', 'style'],
  2. 能成功弹出秀米的弹框,并且能将秀米里的模版成功勾选到本地的UE编辑器里,说明集成就成功了一半啦。

二、秀米域名的图片转存。

要完成秀米图片的转存,首先我们要先完成UE对复制过来的网络图片的转存,图片的转存分为img标签与背景图片的抓取与转存。

  1. 设置抓取白名单catcherLocalDomain,在白名单里的地址,UE不会对其发起转存请求。可以使用两种方式设置:

    • 使用UE.utils.extend方法强制在UE实例化后设置
      1
      2
      3
      UE.utils.extend(editor.options, {
      catcherLocalDomain: ['127.0.0.1', 'localhost', "static-alpha-engage.gridsumdissector.com", "static-beta-engage.gridsumdissector.com", "static-uat-engage.gridsumdissector.com", "static.engage-all.com"],
      });
    • 修改ueditor.all.js里的初始配置代码,在这个js文件大概第8100行的地方,有一个loadServerConfig的函数,可以直接在这里将catcherLocalDomain的初始值设置为我们想要过滤的白名单域名。
  2. 修改抓取图片的后端请求地址。UE发起所有接口请求都依赖于一个名为serverUrl的前端配置,然后通过使用serverUrl这个唯一的请求地址,通过GET参数action指定不同请求类型,比如uploadimage(执行上传图片或截图的action名称), uploadvideo(执行上传视频的action名称), catchimage(执行抓取远程图片的action名称)等等,但是后端为了方便接口的管理,一般会将接口拆分出来,当然UE也支持自定义请求地址。UE推荐的方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    UE.Editor.prototype._bkGetActionUrl = UE.Editor.prototype.getActionUrl;
    UE.Editor.prototype.getActionUrl = function(action) {
    if (action == 'uploadimage' || action == 'uploadscrawl' || action == 'uploadimage') {
    return 'http://a.b.com/upload.php';
    } else if (action == 'uploadvideo') {
    return 'http://a.b.com/video.php';
    } else {
    return this._bkGetActionUrl.call(this, action);
    }
    }

原理就是使用一个名为_bkGetActionUrl的临时变量承载原来的getActionUrl方法,然后重写getActionUrl方法,判断action的类型,来返回不同的接口,同时也还可以执行原来的getActionUrl方法。
但是我在使用这种方法的时候,会偶尔碰到内存溢出,导致上传图片功能不可用的原因,可能是getActionUrl在其他地方也被重新赋值了,产生循环引用该函数的问题。所以我依旧在ueditor.all.js里修改了getActionUrl原始函数,大概在第8040行代码里,判断getActionUrl函数内部一个actionName的参数,来返回你想要的接口名。

  1. 到这里差不多能够抓取到img标签的src并且可以将其替换了,但需要注意俩点地方:

    • 注意抓取后,后端返回的数据格式时候和UE里源码设置的一样,在ueditor.all.js找到UE.plugins[‘catchremoteimage’]这个函数,在其内部执行catchremoteimage的success回掉函数的地方注意获取源路径和新路径的数据路径的地方。

    • UE内部有一个判断是否跨域的方法,如果跨域会使用jsonp的方式请求接口,如果你是本地调试,并且后端已经对跨域crose处理,不需要使用jsonp的方式请求,你可以把它关了。同样在UE.plugins[‘catchremoteimage’]这个函数里,找到定义catchremoteimage函数的地方,将其内部ajax请求的option里,将dataType固定设置为空字符串即可。

  2. 除了img标签的src替换,背景图片也是需要进行图片抓取和替换的。其实就是依葫芦画瓢,看明白了UE是如何对img标签进行src替换的,也就明白该如何对背景图片进行替换了。

    • 首先你需要在ueditor.all.js文件的domUtils参数里新添一个方法getElementsByTagNameStyle。通过元素的style来获取元素节点数组。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      /**
      * 方法getElementsByTagNameStyle的封装
      * @method getElementsByTagNameStyle
      * @param { e } node 目标节点对象
      * @param { t } tagName 需要查找的节点的tagName, 多个tagName以空格分割
      * @param { i } style 节点对象的筛选条件
      * @return { Array } 符合条件的节点集合
      */
      getElementsByTagNameStyle: function (e, t, i) {
      if (i && utils.isString(i)) {
      var n = i;
      i = function (e) {
      for (var t, i = n.split(","), o = !0, r = e.getAttribute("style"), a = 0; t = i[a++];) if (!r || r.indexOf(t) < 0) {
      o = !1;
      break
      }
      return o
      }
      }
      t = utils.trim(t).replace(/[ ]{2,}/g, " ").split(" ");
      for (var o, r = [], a = 0; o = t[a++];) for (var s, l = e.getElementsByTagName(o), d = 0; s = l[d++];) i && !i(s) || r.push(s);
      return r
      },
    • 在UE.plugins[‘catchremoteimage’]下的catchRemoteImage监听函数里添加代码。首先你得的到有背景图片的的元素节点数组。使用刚刚新添的方法

      1
      backgroundimagestags = domUtils.getElementsByTagNameStyle(me.document, "section div p", "background,url")//抓取背景图片所在的标签

      然后将该元素节点数组里的图片地址都抽取出来,放在一个存放图片地址的数组里

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      var backgroundimages = [];
      for (var i = 0, backci; backci = backgroundimagestags[i++];) {

      var bstyle = backci.style;
      var backgroundimgurltag = bstyle['background-image'] || bstyle['background'] || "";
      if (backgroundimgurltag != null && backgroundimgurltag != "") {
      var backsrc = backgroundimgurltag.split("(")[1].split(")")[0].replace(/\"/g, "")
      || backgroundimgurltag.split("(")[1].split(")")[0].replace(/\"/g, "")
      || "";
      if (backsrc != null && backsrc != "") {
      if (/^(https?|ftp):/i.test(backsrc) && !test(backsrc, catcherLocalDomain)) {
      backgroundimages.push(encodeURI(backsrc));
      }
      }
      }
      }

      最后依葫芦画瓢,对该图片地址数组循环,依次发起转存请求,并且将对应的节点内的背景图片url进行替换。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      if(backgroundimages.length) {
      catchremoteimage(backgroundimages, {
      //成功抓取
      success: function (r) {
      try {
      var info = r.state !== undefined ? r:eval("(" + r.responseText + ")");
      } catch (e) {
      return;
      }

      /* 获取源路径和新路径 */
      var i, j, ci, cj, oldSrc, newSrc, styleText ,list = info.data.list;

      for (i = 0; ci = backgroundimagestags[i++];) {
      styleText = ci.getAttribute("style");
      oldSrc = styleText;
      if (oldSrc.indexOf('url("') > 0) {
      oldSrc = oldSrc.split('url("')[1].split('")')[0];
      } else if (oldSrc.indexOf("url('") > 0) {
      oldSrc = oldSrc.split("url('")[1].split("')")[0];
      } else {
      if (!(oldSrc.indexOf("url(") > 0)) continue;
      oldSrc = oldSrc.split("url(")[1].split(")")[0];
      }
      if (oldSrc.indexOf("?") >= 0) {
      oldSrc = oldSrc.split("?")[0];
      }
      for (j = 0; cj = list[j++];) {
      if (oldSrc == cj.source && "SUCCESS" == cj.state) {
      newSrc = catcherUrlPrefix + cj.url, styleText = styleText.replace(oldSrc, newSrc), domUtils.setAttributes(ci, { style: styleText }), domUtils.setAttributes(ci, { is_updata: "true" });
      break
      }
      }
      }
      me.fireEvent('catchremotesuccess')
      },
      //回调失败,本次请求超时
      error: function () {
      me.fireEvent("catchremoteerror");
      }
      });
      }
  1. 到这里,UE对普通的img标签及背景图片的抓取与转存已经成功了,如果想要对秀米过来的图片进行转存需要注意以下俩点:

    • 对某些秀米图片地址的后缀进行处理,去掉?号之后类似?x-oss-process=所有的部分。这个在catchremoteimage函数里对imgs进行一步map循环,将?后面的东西去掉即可,同时注意在图片地址数组循环的过程中oldSrc?后面的内容也要去掉,否则oldSrc == cj.source && “SUCCESS” == cj.state这一步判断不会通过,就无法进行图片地址的替换了。

    • 从秀米回到本地UE编辑器的时候不会触发图片转存请求,这也是秀米文档埋下的一个坑!解决这个问题,你需要在xiumi-ue-dialog-v5.html文件里,在dialog.close();代码前添加一句editor.fireEvent('afterpaste');来主动触发afterpaste这个监听函数。

大功告成

我想吃鸡腿!