在iframe中使用富文本编辑器wangEditor
自己做的邮件项目里面需要使用到富文本编辑器,邮件内容说白了就是HTML代码。前任使用的是wangEditor,部分定制化需求就是直接改的源码。
最近发现有的用户的邮件内容加进去的很多css信息,比如
<link rel="stylesheet" href="style.css"
,用户可能是使用的模板加进去的,或者自己是个懂点代码的,因为我们默认是不会处理删除用户的东西,所以这些信息就带进去了,成为了邮件的一部分。
为了做样式的隔离,所以我们查看邮件内容时使用了iframe,这样邮件内容的样式和我们邮件系统的样式不会发生相互影响。但是前任在新建、编辑邮件时没有使用iframe,当时就很纳闷,为什么没有使用呢?编辑邮件肯定也要进行样式隔离的啊,之前就有时会碰到个别邮件,在点击回复时,整个邮件系统的部分样式都受到了影响,我当时第一时间看了下wangEdtor的官网,没有发现这样的配置,可能需要修改源码,比较麻烦,再加上抽不出时间、出现频率极低,就扔在一边没有管了。最近有时间准备解决下这个问题,再次看了wangEdtor的官网,还是没有发现这样的配置项,估计是没有人有这样的需求,或者作者不知道有这样的需求吧,看来只能自己动手了改了。
由于之前有做翻译的经验,深知多了个iframe会有小麻烦,要处理好 window
、 document
,因为这个是分iframe的。
首先想到的就是直接将内容区域的节点从 div
改成 iframe
,再改动 _initDom
,在里面进行一些是否使用iframe的判断,
const textContainerDom = $(textSelector)[0]; const isIframe = Object.prototype.toString.call(textContainerDom) === '[object HTMLIFrameElement]'; if (isIframe) { $textContainerElem = $(textContainerDom.contentDocument.body); this.win = textContainerDom.contentWindow; } else { $textContainerElem = $(textSelector); this.win = window; }
上的 this.win
就是用来记录 window
的,如果使用了iframe的话, window
自然应该是该iframe对应的 contentWindow
,相应的 document
的也需要变更。
于是我搜了下 window
和 document
,还是有点多了,这样即使做成功了,肯定觉得很low,并且维护性不好。
wangEditor源码改动
改动点一
window
和document
先定义同名变量 window
和 document
分别等于默认的值,如果有iframe的话,直接使用 contentWindow
和 contentDocument
替换即可,想想都觉得很好,不是吗。
在最上面加入了一个方法 fixContext
。
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.wangEditor = factory()); }(this, (function () { 'use strict'; let window = global; let document = window.document; function fixContext() { if (global.$old) { window = global.$old; document = global.document; } else { global.$old = global } const iframe = document.querySelector('#editorIframe'); if (iframe) { window = iframe.contentWindow; document = iframe.contentDocument; } } ... })));
改动点二
在 _initDom
的第一时间运行 fixContext
方法。
_initDom: function _initDom() { var _this = this; fixContext(); var toolbarSelector = this.toolbarSelector; var $toolbarSelector = $(toolbarSelector); ... }
自己感觉这样对源码的侵入性更小。
但是很快出现了这样的问题:
/> 1.Maximum call stack size exceeded。
2.TypeError: elem.appendChild is not a function
(adsbygoogle = window.adsbygoogle || []).push({ google_ad_client: "ca-pub-3013839362871866", enable_page_level_ads: true });
我很是纳闷,怎么可能呢,又没用写死循环。自己刚开始想难道是自己替换 window
和 document
的时机不对,或许个别地方的 window
和 document
不需要替换成iframe的呢。
决定自己一步一步打断点吧,发现问题出在 initSelection
initSelection: function initSelection(newLine) { var $textElem = this.$textElem; var $children = $textElem.children(); if (!$children.length) { // 如果编辑器区域无内容,添加一个空行,重新设置选区 $textElem.append($('')); this.initSelection(); return; } var $last = $children.last(); if (newLine) { // 新增一个空行 var html = $last.html().toLowerCase(); var nodeName = $last.getNodeName(); if (html !== '
' && html !== '
' || nodeName !== 'P') { // 最后一个元素不是,添加一个空行,重新设置选区 $textElem.append($('
')); this.initSelection(); return; } } this.selection.createRangeByElem($last, false, true); this.selection.restoreSelection(); },
上面的 !$children.length
始终是 null
,导致了无限调用 initSelection
方法。
但是其实 $children
已经有新添加进去的子元素了,但是它的 length
却是空。经过打断点发现原来是源码的里面的 querySelectorAll
返回的“有误”导致。
function querySelectorAll(selector) { var result = document.querySelectorAll(selector); if (isDOMList(result)) { return result; } else { return [result]; } }
很显然,这里的 document
已经被我们替换iframe的 contentDocument
了,但是怎么还会有问题呢,其实返回的 result
没有问题,问题就出在 isDOMList
方法的判断上面。
function isDOMList(selector) { if (!selector) { return false; } if (selector instanceof HTMLCollection || selector instanceof NodeList) { return true; } return false; }
仔细排除发现,上面的 document.querySelectorAll
返回的明明是个 NodeList
,但是它并不是上面的 NodeList
的实例,所以上面的 isDOMList
还是会返回 false
。
就这样最终结果错误的返回了 [result]
而非 result
,也就是多了一层数组,而它并没有 length
,所以才引发了上面的死循环问题。
改动点三
可以简单粗暴的改成
if (selector instanceof HTMLCollection || selector instanceof NodeList || Object.prototype.toString.call(selector) === '[object HTMLCollection]' || Object.prototype.toString.call(selector) === '[object NodeList]') { return true; }
wangEditor的改动就此三处即可。
使用页面HTML结构变更
使用wangEdtior的页面建议进行如下改动
这样可以了。