PageSourceSearch

https://pic.iyingdi.com/yingdi_pc_old/prod/js/jq/editor/wangEditor.js?v=20160920

js iyingdi.com collected 2026-09-24 17:42:55 UTC 190,211 bytes, 5,279 lines download raw bytes

1(function (global, factory) {
2	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3	typeof define === 'function' && define.amd ? define(factory) :
4	(global.wangEditor = factory());
5}(this, (function () { 'use strict';
6
7/*
8    poly-fill
9*/
10
11var polyfill = function () {
12
13    // Object.assign
14    if (typeof Object.assign != 'function') {
15        Object.assign = function (target, varArgs) {
16            // .length of function is 2
17            if (target == null) {
18                // TypeError if undefined or null
19                throw new TypeError('Cannot convert undefined or null to object');
20            }
21
22            var to = Object(target);
23
24            for (var index = 1; index < arguments.length; index++) {
25                var nextSource = arguments[index];
26
27                if (nextSource != null) {
28                    // Skip over if undefined or null
29                    for (var nextKey in nextSource) {
30                        // Avoid bugs when hasOwnProperty is shadowed
31                        if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
32                            to[nextKey] = nextSource[nextKey];
33                        }
34                    }
35                }
36            }
37            return to;
38        };
39    }
40
41    // IE 中兼容 Element.prototype.matches
42    if (!Element.prototype.matches) {
43        Element.prototype.matches = Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector || function (s) {
44            var matches = (this.document || this.ownerDocument).querySelectorAll(s),
45                i = matches.length;
46            while (--i >= 0 && matches.item(i) !== this) {}
47            return i > -1;
48        };
49    }
50};
51
52/*
53    DOM 操作 API
54*/
55
56// 根据 html 代码片段创建 dom 对象
57function createElemByHTML(html) {
58    var div = void 0;
59    div = document.createElement('div');
60    div.innerHTML = html;
61    return div.children;
62}
63
64// 是否是 DOM List
65function isDOMList(selector) {
66    if (!selector) {
67        return false;
68    }
69    if (selector instanceof HTMLCollection || selector instanceof NodeList) {
70        return true;
71    }
72    return false;
73}
74
75// 封装 document.querySelectorAll
76function querySelectorAll(selector) {
77    var result = document.querySelectorAll(selector);
78    if (isDOMList(result)) {
79        return result;
80    } else {
81        return [result];
82    }
83}
84
85// 记录所有的事件绑定
86var eventList = [];
87
88// 创建构造函数
89function DomElement(selector) {
90    if (!selector) {
91        return;
92    }
93
94    // selector 本来就是 DomElement 对象,直接返回
95    if (selector instanceof DomElement) {
96        return selector;
97    }
98
99    this.selector = selector;
100    var nodeType = selector.nodeType;
101
102    // 根据 selector 得出的结果(如 DOM,DOM List)
103    var selectorResult = [];
104    if (nodeType === 9) {
105        // document 节点
106        selectorResult = [selector];
107    } else if (nodeType === 1) {
108        // 单个 DOM 节点
109        selectorResult = [selector];
110    } else if (isDOMList(selector) || selector instanceof Array) {
111        // DOM List 或者数组
112        selectorResult = selector;
113    } else if (typeof selector === 'string') {
114        // 字符串
115        selector = selector.replace('/\n/mg', '').trim();
116        if (selector.indexOf('<') === 0) {
117            // 如 <div>
118            selectorResult = createElemByHTML(selector);
119        } else {
120            // 如 #id .class
121            selectorResult = querySelectorAll(selector);
122        }
123    }
124
125    var length = selectorResult.length;
126    if (!length) {
127        // 空数组
128        return this;
129    }
130
131    // 加入 DOM 节点
132    var i = void 0;
133    for (i = 0; i < length; i++) {
134        this[i] = selectorResult[i];
135    }
136    this.length = length;
137}
138
139// 修改原型
140DomElement.prototype = {
141    constructor: DomElement,
142
143    // 类数组,forEach
144    forEach: function forEach(fn) {
145        var i = void 0;
146        for (i = 0; i < this.length; i++) {
147            var elem = this[i];
148            var result = fn.call(elem, elem, i);
149            if (result === false) {
150                break;
151            }
152        }
153        return this;
154    },
155
156    // clone
157    clone: function clone(deep) {
158        var cloneList = [];
159        this.forEach(function (elem) {
160            cloneList.push(elem.cloneNode(!!deep));
161        });
162        return $(cloneList);
163    },
164
165    // 获取第几个元素
166    get: function get(index) {
167        var length = this.length;
168        if (index >= length) {
169            index = index % length;
170        }
171        return $(this[index]);
172    },
173
174    // 第一个
175    first: function first() {
176        return this.get(0);
177    },
178
179    // 最后一个
180    last: function last() {
181        var length = this.length;
182        return this.get(length - 1);
183    },
184
185    // 绑定事件
186    on: function on(type, selector, fn) {
187        // selector 不为空,证明绑定事件要加代理
188        if (!fn) {
189            fn = selector;
190            selector = null;
191        }
192
193        // type 是否有多个
194        var types = [];
195        types = type.split(/\s+/);
196
197        return this.forEach(function (elem) {
198            types.forEach(function (type) {
199                if (!type) {
200                    return;
201                }
202
203                // 记录下,方便后面解绑
204                eventList.push({
205                    elem: elem,
206                    type: type,
207                    fn: fn
208                });
209
210                if (!selector) {
211                    // 无代理
212                    elem.addEventListener(type, fn);
213                    return;
214                }
215
216                // 有代理
217                elem.addEventListener(type, function (e) {
218                    var target = e.target;
219                    if (target.matches(selector)) {
220                        fn.call(target, e);
221                    }
222                });
223            });
224        });
225    },
226
227    // 取消事件绑定
228    off: function off(type, fn) {
229        return this.forEach(function (elem) {
230            elem.removeEventListener(type, fn);
231        });
232    },
233
234    // 获取/设置 属性
235    attr: function attr(key, val) {
236        if (val == null) {
237            // 获取值
238            return this[0].getAttribute(key);
239        } else {
240            // 设置值
241            return this.forEach(function (elem) {
242                elem.setAttribute(key, val);
243            });
244        }
245    },
246
247    // 添加 class
248    addClass: function addClass(className) {
249        if (!className) {
250            return this;
251        }
252        return this.forEach(function (elem) {
253            var arr = void 0;
254            if (elem.className) {
255                // 解析当前 className 转换为数组
256                arr = elem.className.split(/\s/);
257                arr = arr.filter(function (item) {
258                    return !!item.trim();
259                });
260                // 添加 class
261                if (arr.indexOf(className) < 0) {
262                    arr.push(className);
263                }
264                // 修改 elem.class
265                elem.className = arr.join(' ');
266            } else {
267                elem.className = className;
268            }
269        });
270    },
271
272    // 删除 class
273    removeClass: function removeClass(className) {
274        if (!className) {
275            return this;
276        }
277        return this.forEach(function (elem) {
278            var arr = void 0;
279            if (elem.className) {
280                // 解析当前 className 转换为数组
281                arr = elem.className.split(/\s/);
282                arr = arr.filter(function (item) {
283                    item = item.trim();
284                    // 删除 class
285                    if (!item || item === className) {
286                        return false;
287                    }
288                    return true;
289                });
290                // 修改 elem.class
291                elem.className = arr.join(' ');
292            }
293        });
294    },
295
296    // 修改 css
297    css: function css(key, val) {
298        var currentStyle = key + ':' + val + ';';
299        return this.forEach(function (elem) {
300            var style = (elem.getAttribute('style') || '').trim();
301            var styleArr = void 0,
302                resultArr = [];
303            if (style) {
304                // 将 style 按照 ; 拆分为数组
305                styleArr = style.split(';');
306                styleArr.forEach(function (item) {
307                    // 对每项样式,按照 : 拆分为 key 和 value
308                    var arr = item.split(':').map(function (i) {
309                        return i.trim();
310                    });
311                    if (arr.length === 2) {
312                        resultArr.push(arr[0] + ':' + arr[1]);
313                    }
314                });
315                // 替换或者新增
316                resultArr = resultArr.map(function (item) {
317                    if (item.indexOf(key) === 0) {
318                        return currentStyle;
319                    } else {
320                        return item;
321                    }
322                });
323                if (resultArr.indexOf(currentStyle) < 0) {
324                    resultArr.push(currentStyle);
325                }
326                // 结果
327                elem.setAttribute('style', resultArr.join('; '));
328            } else {
329                // style 无值
330                elem.setAttribute('style', currentStyle);
331            }
332        });
333    },
334
335    // 显示
336    show: function show() {
337        return this.css('display', 'block');
338    },
339
340    // 隐藏
341    hide: function hide() {
342        return this.css('display', 'none');
343    },
344
345    // 获取子节点
346    children: function children() {
347        var elem = this[0];
348        if (!elem) {
349            return null;
350        }
351
352        return $(elem.children);
353    },
354
355    // 获取子节点(包括文本节点)
356    childNodes: function childNodes() {
357        var elem = this[0];
358        if (!elem) {
359            return null;
360        }
361
362        return $(elem.childNodes);
363    },
364
365    // 增加子节点
366    append: function append($children) {
367        return this.forEach(function (elem) {
368            $children.forEach(function (child) {
369                elem.appendChild(child);
370            });
371        });
372    },
373
374    // 移除当前节点
375    remove: function remove() {
376        return this.forEach(function (elem) {
377            if (elem.remove) {
378                elem.remove();
379            } else {
380                var parent = elem.parentElement;
381                parent && parent.removeChild(elem);
382            }
383        });
384    },
385
386    // 是否包含某个子节点
387    isContain: function isContain($child) {
388        var elem = this[0];
389        var child = $child[0];
390        return elem.contains(child);
391    },
392
393    // 尺寸数据
394    getSizeData: function getSizeData() {
395        var elem = this[0];
396        return elem.getBoundingClientRect(); // 可得到 bottom height left right top width 的数据
397    },
398
399    // 封装 nodeName
400    getNodeName: function getNodeName() {
401        var elem = this[0];
402        return elem.nodeName;
403    },
404
405    // 从当前元素查找
406    find: function find(selector) {
407        var elem = this[0];
408        return $(elem.querySelectorAll(selector));
409    },
410
411    // 获取当前元素的 text
412    text: function text(val) {
413        if (!val) {
414            // 获取 text
415            var elem = this[0];
416            return elem.innerHTML.replace(/<.*?>/g, function () {
417                return '';
418            });
419        } else {
420            // 设置 text
421            return this.forEach(function (elem) {
422                elem.innerHTML = val;
423            });
424        }
425    },
426
427    // 获取 html
428    html: function html(value) {
429        var elem = this[0];
430        if (value == null) {
431            return elem.innerHTML;
432        } else {
433            elem.innerHTML = value;
434            return this;
435        }
436    },
437
438    // 获取 value
439    val: function val() {
440        var elem = this[0];
441        return elem.value.trim();
442    },
443
444    // focus
445    focus: function focus() {
446        return this.forEach(function (elem) {
447            elem.focus();
448        });
449    },
450
451    // parent
452    parent: function parent() {
453        var elem = this[0];
454        return $(elem.parentElement);
455    },
456
457    // parentUntil 找到符合 selector 的父节点
458    parentUntil: function parentUntil(selector, _currentElem) {
459        var results = document.querySelectorAll(selector);
460        var length = results.length;
461        if (!length) {
462            // 传入的 selector 无效
463            return null;
464        }
465
466        var elem = _currentElem || this[0];
467        if (elem.nodeName === 'BODY') {
468            return null;
469        }
470
471        var parent = elem.parentElement;
472        var i = void 0;
473        for (i = 0; i < length; i++) {
474            if (parent === results[i]) {
475                // 找到,并返回
476                return $(parent);
477            }
478        }
479
480        // 继续查找
481        return this.parentUntil(selector, parent);
482    },
483
484    // 判断两个 elem 是否相等
485    equal: function equal($elem) {
486        if ($elem.nodeType === 1) {
487            return this[0] === $elem;
488        } else {
489            return this[0] === $elem[0];
490        }
491    },
492
493    // 将该元素插入到某个元素前面
494    insertBefore: function insertBefore(selector) {
495        var $referenceNode = $(selector);
496        var referenceNode = $referenceNode[0];
497        if (!referenceNode) {
498            return this;
499        }
500        return this.forEach(function (elem) {
501            var parent = referenceNode.parentNode;
502            parent.insertBefore(elem, referenceNode);
503        });
504    },
505
506    // 将该元素插入到某个元素后面
507    insertAfter: function insertAfter(selector) {
508        var $referenceNode = $(selector);
509        var referenceNode = $referenceNode[0];
510        if (!referenceNode) {
511            return this;
512        }
513        return this.forEach(function (elem) {
514            var parent = referenceNode.parentNode;
515            if (parent.lastChild === referenceNode) {
516                // 最后一个元素
517                parent.appendChild(elem);
518            } else {
519                // 不是最后一个元素
520                parent.insertBefore(elem, referenceNode.nextSibling);
521            }
522        });
523    }
524};
525
526// new 一个对象
527function $(selector) {
528    return new DomElement(selector);
529}
530
531// 解绑所有事件,用于销毁编辑器
532$.offAll = function () {
533    eventList.forEach(function (item) {
534        var elem = item.elem;
535        var type = item.type;
536        var fn = item.fn;
537        // 解绑
538        elem.removeEventListener(type, fn);
539    });
540};
541
542/*
543    配置信息
544*/
545
546var config = {
547
548    // 默认菜单配置
549    menus: ['head', 'bold', 'fontSize', 'fontName', 'italic', 'underline', 'strikeThrough', 'foreColor', 'backColor', 'link', 'allLink', 'list', 'justify', 'quote', 'emoticon', 'image', 'table', 'video', 'code', 'undo', 'redo', 'cardRecognition', 'textSection'],
550
551    fontNames: ['宋体', '微软雅黑', 'Arial', 'Tahoma', 'Verdana'],
552
553    colors: ['#000000', '#eeece0', '#1c487f', '#4d80bf', '#c24f4a', '#8baa4a', '#7b5ba1', '#46acc8', '#f9963b', '#ffffff'],
554
555    // // 语言配置
556    // lang: {
557    //     '设置标题': 'title',
558    //     '正文': 'p',
559    //     '链接文字': 'link text',
560    //     '链接': 'link',
561    //     '插入': 'insert',
562    //     '创建': 'init'
563    // },
564
565    // 表情
566    emotions: [{
567        // tab 的标题
568        title: '默认',
569        // type -> 'emoji' / 'image'
570        type: 'image',
571        // content -> 数组
572        content: [{
573            alt: '[坏笑]',
574            src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/50/pcmoren_huaixiao_org.png'
575        }, {
576            alt: '[舔屏]',
577            src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/40/pcmoren_tian_org.png'
578        }, {
579            alt: '[污]',
580            src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/3c/pcmoren_wu_org.png'
581        }]
582    }, {
583        // tab 的标题
584        title: '新浪',
585        // type -> 'emoji' / 'image'
586        type: 'image',
587        // content -> 数组
588        content: [{
589            src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/7a/shenshou_thumb.gif',
590            alt: '[草泥马]'
591        }, {
592            src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/60/horse2_thumb.gif',
593            alt: '[神马]'
594        }, {
595            src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/bc/fuyun_thumb.gif',
596            alt: '[浮云]'
597        }]
598    }, {
599        // tab 的标题
600        title: 'emoji',
601        // type -> 'emoji' / 'image'
602        type: 'emoji',
603        // content -> 数组
604        content: '😀 😃 😄 😁 😆 😅 😂 😊 😇 🙂 🙃 😉 😓 😪 😴 🙄 🤔 😬 🤐'.split(/\s/)
605    }],
606
607    // 编辑区域的 z-index
608    zIndex: 10000,
609
610    // 是否开启 debug 模式(debug 模式下错误会 throw error 形式抛出)
611    debug: false,
612
613    // 插入链接时候的格式校验
614    linkCheck: function linkCheck(text, link) {
615        // text 是插入的文字
616        // link 是插入的链接
617        return true; // 返回 true 即表示成功
618        // return '校验失败' // 返回字符串即表示失败的提示信息
619    },
620
621    // 插入网络图片的校验
622    linkImgCheck: function linkImgCheck(src) {
623        // src 即图片的地址
624        return true; // 返回 true 即表示成功
625        // return '校验失败'  // 返回字符串即表示失败的提示信息
626    },
627
628    // 粘贴过滤样式,默认开启
629    pasteFilterStyle: true,
630
631    // 粘贴内容时,忽略图片。默认关闭
632    pasteIgnoreImg: true,
633
634    // 对粘贴的文字进行自定义处理,返回处理后的结果。编辑器会将处理后的结果粘贴到编辑区域中。
635    // IE 暂时不支持
636    pasteTextHandle: function pasteTextHandle(content) {
637        // content 即粘贴过来的内容(html 或 纯文本),可进行自定义处理然后返回
638        content = content.replace(/<html(.*?)>/gi,"");
639        content = content.replace(/<\/html>/gi,"");
640        content = content.replace(/<head(.*?)>/gi,"");
641        content = content.replace(/<\/head>/gi,"");
642        content = content.replace(/<body(.*?)>/gi,"");
643        content = content.replace(/<\/body>/gi,"");
644        content = content.replace(/<div(.*?)>/gi,"");
645        content = content.replace(/<\/div>/gi,"");
646        content = content.replace(/<span(.*?)>/gi,"");
647        content = content.replace(/<\/span>/gi,"");
648        content = content.replace(/(^\s*)|(\s*$)/g, "");
649        content = content.replace(/\n/gi,'<br>');
650        content = content.replace(/<br\/>/gi,'<br>');
651        // 过滤掉word部分格式
652        var reg = /<!--\[if\s+gte\s+mso\s+9\]>(?:(?!<!\[endif\]-->)[\s\S])*<!\[endif\]-->/gi;
653        content = content.replace(reg,'');
654        content = content.replace(/<style[^>]*?>[\s\S]*?<\/style>/,'');
655        var textArr = content.split('<br>');
656        var textStr = '';
657        for(var j=0;j<textArr.length;j++){
658            if(textArr[j]!=""&&textArr[j].length>0){
659                textStr += '<p>'+textArr[j]+'</p>';
660            }
661        }
662        return textStr;
663    },
664
665    // onchange 事件
666    // onchange: function (html) {
667    //     // html 即变化之后的内容
668    //     console.log(html)
669    // },
670
671    // 是否显示添加网络图片的 tab
672    showLinkImg: true,
673
674    // 插入网络图片的回调
675    linkImgCallback: function linkImgCallback(url) {
676        // console.log(url)  // url 即插入图片的地址
677    },
678
679    // 默认上传图片 max size: 5M
680    uploadImgMaxSize: 5 * 1024 * 1024,
681
682    // 配置一次最多上传几个图片
683    // uploadImgMaxLength: 5,
684
685    // 上传图片,是否显示 base64 格式
686    uploadImgShowBase64: false,
687
688    // 上传图片,server 地址(如果有值,则 base64 格式的配置则失效)
689    // uploadImgServer: '/upload',
690
691    // 自定义配置 filename
692    uploadFileName: '',
693
694    // 上传图片的自定义参数
695    uploadImgParams: {
696        // token: 'abcdef12345'
697    },
698
699    // 上传图片的自定义header
700    uploadImgHeaders: {
701        // 'Accept': 'text/x-json'
702    },
703
704    // 配置 XHR withCredentials
705    withCredentials: false,
706
707    // 自定义上传图片超时时间 ms
708    uploadImgTimeout: 10000,
709
710    // 上传图片 hook 
711    uploadImgHooks: {
712        // customInsert: function (insertLinkImg, result, editor) {
713        //     console.log('customInsert')
714        //     // 图片上传并返回结果,自定义插入图片的事件,而不是编辑器自动插入图片
715        //     const data = result.data1 || []
716        //     data.forEach(link => {
717        //         insertLinkImg(link)
718        //     })
719        // },
720        before: function before(xhr, editor, files) {
721            // 图片上传之前触发
722
723            // 如果返回的结果是 {prevent: true, msg: 'xxxx'} 则表示用户放弃上传
724            // return {
725            //     prevent: true,
726            //     msg: '放弃上传'
727            // }
728        },
729        success: function success(xhr, editor, result) {
730            // 图片上传并返回结果,图片插入成功之后触发
731        },
732        fail: function fail(xhr, editor, result) {
733            // 图片上传并返回结果,但图片插入错误时触发
734        },
735        error: function error(xhr, editor) {
736            // 图片上传出错时触发
737        },
738        timeout: function timeout(xhr, editor) {
739            // 图片上传超时时触发
740        }
741    },
742
743    // 是否上传七牛云,默认为 false
744    qiniu: false
745
746};
747
748/*
749    工具
750*/
751
752// 和 UA 相关的属性
753var UA = {
754    _ua: navigator.userAgent,
755
756    // 是否 webkit
757    isWebkit: function isWebkit() {
758        var reg = /webkit/i;
759        return reg.test(this._ua);
760    },
761
762    // 是否 IE
763    isIE: function isIE() {
764        return 'ActiveXObject' in window;
765    }
766};
767
768// 遍历对象
769function objForEach(obj, fn) {
770    var key = void 0,
771        result = void 0;
772    for (key in obj) {
773        if (obj.hasOwnProperty(key)) {
774            result = fn.call(obj, key, obj[key]);
775            if (result === false) {
776                break;
777            }
778        }
779    }
780}
781
782// 遍历类数组
783function arrForEach(fakeArr, fn) {
784    var i = void 0,
785        item = void 0,
786        result = void 0;
787    var length = fakeArr.length || 0;
788    for (i = 0; i < length; i++) {
789        item = fakeArr[i];
790        result = fn.call(fakeArr, item, i);
791        if (result === false) {
792            break;
793        }
794    }
795}
796
797// 获取随机数
798function getRandom(prefix) {
799    return prefix + Math.random().toString().slice(2);
800}
801
802// 替换 html 特殊字符
803function replaceHtmlSymbol(html) {
804    if (html == null) {
805        return '';
806    }
807    return html.replace(/</gm, '&lt;').replace(/>/gm, '&gt;').replace(/"/gm, '&quot;').replace(/(\r\n|\r|\n)/g, '<br/>');
808}
809
810// 返回百分比的格式
811
812
813// 判断是不是 function
814function isFunction(fn) {
815    return typeof fn === 'function';
816}
817
818/*
819    bold-menu
820*/
821// 构造函数
822function Bold(editor) {
823    this.editor = editor;
824    this.$elem = $('<div class="w-e-menu">\n            <i class="w-e-icon-bold"></i>\n        </div>');
825    this.type = 'click';
826
827    // 当前是否 active 状态
828    this._active = false;
829}
830
831// 原型
832Bold.prototype = {
833    constructor: Bold,
834
835    // 点击事件
836    onClick: function onClick(e) {
837        // 点击菜单将触发这里
838
839        var editor = this.editor;
840        var isSeleEmpty = editor.selection.isSelectionEmpty();
841
842        if (isSeleEmpty) {
843            // 选区是空的,插入并选中一个“空白”
844            editor.selection.createEmptyRange();
845        }
846
847        // 执行 bold 命令
848        editor.cmd.do('bold');
849
850        if (isSeleEmpty) {
851            // 需要将选取折叠起来
852            editor.selection.collapseRange();
853            editor.selection.restoreSelection();
854        }
855    },
856
857    // 试图改变 active 状态
858    tryChangeActive: function tryChangeActive(e) {
859        var editor = this.editor;
860        var $elem = this.$elem;
861        if (editor.cmd.queryCommandState('bold')) {
862            this._active = true;
863            $elem.addClass('w-e-active');
864        } else {
865            this._active = false;
866            $elem.removeClass('w-e-active');
867        }
868    }
869};
870
871/*
872    替换多语言
873 */
874
875var replaceLang = function (editor, str) {
876    var langArgs = editor.config.langArgs || [];
877    var result = str;
878
879    langArgs.forEach(function (item) {
880        var reg = item.reg;
881        var val = item.val;
882
883        if (reg.test(result)) {
884            result = result.replace(reg, function () {
885                return val;
886            });
887        }
888    });
889
890    return result;
891};
892
893/*
894    droplist
895*/
896var _emptyFn = function _emptyFn() {};
897
898// 构造函数
899function DropList(menu, opt) {
900    var _this = this;
901
902    // droplist 所依附的菜单
903    var editor = menu.editor;
904    this.menu = menu;
905    this.opt = opt;
906    // 容器
907    var $container = $('<div class="w-e-droplist"></div>');
908
909    // 标题
910    var $title = opt.$title;
911    var titleHtml = void 0;
912    if ($title) {
913        // 替换多语言
914        titleHtml = $title.html();
915        titleHtml = replaceLang(editor, titleHtml);
916        $title.html(titleHtml);
917
918        $title.addClass('w-e-dp-title');
919        $container.append($title);
920    }
921
922    var list = opt.list || [];
923    var type = opt.type || 'list'; // 'list' 列表形式(如“标题”菜单) / 'inline-block' 块状形式(如“颜色”菜单)
924    var onClick = opt.onClick || _emptyFn;
925
926    // 加入 DOM 并绑定事件
927    var $list = $('<ul class="' + (type === 'list' ? 'w-e-list' : 'w-e-block') + '"></ul>');
928    $container.append($list);
929    list.forEach(function (item) {
930        var $elem = item.$elem;
931
932        // 替换多语言
933        var elemHtml = $elem.html();
934        elemHtml = replaceLang(editor, elemHtml);
935        $elem.html(elemHtml);
936
937        var value = item.value;
938        var $li = $('<li class="w-e-item"></li>');
939        if ($elem) {
940            $li.append($elem);
941            $list.append($li);
942            $li.on('click', function (e) {
943                onClick(value);
944
945                // 隐藏
946                _this.hideTimeoutId = setTimeout(function () {
947                    _this.hide();
948                }, 0);
949            });
950        }
951    });
952
953    // 绑定隐藏事件
954    $container.on('mouseleave', function (e) {
955        _this.hideTimeoutId = setTimeout(function () {
956            _this.hide();
957        }, 0);
958    });
959
960    // 记录属性
961    this.$container = $container;
962
963    // 基本属性
964    this._rendered = false;
965    this._show = false;
966}
967
968// 原型
969DropList.prototype = {
970    constructor: DropList,
971
972    // 显示(插入DOM)
973    show: function show() {
974        if (this.hideTimeoutId) {
975            // 清除之前的定时隐藏
976            clearTimeout(this.hideTimeoutId);
977        }
978
979        var menu = this.menu;
980        var $menuELem = menu.$elem;
981        var $container = this.$container;
982        if (this._show) {
983            return;
984        }
985        if (this._rendered) {
986            // 显示
987            $container.show();
988        } else {
989            // 加入 DOM 之前先定位位置
990            var menuHeight = $menuELem.getSizeData().height || 0;
991            var width = this.opt.width || 100; // 默认为 100
992            $container.css('margin-top', menuHeight + 'px').css('width', width + 'px');
993
994            // 加入到 DOM
995            $menuELem.append($container);
996            this._rendered = true;
997        }
998
999        // 修改属性
1000        this._show = true;
1001    },
1002
1003    // 隐藏(移除DOM)
1004    hide: function hide() {
1005        if (this.showTimeoutId) {
1006            // 清除之前的定时显示
1007            clearTimeout(this.showTimeoutId);
1008        }
1009
1010        var $container = this.$container;
1011        if (!this._show) {
1012            return;
1013        }
1014        // 隐藏并需改属性
1015        $container.hide();
1016        this._show = false;
1017    }
1018};
1019
1020/*
1021    menu - header
1022*/
1023// 构造函数
1024function Head(editor) {
1025    var _this = this;
1026
1027    this.editor = editor;
1028    this.$elem = $('<div class="w-e-menu"><i class="w-e-icon-header"></i></div>');
1029    this.type = 'droplist';
1030
1031    // 当前是否 active 状态
1032    this._active = false;
1033
1034    // 初始化 droplist
1035    this.droplist = new DropList(this, {
1036        width: 100,
1037        $title: $('<p>设置标题</p>'),
1038        type: 'list', // droplist 以列表形式展示
1039        list: [{ $elem: $('<p>正文</p>'), value: '<p>' }, { $elem: $('<h1>H1</h1>'), value: 'h1' }, { $elem: $('<h2>H2</h2>'), value: 'h2' }, { $elem: $('<h3>H3</h3>'), value: 'h3' }, { $elem: $('<h4>H4</h4>'), value: 'h4' }, { $elem: $('<h5>H5</h5>'), value: 'h5' }],
1040        onClick: function onClick(value) {
1041            // 注意 this 是指向当前的 Head 对象
1042            _this._command(value);
1043        }
1044    });
1045}
1046
1047// 原型
1048Head.prototype = {
1049    constructor: Head,
1050
1051    // 执行命令
1052    _command: function _command(value) {
1053        var editor = this.editor;
1054
1055        var $selectionElem = editor.selection.getSelectionContainerElem();
1056        if (editor.$textElem.equal($selectionElem)) {
1057            // 不能选中多行来设置标题,否则会出现问题
1058            // 例如选中的是 <p>xxx</p><p>yyy</p> 来设置标题,设置之后会成为 <h1>xxx<br>yyy</h1> 不符合预期
1059            return;
1060        }
1061        
1062        editor.cmd.do('formatBlock', value);
1063    },
1064
1065    // 试图改变 active 状态
1066    tryChangeActive: function tryChangeActive(e) {
1067        var editor = this.editor;
1068        var $elem = this.$elem;
1069        var reg = /^h/i;
1070        var cmdValue = editor.cmd.queryCommandValue('formatBlock');
1071        if (reg.test(cmdValue)) {
1072            this._active = true;
1073            $elem.addClass('w-e-active');
1074        } else {
1075            this._active = false;
1076            $elem.removeClass('w-e-active');
1077        }
1078    }
1079};
1080
1081/*
1082    menu - fontSize
1083*/
1084
1085// 构造函数
1086function FontSize(editor) {
1087    var _this = this;
1088
1089    this.editor = editor;
1090    this.$elem = $('<div class="w-e-menu"><i class="w-e-icon-text-heigh"></i></div>');
1091    this.type = 'droplist';
1092
1093    // 当前是否 active 状态
1094    this._active = false;
1095
1096    // 初始化 droplist
1097    this.droplist = new DropList(this, {
1098        width: 160,
1099        $title: $('<p>字号</p>'),
1100        type: 'list', // droplist 以列表形式展示
1101        list: [{ $elem: $('<span style="font-size: x-small;">x-small</span>'), value: '1' }, { $elem: $('<span style="font-size: small;">small</span>'), value: '2' }, { $elem: $('<span>正文</span>'), value: '3' }, { $elem: $('<span style="font-size: large;">large</span>'), value: '4' }, { $elem: $('<span style="font-size: x-large;">x-large</span>'), value: '5' }, { $elem: $('<span style="font-size: xx-large;">xx-large</span>'), value: '6' }],
1102        onClick: function onClick(value) {
1103            // 注意 this 是指向当前的 FontSize 对象
1104            _this._command(value);
1105        }
1106    });
1107}
1108
1109// 原型
1110FontSize.prototype = {
1111    constructor: FontSize,
1112
1113    // 执行命令
1114    _command: function _command(value) {
1115        var editor = this.editor;
1116        editor.cmd.do('fontSize', value);
1117    }
1118};
1119
1120/*
1121    menu - fontName
1122*/
1123
1124// 构造函数
1125function FontName(editor) {
1126    var _this = this;
1127
1128    this.editor = editor;
1129    this.$elem = $('<div class="w-e-menu"><i class="w-e-icon-font"></i></div>');
1130    this.type = 'droplist';
1131
1132    // 当前是否 active 状态
1133    this._active = false;
1134
1135    // 获取配置的字体
1136    var config = editor.config;
1137    var fontNames = config.fontNames || [];
1138
1139    // 初始化 droplist
1140    this.droplist = new DropList(this, {
1141        width: 100,
1142        $title: $('<p>字体</p>'),
1143        type: 'list', // droplist 以列表形式展示
1144        list: fontNames.map(function (fontName) {
1145            return { $elem: $('<span style="font-family: ' + fontName + ';">' + fontName + '</span>'), value: fontName };
1146        }),
1147        onClick: function onClick(value) {
1148            // 注意 this 是指向当前的 FontName 对象
1149            _this._command(value);
1150        }
1151    });
1152}
1153
1154// 原型
1155FontName.prototype = {
1156    constructor: FontName,
1157
1158    _command: function _command(value) {
1159        var editor = this.editor;
1160        editor.cmd.do('fontName', value);
1161    }
1162};
1163
1164/*
1165    panel
1166*/
1167
1168var emptyFn = function emptyFn() {};
1169
1170// 记录已经显示 panel 的菜单
1171var _isCreatedPanelMenus = [];
1172
1173// 构造函数
1174function Panel(menu, opt) {
1175    this.menu = menu;
1176    this.opt = opt;
1177}
1178
1179// 原型
1180Panel.prototype = {
1181    constructor: Panel,
1182
1183    // 显示(插入DOM)
1184    show: function show(index) {
1185        var _this = this;
1186
1187        var menu = this.menu;
1188        if (_isCreatedPanelMenus.indexOf(menu) >= 0) {
1189            // 该菜单已经创建了 panel 不能再创建
1190            return;
1191        }
1192
1193        var editor = menu.editor;
1194        var $body = $('body');
1195        var $textContainerElem = editor.$textContainerElem;
1196        var opt = this.opt;
1197
1198        // panel 的容器
1199        var $container = $('<div class="w-e-panel-container"></div>');
1200        var width = opt.width || 300; // 默认 300px
1201        $container.css('width', width + 'px').css('margin-left', (0 - width) / 2 + 'px');
1202
1203        // 添加关闭按钮
1204        var $closeBtn = $('<i class="w-e-icon-close w-e-panel-close"></i>');
1205        $container.append($closeBtn);
1206        $closeBtn.on('click', function () {
1207            _this.hide();
1208        });
1209
1210        // 准备 tabs 容器
1211        var $tabTitleContainer = $('<ul class="w-e-panel-tab-title"></ul>');
1212        var $tabContentContainer = $('<div class="w-e-panel-tab-content"></div>');
1213        $container.append($tabTitleContainer).append($tabContentContainer);
1214
1215        // 设置高度
1216        var height = opt.height;
1217        if (height) {
1218            $tabContentContainer.css('height', height + 'px').css('overflow-y', 'auto');
1219        }
1220
1221        // tabs
1222        var tabs = opt.tabs || [];
1223        var tabTitleArr = [];
1224        var tabContentArr = [];
1225        if(!index){
1226            index = 0;
1227        }
1228        tabs.forEach(function (tab, tabIndex) {
1229            if (!tab) {
1230                return;
1231            }
1232            var title = tab.title || '';
1233            var tpl = tab.tpl || '';
1234
1235            // 替换多语言
1236            title = replaceLang(editor, title);
1237            tpl = replaceLang(editor, tpl);
1238
1239            // 添加到 DOM
1240            var $title = $('<li class="w-e-item" data-index="'+tabIndex+'">' + title + '</li>');
1241            $tabTitleContainer.append($title);
1242            var $content = $(tpl);
1243            $tabContentContainer.append($content);
1244
1245            // 记录到内存
1246            $title._index = tabIndex;
1247            tabTitleArr.push($title);
1248            tabContentArr.push($content);
1249
1250            // 设置 active 项
1251            if (tabIndex === index) {
1252                $title._active = true;
1253                $title.addClass('w-e-active');
1254            } else {
1255                $content.hide();
1256            }
1257
1258            // 绑定 tab 的事件
1259            $title.on('click', function (e) {
1260                if ($title._active) {
1261                    return;
1262                }
1263                // 隐藏所有的 tab
1264                tabTitleArr.forEach(function ($title) {
1265                    $title._active = false;
1266                    $title.removeClass('w-e-active');
1267                });
1268                tabContentArr.forEach(function ($content) {
1269                    $content.hide();
1270                });
1271
1272                // 显示当前的 tab
1273                $title._active = true;
1274                $title.addClass('w-e-active');
1275                $content.show();
1276            });
1277        });
1278
1279        // 绑定关闭事件
1280        $container.on('click', function (e) {
1281            // 点击时阻止冒泡
1282            e.stopPropagation();
1283        });
1284        $body.on('click', function (e) {
1285            _this.hide();
1286        });
1287
1288        // 添加到 DOM
1289        $textContainerElem.append($container);
1290
1291        // 绑定 opt 的事件,只有添加到 DOM 之后才能绑定成功
1292        tabs.forEach(function (tab, index) {
1293            if (!tab) {
1294                return;
1295            }
1296            var events = tab.events || [];
1297            events.forEach(function (event) {
1298                var selector = event.selector;
1299                var type = event.type;
1300                var fn = event.fn || emptyFn;
1301                var initFn = event.initFn || emptyFn;
1302                var $content = tabContentArr[index];
1303                initFn();
1304                $content.find(selector).on(type, function (e) {
1305                    e.stopPropagation();
1306                    var needToHide = fn(e);
1307                    // 执行完事件之后,是否要关闭 panel
1308                    if (needToHide) {
1309                        _this.hide();
1310                    }
1311                });
1312            });
1313        });
1314
1315        // focus 第一个 elem
1316        var $inputs = $container.find('input[type=text],textarea');
1317        if ($inputs.length) {
1318            $inputs.get(0).focus();
1319        }
1320
1321        // 添加到属性
1322        this.$container = $container;
1323
1324        // 隐藏其他 panel
1325        this._hideOtherPanels();
1326        // 记录该 menu 已经创建了 panel
1327        _isCreatedPanelMenus.push(menu);
1328    },
1329
1330    // 隐藏(移除DOM)
1331    hide: function hide() {
1332        var menu = this.menu;
1333        var $container = this.$container;
1334        if ($container) {
1335            $container.remove();
1336        }
1337
1338        // 将该 menu 记录中移除
1339        _isCreatedPanelMenus = _isCreatedPanelMenus.filter(function (item) {
1340            if (item === menu) {
1341                return false;
1342            } else {
1343                return true;
1344            }
1345        });
1346    },
1347
1348    // 一个 panel 展示时,隐藏其他 panel
1349    _hideOtherPanels: function _hideOtherPanels() {
1350        if (!_isCreatedPanelMenus.length) {
1351            return;
1352        }
1353        _isCreatedPanelMenus.forEach(function (menu) {
1354            var panel = menu.panel || {};
1355            if (panel.hide) {
1356                panel.hide();
1357            }
1358        });
1359    }
1360};
1361
1362/*
1363    menu - link
1364*/
1365// 构造函数
1366function Link(editor) {
1367    this.editor = editor;
1368    this.$elem = $('<div class="w-e-menu"><i class="w-e-icon-link"></i></div>');
1369    this.type = 'panel';
1370
1371    // 当前是否 active 状态
1372    this._active = false;
1373}
1374
1375// 原型
1376Link.prototype = {
1377    constructor: Link,
1378
1379    // 点击事件
1380    onClick: function onClick(e) {
1381        var editor = this.editor;
1382        var $linkelem = void 0;
1383
1384        if (this._active) {
1385            // 当前选区在链接里面
1386            $linkelem = editor.selection.getSelectionContainerElem();
1387            if (!$linkelem) {
1388                return;
1389            }
1390            // 将该元素都包含在选取之内,以便后面整体替换
1391            editor.selection.createRangeByElem($linkelem);
1392            editor.selection.restoreSelection();
1393            // 显示 panel
1394            this._createPanel($linkelem.text(), $linkelem.attr('href'));
1395        } else {
1396            // 当前选区不在链接里面
1397            if (editor.selection.isSelectionEmpty()) {
1398                // 选区是空的,未选中内容
1399                this._createPanel('', '');
1400            } else {
1401                // 选中内容了
1402                this._createPanel(editor.selection.getSelectionText(), '');
1403            }
1404        }
1405    },
1406
1407    // 创建 panel
1408    _createPanel: function _createPanel(text, link) {
1409        var _this = this;
1410
1411        // panel 中需要用到的id
1412        var inputLinkId = getRandom('input-link');
1413        var inputTextId = getRandom('input-text');
1414        var btnOkId = getRandom('btn-ok');
1415        var btnDelId = getRandom('btn-del');
1416
1417        // 是否显示“删除链接”
1418        var delBtnDisplay = this._active ? 'inline-block' : 'none';
1419
1420        // 初始化并显示 panel
1421        var panel = new Panel(this, {
1422            width: 300,
1423            // panel 中可包含多个 tab
1424            tabs: [{
1425                // tab 的标题
1426                title: '链接',
1427                // 模板
1428                tpl: '<div>\n                            <input id="' + inputTextId + '" type="text" class="block" value="' + text 
1428+ '" placeholder="\u94FE\u63A5\u6587\u5B57"/></td>\n                            <input id="' + inputLinkId + '" type="text" class="block" value="' + (link!=''?link:'http://') + '" placeholder="http://..."/></td>\n                            <div class="w-e-button-container">\n                                <button id="' + btnOkId + '" class="right">\u63D2\u5165</button>\n                                <button id="' + btnDelId + '" class="gray right" style="display:' + delBtnDisplay + '">\u5220\u9664\u94FE\u63A5</button>\n                            </div>\n                        </div>',
1429                // 事件绑定
1430                events: [
1431                // 插入链接
1432                {
1433                    selector: '#' + btnOkId,
1434                    type: 'click',
1435                    fn: function fn() {
1436                        // 执行插入链接
1437                        var $link = $('#' + inputLinkId);
1438                        var $text = $('#' + inputTextId);
1439                        var link = $link.val();
1440                        var text = $text.val();
1441                        _this._insertLink(text, link);
1442
1443                        // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
1444                        return true;
1445                    }
1446                },
1447                // 删除链接
1448                {
1449                    selector: '#' + btnDelId,
1450                    type: 'click',
1451                    fn: function fn() {
1452                        // 执行删除链接
1453                        _this._delLink();
1454
1455                        // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
1456                        return true;
1457                    }
1458                }]
1459            }, // tab end
1460            ] // tabs end
1461        });
1462
1463        // 显示 panel
1464        panel.show();
1465
1466        // 记录属性
1467        this.panel = panel;
1468    },
1469
1470    // 删除当前链接
1471    _delLink: function _delLink() {
1472        if (!this._active) {
1473            return;
1474        }
1475        var editor = this.editor;
1476        var $selectionELem = editor.selection.getSelectionContainerElem();
1477        if (!$selectionELem) {
1478            return;
1479        }
1480        var selectionText = editor.selection.getSelectionText();
1481        editor.cmd.do('insertHTML', '<span>' + selectionText + '</span>');
1482    },
1483
1484    // 插入链接
1485    _insertLink: function _insertLink(text, link) {
1486        var editor = this.editor;
1487        var config = editor.config;
1488        var linkCheck = config.linkCheck;
1489        var checkResult = true; // 默认为 true
1490        if (linkCheck && typeof linkCheck === 'function') {
1491            checkResult = linkCheck(text, link);
1492        }
1493        if (checkResult === true) {
1494            editor.cmd.do('insertHTML', '<a href="' + link + '" target="_blank" class="outerLink">' + text + '</a>');
1495        } else {
1496            alert(checkResult);
1497        }
1498    },
1499
1500    // 试图改变 active 状态
1501    tryChangeActive: function tryChangeActive(e) {
1502        var editor = this.editor;
1503        var $elem = this.$elem;
1504        var $selectionELem = editor.selection.getSelectionContainerElem();
1505        if (!$selectionELem) {
1506            return;
1507        }
1508        if ($selectionELem.getNodeName() === 'A') {
1509            this._active = true;
1510            $elem.addClass('w-e-active');
1511        } else {
1512            this._active = false;
1513            $elem.removeClass('w-e-active');
1514        }
1515    }
1516};
1517
1518
1519/*
1520    menu - allLink 所有链接
1521*/
1522// 构造函数
1523function AllLink(editor) {
1524    this.editor = editor;
1525    this.$elem = $('<div class="w-e-menu"><i class="w-e-icon-link"></i></div>');
1526    this.type = 'panel';
1527
1528    // 当前是否 active 状态
1529    this._active = false;
1530}
1531
1532// 原型
1533AllLink.prototype = {
1534    constructor: AllLink,
1535
1536    // 点击事件
1537    onClick: function onClick(e) {
1538        var editor = this.editor;
1539        var $linkelem = void 0;
1540
1541        if (this._active) {
1542            // 当前选区在链接里面
1543            $linkelem = editor.selection.getSelectionContainerElem();
1544            if (!$linkelem) {
1545                return;
1546            }
1547            // 将该元素都包含在选取之内,以便后面整体替换
1548            editor.selection.createRangeByElem($linkelem);
1549            editor.selection.restoreSelection();
1550
1551            // 显示 panel
1552            this._createPanel($linkelem.text(), $linkelem.attr('href'),$linkelem.selector.className);
1553        } else {
1554            // 当前选区不在链接里面
1555            if (editor.selection.isSelectionEmpty()) {
1556                // 选区是空的,未选中内容
1557                this._createPanel('', '');
1558            } else {
1559                // 选中内容了
1560                this._createPanel(editor.selection.getSelectionText(), '');
1561            }
1562        }
1563    },
1564
1565    // 创建 panel
1566    _createPanel: function _createPanel(text, link, linkType) {
1567        var _this = this;
1568
1569        // panel 中需要用到的id
1570        var btnOkId = getRandom('btn-ok');
1571        var btnDelId = getRandom('btn-del');
1572
1573        // 是否显示“删除链接”
1574        var delBtnDisplay = this._active ? 'inline-block' : 'none';
1575
1576        // 初始化并显示 panel
1577        var panel = new Panel(this, {
1578            width: 330,
1579            // panel 中可包含多个 tab
1580            tabs: [{
1581                // tab 的标题
1582                title: '外链',
1583                // 模板
1584                tpl: '<div>\n                            <input id="out_link_text" type="text" class="block" value="' + text 
1584+ '" placeholder="\u94FE\u63A5\u6587\u5B57"/></td>\n                            <input id="out_link_url" type="text" class="block" value="' + (link!=''?link:'http://') + '" placeholder="http://..."/></td>\n                            <div class="w-e-button-container">\n                                <button id="' + btnOkId + '" class="right">\u63D2\u5165</button>\n                                <button id="' + btnDelId + '" class="gray right" style="display:' + delBtnDisplay + '">\u5220\u9664\u94FE\u63A5</button>\n                            </div>\n                        </div>',
1585                // 事件绑定
1586                events: [
1587                // 插入链接
1588                {
1589                    selector: '#' + btnOkId,
1590                    type: 'click',
1591                    fn: function fn() {
1592                        // 执行插入链接
1593                        var $link = $('#out_link_url');
1594                        var $text = $('#out_link_text');
1595                        var link = $link.val();
1596                        var text = $text.val();
1597                        _this._insertLink(text, link, 'outer_link');
1598
1599                        // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
1600                        return true;
1601                    }
1602                },
1603                // 删除链接
1604                {
1605                    selector: '#' + btnDelId,
1606                    type: 'click',
1607                    fn: function fn() {
1608                        // 执行删除链接
1609                        _this._delLink();
1610
1611                        // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
1612                        return true;
1613                    }
1614                }]
1615            }, // tab end
1616            {
1617                title: '文章内链',
1618                tpl: '<div>\n                            <input id="article_link_text" type="text" class="block" value="' + text + '" placeholder="\u94FE\u63A5\u6587\u5B57"/></td>\n                            <input id="article_link_url" type="text"  style="width:70%;display:inline-block;" class="block" value="' + link + '" placeholder="请输入内链文章ID"/></td><td><button id="get_title_byID" style="margin-left:20px;border:none;background-color:transparent;cursor:pointer;color:#3b68b8;">获取标题</button></td>\n                            <div class="w-e-button-container">\n                                <button id="' + btnOkId + '" class="right">\u63D2\u5165</button>\n                                <button id="' + btnDelId + '" class="gray right" style="display:' + delBtnDisplay + '">\u5220\u9664\u94FE\u63A5</button>\n                            </div>\n                        </div>',
1619                events: [
1620                {
1621                    selector: '#' + btnOkId,
1622                    type: 'click',
1623                    initFn: function initFn(){
1624                        // 面板生成后初始化
1625                        document.getElementById('get_title_byID').addEventListener('click',function(){
1626                            var articleId = document.getElementById('article_link_url').value;
1627                            var token = web.user.getUser('token');
1628                            jQuery.ajax({
1629                                type:'GET',
1630                                url:web.hostMap.host_pre+'article/'+articleId,
1631                                data:{token:token,articleId},
1632                                success:function(response){
1633                                    if(response.success){
1634                                        document.getElementById('article_link_text').value = response.article.title;
1635                                    }
1636                                    else{
1637                                        web.utility.showBasicPrompt({
1638                                            style:'alert-error',
1639                                            content:response.msg
1640                                        });
1641                                    }
1642                                }
1643                            });
1644                        },false);
1645                    },
1646                    fn: function fn() {
1647                        // 执行插入链接
1648                        var $link = $('#article_link_url');
1649                        var $text = $('#article_link_text');
1650                        var link = $link.val();
1651                        var text = $text.val();
1652                        _this._insertLink(text, link, 'article_link');
1653                        return true;
1654                    }
1655                },
1656                {
1657                    selector: '#' + btnDelId,
1658                    type: 'click',
1659                    fn: function fn() {
1660                        _this._delLink();
1661                        return true;
1662                    }
1663                }]
1664            }, // tab end
1665            {
1666                title: '帖子内链',
1667                tpl: '<div>\n                            <input id="post_link_text" type="text" class="block" value="' + text 
1667+ '" placeholder="\u94FE\u63A5\u6587\u5B57"/></td>\n                            <input id="post_link_url" type="text" class="block" value="' + link + '" placeholder="插入帖子ID"/></td>\n                            <div class="w-e-button-container">\n                                <button id="' + btnOkId + '" class="right">\u63D2\u5165</button>\n                                <button id="' + btnDelId + '" class="gray right" style="display:' + delBtnDisplay + '">\u5220\u9664\u94FE\u63A5</button>\n                            </div>\n                        </div>',
1668                events: [
1669                {
1670                    selector: '#' + btnOkId,
1671                    type: 'click',
1672                    fn: function fn() {
1673                        // 执行插入链接
1674                        var $link = $('#post_link_url');
1675                        var $text = $('#post_link_text');
1676                        var link = $link.val();
1677                        var text = $text.val();
1678                        _this._insertLink(text, link, 'post_link');
1679                        return true;
1680                    }
1681                },
1682                {
1683                    selector: '#' + btnDelId,
1684                    type: 'click',
1685                    fn: function fn() {
1686                        _this._delLink();
1687                        return true;
1688                    }
1689                }]
1690            }, // tab end
1691            {
1692                title: '游戏内链',
1693                tpl: '<div>\n                            <input id="game_link_text" type="text" class="block" value="' + text + '" placeholder="\u94FE\u63A5\u6587\u5B57"/></td>\n                            <input id="game_link_url" type="text" class="block" value="' + link + '" placeholder="请输入游戏ID"/></td>\n                            <div class="w-e-button-container">\n                                <button id="' + btnOkId + '" class="right">\u63D2\u5165</button>\n                                <button id="' + btnDelId + '" class="gray right" style="display:' + delBtnDisplay + '">\u5220\u9664\u94FE\u63A5</button>\n                            </div>\n                        </div>',
1694                events: [
1695                {
1696                    selector: '#' + btnOkId,
1697                    type: 'click',
1698                    fn: function fn() {
1699                        // 执行插入链接
1700                        var $link = $('#game_link_url');
1701                        var $text = $('#game_link_text');
1702                        var link = $link.val();
1703                        var text = $text.val();
1704                        _this._insertLink(text, link, 'game_link');
1705                        return true;
1706                    }
1707                },
1708                {
1709                    selector: '#' + btnDelId,
1710                    type: 'click',
1711                    fn: function fn() {
1712                        _this._delLink();
1713                        return true;
1714                    }
1715                }]
1716            }, // tab end
1717            ] // tabs end
1718        });
1719
1720        // 选中对应tab
1721        var tab_index = 0;
1722        if(linkType){
1723            switch(linkType){
1724                case 'article_link':
1725                    tab_index = 1;
1726                    break;
1727                case 'post_link':
1728                    tab_index = 2;
1729                    break;
1730                case 'game_link':
1731                    tab_index = 3;
1732                    break;
1733            }
1734        }
1735
1736        // 显示 panel
1737        panel.show(tab_index);
1738
1739        // 记录属性
1740        this.panel = panel;
1741    },
1742
1743    // 删除当前链接
1744    _delLink: function _delLink() {
1745        if (!this._active) {
1746            return;
1747        }
1748        var editor = this.editor;
1749        var $selectionELem = editor.selection.getSelectionContainerElem();
1750        if (!$selectionELem) {
1751            return;
1752        }
1753        var selectionText = editor.selection.getSelectionText();
1754        editor.cmd.do('insertHTML', '<span>
1754' + selectionText + '</span>');
1755    },
1756
1757    // 插入链接
1758    _insertLink: function _insertLink(text, link, type) {
1759        var editor = this.editor;
1760        var config = editor.config;
1761        var linkCheck = config.linkCheck;
1762        var checkResult = true; // 默认为 true
1763        if (linkCheck && typeof linkCheck === 'function') {
1764            checkResult = linkCheck(text, link);
1765        }
1766        if (checkResult === true) {
1767            switch(type){
1768                case 'outer_link':
1769                    editor.cmd.do('insertHTML', '<a href="' + link + '" target="_blank" class="outerLink">' + text + '</a>');
1770                    break;
1771                case 'article_link':
1772                    var applink = 'wanxiu://innerlink?type=article_link&id='+link;
1773                    var weblink = '/web/article/search/'+link;
1774                    editor.cmd.do('insertHTML', '<a href="'+ applink +'" target="_blank" article_link="'+ weblink +'" class="article_link">' + text + '</a>');
1775                    break;
1776                case 'post_link':
1777                    var applink = 'wanxiu://innerlink?type=bbspost_url&id='+link;
1778                    var weblink = '/web/bbspost/detail/'+link; 
1779                    editor.cmd.do('insertHTML', '<a href="'+ applink +'" target="_blank" post_link="'+ weblink +'" class="post_link">' + text + '</a>');
1780                    break;
1781                case 'game_link':
1782                    var applink = 'wanxiu://innerlink?type=game&id='+link;
1783                    var weblink = '/web/gamecenter/gamedetail?gameid='+link; 
1784                    editor.cmd.do('insertHTML', '<a href="'+ applink +'" target="_blank" game_link="'+ weblink +'" class="game_link">' + text + '</a>');
1785                    break;
1786            }
1787        } else {
1788            alert(checkResult);
1789        }
1790    },
1791
1792    // 试图改变 active 状态
1793    tryChangeActive: function tryChangeActive(e) {
1794        var editor = this.editor;
1795        var $elem = this.$elem;
1796        var $selectionELem = editor.selection.getSelectionContainerElem();
1797        if (!$selectionELem) {
1798            return;
1799        }
1800        if ($selectionELem.getNodeName() === 'A') {
1801            this._active = true;
1802            $elem.addClass('w-e-active');
1803        } else {
1804            this._active = false;
1805            $elem.removeClass('w-e-active');
1806        }
1807    }
1808};
1809
1810
1811/*
1812    italic-menu
1813*/
1814// 构造函数
1815function Italic(editor) {
1816    this.editor = editor;
1817    this.$elem = $('<div class="w-e-menu">\n            <i class="w-e-icon-italic"></i>\n        </div>');
1818    this.type = 'click';
1819
1820    // 当前是否 active 状态
1821    this._active = false;
1822}
1823
1824// 原型
1825Italic.prototype = {
1826    constructor: Italic,
1827
1828    // 点击事件
1829    onClick: function onClick(e) {
1830        // 点击菜单将触发这里
1831
1832        var editor = this.editor;
1833        var isSeleEmpty = editor.selection.isSelectionEmpty();
1834
1835        if (isSeleEmpty) {
1836            // 选区是空的,插入并选中一个“空白”
1837            editor.selection.createEmptyRange();
1838        }
1839
1840        // 执行 italic 命令
1841        editor.cmd.do('italic');
1842
1843        if (isSeleEmpty) {
1844            // 需要将选取折叠起来
1845            editor.selection.collapseRange();
1846            editor.selection.restoreSelection();
1847        }
1848    },
1849
1850    // 试图改变 active 状态
1851    tryChangeActive: function tryChangeActive(e) {
1852        var editor = this.editor;
1853        var $elem = this.$elem;
1854        if (editor.cmd.queryCommandState('italic')) {
1855            this._active = true;
1856            $elem.addClass('w-e-active');
1857        } else {
1858            this._active = false;
1859            $elem.removeClass('w-e-active');
1860        }
1861    }
1862};
1863
1864/*
1865    strikeThrough-menu
1866*/
1867// 构造函数
1868function StrikeThrough(editor) {
1869    this.editor = editor;
1870    this.$elem = $('<div class="w-e-menu">\n            <i class="w-e-icon-strikethrough"></i>\n        </div>');
1871    this.type = 'click';
1872
1873    // 当前是否 active 状态
1874    this._active = false;
1875}
1876
1877// 原型
1878StrikeThrough.prototype = {
1879    constructor: StrikeThrough,
1880
1881    // 点击事件
1882    onClick: function onClick(e) {
1883        // 点击菜单将触发这里
1884
1885        var editor = this.editor;
1886        var isSeleEmpty = editor.selection.isSelectionEmpty();
1887
1888        if (isSeleEmpty) {
1889            // 选区是空的,插入并选中一个“空白”
1890            editor.selection.createEmptyRange();
1891        }
1892
1893        // 执行 strikeThrough 命令
1894        editor.cmd.do('strikeThrough');
1895
1896        if (isSeleEmpty) {
1897            // 需要将选取折叠起来
1898            editor.selection.collapseRange();
1899            editor.selection.restoreSelection();
1900        }
1901    },
1902
1903    // 试图改变 active 状态
1904    tryChangeActive: function tryChangeActive(e) {
1905        var editor = this.editor;
1906        var $elem = this.$elem;
1907        if (editor.cmd.queryCommandState('strikeThrough')) {
1908            this._active = true;
1909            $elem.addClass('w-e-active');
1910        } else {
1911            this._active = false;
1912            $elem.removeClass('w-e-active');
1913        }
1914    }
1915};
1916
1917/*
1918    underline-menu
1919*/
1920// 构造函数
1921function Underline(editor) {
1922    this.editor = editor;
1923    this.$elem = $('<div class="w-e-menu">\n            <i class="w-e-icon-underline"></i>\n        </div>');
1924    this.type = 'click';
1925
1926    // 当前是否 active 状态
1927    this._active = false;
1928}
1929
1930// 原型
1931Underline.prototype = {
1932    constructor: Underline,
1933
1934    // 点击事件
1935    onClick: function onClick(e) {
1936        // 点击菜单将触发这里
1937
1938        var editor = this.editor;
1939        var isSeleEmpty = editor.selection.isSelectionEmpty();
1940
1941        if (isSeleEmpty) {
1942            // 选区是空的,插入并选中一个“空白”
1943            editor.selection.createEmptyRange();
1944        }
1945
1946        // 执行 underline 命令
1947        editor.cmd.do('underline');
1948
1949        if (isSeleEmpty) {
1950            // 需要将选取折叠起来
1951            editor.selection.collapseRange();
1952            editor.selection.restoreSelection();
1953        }
1954    },
1955
1956    // 试图改变 active 状态
1957    tryChangeActive: function tryChangeActive(e) {
1958        var editor = this.editor;
1959        var $elem = this.$elem;
1960        if (editor.cmd.queryCommandState('underline')) {
1961            this._active = true;
1962            $elem.addClass('w-e-active');
1963        } else {
1964            this._active = false;
1965            $elem.removeClass('w-e-active');
1966        }
1967    }
1968};
1969
1970/*
1971    undo-menu
1972*/
1973// 构造函数
1974function Undo(editor) {
1975    this.editor = editor;
1976    this.$elem = $('<div class="w-e-menu">\n            <i class="w-e-icon-undo"></i>\n        </div>');
1977    this.type = 'click';
1978    this.redoList = [];
1979    this.undoList = [];
1980
1981    // 当前是否 active 状态
1982    this._active = false;
1983}
1984
1985// 原型
1986Undo.prototype = {
1987    constructor: Undo,
1988
1989    _handle: function _handle(editor, data){
1990        var val = data.val;
1991
1992        if(val == null) {
1993            return;
1994        }
1995
1996        // 保存数据
1997        editor.txt.html(val);
1998        // editor.updateValue();
1999    },
2000
2001    // 点击事件
2002    onClick: function onClick(e) {
2003        // 点击菜单将触发这里
2004
2005        var editor = this.editor;
2006
2007        // 执行 undo 命令
2008        // editor.cmd.do('undo');
2009
2010        if(!editor.undoList.length){
2011            return;
2012        }
2013        // 取出 undolist 第一个值,加入 redolist
2014        var data = editor.undoList.shift();
2015        editor.redoList.unshift({range:data.range,val:editor.txt.html()});
2016
2017        // 并修改编辑器的内容
2018        Undo.prototype._handle(this.editor, data);
2019    }
2020};
2021
2022/*
2023    redo-menu
2024*/
2025// 构造函数
2026function Redo(editor) {
2027    this.editor = editor;
2028    this.$elem = $('<div class="w-e-menu">\n            <i class="w-e-icon-redo"></i>\n        </div>');
2029    this.type = 'click';
2030
2031    // 当前是否 active 状态
2032    this._active = false;
2033}
2034
2035// 原型
2036Redo.prototype = {
2037    constructor: Redo,
2038
2039    _handle: function _handle(editor, data){
2040        var val = data.val;
2041
2042        if(val == null) {
2043            return;
2044        }
2045
2046        // 保存数据
2047        editor.txt.html(val);
2048    },
2049
2050    // 点击事件
2051    onClick: function onClick(e) {
2052        // 点击菜单将触发这里
2053
2054        var editor = this.editor;
2055
2056        // 执行 redo 命令
2057        // editor.cmd.do('redo');
2058
2059        if (!editor.redoList.length) {
2060            return;
2061        }
2062
2063        // 取出 redolist 第一个值,加入 undolist
2064        var data = editor.redoList.shift();
2065        editor.undoList.unshift({range:data.range,val:editor.txt.html()});
2066
2067        // 并修改编辑器的内容
2068        Redo.prototype._handle(this.editor, data);
2069    }
2070};
2071
2072/*
2073    menu - list
2074*/
2075// 构造函数
2076function List(editor) {
2077    var _this = this;
2078
2079    this.editor = editor;
2080    this.$elem = $('<div class="w-e-menu"><i class="w-e-icon-list2"></i></div>');
2081    this.type = 'droplist';
2082
2083    // 当前是否 active 状态
2084    this._active = false;
2085
2086    // 初始化 droplist
2087    this.droplist = new DropList(this, {
2088        width: 120,
2089        $title: $('<p>设置列表</p>'),
2090        type: 'list', // droplist 以列表形式展示
2091        list: [{ $elem: $('<span><i class="w-e-icon-list-numbered"></i>
2091 有序列表</span>'), value: 'insertOrderedList' }, { $elem: $('<span><i class="w-e-icon-list2"></i> 无序列表</span>'), value: 'insertUnorderedList' }],
2092        onClick: function onClick(value) {
2093            // 注意 this 是指向当前的 List 对象
2094            _this._command(value);
2095        }
2096    });
2097}
2098
2099// 原型
2100List.prototype = {
2101    constructor: List,
2102
2103    // 执行命令
2104    _command: function _command(value) {
2105        var editor = this.editor;
2106        var $textElem = editor.$textElem;
2107        editor.selection.restoreSelection();
2108        if (editor.cmd.queryCommandState(value)) {
2109            return;
2110        }
2111        editor.cmd.do(value);
2112
2113        // 验证列表是否被包裹在 <p> 之内
2114        var $selectionElem = editor.selection.getSelectionContainerElem();
2115        if ($selectionElem.getNodeName() === 'LI') {
2116            $selectionElem = $selectionElem.parent();
2117        }
2118        if (/^ol|ul$/i.test($selectionElem.getNodeName()) === false) {
2119            return;
2120        }
2121        if ($selectionElem.equal($textElem)) {
2122            // 证明是顶级标签,没有被 <p> 包裹
2123            return;
2124        }
2125        var $parent = $selectionElem.parent();
2126        if ($parent.equal($textElem)) {
2127            // $parent 是顶级标签,不能删除
2128            return;
2129        }
2130
2131        $selectionElem.insertAfter($parent);
2132        $parent.remove();
2133    },
2134
2135    // 试图改变 active 状态
2136    tryChangeActive: function tryChangeActive(e) {
2137        var editor = this.editor;
2138        var $elem = this.$elem;
2139        if (editor.cmd.queryCommandState('insertUnOrderedList') || editor.cmd.queryCommandState('insertOrderedList')) {
2140            this._active = true;
2141            $elem.addClass('w-e-active');
2142        } else {
2143            this._active = false;
2144            $elem.removeClass('w-e-active');
2145        }
2146    }
2147};
2148
2149/*
2150    menu - cardRecognition
2151*/
2152// 构造函数
2153function CardRecognition(editor) {
2154    var _this = this;
2155
2156    this.editor = editor;
2157    this.$elem = $('<div class="w-e-menu"><i class="w-e-icon-location"></i></div>');
2158    this.type = 'droplist';
2159
2160    // 当前是否 active 状态
2161    this._active = false;
2162
2163    // 初始化 droplist
2164    this.droplist = new DropList(this, {
2165        width: 120,
2166        $title: $('<p>单卡识别</p>'),
2167        type: 'list', // droplist 以列表形式展示
2168        list: [
2169            { $elem: $('<span data-type="magic">万智牌</span>'), value: 'magic' }, 
2170            { $elem: $('<span data-type="hearthstone">炉石传说</span>'), value: 'hearthstone' },
2171            { $elem: $('<span data-type="gwent">昆特牌</span>'), value: 'gwent' },
2172            { $elem: $('<span data-type="verse">影之诗</span>'), value: 'verse' },
2173            { $elem: $('<span data-type="hundred">百闻牌</span>'), value: 'hundred' },
2174            { $elem: $('<span data-type="yugioh">游戏王</span>'), value: 'yugioh' },
2175            { $elem: $('<span data-type="artifact">Artifact</span>'), value: 'artifact' },
2176            { $elem: $('<span data-type="lor">符文之地</span>'), value: 'lor' },
2177            { $elem: $('<span data-type="cancel" style="color:red;">取消单卡识别</span>'), value: 'cancel' },
2178        ],
2179        onClick: function onClick(value) {
2180            // 注意 this 是指向当前的 List 对象
2181            _this._command(value);
2182        }
2183    });
2184}
2185
2186// 原型
2187CardRecognition.prototype = {
2188    constructor: CardRecognition,
2189
2190    // 执行命令
2191    _command: function _command(value) {
2192        var editor = this.editor;
2193        var $textElem = editor.$textElem;
2194        editor.selection.restoreSelection();
2195        if (editor.cmd.queryCommandState(value)) {
2196            return;
2197        }
2198        var content_html = $textElem.html();
2199        jQuery.ajax({
2200            type:'POST',
2201            url:web.hostMap.host_pre+'contribute/card/scancard',
2202            data:{token:web.user.getUser('token'), content:content_html, scanning:value},
2203            success:function(response){
2204                if(response.success){
2205                    $textElem.html(response.content);
2206                }
2207                else{
2208                    web.utility.showBasicPrompt({
2209                        style:'alert-error',
2210                        content:response.msg
2211                    });
2212                }
2213            }
2214        });
2215    },
2216
2217    // 试图改变 active 状态
2218    tryChangeActive: function tryChangeActive(e) {
2219        var editor = this.editor;
2220        var $elem = this.$elem;
2221        // if (editor.cmd.queryCommandState('insertUnOrderedList') || editor.cmd.queryCommandState('insertOrderedList')) {
2222        //     this._active = true;
2223        //     $elem.addClass('w-e-active');
2224        // } else {
2225        //     this._active = false;
2226        //     $elem.removeClass('w-e-active');
2227        // }
2228    }
2229};
2230
2231
2232/*
2233    menu - cardRecognition
2234    拆分段落
2235*/
2236// 构造函数
2237function TextSection(editor){
2238    this.editor = editor;
2239    this.$elem = $('<div class="w-e-menu">\n            <i class="w-e-icon-page-break"></i>\n        </div>');
2240    this.type = 'click';
2241}
2242
2243TextSection.prototype = {
2244    constructor: TextSection,
2245    // 点击事件
2246    onClick: function onClick(e) {
2247        // 点击菜单将触发这里
2248        var editor = this.editor;
2249        var $selectElem = editor.selection.getSelectionContainerElem().selector;
2250        if($selectElem.localName=="p"){
2251            var content_html = '',nextSiblings = $selectElem;
2252            while(nextSiblings.nextSibling){
2253                content_html+=nextSiblings.nextSibling.outerHTML;
2254                nextSiblings.nextSibling.className = 'editor-wait-delete-node';
2255                nextSiblings=nextSiblings.nextSibling;
2256            }
2257            var $currentEditorBlockId = $selectElem.offsetParent.offsetParent.offsetParent.id;
2258            var $currentEditorBlock = document.getElementById($currentEditorBlockId);
2259            if(content_html!=''){
2260                $('.editor-wait-delete-node').remove();
2261                web.editor.blocks.addBlock(jQuery($currentEditorBlock),'text',{content:content_html});
2262            }
2263            else{
2264                web.utility.showBasicPrompt({
2265                    style:'alert-error',
2266                    content:'该段落后面无其他文字可以拆分段落',
2267                });
2268            }
2269        }
2270    }
2271}; 
2272
2273/*
2274    menu - justify
2275*/
2276// 构造函数
2277function Justify(editor) {
2278    var _this = this;
2279
2280    this.editor = editor;
2281    this.$elem = $('<div class="w-e-menu"><i class="w-e-icon-paragraph-left"></i></div>');
2282    this.type = 'droplist';
2283
2284    // 当前是否 active 状态
2285    this._active = false;
2286
2287    // 初始化 droplist
2288    this.droplist = new DropList(this, {
2289        width: 100,
2290        $title: $('<p>对齐方式</p>'),
2291        type: 'list', // droplist 以列表形式展示
2292        list: [{ $elem: $('<span><i class="w-e-icon-paragraph-left"></i> 靠左</span>'), value: 'justifyLeft' }, { $elem: $('<span><i class="w-e-icon-paragraph-center"></i> 居中</span>'), value: 'justifyCenter' }, { $elem: $('<span><i class="w-e-icon-paragraph-right"></i> 靠右</span>'), value: 'justifyRight' }],
2293        onClick: function onClick(value) {
2294            // 注意 this 是指向当前的 List 对象
2295            _this._command(value);
2296        }
2297    });
2298}
2299
2300// 原型
2301Justify.prototype = {
2302    constructor: Justify,
2303
2304    // 执行命令
2305    _command: function _command(value) {
2306        var editor = this.editor;
2307        editor.cmd.do(value);
2308    }
2309};
2310
2311/*
2312    menu - Forecolor
2313*/
2314// 构造函数
2315function ForeColor(editor) {
2316    var _this = this;
2317
2318    this.editor = editor;
2319    this.$elem = $('<div class="w-e-menu"><i class="w-e-icon-pencil2"></i></div>');
2320    this.type = 'droplist';
2321
2322    // 获取配置的颜色
2323    var config = editor.config;
2324    var colors = config.colors || [];
2325
2326    // 当前是否 active 状态
2327    this._active = false;
2328
2329    // 初始化 droplist
2330    this.droplist = new DropList(this, {
2331        width: 120,
2332        $title: $('<p>文字颜色</p>'),
2333        type: 'inline-block', // droplist 内容以 block 形式展示
2334        list: colors.map(function (color) {
2335            return { $elem: $('<i style="color:' + color + ';" class="w-e-icon-pencil2"></i>'), value: color };
2336        }),
2337        onClick: function onClick(value) {
2338            // 注意 this 是指向当前的 ForeColor 对象
2339            _this._command(value);
2340        }
2341    });
2342}
2343
2344// 原型
2345ForeColor.prototype = {
2346    constructor: ForeColor,
2347
2348    // 执行命令
2349    _command: function _command(value) {
2350        var editor = this.editor;
2351        editor.cmd.do('foreColor', value);
2352    }
2353};
2354
2355/*
2356    menu - BackColor
2357*/
2358// 构造函数
2359function BackColor(editor) {
2360    var _this = this;
2361
2362    this.editor = editor;
2363    this.$elem = $('<div class="w-e-menu"><i class="w-e-icon-paint-brush"></i></div>');
2364    this.type = 'droplist';
2365
2366    // 获取配置的颜色
2367    var config = editor.config;
2368    var colors = config.colors || [];
2369
2370    // 当前是否 active 状态
2371    this._active = false;
2372
2373    // 初始化 droplist
2374    this.droplist = new DropList(this, {
2375        width: 120,
2376        $title: $('<p>背景色</p>'),
2377        type: 'inline-block', // droplist 内容以 block 形式展示
2378        list: colors.map(function (color) {
2379            return { $elem: $('<i style="color:' + color + ';" class="w-e-icon-paint-brush"></i>'), value: color };
2380        }),
2381        onClick: function onClick(value) {
2382            // 注意 this 是指向当前的 BackColor 对象
2383            _this._command(value);
2384        }
2385    });
2386}
2387
2388// 原型
2389BackColor.prototype = {
2390    constructor: BackColor,
2391
2392    // 执行命令
2393    _command: function _command(value) {
2394        var editor = this.editor;
2395        editor.cmd.do('backColor', value);
2396    }
2397};
2398
2399/*
2400    menu - quote
2401*/
2402// 构造函数
2403function Quote(editor) {
2404    this.editor = editor;
2405    this.$elem = $('<div class="w-e-menu">\n            <i class="w-e-icon-quotes-left"></i>\n        </div>');
2406    this.type = 'click';
2407
2408    // 当前是否 active 状态
2409    this._active = false;
2410}
2411
2412// 原型
2413Quote.prototype = {
2414    constructor: Quote,
2415
2416    onClick: function onClick(e) {
2417        var editor = this.editor;
2418        var $selectionElem = editor.selection.getSelectionContainerElem();
2419        var nodeName = $selectionElem.getNodeName();
2420
2421        // if (!UA.isIE()) {
2422        //     if (nodeName === 'BLOCKQUOTE') {
2423        //         // 撤销 quote
2424        //         editor.cmd.do('formatBlock', '<P>');
2425        //     } else {
2426        //         // 转换为 quote
2427        //         editor.cmd.do('formatBlock', '<BLOCKQUOTE>');
2428        //     }
2429        //     return;
2430        // }
2431
2432        // IE 中不支持 formatBlock <BLOCKQUOTE> ,要用其他方式兼容
2433        var content = void 0,
2434            $targetELem = void 0;
2435        if (nodeName === 'P') {
2436            // 将 P 转换为 quote
2437            content = $selectionElem.text();
2438            $targetELem = $('<blockquote><p>' + content + '</p></blockquote>');
2439            $targetELem.insertAfter($selectionElem);
2440            $selectionElem.remove();
2441            return;
2442        }
2443        if (nodeName === 'BLOCKQUOTE') {
2444            // 撤销 quote
2445            content = $selectionElem.text();
2446            $targetELem = $('<p>' + content + '</p>');
2447            $targetELem.insertAfter($selectionElem);
2448            $selectionElem.remove();
2449        }
2450    },
2451
2452    tryChangeActive: function tryChangeActive(e) {
2453        var editor = this.editor;
2454        var $elem = this.$elem;
2455        var reg = /^BLOCKQUOTE$/i;
2456        var cmdValue = editor.cmd.queryCommandValue('formatBlock');
2457        if (reg.test(cmdValue)) {
2458            this._active = true;
2459            $elem.addClass('w-e-active');
2460        } else {
2461            this._active = false;
2462            $elem.removeClass('w-e-active');
2463        }
2464    }
2465};
2466
2467/*
2468    menu - code
2469*/
2470// 构造函数
2471function Code(editor) {
2472    this.editor = editor;
2473    this.$elem = $('<div class="w-e-menu">\n            <i class="w-e-icon-terminal"></i>\n        </div>');
2474    this.type = 'panel';
2475
2476    // 当前是否 active 状态
2477    this._active = false;
2478}
2479
2480// 原型
2481Code.prototype = {
2482    constructor: Code,
2483
2484    onClick: function onClick(e) {
2485        var editor = this.editor;
2486        var $startElem = editor.selection.getSelectionStartElem();
2487        var $endElem = editor.selection.getSelectionEndElem();
2488        var isSeleEmpty = editor.selection.isSelectionEmpty();
2489        var selectionText = editor.selection.getSelectionText();
2490        var $code = void 0;
2491
2492        if (!$startElem.equal($endElem)) {
2493            // 跨元素选择,不做处理
2494            editor.selection.restoreSelection();
2495            return;
2496        }
2497        if (!isSeleEmpty) {
2498            // 选取不是空,用 <code> 包裹即可
2499            $code = $('<code>
2499' + selectionText + '</code>');
2500            editor.cmd.do('insertElem', $code);
2501            editor.selection.createRangeByElem($code, false);
2502            editor.selection.restoreSelection();
2503            return;
2504        }
2505
2506        // 选取是空,且没有夸元素选择,则插入 <pre><code></code></prev>
2507        if (this._active) {
2508            // 选中状态,将编辑内容
2509            this._createPanel($startElem.html());
2510        } else {
2511            // 未选中状态,将创建内容
2512            this._createPanel();
2513        }
2514    },
2515
2516    _createPanel: function _createPanel(value) {
2517        var _this = this;
2518
2519        // value - 要编辑的内容
2520        value = value || '';
2521        var type = !value ? 'new' : 'edit';
2522        var textId = getRandom('texxt');
2523        var btnId = getRandom('btn');
2524
2525        var panel = new Panel(this, {
2526            width: 500,
2527            // 一个 Panel 包含多个 tab
2528            tabs: [{
2529                // 标题
2530                title: '插入代码',
2531                // 模板
2532                tpl: '<div>\n                        <textarea id="' + textId + '" style="height:145px;;">' + value + '</textarea>\n                        <div class="w-e-button-container">\n                            <button id="' + btnId + '" class="right">\u63D2\u5165</button>\n                        </div>\n                    <div>',
2533                // 事件绑定
2534                events: [
2535                // 插入代码
2536                {
2537                    selector: '#' + btnId,
2538                    type: 'click',
2539                    fn: function fn() {
2540                        var $text = $('#' + textId);
2541                        var text = $text.val() || $text.html();
2542                        text = replaceHtmlSymbol(text);
2543                        if (type === 'new') {
2544                            // 新插入
2545                            _this._insertCode(text);
2546                        } else {
2547                            // 编辑更新
2548                            _this._updateCode(text);
2549                        }
2550
2551                        // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
2552                        return true;
2553                    }
2554                }]
2555            } // first tab end
2556            ] // tabs end
2557        }); // new Panel end
2558
2559        // 显示 panel
2560        panel.show();
2561
2562        // 记录属性
2563        this.panel = panel;
2564    },
2565
2566    // 插入代码
2567    _insertCode: function _insertCode(value) {
2568        var editor = this.editor;
2569        editor.cmd.do('insertHTML', '<pre><code>' + value + '</code></pre><p><br></p>');
2570    },
2571
2572    // 更新代码
2573    _updateCode: function _updateCode(value) {
2574        var editor = this.editor;
2575        var $selectionELem = editor.selection.getSelectionContainerElem();
2576        if (!$selectionELem) {
2577            return;
2578        }
2579        $selectionELem.html(value);
2580        editor.selection.restoreSelection();
2581    },
2582
2583    // 试图改变 active 状态
2584    tryChangeActive: function tryChangeActive(e) {
2585        var editor = this.editor;
2586        var $elem = this.$elem;
2587        var $selectionELem = editor.selection.getSelectionContainerElem();
2588        if (!$selectionELem) {
2589            return;
2590        }
2591        var $parentElem = $selectionELem.parent();
2592        if ($selectionELem.getNodeName() === 'CODE' && $parentElem.getNodeName() === 'PRE') {
2593            this._active = true;
2594            $elem.addClass('w-e-active');
2595        } else {
2596            this._active = false;
2597            $elem.removeClass('w-e-active');
2598        }
2599    }
2600};
2601
2602/*
2603    menu - emoticon
2604*/
2605// 构造函数
2606function Emoticon(editor) {
2607    this.editor = editor;
2608    this.$elem = $('<div class="w-e-menu">\n            <i class="w-e-icon-happy"></i>\n        </div>');
2609    this.type = 'panel';
2610
2611    // 当前是否 active 状态
2612    this._active = false;
2613}
2614
2615// 原型
2616Emoticon.prototype = {
2617    constructor: Emoticon,
2618
2619    onClick: function onClick() {
2620        this._createPanel();
2621    },
2622
2623    _createPanel: function _createPanel() {
2624        var _this = this;
2625
2626        var editor = this.editor;
2627        var config = editor.config;
2628        // 获取表情配置
2629        var emotions = config.emotions || [];
2630
2631        // 创建表情 dropPanel 的配置
2632        var tabConfig = [];
2633        emotions.forEach(function (emotData) {
2634            var emotType = emotData.type;
2635            var content = emotData.content || [];
2636
2637            // 这一组表情最终拼接出来的 html
2638            var faceHtml = '';
2639
2640            // emoji 表情
2641            if (emotType === 'emoji') {
2642                content.forEach(function (item) {
2643                    if (item) {
2644                        faceHtml += '<span class="w-e-item">' + item + '</span>';
2645                    }
2646                });
2647            }
2648            // 图片表情
2649            if (emotType === 'image') {
2650                content.forEach(function (item) {
2651                    var src = item.src;
2652                    var alt = item.alt;
2653                    if (src) {
2654                        // 加一个 data-w-e 属性,点击图片的时候不再提示编辑图片
2655                        faceHtml += '<span class="w-e-item"><img src="' + src + '" alt="' + alt + '" data-w-e="1"/></span>';
2656                    }
2657                });
2658            }
2659
2660            tabConfig.push({
2661                title: emotData.title,
2662                tpl: '<div class="w-e-emoticon-container">' + faceHtml + '</div>',
2663                events: [{
2664                    selector: 'span.w-e-item',
2665                    type: 'click',
2666                    fn: function fn(e) {
2667                        var target = e.target;
2668                        var $target = $(target);
2669                        var nodeName = $target.getNodeName();
2670
2671                        var insertHtml = void 0;
2672                        if (nodeName === 'IMG') {
2673                            // 插入图片
2674                            insertHtml = $target.parent().html();
2675                        } else {
2676                            // 插入 emoji
2677                            insertHtml = '<span>' + $target.html() + '</span>';
2678                        }
2679
2680                        _this._insert(insertHtml);
2681                        // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
2682                        return true;
2683                    }
2684                }]
2685            });
2686        });
2687
2688        var panel = new Panel(this, {
2689            width: 300,
2690            height: 200,
2691            // 一个 Panel 包含多个 tab
2692            tabs: tabConfig
2693        });
2694
2695        // 显示 panel
2696        panel.show();
2697
2698        // 记录属性
2699        this.panel = panel;
2700    },
2701
2702    // 插入表情
2703    _insert: function _insert(emotHtml) {
2704        var editor = this.editor;
2705        editor.cmd.do('insertHTML', emotHtml);
2706    }
2707};
2708
2709/*
2710    menu - table
2711*/
2712// 构造函数
2713function Table(editor) {
2714    this.editor = editor;
2715    this.$elem = $('<div class="w-e-menu"><i class="w-e-icon-table2"></i></div>');
2716    this.type = 'panel';
2717
2718    // 当前是否 active 状态
2719    this._active = false;
2720}
2721
2722// 原型
2723Table.prototype = {
2724    constructor: Table,
2725
2726    onClick: function onClick() {
2727        if (this._active) {
2728            // 编辑现有表格
2729            this._createEditPanel();
2730        } else {
2731            // 插入新表格
2732            this._createInsertPanel();
2733        }
2734    },
2735
2736    // 创建插入新表格的 panel
2737    _createInsertPanel: function _createInsertPanel() {
2738        var _this = this;
2739
2740        // 用到的 id
2741        var btnInsertId = getRandom('btn');
2742        var textRowNum = getRandom('row');
2743        var textColNum = getRandom('col');
2744
2745        var panel = new Panel(this, {
2746            width: 250,
2747            // panel 包含多个 tab
2748            tabs: [{
2749                // 标题
2750                title: '插入表格',
2751                // 模板
2752                tpl: '<div>\n                        <p style="text-align:left; padding:5px 0;">\n                            \u521B\u5EFA\n                            <input id="' + textRowNum + '" type="text" value="5" style="width:40px;text-align:center;"/>\n                            \u884C\n                            <input id="' + textColNum + '" type="text" value="5" style="width:40px;text-align:center;"/>\n                            \u5217\u7684\u8868\u683C\n                        </p>\n                        <div class="w-e-button-container">\n                            <button id="' + btnInsertId + '" class="right">\u63D2\u5165</button>\n                        </div>\n                    </div>',
2753                // 事件绑定
2754                events: [{
2755                    // 点击按钮,插入表格
2756                    selector: '#' + btnInsertId,
2757                    type: 'click',
2758                    fn: function fn() {
2759                        var rowNum = parseInt($('#' + textRowNum).val());
2760                        var colNum = parseInt($('#' + textColNum).val());
2761
2762                        if (rowNum && colNum && rowNum > 0 && colNum > 0) {
2763                            // form 数据有效
2764                            _this._insert(rowNum, colNum);
2765                        }
2766
2767                        // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
2768                        return true;
2769                    }
2770                }]
2771            } // first tab end
2772            ] // tabs end
2773        }); // panel end
2774
2775        // 展示 panel
2776        panel.show();
2777
2778        // 记录属性
2779        this.panel = panel;
2780    },
2781
2782    // 插入表格
2783    _insert: function _insert(rowNum, colNum) {
2784        // 拼接 table 模板
2785        var r = void 0,
2786            c = void 0;
2787        var html = '<table border="0" width="100%" cellpadding="0" cellspacing="0">';
2788        for (r = 0; r < rowNum; r++) {
2789            html += '<tr>';
2790            if (r === 0) {
2791                for (c = 0; c < colNum; c++) {
2792                    html += '<th>&nbsp;</th>';
2793                }
2794            } else {
2795                for (c = 0; c < colNum; c++) {
2796                    html += '<td>&nbsp;</td>';
2797                }
2798            }
2799            html += '</tr>';
2800        }
2801        html += '</table><p><br></p>';
2802
2803        // 执行命令
2804        var editor = this.editor;
2805        editor.cmd.do('insertHTML', html);
2806
2807        // 防止 firefox 下出现 resize 的控制点
2808        editor.cmd.do('enableObjectResizing', false);
2809        editor.cmd.do('enableInlineTableEditing', false);
2810    },
2811
2812    // 创建编辑表格的 panel
2813    _createEditPanel: function _createEditPanel() {
2814        var _this2 = this;
2815
2816        // 可用的 id
2817        var addRowBtnId = getRandom('add-row');
2818        var addColBtnId = getRandom('add-col');
2819        var delRowBtnId = getRandom('del-row');
2820        var delColBtnId = getRandom('del-col');
2821        var delTableBtnId = getRandom('del-table');
2822
2823        // 创建 panel 对象
2824        var panel = new Panel(this, {
2825            width: 320,
2826            // panel 包含多个 tab
2827            tabs: [{
2828                // 标题
2829                title: '编辑表格',
2830                // 模板
2831                tpl: '<div>\n                        <div class="w-e-button-container" style="border-bottom:1px solid #f1f1f1;padding-bottom:5px;margin-bottom:5px;">\n                            <button id="' + addRowBtnId + '" class="left">\u589E\u52A0\u884C</button>\n                            <button id="' + delRowBtnId + '" class="red left">\u5220\u9664\u884C</button>\n                            <button id="' + addColBtnId + '" class="left">\u589E\u52A0\u5217</button>\n                            <button id="' + delColBtnId + '" class="red left">\u5220\u9664\u5217</button>\n                        </div>\n                        <div class="w-e-button-container">\n                            <button id="' + delTableBtnId + '" class="gray left">\u5220\u9664\u8868\u683C</button>\n                        </dv>\n                    </div>',
2832                // 事件绑定
2833                events: [{
2834                    // 增加行
2835                    selector: '#' + addRowBtnId,
2836                    type: 'click',
2837                    fn: function fn() {
2838                        _this2._addRow();
2839                        // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
2840                        return true;
2841                    }
2842                }, {
2843                    // 增加列
2844                    selector: '#' + addColBtnId,
2845                    type: 'click',
2846                    fn: function fn() {
2847                        _this2._addCol();
2848                        // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
2849                        return true;
2850                    }
2851                }, {
2852                    // 删除行
2853                    selector: '#' + delRowBtnId,
2854                    type: 'click',
2855                    fn: function fn() {
2856                        _this2._delRow();
2857                        // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
2858                        return true;
2859                    }
2860                }, {
2861                    // 删除列
2862                    selector: '#' + delColBtnId,
2863                    type: 'click',
2864                    fn: function fn() {
2865                        _this2._delCol();
2866                        // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
2867                        return true;
2868                    }
2869                }, {
2870                    // 删除表格
2871                    selector: '#' + delTableBtnId,
2872                    type: 'click',
2873                    fn: function fn() {
2874                        _this2._delTable();
2875                        // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
2876                        return true;
2877                    }
2878                }]
2879            }]
2880        });
2881        // 显示 panel
2882        panel.show();
2883    },
2884
2885    // 获取选中的单元格的位置信息
2886    _getLocationData: function _getLocationData() {
2887        var result = {};
2888        var editor = this.editor;
2889        var $selectionELem = editor.selection.getSelectionContainerElem();
2890        if (!$selectionELem) {
2891            return;
2892        }
2893        var nodeName = $selectionELem.getNodeName();
2894        if (nodeName !== 'TD' && nodeName !== 'TH') {
2895            return;
2896        }
2897
2898        // 获取 td index
2899        var $tr = $selectionELem.parent();
2900        var $tds = $tr.children();
2901        var tdLength = $tds.length;
2902        $tds.forEach(function (td, index) {
2903            if (td === $selectionELem[0]) {
2904                // 记录并跳出循环
2905                result.td = {
2906                    index: index,
2907                    elem: td,
2908                    length: tdLength
2909                };
2910                return false;
2911            }
2912        });
2913
2914        // 获取 tr index
2915        var $tbody = $tr.parent();
2916        var $trs = $tbody.children();
2917        var trLength = $trs.length;
2918        $trs.forEach(function (tr, index) {
2919            if (tr === $tr[0]) {
2920                // 记录并跳出循环
2921                result.tr = {
2922                    index: index,
2923                    elem: tr,
2924                    length: trLength
2925                };
2926                return false;
2927            }
2928        });
2929
2930        // 返回结果
2931        return result;
2932    },
2933
2934    // 增加行
2935    _addRow: function _addRow() {
2936        // 获取当前单元格的位置信息
2937        var locationData = this._getLocationData();
2938        if (!locationData) {
2939            return;
2940        }
2941        var trData = locationData.tr;
2942        var $currentTr = $(trData.elem);
2943        var tdData = locationData.td;
2944        var tdLength = tdData.length;
2945
2946        // 拼接即将插入的字符串
2947        var newTr = document.createElement('tr');
2948        var tpl = '',
2949            i = void 0;
2950        for (i = 0; i < tdLength; i++) {
2951            tpl += '<td>&nbsp;</td>';
2952        }
2953        newTr.innerHTML = tpl;
2954        // 插入
2955        $(newTr).insertAfter($currentTr);
2956    },
2957
2958    // 增加列
2959    _addCol: function _addCol() {
2960        // 获取当前单元格的位置信息
2961        var locationData = this._getLocationData();
2962        if (!locationData) {
2963            return;
2964        }
2965        var trData = locationData.tr;
2966        var tdData = locationData.td;
2967        var tdIndex = tdData.index;
2968        var $currentTr = $(trData.elem);
2969        var $trParent = $currentTr.parent();
2970        var $trs = $trParent.children();
2971
2972        // 遍历所有行
2973        $trs.forEach(function (tr) {
2974            var $tr = $(tr);
2975            var $tds = $tr.children();
2976            var $currentTd = $tds.get(tdIndex);
2977            var name = $currentTd.getNodeName().toLowerCase();
2978
2979            // new 一个 td,并插入
2980            var newTd = document.createElement(name);
2981            $(newTd).insertAfter($currentTd);
2982        });
2983    },
2984
2985    // 删除行
2986    _delRow: function _delRow() {
2987        // 获取当前单元格的位置信息
2988        var locationData = this._getLocationData();
2989        if (!locationData) {
2990            return;
2991        }
2992        var trData = locationData.tr;
2993        var $currentTr = $(trData.elem);
2994        $currentTr.remove();
2995    },
2996
2997    // 删除列
2998    _delCol: function _delCol() {
2999        // 获取当前单元格的位置信息
3000        var locationData = this._getLocationData();
3001        if (!locationData) {
3002            return;
3003        }
3004        var trData = locationData.tr;
3005        var tdData = locationData.td;
3006        var tdIndex = tdData.index;
3007        var $currentTr = $(trData.elem);
3008        var $trParent = $currentTr.parent();
3009        var $trs = $trParent.children();
3010
3011        // 遍历所有行
3012        $trs.forEach(function (tr) {
3013            var $tr = $(tr);
3014            var $tds = $tr.children();
3015            var $currentTd = $tds.get(tdIndex);
3016            // 删除
3017            $currentTd.remove();
3018        });
3019    },
3020
3021    // 删除表格
3022    _delTable: function _delTable() {
3023        var editor = this.editor;
3024        var $selectionELem = editor.selection.getSelectionContainerElem();
3025        if (!$selectionELem) {
3026            return;
3027        }
3028        var $table = $selectionELem.parentUntil('table');
3029        if (!$table) {
3030            return;
3031        }
3032        $table.remove();
3033    },
3034
3035    // 试图改变 active 状态
3036    tryChangeActive: function tryChangeActive(e) {
3037        var editor = this.editor;
3038        var $elem = this.$elem;
3039        var $selectionELem = editor.selection.getSelectionContainerElem();
3040        if (!$selectionELem) {
3041            return;
3042        }
3043        var nodeName = $selectionELem.getNodeName();
3044        if (nodeName === 'TD' || nodeName === 'TH') {
3045            this._active = true;
3046            $elem.addClass('w-e-active');
3047        } else {
3048            this._active = false;
3049            $elem.removeClass('w-e-active');
3050        }
3051    }
3052};
3053
3054/*
3055    menu - video
3056*/
3057// 构造函数
3058function Video(editor) {
3059    this.editor = editor;
3060    this.$elem = $('<div class="w-e-menu"><i class="w-e-icon-play"></i></div>');
3061    this.type = 'panel';
3062
3063    // 当前是否 active 状态
3064    this._active = false;
3065}
3066
3067// 原型
3068Video.prototype = {
3069    constructor: Video,
3070
3071    onClick: function onClick() {
3072        this._createPanel();
3073    },
3074
3075    _createPanel: function _createPanel() {
3076        var _this = this;
3077
3078        // 创建 id
3079        var textValId = getRandom('text-val');
3080        var btnId = getRandom('btn');
3081
3082        // 创建 panel
3083        var panel = new Panel(this, {
3084            width: 350,
3085            // 一个 panel 多个 tab
3086            tabs: [{
3087                // 标题
3088                title: '插入视频',
3089                // 模板
3090                tpl: '<div>\n                        <input id="' + textValId + '" type="text" class="block" placeholder="\u683C\u5F0F\u5982\uFF1A<iframe src=... ></iframe>"/>\n                        <div class="w-e-button-container">\n                            <button id="' + btnId + '" class="right">\u63D2\u5165</button>\n                        </div>\n                    </div>',
3091                // 事件绑定
3092                events: [{
3093                    selector: '#' + btnId,
3094                    type: 'click',
3095                    fn: function fn() {
3096                        var $text = $('#' + textValId);
3097                        var val = $text.val().trim();
3098
3099                        // 测试用视频地址
3100                        // <iframe height=498 width=510 src='http://player.youku.com/embed/XMjcwMzc3MzM3Mg==' frameborder=0 'allowfullscreen'></iframe>
3101
3102                        if (val) {
3103                            // 插入视频
3104                            _this._insert(val);
3105                        }
3106
3107                        // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
3108                        return true;
3109                    }
3110                }]
3111            } // first tab end
3112            ] // tabs end
3113        }); // panel end
3114
3115        // 显示 panel
3116        panel.show();
3117
3118        // 记录属性
3119        this.panel = panel;
3120    },
3121
3122    // 插入视频
3123    _insert: function _insert(val) {
3124        var editor = this.editor;
3125        editor.cmd.do('insertHTML', val + '<p><br></p>');
3126    }
3127};
3128
3129/*
3130    menu - img
3131*/
3132// 构造函数
3133function Image(editor) {
3134    this.editor = editor;
3135    var imgMenuId = getRandom('w-e-img');
3136    this.$elem = $('<div class="w-e-menu" id="' + imgMenuId + '"><i class="w-e-icon-image"></i></div>');
3137    editor.imgMenuId = imgMenuId;
3138    this.type = 'panel';
3139
3140    // 当前是否 active 状态
3141    this._active = false;
3142}
3143
3144// 原型
3145Image.prototype = {
3146    constructor: Image,
3147
3148    onClick: function onClick() {
3149        var editor = this.editor;
3150        var config = editor.config;
3151        if (config.qiniu) {
3152            return;
3153        }
3154        if (this._active) {
3155            this._createEditPanel();
3156        } else {
3157            this._createInsertPanel();
3158        }
3159    },
3160
3161    _createEditPanel: function _createEditPanel() {
3162        var editor = this.editor;
3163
3164        // id
3165        var width30 = getRandom('width-30');
3166        var width50 = getRandom('width-50');
3167        var width100 = getRandom('width-100');
3168        var delBtn = getRandom('del-btn');
3169
3170        // tab 配置
3171        var tabsConfig = [{
3172            title: '编辑图片',
3173            tpl: '<div>\n                    <div class="w-e-button-container" style="border-bottom:1px solid #f1f1f1;padding-bottom:5px;margin-bottom:5px;">\n                        <span style="float:left;font-size:14px;margin:4px 5px 0 5px;color:#333;">\u6700\u5927\u5BBD\u5EA6\uFF1A</span>\n                        <button id="' + width30 + '" class="left">30%</button>\n                        <button id="' + width50 + '" class="left">50%</button>\n                        <button id="' + width100 + '" class="left">100%</button>\n                    </div>\n                    <div class="w-e-button-container">\n                        <button id="' + delBtn + '" class="gray left">\u5220\u9664\u56FE\u7247</button>\n                    </dv>\n                </div>',
3174            events: [{
3175                selector: '#' + width30,
3176                type: 'click',
3177                fn: function fn() {
3178                    var $img = editor._selectedImg;
3179                    if ($img) {
3180                        $img.css('max-width', '30%');
3181                    }
3182                    // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
3183                    return true;
3184                }
3185            }, {
3186                selector: '#' + width50,
3187                type: 'click',
3188                fn: function fn() {
3189                    var $img = editor._selectedImg;
3190                    if ($img) {
3191                        $img.css('max-width', '50%');
3192                    }
3193                    // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
3194                    return true;
3195                }
3196            }, {
3197                selector: '#' + width100,
3198                type: 'click',
3199                fn: function fn() {
3200                    var $img = editor._selectedImg;
3201                    if ($img) {
3202                        $img.css('max-width', '100%');
3203                    }
3204                    // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
3205                    return true;
3206                }
3207            }, {
3208                selector: '#' + delBtn,
3209                type: 'click',
3210                fn: function fn() {
3211                    var $img = editor._selectedImg;
3212                    if ($img) {
3213                        $img.remove();
3214                    }
3215                    // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
3216                    return true;
3217                }
3218            }]
3219        }];
3220
3221        // 创建 panel 并显示
3222        var panel = new Panel(this, {
3223            width: 300,
3224            tabs: tabsConfig
3225        });
3226        panel.show();
3227
3228        // 记录属性
3229        this.panel = panel;
3230    },
3231
3232    _createInsertPanel: function _createInsertPanel() {
3233        var editor = this.editor;
3234        var uploadImg = editor.uploadImg;
3235        var config = editor.config;
3236
3237        // id
3238        var upTriggerId = getRandom('up-trigger');
3239        var upFileId = getRandom('up-file');
3240        var linkUrlId = getRandom('link-url');
3241        var linkBtnId = getRandom('link-btn');
3242
3243        // tabs 的配置
3244        var tabsConfig = [{
3245            title: '上传图片',
3246            tpl: '<div class="w-e-up-img-container">\n                    <div id="' + upTriggerId + '" class="w-e-up-btn">\n                        <i class="w-e-icon-upload2"></i>\n                    </div>\n                    <div style="display:none;">\n                        <input id="' + upFileId + '" type="file" multiple="multiple" accept="image/jpg,image/jpeg,image/png,image/gif,image/bmp"/>\n                    </div>\n                </div>',
3247            events: [{
3248                // 触发选择图片
3249                selector: '#' + upTriggerId,
3250                type: 'click',
3251                fn: function fn() {
3252                    var $file = $('#' + upFileId);
3253                    var fileElem = $file[0];
3254                    if (fileElem) {
3255                        fileElem.click();
3256                    } else {
3257                        // 返回 true 可关闭 panel
3258                        return true;
3259                    }
3260                }
3261            }, {
3262                // 选择图片完毕
3263                selector: '#' + upFileId,
3264                type: 'change',
3265                fn: function fn() {
3266                    var $file = $('#' + upFileId);
3267                    var fileElem = $file[0];
3268                    if (!fileElem) {
3269                        // 返回 true 可关闭 panel
3270                        return true;
3271                    }
3272
3273                    // 获取选中的 file 对象列表
3274                    var fileList = fileElem.files;
3275                    if (fileList.length) {
3276                        uploadImg.uploadImg(fileList);
3277                    }
3278
3279                    // 返回 true 可关闭 panel
3280                    return true;
3281                }
3282            }]
3283        }, // first tab end
3284        {
3285            title: '网络图片',
3286            tpl: '<div>\n                    <input id="' + linkUrlId + '" type="text" class="block" placeholder="\u56FE\u7247\u94FE\u63A5"/></td>\n                    <div class="w-e-button-container">\n                        <button id="' + linkBtnId + '" class="right">\u63D2\u5165</button>\n                    </div>\n                </div>',
3287            events: [{
3288                selector: '#' + linkBtnId,
3289                type: 'click',
3290                fn: function fn() {
3291                    var $linkUrl = $('#' + linkUrlId);
3292                    var url = $linkUrl.val().trim();
3293
3294                    if (url) {
3295                        uploadImg.insertLinkImg(url);
3296                    }
3297
3298                    // 返回 true 表示函数执行结束之后关闭 panel
3299                    return true;
3300                }
3301            }]
3302        } // second tab end
3303        ]; // tabs end
3304
3305        // 判断 tabs 的显示
3306        var tabsConfigResult = [];
3307        if ((config.uploadImgShowBase64 || config.uploadImgServer || config.customUploadImg) && window.FileReader) {
3308            // 显示“上传图片”
3309            tabsConfigResult.push(tabsConfig[0]);
3310        }
3311        if (config.showLinkImg) {
3312            // 显示“网络图片”
3313            tabsConfigResult.push(tabsConfig[1]);
3314        }
3315
3316        // 创建 panel 并显示
3317        var panel = new Panel(this, {
3318            width: 300,
3319            tabs: tabsConfigResult
3320        });
3321        panel.show();
3322
3323        // 记录属性
3324        this.panel = panel;
3325    },
3326
3327    // 试图改变 active 状态
3328    tryChangeActive: function tryChangeActive(e) {
3329        var editor = this.editor;
3330        var $elem = this.$elem;
3331        if (editor._selectedImg) {
3332            this._active = true;
3333            $elem.addClass('w-e-active');
3334        } else {
3335            this._active = false;
3336            $elem.removeClass('w-e-active');
3337        }
3338    }
3339};
3340
3341/*
3342    所有菜单的汇总
3343*/
3344
3345// 存储菜单的构造函数
3346var MenuConstructors = {};
3347
3348MenuConstructors.bold = Bold;
3349
3350MenuConstructors.head = Head;
3351
3352MenuConstructors.fontSize = FontSize;
3353
3354MenuConstructors.fontName = FontName;
3355
3356MenuConstructors.link = Link;
3357
3358MenuConstructors.allLink = AllLink;
3359
3360MenuConstructors.italic = Italic;
3361
3362MenuConstructors.strikeThrough = StrikeThrough;
3363
3364MenuConstructors.underline = Underline;
3365
3366MenuConstructors.undo = Undo;
3367
3368MenuConstructors.redo = Redo;
3369
3370MenuConstructors.list = List;
3371
3372MenuConstructors.cardRecognition = CardRecognition;
3373
3374MenuConstructors.textSection = TextSection;
3375
3376MenuConstructors.justify = Justify;
3377
3378MenuConstructors.foreColor = ForeColor;
3379
3380MenuConstructors.backColor = BackColor;
3381
3382MenuConstructors.quote = Quote;
3383
3384MenuConstructors.code = Code;
3385
3386MenuConstructors.emoticon = Emoticon;
3387
3388MenuConstructors.table = Table;
3389
3390MenuConstructors.video = Video;
3391
3392MenuConstructors.image = Image;
3393
3394/*
3395    菜单集合
3396*/
3397// 构造函数
3398function Menus(editor) {
3399    this.editor = editor;
3400    this.menus = {};
3401}
3402
3403// 修改原型
3404Menus.prototype = {
3405    constructor: Menus,
3406
3407    // 初始化菜单
3408    init: function init() {
3409        var _this = this;
3410
3411        var editor = this.editor;
3412        var config = editor.config || {};
3413        var configMenus = config.menus || []; // 获取配置中的菜单
3414
3415        // 根据配置信息,创建菜单
3416        configMenus.forEach(function (menuKey) {
3417            var MenuConstructor = MenuConstructors[menuKey];
3418            if (MenuConstructor && typeof MenuConstructor === 'function') {
3419                // 创建单个菜单
3420                _this.menus[menuKey] = new MenuConstructor(editor);
3421            }
3422        });
3423
3424        // 添加到菜单栏
3425        this._addToToolbar();
3426
3427        // 绑定事件
3428        this._bindEvent();
3429    },
3430
3431    // 添加到菜单栏
3432    _addToToolbar: function _addToToolbar() {
3433        var editor = this.editor;
3434        var $toolbarElem = editor.$toolbarElem;
3435        var menus = this.menus;
3436        var config = editor.config;
3437        // config.zIndex 是配置的编辑区域的 z-index,菜单的 z-index 得在其基础上 +1
3438        var zIndex = config.zIndex + 1;
3439        objForEach(menus, function (key, menu) {
3440            var $elem = menu.$elem;
3441            if ($elem) {
3442                // 设置 z-index
3443                $elem.css('z-index', zIndex);
3444                $toolbarElem.append($elem);
3445            }
3446        });
3447    },
3448
3449    // 绑定菜单 click mouseenter 事件
3450    _bindEvent: function _bindEvent() {
3451        var menus = this.menus;
3452        var editor = this.editor;
3453        objForEach(menus, function (key, menu) {
3454            var type = menu.type;
3455            if (!type) {
3456                return;
3457            }
3458            var $elem = menu.$elem;
3459            var droplist = menu.droplist;
3460            var panel = menu.panel;
3461
3462            // 点击类型,例如 bold
3463            if (type === 'click' && menu.onClick) {
3464                $elem.on('click', function (e) {
3465                    if (editor.selection.getRange() == null) {
3466                        return;
3467                    }
3468                    menu.onClick(e);
3469                });
3470            }
3471
3472            // 下拉框,例如 head
3473            if (type === 'droplist' && droplist) {
3474                $elem.on('mouseenter', function (e) {
3475                    if (editor.selection.getRange() == null) {
3476                        return;
3477                    }
3478                    // 显示
3479                    droplist.showTimeoutId = setTimeout(function () {
3480                        droplist.show();
3481                    }, 200);
3482                }).on('mouseleave', function (e) {
3483                    // 隐藏
3484                    droplist.hideTimeoutId = setTimeout(function () {
3485                        droplist.hide();
3486                    }, 0);
3487                });
3488            }
3489
3490            // 弹框类型,例如 link
3491            if (type === 'panel' && menu.onClick) {
3492                $elem.on('click', function (e) {
3493                    e.stopPropagation();
3494                    if (editor.selection.getRange() == null) {
3495                        return;
3496                    }
3497                    // 在自定义事件中显示 panel
3498                    menu.onClick(e);
3499                });
3500            }
3501        });
3502    },
3503
3504    // 尝试修改菜单状态
3505    changeActive: function changeActive() {
3506        var menus = this.menus;
3507        objForEach(menus, function (key, menu) {
3508            if (menu.tryChangeActive) {
3509                setTimeout(function () {
3510                    menu.tryChangeActive();
3511                }, 100);
3512            }
3513        });
3514    }
3515};
3516
3517/*
3518    粘贴信息的处理
3519*/
3520
3521// 获取粘贴的纯文本
3522function getPasteText(e) {
3523    var clipboardData = e.clipboardData || e.originalEvent && e.originalEvent.clipboardData;
3524    var pasteText = void 0;
3525    if (clipboardData == null) {
3526        pasteText = window.clipboardData && window.clipboardData.getData('text');
3527    } else {
3528        pasteText = clipboardData.getData('text/plain');
3529    }
3530
3531    return replaceHtmlSymbol(pasteText);
3532}
3533
3534// 获取粘贴的html
3535function getPasteHtml(e, filterStyle, ignoreImg) {
3536    var clipboardData = e.clipboardData || e.originalEvent && e.originalEvent.clipboardData;
3537    var pasteText = void 0,
3538        pasteHtml = void 0;
3539    if (clipboardData == null) {
3540        pasteText = window.clipboardData && window.clipboardData.getData('text');
3541    } else {
3542        pasteText = clipboardData.getData('text/plain');
3543        pasteHtml = clipboardData.getData('text/html');
3544    }
3545    if (!pasteHtml && pasteText) {
3546        pasteHtml = '<p>' + replaceHtmlSymbol(pasteText) + '</p>';
3547    }
3548    if (!pasteHtml) {
3549        return;
3550    }
3551
3552    // 过滤word中状态过来的无用字符
3553    var docSplitHtml = pasteHtml.split('</html>');
3554    if (docSplitHtml.length === 2) {
3555        pasteHtml = docSplitHtml[0];
3556    }
3557
3558    // 过滤无用标签
3559    pasteHtml = pasteHtml.replace(/<(meta|script|link|html|body|div|font|i).*?>/igm, '');
3560    pasteHtml = pasteHtml.replace(/<\/(html|body|div)>/igm, '');
3561
3562    // 去掉注释
3563    pasteHtml = pasteHtml.replace(/<!--.*?-->/mg, '');
3564    // 过滤 data-xxx 属性
3565    pasteHtml = pasteHtml.replace(/\s?data-.+?=('|").+?('|")/igm, '');
3566
3567    if (ignoreImg) {
3568        // 忽略图片
3569        pasteHtml = pasteHtml.replace(/<img.+?>/igm, '');
3570    }
3571
3572    if (filterStyle) {
3573        // 过滤样式
3574        pasteHtml = pasteHtml.replace(/\s?(class|style)=('|").*?('|")/igm, '');
3575    } else {
3576        // 保留样式
3577        pasteHtml = pasteHtml.replace(/\s?class=('|").*?('|")/igm, '');
3578    }
3579
3580    return pasteHtml;
3581}
3582
3583// 获取粘贴的图片文件
3584function getPasteImgs(e) {
3585    var result = [];
3586    var txt = getPasteText(e);
3587    if (txt) {
3588        // 有文字,就忽略图片
3589        return result;
3590    }
3591
3592    var clipboardData = e.clipboardData || e.originalEvent && e.originalEvent.clipboardData || {};
3593    var items = clipboardData.items;
3594    if (!items) {
3595        return result;
3596    }
3597
3598    objForEach(items, function (key, value) {
3599        var type = value.type;
3600        if (/image/i.test(type)) {
3601            result.push(value.getAsFile());
3602        }
3603    });
3604
3605    return result;
3606}
3607
3608/*
3609    编辑区域
3610*/
3611
3612// 获取一个 elem.childNodes 的 JSON 数据
3613function getChildrenJSON($elem) {
3614    var result = [];
3615    var $children = $elem.childNodes() || []; // 注意 childNodes() 可以获取文本节点
3616    $children.forEach(function (curElem) {
3617        var elemResult = void 0;
3618        var nodeType = curElem.nodeType;
3619
3620        // 文本节点
3621        if (nodeType === 3) {
3622            elemResult = curElem.textContent;
3623            elemResult = replaceHtmlSymbol(elemResult);
3624        }
3625
3626        // 普通 DOM 节点
3627        if (nodeType === 1) {
3628            elemResult = {};
3629
3630            // tag
3631            elemResult.tag = curElem.nodeName.toLowerCase();
3632            // attr
3633            var attrData = [];
3634            var attrList = curElem.attributes || {};
3635            var attrListLength = attrList.length || 0;
3636            for (var i = 0; i < attrListLength; i++) {
3637                var attr = attrList[i];
3638                attrData.push({
3639                    name: attr.name,
3640                    value: attr.value
3641                });
3642            }
3643            elemResult.attrs = attrData;
3644            // children(递归)
3645            elemResult.children = getChildrenJSON($(curElem));
3646        }
3647
3648        result.push(elemResult);
3649    });
3650    return result;
3651}
3652
3653// 构造函数
3654function Text(editor) {
3655    this.editor = editor;
3656}
3657
3658// 修改原型
3659Text.prototype = {
3660    constructor: Text,
3661
3662    // 初始化
3663    init: function init() {
3664        // 绑定事件
3665        this._bindEvent();
3666    },
3667
3668    // 清空内容
3669    clear: function clear() {
3670        this.html('<p><br></p>');
3671    },
3672
3673    // 获取 设置 html
3674    html: function html(val) {
3675        var editor = this.editor;
3676        var $textElem = editor.$textElem;
3677        var html = void 0;
3678        if (val == null) {
3679            html = $textElem.html();
3680            // 未选中任何内容的时候点击“加粗”或者“斜体”等按钮,就得需要一个空的占位符 &#8203 ,这里替换掉
3681            html = html.replace(/\u200b/gm, '');
3682            return html;
3683        } else {
3684            $textElem.html(val);
3685
3686            // 初始化选取,将光标定位到内容尾部
3687            editor.initSelection();
3688        }
3689    },
3690
3691    // 获取 JSON
3692    getJSON: function getJSON() {
3693        var editor = this.editor;
3694        var $textElem = editor.$textElem;
3695        return getChildrenJSON($textElem);
3696    },
3697
3698    // 获取 设置 text
3699    text: function text(val) {
3700        var editor = this.editor;
3701        var $textElem = editor.$textElem;
3702        var text = void 0;
3703        if (val == null) {
3704            text = $textElem.text();
3705            // 未选中任何内容的时候点击“加粗”或者“斜体”等按钮,就得需要一个空的占位符 &#8203 ,这里替换掉
3706            text = text.replace(/\u200b/gm, '');
3707            return text;
3708        } else {
3709            $textElem.text('<p>' + val + '</p>');
3710
3711            // 初始化选取,将光标定位到内容尾部
3712            editor.initSelection();
3713        }
3714    },
3715
3716    // 追加内容
3717    append: function append(html) {
3718        var editor = this.editor;
3719        var $textElem = editor.$textElem;
3720        $textElem.append($(html));
3721
3722        // 初始化选取,将光标定位到内容尾部
3723        editor.initSelection();
3724    },
3725
3726    // 绑定事件
3727    _bindEvent: function _bindEvent() {
3728        // 实时保存选取
3729        this._saveRangeRealTime();
3730
3731        // 按回车建时的特殊处理
3732        this._enterKeyHandle();
3733
3734        // 清空时保留 <p><br></p>
3735        this._clearHandle();
3736
3737        // 粘贴事件(粘贴文字,粘贴图片)
3738        this._pasteHandle();
3739
3740        // tab 特殊处理
3741        this._tabHandle();
3742
3743        // img 点击
3744        this._imgHandle();
3745
3746        // 拖拽事件
3747        this._dragHandle();
3748    },
3749
3750    // 实时保存选取
3751    _saveRangeRealTime: function _saveRangeRealTime() {
3752        var editor = this.editor;
3753        var $textElem = editor.$textElem;
3754
3755        // 保存当前的选区
3756        function saveRange(e) {
3757            // 随时保存选区
3758            editor.selection.saveRange();
3759            // 更新按钮 ative 状态
3760            editor.menus.changeActive();
3761        }
3762        // 按键后保存
3763        $textElem.on('keyup', saveRange);
3764        $textElem.on('mousedown', function (e) {
3765            // mousedown 状态下,鼠标滑动到编辑区域外面,也需要保存选区
3766            $textElem.on('mouseleave', saveRange);
3767        });
3768        $textElem.on('mouseup', function (e) {
3769            saveRange();
3770            // 在编辑器区域之内完成点击,取消鼠标滑动到编辑区外面的事件
3771            $textElem.off('mouseleave', saveRange);
3772        });
3773    },
3774
3775    // 按回车键时的特殊处理
3776    _enterKeyHandle: function _enterKeyHandle() {
3777        var editor = this.editor;
3778        var $textElem = editor.$textElem;
3779
3780        function insertEmptyP($selectionElem) {
3781            var $p = $('<p><br></p>');
3782            $p.insertBefore($selectionElem);
3783            editor.selection.createRangeByElem($p, true);
3784            editor.selection.restoreSelection();
3785            $selectionElem.remove();
3786        }
3787
3788        // 将回车之后生成的非 <p> 的顶级标签,改为 <p>
3789        function pHandle(e) {
3790            var $selectionElem = editor.selection.getSelectionContainerElem();
3791            var $parentElem = $selectionElem.parent();
3792
3793            if ($parentElem.html() === '<code><br></code>') {
3794                // 回车之前光标所在一个 <p><code>.....</code></p> ,忽然回车生成一个空的 <p><code><br></code></p>
3795                // 而且继续回车跳不出去,因此只能特殊处理
3796                insertEmptyP($selectionElem);
3797                return;
3798            }
3799
3800            if (!$parentElem.equal($textElem)) {
3801                // 不是顶级标签
3802                return;
3803            }
3804
3805            var nodeName = $selectionElem.getNodeName();
3806            if (nodeName === 'P') {
3807                // 当前的标签是 P ,不用做处理
3808                return;
3809            }
3810
3811            if ($selectionElem.text()) {
3812                // 有内容,不做处理
3813                return;
3814            }
3815
3816            // 插入 <p> ,并将选取定位到 <p>,删除当前标签
3817            insertEmptyP($selectionElem);
3818        }
3819
3820        $textElem.on('keyup', function (e) {
3821            if (e.keyCode !== 13) {
3822                // 不是回车键
3823                return;
3824            }
3825            // 将回车之后生成的非 <p> 的顶级标签,改为 <p>
3826            pHandle(e);
3827        });
3828
3829        // <pre><code></code></pre> 回车时 特殊处理
3830        function codeHandle(e) {
3831            var $selectionElem = editor.selection.getSelectionContainerElem();
3832            if (!$selectionElem) {
3833                return;
3834            }
3835            var $parentElem = $selectionElem.parent();
3836            var selectionNodeName = $selectionElem.getNodeName();
3837            var parentNodeName = $parentElem.getNodeName();
3838
3839            if (selectionNodeName !== 'CODE' || parentNodeName !== 'PRE') {
3840                // 不符合要求 忽略
3841                return;
3842            }
3843
3844            if (!editor.cmd.queryCommandSupported('insertHTML')) {
3845                // 必须原生支持 insertHTML 命令
3846                return;
3847            }
3848
3849            // 处理:光标定位到代码末尾,联系点击两次回车,即跳出代码块
3850            if (editor._willBreakCode === true) {
3851                // 此时可以跳出代码块
3852                // 插入 <p> ,并将选取定位到 <p>
3853                var $p = $('<p><br></p>');
3854                $p.insertAfter($parentElem);
3855                editor.selection.createRangeByElem($p, true);
3856                editor.selection.restoreSelection();
3857
3858                // 修改状态
3859                editor._willBreakCode = false;
3860
3861                e.preventDefault();
3862                return;
3863            }
3864
3865            var _startOffset = editor.selection.getRange().startOffset;
3866
3867            // 处理:回车时,不能插入 <br> 而是插入 \n ,因为是在 pre 标签里面
3868            editor.cmd.do('insertHTML', '\n');
3869            editor.selection.saveRange();
3870            if (editor.selection.getRange().startOffset === _startOffset) {
3871                // 没起作用,再来一遍
3872                editor.cmd.do('insertHTML', '\n');
3873            }
3874
3875            var codeLength = $selectionElem.html().length;
3876            if (editor.selection.getRange().startOffset + 1 === codeLength) {
3877                // 说明光标在代码最后的位置,执行了回车操作
3878                // 记录下来,以便下次回车时候跳出 code
3879                editor._willBreakCode = true;
3880            }
3881
3882            // 阻止默认行为
3883            e.preventDefault();
3884        }
3885
3886        $textElem.on('keydown', function (e) {
3887            if (e.keyCode !== 13) {
3888                // 不是回车键
3889                // 取消即将跳转代码块的记录
3890                editor._willBreakCode = false;
3891                return;
3892            }
3893            editor._undoRecord();
3894            // <pre><code></code></pre> 回车时 特殊处理
3895            codeHandle(e);
3896        });
3897    },
3898
3899    // 清空时保留 <p><br></p>
3900    _clearHandle: function _clearHandle() {
3901        var editor = this.editor;
3902        var $textElem = editor.$textElem;
3903
3904        $textElem.on('keydown', function (e) {
3905            if (e.keyCode !== 8) {
3906                return;
3907            }
3908            var txtHtml = $textElem.html().toLowerCase().trim();
3909            if (txtHtml === '<p><br></p>') {
3910                // 最后剩下一个空行,就不再删除了
3911                e.preventDefault();
3912                return;
3913            }
3914        });
3915
3916        $textElem.on('keyup', function (e) {
3917            if (e.keyCode !== 8) {
3918                return;
3919            }
3920            var $p = void 0;
3921            var txtHtml = $textElem.html().toLowerCase().trim();
3922
3923            // firefox 时用 txtHtml === '<br>' 判断,其他用 !txtHtml 判断
3924            if (!txtHtml || txtHtml === '<br>') {
3925                // 内容空了
3926                $p = $('<p><br/></p>');
3927                $textElem.html(''); // 一定要先清空,否则在 firefox 下有问题
3928                $textElem.append($p);
3929                editor.selection.createRangeByElem($p, false, true);
3930                editor.selection.restoreSelection();
3931            }
3932        });
3933    },
3934
3935    // 粘贴事件(粘贴文字 粘贴图片)
3936    _pasteHandle: function _pasteHandle() {
3937        var editor = this.editor;
3938        var config = editor.config;
3939        var pasteFilterStyle = config.pasteFilterStyle;
3940        var pasteTextHandle = config.pasteTextHandle;
3941        var ignoreImg = config.pasteIgnoreImg;
3942        var $textElem = editor.$textElem;
3943
3944        // 粘贴图片、文本的事件,每次只能执行一个
3945        // 判断该次粘贴事件是否可以执行
3946        var pasteTime = 0;
3947        function canDo() {
3948            var now = Date.now();
3949            var flag = false;
3950            if (now - pasteTime >= 100) {
3951                // 间隔大于 100 ms ,可以执行
3952                flag = true;
3953            }
3954            pasteTime = now;
3955            return flag;
3956        }
3957        function resetTime() {
3958            pasteTime = 0;
3959        }
3960
3961        // 粘贴文字
3962        $textElem.on('paste', function (e) {
3963            if (UA.isIE()) {
3964                return;
3965            } else {
3966                // 阻止默认行为,使用 execCommand 的粘贴命令
3967                e.preventDefault();
3968            }
3969
3970            // 粘贴图片和文本,只能同时使用一个
3971            if (!canDo()) {
3972                return;
3973            }
3974
3975            // 获取粘贴的文字
3976            var pasteHtml = getPasteHtml(e, pasteFilterStyle, ignoreImg);
3977            var pasteText = getPasteText(e);
3978            // pasteText = pasteText.replace(/\n/gm, '<br>');
3979            // pasteText = pasteText.replace(/\n/gm, '</p><p>');
3980
3981            var $selectionElem = editor.selection.getSelectionContainerElem();
3982            if (!$selectionElem) {
3983                return;
3984            }
3985            var nodeName = $selectionElem.getNodeName();
3986
3987            // code 中只能粘贴纯文本
3988            if (nodeName === 'CODE' || nodeName === 'PRE') {
3989                if (pasteTextHandle && isFunction(pasteTextHandle)) {
3990                    // 用户自定义过滤处理粘贴内容
3991                    pasteText = '' + (pasteTextHandle(pasteText) || '');
3992                }
3993                editor.cmd.do('insertHTML', '<p>' + pasteText + '</p>');
3994                return;
3995            }
3996
3997            // 先放开注释,有问题再追查 ————
3998            // // 表格中忽略,可能会出现异常问题
3999            // if (nodeName === 'TD' || nodeName === 'TH') {
4000            //     return
4001            // }
4002
4003            if (!pasteHtml) {
4004                // 没有内容,可继续执行下面的图片粘贴
4005                resetTime();
4006                return;
4007            }
4008            try {
4009                // firefox 中,获取的 pasteHtml 可能是没有 <ul> 包裹的 <li>
4010                // 因此执行 insertHTML 会报错
4011                if (pasteTextHandle && isFunction(pasteTextHandle)) {
4012                    // 用户自定义过滤处理粘贴内容
4013                    pasteHtml = '' + (pasteTextHandle(pasteHtml) || '');
4014                }
4015                editor.cmd.do('insertHTML', pasteHtml);
4016            } catch (ex) {
4017                // 此时使用 pasteText 来兼容一下
4018                if (pasteTextHandle && isFunction(pasteTextHandle)) {
4019                    // 用户自定义过滤处理粘贴内容
4020                    pasteText = '' + (pasteTextHandle(pasteText) || '');
4021                }
4022                editor.cmd.do('insertHTML', '<p>' + pasteText + '</p>');
4023            }
4024        });
4025
4026        // 粘贴图片
4027        $textElem.on('paste', function (e) {
4028            if (UA.isIE()) {
4029                return;
4030            } else {
4031                e.preventDefault();
4032            }
4033
4034            // 粘贴图片和文本,只能同时使用一个
4035            if (!canDo()) {
4036                return;
4037            }
4038
4039            // 获取粘贴的图片
4040            var pasteFiles = getPasteImgs(e);
4041            if (!pasteFiles || !pasteFiles.length) {
4042                return;
4043            }
4044
4045            // 获取当前的元素
4046            var $selectionElem = editor.selection.getSelectionContainerElem();
4047            if (!$selectionElem) {
4048                return;
4049            }
4050            var nodeName = $selectionElem.getNodeName();
4051
4052            // code 中粘贴忽略
4053            if (nodeName === 'CODE' || nodeName === 'PRE') {
4054                return;
4055            }
4056
4057            // 上传图片
4058            var uploadImg = editor.uploadImg;
4059            uploadImg.uploadImg(pasteFiles);
4060        });
4061    },
4062
4063    // tab 特殊处理
4064    _tabHandle: function _tabHandle() {
4065        var editor = this.editor;
4066        var $textElem = editor.$textElem;
4067
4068        $textElem.on('keydown', function (e) {
4069            if (e.keyCode !== 9) {
4070                return;
4071            }
4072            if (!editor.cmd.queryCommandSupported('insertHTML')) {
4073                // 必须原生支持 insertHTML 命令
4074                return;
4075            }
4076            var $selectionElem = editor.selection.getSelectionContainerElem();
4077            if (!$selectionElem) {
4078                return;
4079            }
4080            var $parentElem = $selectionElem.parent();
4081            var selectionNodeName = $selectionElem.getNodeName();
4082            var parentNodeName = $parentElem.getNodeName();
4083
4084            if (selectionNodeName === 'CODE' && parentNodeName === 'PRE') {
4085                // <pre><code> 里面
4086                editor.cmd.do('insertHTML', '    ');
4087            } else {
4088                // 普通文字
4089                editor.cmd.do('insertHTML', '&nbsp;&nbsp;&nbsp;&nbsp;');
4090            }
4091
4092            e.preventDefault();
4093        });
4094    },
4095
4096    // img 点击
4097    _imgHandle: function _imgHandle() {
4098        var editor = this.editor;
4099        var $textElem = editor.$textElem;
4100
4101        // 为图片增加 selected 样式
4102        $textElem.on('click', 'img', function (e) {
4103            var img = this;
4104            var $img = $(img);
4105
4106            if ($img.attr('data-w-e') === '1') {
4107                // 是表情图片,忽略
4108                return;
4109            }
4110
4111            // 记录当前点击过的图片
4112            editor._selectedImg = $img;
4113
4114            // 修改选区并 restore ,防止用户此时点击退格键,会删除其他内容
4115            editor.selection.createRangeByElem($img);
4116            editor.selection.restoreSelection();
4117        });
4118
4119        // 去掉图片的 selected 样式
4120        $textElem.on('click  keyup', function (e) {
4121            if (e.target.matches('img')) {
4122                // 点击的是图片,忽略
4123                return;
4124            }
4125            // 删除记录
4126            editor._selectedImg = null;
4127        });
4128    },
4129
4130    // 拖拽事件
4131    _dragHandle: function _dragHandle() {
4132        var editor = this.editor;
4133
4134        // 禁用 document 拖拽事件
4135        var $document = $(document);
4136        $document.on('dragleave drop dragenter dragover', function (e) {
4137            e.preventDefault();
4138        });
4139
4140        // 添加编辑区域拖拽事件
4141        var $textElem = editor.$textElem;
4142        $textElem.on('drop', function (e) {
4143            e.preventDefault();
4144            var files = e.dataTransfer && e.dataTransfer.files;
4145            if (!files || !files.length) {
4146                return;
4147            }
4148
4149            // 上传图片
4150            var uploadImg = editor.uploadImg;
4151            uploadImg.uploadImg(files);
4152        });
4153    }
4154};
4155
4156/*
4157    命令,封装 document.execCommand
4158*/
4159
4160// 构造函数
4161function Command(editor) {
4162    this.editor = editor;
4163}
4164
4165// 修改原型
4166Command.prototype = {
4167    constructor: Command,
4168
4169    // 执行命令
4170    do: function _do(name, value) {
4171        var editor = this.editor;
4172
4173        // 使用 styleWithCSS
4174        if (!editor._useStyleWithCSS) {
4175            document.execCommand('styleWithCSS', null, true);
4176            editor._useStyleWithCSS = true;
4177        }
4178
4179        // 如果无选区,忽略
4180        if (!editor.selection.getRange()) {
4181            return;
4182        }
4183
4184        editor._undoRecord();
4185
4186        // 恢复选取
4187        editor.selection.restoreSelection();
4188
4189        // 执行
4190        var _name = '_' + name;
4191        if (this[_name]) {
4192            // 有自定义事件
4193            this[_name](value);
4194        } else {
4195            // 默认 command
4196            this._execCommand(name, value);
4197        }
4198
4199        // 修改菜单状态
4200        editor.menus.changeActive();
4201
4202        // 最后,恢复选取保证光标在原来的位置闪烁
4203        editor.selection.saveRange();
4204        editor.selection.restoreSelection();
4205
4206        // 触发 onchange
4207        editor.change && editor.change();
4208    },
4209
4210    // 自定义 insertHTML 事件
4211    _insertHTML: function _insertHTML(html) {
4212        var editor = this.editor;
4213        var range = editor.selection.getRange();
4214
4215        if (this.queryCommandSupported('insertHTML')) {
4216            // W3C
4217            this._execCommand('insertHTML', html);
4218        }
4218 else if (range.insertNode) {
4219            // IE
4220            range.deleteContents();
4221            range.insertNode($(html)[0]);
4222        } else if (range.pasteHTML) {
4223            // IE <= 10
4224            range.pasteHTML(html);
4225        }
4226    },
4227
4228    // 插入 elem
4229    _insertElem: function _insertElem($elem) {
4230        var editor = this.editor;
4231        var range = editor.selection.getRange();
4232
4233        if (range.insertNode) {
4234            range.deleteContents();
4235            range.insertNode($elem[0]);
4236        }
4237    },
4238
4239    // 封装 execCommand
4240    _execCommand: function _execCommand(name, value) {
4241        document.execCommand(name, false, value);
4242    },
4243
4244    // 封装 document.queryCommandValue
4245    queryCommandValue: function queryCommandValue(name) {
4246        return document.queryCommandValue(name);
4247    },
4248
4249    // 封装 document.queryCommandState
4250    queryCommandState: function queryCommandState(name) {
4251        return document.queryCommandState(name);
4252    },
4253
4254    // 封装 document.queryCommandSupported
4255    queryCommandSupported: function queryCommandSupported(name) {
4256        return document.queryCommandSupported(name);
4257    }
4258};
4259
4260/*
4261    selection range API
4262*/
4263
4264// 构造函数
4265function API(editor) {
4266    this.editor = editor;
4267    this._currentRange = null;
4268}
4269
4270// 修改原型
4271API.prototype = {
4272    constructor: API,
4273
4274    // 获取 range 对象
4275    getRange: function getRange() {
4276        return this._currentRange;
4277    },
4278
4279    // 保存选区
4280    saveRange: function saveRange(_range) {
4281        if (_range) {
4282            // 保存已有选区
4283            this._currentRange = _range;
4284            return;
4285        }
4286
4287        // 获取当前的选区
4288        var selection = window.getSelection();
4289        if (selection.rangeCount === 0) {
4290            return;
4291        }
4292        var range = selection.getRangeAt(0);
4293
4294        // 判断选区内容是否在编辑内容之内
4295        var $containerElem = this.getSelectionContainerElem(range);
4296        if (!$containerElem) {
4297            return;
4298        }
4299
4300        // 判断选区内容是否在不可编辑区域之内
4301        if ($containerElem.attr('contenteditable') === 'false' || $containerElem.parentUntil('[contenteditable=false]')) {
4302            return;
4303        }
4304
4305        var editor = this.editor;
4306        var $textElem = editor.$textElem;
4307        if ($textElem.isContain($containerElem)) {
4308            // 是编辑内容之内的
4309            this._currentRange = range;
4310        }
4311    },
4312
4313    // 折叠选区
4314    collapseRange: function collapseRange(toStart) {
4315        if (toStart == null) {
4316            // 默认为 false
4317            toStart = false;
4318        }
4319        var range = this._currentRange;
4320        if (range) {
4321            range.collapse(toStart);
4322        }
4323    },
4324
4325    // 选中区域的文字
4326    getSelectionText: function getSelectionText() {
4327        var range = this._currentRange;
4328        if (range) {
4329            return this._currentRange.toString();
4330        } else {
4331            return '';
4332        }
4333    },
4334
4335    // 选区的 $Elem
4336    getSelectionContainerElem: function getSelectionContainerElem(range) {
4337        range = range || this._currentRange;
4338        var elem = void 0;
4339        if (range) {
4340            elem = range.commonAncestorContainer;
4341            return $(elem.nodeType === 1 ? elem : elem.parentNode);
4342        }
4343    },
4344    getSelectionStartElem: function getSelectionStartElem(range) {
4345        range = range || this._currentRange;
4346        var elem = void 0;
4347        if (range) {
4348            elem = range.startContainer;
4349            return $(elem.nodeType === 1 ? elem : elem.parentNode);
4350        }
4351    },
4352    getSelectionEndElem: function getSelectionEndElem(range) {
4353        range = range || this._currentRange;
4354        var elem = void 0;
4355        if (range) {
4356            elem = range.endContainer;
4357            return $(elem.nodeType === 1 ? elem : elem.parentNode);
4358        }
4359    },
4360
4361    // 选区是否为空
4362    isSelectionEmpty: function isSelectionEmpty() {
4363        var range = this._currentRange;
4364        if (range && range.startContainer) {
4365            if (range.startContainer === range.endContainer) {
4366                if (range.startOffset === range.endOffset) {
4367                    return true;
4368                }
4369            }
4370        }
4371        return false;
4372    },
4373
4374    // 恢复选区
4375    restoreSelection: function restoreSelection() {
4376        var selection = window.getSelection();
4377        selection.removeAllRanges();
4378        selection.addRange(this._currentRange);
4379    },
4380
4381    // 创建一个空白(即 &#8203 字符)选区
4382    createEmptyRange: function createEmptyRange() {
4383        var editor = this.editor;
4384        var range = this.getRange();
4385        var $elem = void 0;
4386
4387        if (!range) {
4388            // 当前无 range
4389            return;
4390        }
4391        if (!this.isSelectionEmpty()) {
4392            // 当前选区必须没有内容才可以
4393            return;
4394        }
4395
4396        try {
4397            // 目前只支持 webkit 内核
4398            if (UA.isWebkit()) {
4399                // 插入 &#8203
4400                editor.cmd.do('insertHTML', '&#8203;');
4401                // 修改 offset 位置
4402                range.setEnd(range.endContainer, range.endOffset + 1);
4403                // 存储
4404                this.saveRange(range);
4405            } else {
4406                $elem = $('<strong>&#8203;</strong>');
4407                editor.cmd.do('insertElem', $elem);
4408                this.createRangeByElem($elem, true);
4409            }
4410        } catch (ex) {
4411            // 部分情况下会报错,兼容一下
4412        }
4413    },
4414
4415    // 根据 $Elem 设置选区
4416    createRangeByElem: function createRangeByElem($elem, toStart, isContent) {
4417        // $elem - 经过封装的 elem
4418        // toStart - true 开始位置,false 结束位置
4419        // isContent - 是否选中Elem的内容
4420        if (!$elem.length) {
4421            return;
4422        }
4423
4424        var elem = $elem[0];
4425        var range = document.createRange();
4426
4427        if (isContent) {
4428            range.selectNodeContents(elem);
4429        } else {
4430            range.selectNode(elem);
4431        }
4432
4433        if (typeof toStart === 'boolean') {
4434            range.collapse(toStart);
4435        }
4436
4437        // 存储 range
4438        this.saveRange(range);
4439    }
4440};
4441
4442/*
4443    上传进度条
4444*/
4445
4446function Progress(editor) {
4447    this.editor = editor;
4448    this._time = 0;
4449    this._isShow = false;
4450    this._isRender = false;
4451    this._timeoutId = 0;
4452    this.$textContainer = editor.$textContainerElem;
4453    this.$bar = $('<div class="w-e-progress"></div>');
4454}
4455
4456Progress.prototype = {
4457    constructor: Progress,
4458
4459    show: function show(progress) {
4460        var _this = this;
4461
4462        // 状态处理
4463        if (this._isShow) {
4464            return;
4465        }
4466        this._isShow = true;
4467
4468        // 渲染
4469        var $bar = this.$bar;
4470        if (!this._isRender) {
4471            var $textContainer = this.$textContainer;
4472            $textContainer.append($bar);
4473        } else {
4474            this._isRender = true;
4475        }
4476
4477        // 改变进度(节流,100ms 渲染一次)
4478        if (Date.now() - this._time > 100) {
4479            if (progress <= 1) {
4480                $bar.css('width', progress * 100 + '%');
4481                this._time = Date.now();
4482            }
4483        }
4484
4485        // 隐藏
4486        var timeoutId = this._timeoutId;
4487        if (timeoutId) {
4488            clearTimeout(timeoutId);
4489        }
4490        timeoutId = setTimeout(function () {
4491            _this._hide();
4492        }, 500);
4493    },
4494
4495    _hide: function _hide() {
4496        var $bar = this.$bar;
4497        $bar.remove();
4498
4499        // 修改状态
4500        this._time = 0;
4501        this._isShow = false;
4502        this._isRender = false;
4503    }
4504};
4505
4506var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
4507  return typeof obj;
4508} : function (obj) {
4509  return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
4510};
4511
4512/*
4513    上传图片
4514*/
4515
4516// 构造函数
4517function UploadImg(editor) {
4518    this.editor = editor;
4519}
4520
4521// 原型
4522UploadImg.prototype = {
4523    constructor: UploadImg,
4524
4525    // 根据 debug 弹出不同的信息
4526    _alert: function _alert(alertInfo, debugInfo) {
4527        var editor = this.editor;
4528        var debug = editor.config.debug;
4529        var customAlert = editor.config.customAlert;
4530
4531        if (debug) {
4532            throw new Error('wangEditor: ' + (debugInfo || alertInfo));
4533        } else {
4534            if (customAlert && typeof customAlert === 'function') {
4535                customAlert(alertInfo);
4536            } else {
4537                alert(alertInfo);
4538            }
4539        }
4540    },
4541
4542    // 根据链接插入图片
4543    insertLinkImg: function insertLinkImg(link) {
4544        var _this2 = this;
4545
4546        if (!link) {
4547            return;
4548        }
4549        var editor = this.editor;
4550        var config = editor.config;
4551
4552        // 校验格式
4553        var linkImgCheck = config.linkImgCheck;
4554        var checkResult = void 0;
4555        if (linkImgCheck && typeof linkImgCheck === 'function') {
4556            checkResult = linkImgCheck(link);
4557            if (typeof checkResult === 'string') {
4558                // 校验失败,提示信息
4559                alert(checkResult);
4560                return;
4561            }
4562        }
4563
4564        editor.cmd.do('insertHTML', '<img src="' + link + '" style="max-width:100%;"/>');
4565
4566        // 验证图片 url 是否有效,无效的话给出提示
4567        var img = document.createElement('img');
4568        img.onload = function () {
4569            var callback = config.linkImgCallback;
4570            if (callback && typeof callback === 'function') {
4571                callback(link);
4572            }
4573
4574            img = null;
4575        };
4576        img.onerror = function () {
4577            img = null;
4578            // 无法成功下载图片
4579            _this2._alert('插入图片错误', 'wangEditor: \u63D2\u5165\u56FE\u7247\u51FA\u9519\uFF0C\u56FE\u7247\u94FE\u63A5\u662F "' + link + '"\uFF0C\u4E0B\u8F7D\u8BE5\u94FE\u63A5\u5931\u8D25');
4580            return;
4581        };
4582        img.onabort = function () {
4583            img = null;
4584        };
4585        img.src = link;
4586    },
4587
4588    // 上传图片
4589    uploadImg: function uploadImg(files) {
4590        var _this3 = this;
4591
4592        if (!files || !files.length) {
4593            return;
4594        }
4595
4596        // ------------------------------ 获取配置信息 ------------------------------
4597        var editor = this.editor;
4598        var config = editor.config;
4599        var uploadImgServer = config.uploadImgServer;
4600        var uploadImgShowBase64 = config.uploadImgShowBase64;
4601
4602        var maxSize = config.uploadImgMaxSize;
4603        var maxSizeM = maxSize / 1024 / 1024;
4604        var maxLength = config.uploadImgMaxLength || 10000;
4605        var uploadFileName = config.uploadFileName || '';
4606        var uploadImgParams = config.uploadImgParams || {};
4607        var uploadImgParamsWithUrl = config.uploadImgParamsWithUrl;
4608        var uploadImgHeaders = config.uploadImgHeaders || {};
4609        var hooks = config.uploadImgHooks || {};
4610        var timeout = config.uploadImgTimeout || 3000;
4611        var withCredentials = config.withCredentials;
4612        if (withCredentials == null) {
4613            withCredentials = false;
4614        }
4615        var customUploadImg = config.customUploadImg;
4616
4617        if (!customUploadImg) {
4618            // 没有 customUploadImg 的情况下,需要如下两个配置才能继续进行图片上传
4619            if (!uploadImgServer && !uploadImgShowBase64) {
4620                return;
4621            }
4622        }
4623
4624        // ------------------------------ 验证文件信息 ------------------------------
4625        var resultFiles = [];
4626        var errInfo = [];
4627        arrForEach(files, function (file) {
4628            var name = file.name;
4629            var size = file.size;
4630
4631            // chrome 低版本 name === undefined
4632            if (!name || !size) {
4633                return;
4634            }
4635
4636            if (/\.(jpg|jpeg|png|bmp|gif|webp)$/i.test(name) === false) {
4637                // 后缀名不合法,不是图片
4638                errInfo.push('\u3010' + name + '\u3011\u4E0D\u662F\u56FE\u7247');
4639                return;
4640            }
4641            if (maxSize < size) {
4642                // 上传图片过大
4643                errInfo.push('\u3010' + name + '\u3011\u5927\u4E8E ' + maxSizeM + 'M');
4644                return;
4645            }
4646
4647            // 验证通过的加入结果列表
4648            resultFiles.push(file);
4649        });
4650        // 抛出验证信息
4651        if (errInfo.length) {
4652            this._alert('图片验证未通过: \n' + errInfo.join('\n'));
4653            return;
4654        }
4655        if (resultFiles.length > maxLength) {
4656            this._alert('一次最多上传' + maxLength + '张图片');
4657            return;
4658        }
4659
4660        // ------------------------------ 自定义上传 ------------------------------
4661        if (customUploadImg && typeof customUploadImg === 'function') {
4662            customUploadImg(resultFiles, this.insertLinkImg.bind(this));
4663
4664            // 阻止以下代码执行
4665            return;
4666        }
4667
4668        // 添加图片数据
4669        var formdata = new FormData();
4670        arrForEach(resultFiles, function (file) {
4671            var name = uploadFileName || file.name;
4672            formdata.append(name, file);
4673        });
4674
4675        // ------------------------------ 上传图片 ------------------------------
4676        if (uploadImgServer && typeof uploadImgServer === 'string') {
4677            // 添加参数
4678            var uploadImgServerArr = uploadImgServer.split('#');
4679            uploadImgServer = uploadImgServerArr[0];
4680            var uploadImgServerHash = uploadImgServerArr[1] || '';
4681            objForEach(uploadImgParams, function (key, val) {
4682                // 因使用者反应,自定义参数不能默认 encode ,由 v3.1.1 版本开始注释掉
4683                // val = encodeURIComponent(val)
4684
4685                // 第一,将参数拼接到 url 中
4686                if (uploadImgParamsWithUrl) {
4687                    if (uploadImgServer.indexOf('?') > 0) {
4688                        uploadImgServer += '&';
4689                    } else {
4690                        uploadImgServer += '?';
4691                    }
4692                    uploadImgServer = uploadImgServer + key + '=' + val;
4693                }
4694
4695                // 第二,将参数添加到 formdata 中
4696                formdata.append(key, val);
4697            });
4698            if (uploadImgServerHash) {
4699                uploadImgServer += '#' + uploadImgServerHash;
4700            }
4701
4702            // 定义 xhr
4703            var xhr = new XMLHttpRequest();
4704            xhr.open('POST', uploadImgServer);
4705
4706            // 设置超时
4707            xhr.timeout = timeout;
4708            xhr.ontimeout = function () {
4709                // hook - timeout
4710                if (hooks.timeout && typeof hooks.timeout === 'function') {
4711                    hooks.timeout(xhr, editor);
4712                }
4713
4714                _this3._alert('上传图片超时');
4715            };
4716
4717            // 监控 progress
4718            if (xhr.upload) {
4719                xhr.upload.onprogress = function (e) {
4720                    var percent = void 0;
4721                    // 进度条
4722                    var progressBar = new Progress(editor);
4723                    if (e.lengthComputable) {
4724                        percent = e.loaded / e.total;
4725                        progressBar.show(percent);
4726                    }
4727                };
4728            }
4729
4730            // 返回数据
4731            xhr.onreadystatechange = function () {
4732                var result = void 0;
4733                if (xhr.readyState === 4) {
4734                    if (xhr.status < 200 || xhr.status >= 300) {
4735                        // hook - error
4736                        if (hooks.error && typeof hooks.error === 'function') {
4737                            hooks.error(xhr, editor);
4738                        }
4739
4740                        // xhr 返回状态错误
4741                        _this3._alert('上传图片发生错误', '\u4E0A\u4F20\u56FE\u7247\u53D1\u751F\u9519\u8BEF\uFF0C\u670D\u52A1\u5668\u8FD4\u56DE\u72B6\u6001\u662F ' + xhr.status);
4742                        return;
4743                    }
4744
4745                    result = xhr.responseText;
4746                    if ((typeof result === 'undefined' ? 'undefined' : _typeof(result)) !== 'object') {
4747                        try {
4748                            result = JSON.parse(result);
4749                        } catch (ex) {
4750                            // hook - fail
4751                            if (hooks.fail && typeof hooks.fail === 'function') {
4752                                hooks.fail(xhr, editor, result);
4753                            }
4754
4755                            _this3._alert('上传图片失败', '上传图片返回结果错误,返回结果是: ' + result);
4756                            return;
4757                        }
4758                    }
4759                    if (!hooks.customInsert && result.errno != '0') {
4760                        // hook - fail
4761                        if (hooks.fail && typeof hooks.fail === 'function') {
4762                            hooks.fail(xhr, editor, result);
4763                        }
4764
4765                        // 数据错误
4766                        _this3._alert('上传图片失败', '上传图片返回结果错误,返回结果 errno=' + result.errno);
4767                    } else {
4768                        if (hooks.customInsert && typeof hooks.customInsert === 'function') {
4769                            // 使用者自定义插入方法
4770                            hooks.customInsert(_this3.insertLinkImg.bind(_this3), result, editor);
4771                        } else {
4772                            // 将图片插入编辑器
4773                            var data = result.data || [];
4774                            data.forEach(function (link) {
4775                                _this3.insertLinkImg(link);
4776                            });
4777                        }
4778
4779                        // hook - success
4780                        if (hooks.success && typeof hooks.success === 'function') {
4781                            hooks.success(xhr, editor, result);
4782                        }
4783                    }
4784                }
4785            };
4786
4787            // hook - before
4788            if (hooks.before && typeof hooks.before === 'function') {
4789                var beforeResult = hooks.before(xhr, editor, resultFiles);
4790                if (beforeResult && (typeof beforeResult === 'undefined' ? 'undefined' : _typeof(beforeResult)) === 'object') {
4791                    if (beforeResult.prevent) {
4792                        // 如果返回的结果是 {prevent: true, msg: 'xxxx'} 则表示用户放弃上传
4793                        this._alert(beforeResult.msg);
4794                        return;
4795                    }
4796                }
4797            }
4798
4799            // 自定义 headers
4800            objForEach(uploadImgHeaders, function (key, val) {
4801                xhr.setRequestHeader(key, val);
4802            });
4803
4804            // 跨域传 cookie
4805            xhr.withCredentials = withCredentials;
4806
4807            // 发送请求
4808            xhr.send(formdata);
4809
4810            // 注意,要 return 。不去操作接下来的 base64 显示方式
4811            return;
4812        }
4813
4814        // ------------------------------ 显示 base64 格式 ------------------------------
4815        if (uploadImgShowBase64) {
4816            arrForEach(files, function (file) {
4817                var _this = _this3;
4818                var reader = new FileReader();
4819                reader.readAsDataURL(file);
4820                reader.onload = function () {
4821                    _this.insertLinkImg(this.result);
4822                };
4823            });
4824        }
4825    }
4826};
4827
4828/*
4829    编辑器构造函数
4830*/
4831
4832// id,累加
4833var editorId = 1;
4834
4835// 构造函数
4836function Editor(toolbarSelector, textSelector) {
4837    if (toolbarSelector == null) {
4838        // 没有传入任何参数,报错
4839        throw new Error('错误:初始化编辑器时候未传入任何参数,请查阅文档');
4840    }
4841    // id,用以区分单个页面不同的编辑器对象
4842    this.id = 'wangEditor-' + editorId++;
4843
4844    this.toolbarSelector = toolbarSelector;
4845    this.textSelector = textSelector;
4846
4847    // 自定义配置
4848    this.customConfig = {};
4849
4850    // undo列表记录、redo列表记录
4851    this.undoList = [];
4852    this.redoList = [];
4853}
4854
4855// 修改原型
4856Editor.prototype = {
4857    constructor: Editor,
4858
4859    // 初始化配置
4860    _initConfig: function _initConfig() {
4861        // _config 是默认配置,this.customConfig 是用户自定义配置,将它们 merge 之后再赋值
4862        var target = {};
4863        this.config = Object.assign(target, config, this.customConfig);
4864
4865        // 将语言配置,生成正则表达式
4866        var langConfig = this.config.lang || {};
4867        var langArgs = [];
4868        objForEach(langConfig, function (key, val) {
4869            // key 即需要生成正则表达式的规则,如“插入链接”
4870            // val 即需要被替换成的语言,如“insert link”
4871            langArgs.push({
4872                reg: new RegExp(key, 'img'),
4873                val: val
4874
4875            });
4876        });
4877        this.config.langArgs = langArgs;
4878    },
4879
4880    // 初始化 DOM
4881    _initDom: function _initDom() {
4882        var _this = this;
4883
4884        var toolbarSelector = this.toolbarSelector;
4885        var $toolbarSelector = $(toolbarSelector);
4886        var textSelector = this.textSelector;
4887
4888        var config$$1 = this.config;
4889        var zIndex = config$$1.zIndex;
4890
4891        // 定义变量
4892        var $toolbarElem = void 0,
4893            $textContainerElem = void 0,
4894            $textElem = void 0,
4895            $children = void 0;
4896
4897        if (textSelector == null) {
4898            // 只传入一个参数,即是容器的选择器或元素,toolbar 和 text 的元素自行创建
4899            $toolbarElem = $('<div></div>');
4900            $textContainerElem = $('<div></div>');
4901
4902            // 将编辑器区域原有的内容,暂存起来
4903            $children = $toolbarSelector.children();
4904
4905            // 添加到 DOM 结构中
4906            $toolbarSelector.append($toolbarElem).append($textContainerElem);
4907
4908            // 自行创建的,需要配置默认的样式
4909            $toolbarElem.css('background-color', '#f1f1f1').css('border', '1px solid #ccc');
4910            $textContainerElem.css('border', '1px solid #ccc').css('border-top', 'none').css('min-height', '32px');
4911        } else {
4912            // toolbar 和 text 的选择器都有值,记录属性
4913            $toolbarElem = $toolbarSelector;
4914            $textContainerElem = $(textSelector);
4915            // 将编辑器区域原有的内容,暂存起来
4916            $children = $textContainerElem.children();
4917        }
4918
4919        // 编辑区域
4920        $textElem = $('<div></div>');
4921        $textElem.attr('contenteditable', 'true').css('width', '100%').css('height', '100%');
4922
4923        // 初始化编辑区域内容
4924        if ($children && $children.length) {
4925            $textElem.append($children);
4926        } else {
4927            $textElem.append($('<p><br></p>'));
4928        }
4929
4930        // 编辑区域加入DOM
4931        $textContainerElem.append($textElem);
4932
4933        // 设置通用的 class
4934        $toolbarElem.addClass('w-e-toolbar');
4935        $textContainerElem.addClass('w-e-text-container');
4936        $textContainerElem.css('z-index', zIndex);
4937        $textElem.addClass('w-e-text');
4938
4939        // 添加 ID
4940        var toolbarElemId = getRandom('toolbar-elem');
4941        $toolbarElem.attr('id', toolbarElemId);
4942        var textElemId = getRandom('text-elem');
4943        $textElem.attr('id', textElemId);
4944
4945        // 记录属性
4946        this.$toolbarElem = $toolbarElem;
4947        this.$textContainerElem = $textContainerElem;
4948        this.$textElem = $textElem;
4949        this.toolbarElemId = toolbarElemId;
4950        this.textElemId = textElemId;
4951
4952        // 记录输入法的开始和结束
4953        var compositionEnd = true;
4954        $textContainerElem.on('compositionstart', function () {
4955            // 输入法开始输入
4956            compositionEnd = false;
4957        });
4958        $textContainerElem.on('compositionend', function () {
4959            // 输入法结束输入
4960            compositionEnd = true;
4961        });
4962
4963        // 绑定 onchange
4964        $textContainerElem.on('click keyup', function () {
4965            // 输入法结束才出发 onchange
4966            compositionEnd && _this.change && _this.change();
4967        });
4968        $toolbarElem.on('click', function () {
4969            this.change && this.change();
4970        });
4971
4972        //绑定 onfocus 与 onblur 事件
4973        if (config$$1.onfocus || config$$1.onblur) {
4974            // 当前编辑器是否是焦点状态
4975            this.isFocus = false;
4976
4977            $(document).on('click', function (e) {
4978                //判断当前点击元素是否在编辑器内
4979                var isChild = $textElem.isContain($(e.target));
4980
4981                //判断当前点击元素是否为工具栏
4982                var isToolbar = $toolbarElem.isContain($(e.target));
4983                var isMenu = $toolbarElem[0] == e.target ? true : false;
4984
4985                if (!isChild) {
4986                    //若为选择工具栏中的功能,则不视为成blur操作
4987                    if (isToolbar && !isMenu) {
4988                        return;
4989                    }
4990
4991                    if (_this.isFocus) {
4992                        _this.onblur && _this.onblur();
4993                    }
4994                    _this.isFocus = false;
4995                } else {
4996                    if (!_this.isFocus) {
4997                        _this.onfocus && _this.onfocus();
4998                    }
4999                    _this.isFocus = true;
5000                }
5001            });
5002        }
5003    },
5004
5005    // 封装 command
5006    _initCommand: function _initCommand() {
5007        this.cmd = new Command(this);
5008    },
5009
5010    // 封装 selection range API
5011    _initSelectionAPI: function _initSelectionAPI() {
5012        this.selection = new API(this);
5013    },
5014
5015    // 添加图片上传
5016    _initUploadImg: function _initUploadImg() {
5017        this.uploadImg = new UploadImg(this);
5018    },
5019
5020    // 初始化菜单
5021    _initMenus: function _initMenus() {
5022        this.menus = new Menus(this);
5023        this.menus.init();
5024    },
5025
5026    // 添加 text 区域
5027    _initText: function _initText() {
5028        this.txt = new Text(this);
5029        this.txt.init();
5030    },
5031
5032    // 初始化选区,将光标定位到内容尾部
5033    initSelection: function initSelection(newLine) {
5034        var $textElem = this.$textElem;
5035        var $children = $textElem.children();
5036        
5037        if (!$children.length) {
5038            // 如果编辑器区域无内容,添加一个空行,重新设置选区
5039            $textElem.append($('<p><br></p>'));
5040            this.initSelection();
5041            return;
5042        }
5043
5044        var $last = $children.last();
5045
5046        if (newLine) {
5047            // 新增一个空行
5048            var html = $last.html().toLowerCase();
5049            var nodeName = $last.getNodeName();
5050            if (html !== '<br>' && html !== '<br\/>' || nodeName !== 'P') {
5051                // 最后一个元素不是 <p><br></p>,添加一个空行,重新设置选区
5052                $textElem.append($('<p><br></p>'));
5053                this.initSelection();
5054                return;
5055            }
5056        }
5057
5058        this.selection.createRangeByElem($last, false, true);
5059        this.selection.restoreSelection();
5060    },
5061
5062    // undo记录
5063    _undoRecord: function _undoRecord(){
5064        var val = this.txt.html();
5065        var currentVal = this.undoList.length ? this.undoList[0] : '';
5066
5067        if (val === currentVal) {
5068            return;
5069        }
5070
5071        // 清空 redolist
5072        if (this.redoList.length) {
5073            this.redoList = [];
5074        }
5075
5076        // 添加数据到 undoList
5077        this.undoList.unshift({
5078            range: this.selection.getRange(),  // 将当前的range也记录下
5079            val: val
5080        });
5081
5082        // 限制 undoList 长度 限制为20
5083        if (this.undoList.length > 20) {
5084            this.undoList.pop();
5085        }
5086    },
5087
5088    // 绑定事件
5089    _bindEvent: function _bindEvent() {
5090        // -------- 绑定 onchange 事件 --------
5091        var onChangeTimeoutId = 0;
5092        var beforeChangeHtml = this.txt.html();
5093        var config$$1 = this.config;
5094
5095        // onchange 触发延迟时间
5096        var onchangeTimeout = config$$1.onchangeTimeout;
5097        onchangeTimeout = parseInt(onchangeTimeout, 10);
5098        if (!onchangeTimeout || onchangeTimeout <= 0) {
5099            onchangeTimeout = 200;
5100        }
5101
5102        var onchange = config$$1.onchange;
5103        if (onchange && typeof onchange === 'function') {
5104            // 触发 change 的有三个场景:
5105            // 1. $textContainerElem.on('click keyup')
5106            // 2. $toolbarElem.on('click')
5107            // 3. editor.cmd.do()
5108            this.change = function () {
5109                // 判断是否有变化
5110                var currentHtml = this.txt.html();
5111
5112                // 记录在undolist中
5113                this._undoRecord();
5114
5115                if (currentHtml.length === beforeChangeHtml.length) {
5116                    // 需要比较每一个字符
5117                    if (currentHtml === beforeChangeHtml) {
5118                        return;
5119                    }
5120                }
5121
5122                // 执行,使用节流
5123                if (onChangeTimeoutId) {
5124                    clearTimeout(onChangeTimeoutId);
5125                }
5126                onChangeTimeoutId = setTimeout(function () {
5127                    // 触发配置的 onchange 函数
5128                    onchange(currentHtml);
5129                    beforeChangeHtml = currentHtml;
5130                }, onchangeTimeout);
5131            };
5132        }
5133
5134        // -------- 绑定 onblur 事件 --------
5135        var onblur = config$$1.onblur;
5136        if (onblur && typeof onblur === 'function') {
5137            this.onblur = function () {
5138                var currentHtml = this.txt.html();
5139                onblur(currentHtml);
5140            };
5141        }
5142
5143        // -------- 绑定 onfocus 事件 --------
5144        var onfocus = config$$1.onfocus;
5145        if (onfocus && typeof onfocus === 'function') {
5146            this.onfocus = function () {
5147                onfocus();
5148            };
5149        }
5150    },
5151
5152    // 创建编辑器
5153    create: function create(data_init) {
5154        // 初始化配置信息
5155        this._initConfig();
5156
5157        // 初始化 DOM
5158        this._initDom();
5159
5160        // 封装 command API
5161        this._initCommand();
5162
5163        // 封装 selection range API
5164        this._initSelectionAPI();
5165
5166        // 添加 text
5167        this._initText();
5168
5169        // 初始化菜单
5170        this._initMenus();
5171
5172        // 添加 图片上传
5173        this._initUploadImg();
5174
5175        // 初始化选区,将光标定位到内容尾部
5176        if(data_init){
5177            // 编辑区域已存在文本数据,无需新增一行
5178            this.initSelection();
5179        }
5180        else{
5181            this.initSelection(true);
5182        }
5183
5184        // 绑定事件
5185        this._bindEvent();
5186
5187        this._undoRecord();
5188    },
5189
5190    // 解绑所有事件(暂时不对外开放)
5191    _offAllEvent: function _offAllEvent() {
5192        $.offAll();
5193    },
5194
5195    /* 封装ajax函数  
5196    * @param {string}opt.type http连接的方式,包括POST和GET两种方式  
5197    * @param {string}opt.url 发送请求的url  
5198    * @param {boolean}opt.async 是否为异步请求,true为异步的,false为同步的  
5199    * @param {object}opt.data 发送的参数,格式为对象类型  
5200    * @param {function}opt.success ajax发送并接收成功调用的回调函数  
5201    */  
5202    ajax:function ajax(opt) {  
5203        opt = opt || {};  
5204        opt.method = opt.method.toUpperCase() || 'POST';  
5205        opt.url = opt.url || '';  
5206        opt.async = opt.async || true;  
5207        opt.data = opt.data || null;  
5208        opt.success = opt.success || function () {};  
5209        var xmlHttp = null;  
5210        if (XMLHttpRequest) {  
5211            xmlHttp = new XMLHttpRequest();  
5212        }  
5213        else {  
5214            xmlHttp = new ActiveXObject('Microsoft.XMLHTTP');  
5215        }var params = [];  
5216        for (var key in opt.data){  
5217            params.push(key + '=' + opt.data[key]);  
5218        }  
5219        var postData = params.join('&');  
5220        if (opt.method.toUpperCase() === 'POST') {  
5221            xmlHttp.open(opt.method, opt.url, opt.async);  
5222            xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=utf-8');  
5223            xmlHttp.send(postData);  
5224        }  
5225        else if (opt.method.toUpperCase() === 'GET') {  
5226            xmlHttp.open(opt.method, opt.url + '?' + postData, opt.async);  
5227            xmlHttp.send(null);  
5228        }   
5229        xmlHttp.onreadystatechange = function () {  
5230            if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {  
5231                opt.success(JSON.parse(xmlHttp.responseText));  
5232            }  
5233        };  
5234    },
5235
5236    // 销毁编辑器
5237    destroy: function () {
5238        this.$toolbarElem.hide();
5239        this.$textContainerElem.addClass('stopediting');
5240        this.$textContainerElem.css('border','none');
5241        this.$textContainerElem.css('z-index',0);
5242        // this._offAllEvent();
5243    },
5244
5245    // 激活编辑器
5246    active: function(){
5247        this.$toolbarElem.css('display','flex');
5248        this.$textContainerElem.removeClass('stopediting');
5249        this.$textContainerElem.css('border','1px solid #ccc');
5250        this.$textContainerElem.css('z-index',config.zIndex);
5251        
5252    },
5253};
5254
5255// 检验是否浏览器环境
5256try {
5257    document;
5258} catch (ex) {
5259    throw new Error('请在浏览器环境下运行');
5260}
5261
5262// polyfill
5263polyfill();
5264
5265// 这里的 `inlinecss` 将被替换成 css 代码的内容,详情可去 ./gulpfile.js 中搜索 `inlinecss` 关键字
5266var inlinecss = '.w-e-toolbar,.w-e-text-container,.w-e-menu-panel {  padding: 0;  margin: 0;  box-sizing: border-box;}.w-e-toolbar *,.w-e-text-container *,.w-e-menu-panel * {  padding: 0;  margin: 0;  box-sizing: border-box;}.w-e-clear-fix:after {  content: "";  display: table;  clear: both;}.w-e-toolbar .w-e-droplist {  position: absolute;  left: 0;  top: 0;  background-color: #fff;  border: 1px solid #f1f1f1;  border-right-color: #ccc;  border-bottom-color: #ccc;}.w-e-toolbar .w-e-droplist .w-e-dp-title {  text-align: center;  color: #999;  line-height: 2;  border-bottom: 1px solid #f1f1f1;  font-size: 13px;}.w-e-toolbar .w-e-droplist ul.w-e-list {  list-style: none;  line-height: 1;}.w-e-toolbar .w-e-droplist ul.w-e-list li.w-e-item {  color: #333;  padding: 5px 0;}.w-e-toolbar .w-e-droplist ul.w-e-list li.w-e-item:hover {  background-color: #f1f1f1;}.w-e-toolbar .w-e-droplist ul.w-e-block {  list-style: none;  text-align: left;  padding: 5px;}.w-e-toolbar .w-e-droplist ul.w-e-block li.w-e-item {  display: inline-block;  *display: inline;  *zoom: 1;  padding: 3px 5px;}.w-e-toolbar .w-e-droplist ul.w-e-block li.w-e-item:hover {  background-color: #f1f1f1;}@font-face {  font-family: \'w-e-icon\';  src: url(data:application/x-font-woff;charset=utf-8;base64,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) format(\'truetype\');  font-weight: normal;  font-style: normal;}[class^="w-e-icon-"],[class*=" w-e-icon-"] {  /* use !important to prevent issues with browser extensions that change fonts */  font-family: \'w-e-icon\' !important;  speak: none;  font-style: normal;  font-weight: normal;  font-variant: normal;  text-transform: none;  line-height: 1;  /* Better Font Rendering =========== */  -webkit-font-smoothing: antialiased;  -moz-osx-font-smoothing: grayscale;}.w-e-icon-close:before {  content: "\\f00d";}.w-e-icon-upload2:before {  content: "\\e9c6";}.w-e-icon-trash-o:before {  content: "\\f014";}.w-e-icon-header:before {  content: "\\f1dc";}.w-e-icon-pencil2:before {  content: "\\e906";}.w-e-icon-paint-brush:before {  content: "\\f1fc";}.w-e-icon-image:before {  content: "\\e90d";}.w-e-icon-play:before {  content: "\\e912";}.w-e-icon-location:before {  content: "\\e947";}.w-e-icon-undo:before {  content: "\\e965";}.w-e-icon-redo:before {  content: "\\e966";}.w-e-icon-quotes-left:before {  content: "\\e977";}.w-e-icon-list-numbered:before {  content: "\\e9b9";}.w-e-icon-list2:before {  content: "\\e9bb";}.w-e-icon-link:before {  content: "\\e9cb";}.w-e-icon-happy:before {  content: "\\e9df";}.w-e-icon-bold:before {  content: "\\ea62";}.w-e-icon-underline:before {  content: "\\ea63";}.w-e-icon-italic:before {  content: "\\ea64";}.w-e-icon-strikethrough:before {  content: "\\ea65";}.w-e-icon-table2:before {  content: "\\ea71";}.w-e-icon-paragraph-left:before {  content: "\\ea77";}.w-e-icon-paragraph-center:before {  content: "\\ea78";}.w-e-icon-paragraph-right:before {  content: "\\ea79";}.w-e-icon-terminal:before {  content: "\\f120";}.w-e-icon-page-break:before {  content: "\\ea68";}.w-e-icon-cancel-circle:before {  content: "\\ea0d";}.w-e-icon-font:before {  content: "\\ea5c";}.w-e-icon-text-heigh:before {  content: "\\ea5f";}.w-e-toolbar {  display: -webkit-box;  display: -ms-flexbox;  display: flex;  padding: 0 5px;  /* flex-wrap: wrap; */  /* 单个菜单 */}.w-e-toolbar .w-e-menu {  position: relative;  text-align: center;  padding: 5px 10px;  cursor: pointer;}.w-e-toolbar .w-e-menu i {  color: #999;}.w-e-toolbar .w-e-menu:hover i {  color: #333;}.w-e-toolbar .w-e-active i {  color: #1e88e5;}.w-e-toolbar .w-e-active:hover i {  color: #1e88e5;}.w-e-text-container .w-e-panel-container {  position: absolute;  top: 0;
5266  left: 50%;  border: 1px solid #ccc;  border-top: 0;  box-shadow: 1px 1px 2px #ccc;  color: #333;  background-color: #fff;  /* 为 emotion panel 定制的样式 */  /* 上传图片的 panel 定制样式 */}.w-e-text-container .w-e-panel-container .w-e-panel-close {  position: absolute;  right: 0;  top: 0;  padding: 5px;  margin: 2px 5px 0 0;  cursor: pointer;  color: #999;}.w-e-text-container .w-e-panel-container .w-e-panel-close:hover {  color: #333;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-title {  list-style: none;  display: -webkit-box;  display: -ms-flexbox;  display: flex;  font-size: 14px;  margin: 2px 10px 0 10px;  border-bottom: 1px solid #f1f1f1;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-title .w-e-item {  padding: 3px 5px;  color: #999;  cursor: pointer;  margin: 0 3px;  position: relative;  top: 1px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-title .w-e-active {  color: #333;  border-bottom: 1px solid #333;  cursor: default;  font-weight: 700;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content {  padding: 10px 15px 10px 15px;  font-size: 16px;  /* 输入框的样式 */  /* 按钮的样式 */}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input:focus,.w-e-text-container .w-e-panel-container .w-e-panel-tab-content textarea:focus,.w-e-text-container .w-e-panel-container .w-e-panel-tab-content button:focus {  outline: none;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content textarea {  width: 100%;  border: 1px solid #ccc;  padding: 5px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content textarea:focus {  border-color: #1e88e5;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text] {  border: none;  border-bottom: 1px solid #ccc;  font-size: 14px;  height: 20px;  color: #333;  text-align: left;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text].small {  width: 30px;  text-align: center;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text].block {  display: block;  width: 100%;  margin: 10px 0;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text]:focus {  border-bottom: 2px solid #1e88e5;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button {  font-size: 14px;  color: #1e88e5;  border: none;  padding: 5px 10px;  background-color: #fff;  cursor: pointer;  border-radius: 3px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.left {  float: left;  margin-right: 10px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.right {  float: right;  margin-left: 10px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.gray {  color: #999;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.red {  color: #c24f4a;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button:hover {  background-color: #f1f1f1;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container:after {  content: "";  display: table;  clear: both;}.w-e-text-container .w-e-panel-container .w-e-emoticon-container .w-e-item {  cursor: pointer;  font-size: 18px;  padding: 0 3px;  display: inline-block;  *display: inline;  *zoom: 1;}.w-e-text-container .w-e-panel-container .w-e-up-img-container {  text-align: center;}.w-e-text-container .w-e-panel-container .w-e-up-img-container .w-e-up-btn {  display: inline-block;  *display: inline;  *zoom: 1;  color: #999;  cursor: pointer;  font-size: 60px;  line-height: 1;}.w-e-text-container .w-e-panel-container .w-e-up-img-container .w-e-up-btn:hover {  color: #333;}.w-e-text-container {  position: relative;}.w-e-text-container .w-e-progress {  position: absolute;  background-color: #1e88e5;  bottom: 0;  left: 0;  height: 1px;}.w-e-text {  padding: 0 10px;  overflow-y: scroll;}.w-e-text p,.w-e-text h1,.w-e-text h2,.w-e-text h3,.w-e-text h4,.w-e-text h5,.w-e-text table,.w-e-text pre {  margin: 10px 0;  line-height: 1.5;}.w-e-text ul,.w-e-text ol {  margin: 10px 0 10px 20px;}.w-e-text blockquote {  display: block;  border-left: 8px solid #d0e5f2;  padding: 5px 10px;  margin: 10px 0;  line-height: 1.4;  font-size: 100%;  background-color: #f1f1f1;}.w-e-text code {  display: inline-block;  *display: inline;  *zoom: 1;  background-color: #f1f1f1;  border-radius: 3px;  padding: 3px 5px;  margin: 0 3px;}.w-e-text pre code {  display: block;}.w-e-text table {  border-top: 1px solid #ccc;  border-left: 1px solid #ccc;}.w-e-text table td,.w-e-text table th {  border-bottom: 1px solid #ccc;  border-right: 1px solid #ccc;  padding: 3px 5px;}.w-e-text table th {  border-bottom: 2px solid #ccc;  text-align: center;}.w-e-text:focus {  outline: none;}.w-e-text img {  cursor: pointer;}.w-e-text img:hover {  box-shadow: 0 0 5px #333;}';
5267
5268// 将 css 代码添加到 <style> 中
5269var style = document.createElement('style');
5270style.type = 'text/css';
5271style.innerHTML = inlinecss;
5272// document.getElementsByTagName('HEAD').item(0).appendChild(style);
5273
5274// 返回
5275var index = window.wangEditor || Editor;
5276
5277return index;
5278
5279})));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.