(function (factory) { if (typeof window.define === 'function') { if (window.define.amd) { // AMD模式 window.define('wangEditor', ["jquery"], factory); } else if (window.define.cmd) { // CMD模式 window.define(function (require, exports, module) { return factory; }); } else { // 全局模式 factory(window.jQuery); } } else if (typeof module === "object" && typeof module.exports === "object") { // commonjs // 引用 css —— webapck require('../css/wangEditor.css'); module.exports = factory( // 传入 jquery ,支持使用 npm 方式或者自己定义jquery的路径 require('jquery') ); } else { // 全局模式 factory(window.jQuery); } })(function($){ // 验证是否引用jquery if (!$ || !$.fn || !$.fn.jquery) { alert('在引用wangEditor.js之前,先引用jQuery,否则无法使用 wangEditor'); return; } // 定义扩展函数 var _e = function (fn) { var E = window.wangEditor; if (E) { // 执行传入的函数 fn(E, $); } }; // 定义构造函数 (function (window, $) { if (window.wangEditor) { // 重复引用 alert('一个页面不能重复引用 wangEditor.js 或 wangEditor.min.js !!!'); return; } // 编辑器(整体)构造函数 var E = function (elem) { // 支持 id 和 element 两种形式 if (typeof elem === 'string') { elem = '#' + elem; } // ---------------获取基本节点------------------ var $elem = $(elem); if ($elem.length !== 1) { return; } var nodeName = $elem[0].nodeName; if (nodeName !== 'TEXTAREA' && nodeName !== 'DIV') { // 只能是 textarea 和 div ,其他类型的元素不行 return; } this.valueNodeName = nodeName.toLowerCase(); this.$valueContainer = $elem; // 记录 elem 的 prev 和 parent(最后渲染 editor 要用到) this.$prev = $elem.prev(); this.$parent = $elem.parent(); // ------------------初始化------------------ this.init(); }; E.fn = E.prototype; E.$body = $('body'); E.$document = $(document); E.$window = $(window); E.userAgent = navigator.userAgent; E.getComputedStyle = window.getComputedStyle; E.w3cRange = typeof document.createRange === 'function'; E.hostname = location.hostname.toLowerCase(); E.websiteHost = 'wangeditor.github.io|www.wangeditor.com|wangeditor.coding.me'; E.isOnWebsite = E.websiteHost.indexOf(E.hostname) >= 0; E.docsite = 'http://www.kancloud.cn/wangfupeng/wangeditor2/113961'; // 暴露给全局对象 window.wangEditor = E; // 注册 plugin 事件,用于用户自定义插件 // 用户在引用 wangEditor.js 之后,还可以通过 E.plugin() 注入自定义函数, // 该函数将会在 editor.create() 方法的最后一步执行 E.plugin = function (fn) { if (!E._plugins) { E._plugins = []; } if (typeof fn === 'function') { E._plugins.push(fn); } }; })(window, $); // editor 绑定事件 _e(function (E, $) { E.fn.init = function () { // 初始化 editor 默认配置 this.initDefaultConfig(); // 增加container this.addEditorContainer(); // 增加编辑区域 this.addTxt(); // 增加menuContainer this.addMenuContainer(); // 初始化菜单集合 this.menus = {}; // 初始化commandHooks this.commandHooks(); }; }); // editor api _e(function (E, $) { // 预定义 ready 事件 E.fn.ready = function (fn) { if (!this.readyFns) { this.readyFns = []; } this.readyFns.push(fn); }; // 处理ready事件 E.fn.readyHeadler = function () { var fns = this.readyFns; while (fns.length) { fns.shift().call(this); } }; // 更新内容到 $valueContainer E.fn.updateValue = function () { var editor = this; var $valueContainer = editor.$valueContainer; var $txt = editor.txt.$txt; if ($valueContainer === $txt) { // 传入生成编辑器的div,即是编辑区域 return; } var value = $txt.html(); $valueContainer.val(value); }; // 获取初始化的内容 E.fn.getInitValue = function () { var editor = this; var $valueContainer = editor.$valueContainer; var currentValue = ''; var nodeName = editor.valueNodeName; if (nodeName === 'div') { currentValue = $valueContainer.html(); } else if (nodeName === 'textarea') { currentValue = $valueContainer.val(); } return currentValue; }; // 触发菜单updatestyle E.fn.updateMenuStyle = function () { var menus = this.menus; $.each(menus, function (k, menu) { menu.updateSelected(); }); }; // 除了传入的 menuIds,其他全部启用 E.fn.enableMenusExcept = function (menuIds) { if (this._disabled) { // 编辑器处于禁用状态,则不执行改操作 return; } // menuIds参数:支持数组和字符串 menuIds = menuIds || []; if (typeof menuIds === 'string') { menuIds = [menuIds]; } $.each(this.menus, function (k, menu) { if (menuIds.indexOf(k) >= 0) { return; } menu.disabled(false); }); }; // 除了传入的 menuIds,其他全部禁用 E.fn.disableMenusExcept = function (menuIds) { if (this._disabled) { // 编辑器处于禁用状态,则不执行改操作 return; } // menuIds参数:支持数组和字符串 menuIds = menuIds || []; if (typeof menuIds === 'string') { menuIds = [menuIds]; } $.each(this.menus, function (k, menu) { if (menuIds.indexOf(k) >= 0) { return; } menu.disabled(true); }); }; // 隐藏所有 dropPanel droplist modal E.fn.hideDropPanelAndModal = function () { var menus = this.menus; $.each(menus, function (k, menu) { var m = menu.dropPanel || menu.dropList || menu.modal; if (m && m.hide) { m.hide(); } }); }; }); // selection range API _e(function (E, $) { // 用到 w3c range 的函数,如果检测到浏览器不支持 w3c range,则赋值为空函数 var ieRange = !E.w3cRange; function emptyFn() {} // 设置或读取当前的range E.fn.currentRange = function (cr){ if (cr) { this._rangeData = cr; } else { return this._rangeData; } }; // 将当前选区折叠 E.fn.collapseRange = function (range, opt) { // opt 参数说明:'start'-折叠到开始; 'end'-折叠到结束 opt = opt || 'end'; opt = opt === 'start' ? true : false; range = range || this.currentRange(); if (range) { // 合并,保存 range.collapse(opt); this.currentRange(range); } }; // 获取选区的文字 E.fn.getRangeText = ieRange ? emptyFn : function (range) { range = range || this.currentRange(); if (!range) { return; } return range.toString(); }; // 获取选区对应的DOM对象 E.fn.getRangeElem = ieRange ? emptyFn : function (range) { range = range || this.currentRange(); var dom = range.commonAncestorContainer; if (dom.nodeType === 1) { return dom; } else { return dom.parentNode; } }; // 选区内容是否为空? E.fn.isRangeEmpty = ieRange ? emptyFn : function (range) { range = range || this.currentRange(); if (range && range.startContainer) { if (range.startContainer === range.endContainer) { if (range.startOffset === range.endOffset) { return true; } } } return false; }; // 保存选区数据 E.fn.saveSelection = ieRange ? emptyFn : function (range) { var self = this, _parentElem, selection, txt = self.txt.$txt.get(0); if (range) { _parentElem = range.commonAncestorContainer; } else { selection = document.getSelection(); if (selection.getRangeAt && selection.rangeCount) { range = document.getSelection().getRangeAt(0); _parentElem = range.commonAncestorContainer; } } // 确定父元素一定要包含在编辑器区域内 if (_parentElem && ($.contains(txt, _parentElem) || txt === _parentElem) ) { // 保存选择区域 self.currentRange(range); } }; // 恢复选中区域 E.fn.restoreSelection = ieRange ? emptyFn : function (range) { var selection; range = range || this.currentRange(); if (!range) { return; } // 使用 try catch 来防止 IE 某些情况报错 try { selection = document.getSelection(); selection.removeAllRanges(); selection.addRange(range); } catch (ex) { E.error('执行 editor.restoreSelection 时,IE可能会有异常,不影响使用'); } }; // 根据elem恢复选区 E.fn.restoreSelectionByElem = ieRange ? emptyFn : function (elem, opt) { // opt参数说明:'start'-折叠到开始,'end'-折叠到结束,'all'-全部选中 if (!elem) { return; } opt = opt || 'end'; // 默认为折叠到结束 // 根据elem获取选区 this.setRangeByElem(elem); // 根据 opt 折叠选区 if (opt === 'start') { this.collapseRange(this.currentRange(), 'start'); } if (opt === 'end') { this.collapseRange(this.currentRange(), 'end'); } // 恢复选区 this.restoreSelection(); }; // 初始化选区 E.fn.initSelection = ieRange ? emptyFn : function () { var editor = this; if( editor.currentRange() ){ //如果currentRange有值,则不用再初始化 return; } var range; var $txt = editor.txt.$txt; var $firstChild = $txt.children().first(); if ($firstChild.length) { editor.restoreSelectionByElem($firstChild.get(0)); } }; // 根据元素创建选区 E.fn.setRangeByElem = ieRange ? emptyFn : function (elem) { var editor = this; var txtElem = editor.txt.$txt.get(0); if (!elem || !$.contains(txtElem, elem)) { return; } // 找到elem的第一个 textNode 和 最后一个 textNode var firstTextNode = elem.firstChild; while (firstTextNode) { if (firstTextNode.nodeType === 3) { break; } // 继续向下 firstTextNode = firstTextNode.firstChild; } var lastTextNode = elem.lastChild; while (lastTextNode) { if (lastTextNode.nodeType === 3) { break; } // 继续向下 lastTextNode = lastTextNode.lastChild; } var range = document.createRange(); if (firstTextNode && lastTextNode) { // 说明 elem 有内容,能取到子元素 range.setStart(firstTextNode, 0); range.setEnd(lastTextNode, lastTextNode.textContent.length); } else { // 说明 elem 无内容 range.setStart(elem, 0); range.setEnd(elem, 0); } // 保存选区 editor.saveSelection(range); }; }); // selection range API - IE8及以下 _e(function (E, $) { if (E.w3cRange) { // 说明支持 W3C 的range方法 return; } // -----------------IE8时,需要重写以下方法------------------- // 获取选区的文字 E.fn.getRangeText = function (range) { range = range || this.currentRange(); if (!range) { return; } return range.text; }; // 获取选区对应的DOM对象 E.fn.getRangeElem = function (range) { range = range || this.currentRange(); if (!range) { return; } var dom = range.parentElement(); if (dom.nodeType === 1) { return dom; } else { return dom.parentNode; } }; // 选区内容是否为空? E.fn.isRangeEmpty = function (range) { range = range || this.currentRange(); if (range && range.text) { return false; } return true; }; // 保存选区数据 E.fn.saveSelection = function (range) { var self = this, _parentElem, selection, txt = self.txt.$txt.get(0); if (range) { _parentElem = range.parentElement(); } else { range = document.selection.createRange(); if(typeof range.parentElement === 'undefined'){ //IE6、7中,insertImage后会执行此处 //由于找不到range.parentElement,所以干脆将_parentElem赋值为null _parentElem = null; }else{ _parentElem = range.parentElement(); } } // 确定父元素一定要包含在编辑器区域内 if (_parentElem && ($.contains(txt, _parentElem) || txt === _parentElem) ) { // 保存选择区域 self.currentRange(range); } }; // 恢复选中区域 E.fn.restoreSelection = function (currentRange){ var editor = this, selection, range; currentRange = currentRange || editor.currentRange(); if(!currentRange){ return; } range = document.selection.createRange(); try { // 此处,plupload上传上传图片时,IE8-会报一个『参数无效』的错误 range.setEndPoint('EndToEnd', currentRange); } catch (ex) { } if(currentRange.text.length === 0){ try { // IE8 插入表情会报错 range.collapse(false); } catch (ex) { } }else{ range.setEndPoint('StartToStart', currentRange); } range.select(); }; }); // editor command hooks _e(function (E, $) { E.fn.commandHooks = function () { var editor = this; var commandHooks = {}; // insertHtml commandHooks.insertHtml = function (html) { var $elem = $(html); var rangeElem = editor.getRangeElem(); var targetElem; targetElem = editor.getLegalTags(rangeElem); if (!targetElem) { return; } $(targetElem).after($elem); }; // 保存到对象 editor.commandHooks = commandHooks; }; }); // editor command API _e(function (E, $) { // 基本命令 E.fn.command = function (e, commandName, commandValue, callback) { var editor = this; var hooks; function commandFn() { if (!commandName) { return; } if (editor.queryCommandSupported(commandName)) { // 默认命令 document.execCommand(commandName, false, commandValue); } else { // hooks 命令 hooks = editor.commandHooks; if (commandName in hooks) { hooks[commandName](commandValue); } } } this.customCommand(e, commandFn, callback); }; // 针对一个elem对象执行基础命令 E.fn.commandForElem = function (elemOpt, e, commandName, commandValue, callback) { // 取得查询elem的查询条件和验证函数 var selector; var check; if (typeof elemOpt === 'string') { selector = elemOpt; } else { selector = elemOpt.selector; check = elemOpt.check; } // 查询elem var rangeElem = this.getRangeElem(); rangeElem = this.getSelfOrParentByName(rangeElem, selector, check); // 根据elem设置range if (rangeElem) { this.setRangeByElem(rangeElem); } // 然后执行基础命令 this.command(e, commandName, commandValue, callback); }; // 自定义命令 E.fn.customCommand = function (e, commandFn, callback) { var editor = this; var range = editor.currentRange(); if (!range) { // 目前没有选区,则无法执行命令 e && e.preventDefault(); return; } // 记录内容,以便撤销(执行命令之前就要记录) editor.undoRecord(); // 恢复选区(有 range 参数) this.restoreSelection(range); // 执行命令事件 commandFn.call(editor); // 保存选区(无参数,要从浏览器直接获取range信息) this.saveSelection(); // 重新恢复选区(无参数,要取得刚刚从浏览器得到的range信息) this.restoreSelection(); // 执行 callback if (callback && typeof callback === 'function') { callback.call(editor); } // 最后插入空行 editor.txt.insertEmptyP(); // 包裹暴露的img和text editor.txt.wrapImgAndText(); // 更新内容 editor.updateValue(); // 更新菜单样式 editor.updateMenuStyle(); // 隐藏 dropPanel dropList modal 设置 200ms 间隔 function hidePanelAndModal() { editor.hideDropPanelAndModal(); } setTimeout(hidePanelAndModal, 200); if (e) { e.preventDefault(); } }; // 封装 document.queryCommandValue 函数 // IE8 直接执行偶尔会报错,因此直接用 try catch 封装一下 E.fn.queryCommandValue = function (commandName) { var result = ''; try { result = document.queryCommandValue(commandName); } catch (ex) { } return result; }; // 封装 document.queryCommandState 函数 // IE8 直接执行偶尔会报错,因此直接用 try catch 封装一下 E.fn.queryCommandState = function (commandName) { var result = false; try { result = document.queryCommandState(commandName); } catch (ex) { } return result; }; // 封装 document.queryCommandSupported 函数 E.fn.queryCommandSupported = function (commandName) { var result = false; try { result = document.queryCommandSupported(commandName); } catch (ex) { } return result; }; }); // dom selector _e(function (E, $) { var matchesSelector; // matchesSelector hook function _matchesSelectorForIE(selector) { var elem = this; var $elems = $(selector); var result = false; // 用jquery查找 selector 所有对象,如果其中有一个和传入 elem 相同,则证明 elem 符合 selector $elems.each(function () { if (this === elem) { result = true; return false; } }); return result; } // 从当前的elem,往上去查找合法标签 如 p head table blockquote ul ol 等 E.fn.getLegalTags = function (elem) { var legalTags = this.config.legalTags; if (!legalTags) { E.error('配置项中缺少 legalTags 的配置'); return; } return this.getSelfOrParentByName(elem, legalTags); }; // 根据条件,查询自身或者父元素,符合即返回 E.fn.getSelfOrParentByName = function (elem, selector, check) { if (!elem || !selector) { return; } if (!matchesSelector) { // 定义 matchesSelector 函数 matchesSelector = elem.webkitMatchesSelector || elem.mozMatchesSelector || elem.oMatchesSelector || elem.matchesSelector; } if (!matchesSelector) { // 如果浏览器本身不支持 matchesSelector 则使用自定义的hook matchesSelector = _matchesSelectorForIE; } var txt = this.txt.$txt.get(0); while (elem && txt !== elem && $.contains(txt, elem)) { if (matchesSelector.call(elem, selector)) { // 符合 selector 查询条件 if (!check) { // 没有 check 验证函数,直接返回即可 return elem; } if (check(elem)) { // 如果有 check 验证函数,还需 check 函数的确认 return elem; } } // 如果上一步没经过验证,则将跳转到父元素 elem = elem.parentNode; } return; }; }); // undo redo _e(function (E, $) { var length = 20; // 缓存的最大长度 function _getRedoList(editor) { if (editor._redoList == null) { editor._redoList = []; } return editor._redoList; } function _getUndoList(editor) { if (editor._undoList == null) { editor._undoList = []; } return editor._undoList; } // 数据处理 function _handle(editor, data, type) { // var range = data.range; // var range2 = range.cloneRange && range.cloneRange(); var val = data.val; var html = editor.txt.$txt.html(); if(val == null) { return; } if (val === html) { if (type === 'redo') { editor.redo(); return; } else if (type === 'undo') { editor.undo(); return; } else { return; } } // 保存数据 editor.txt.$txt.html(val); // 更新数据到textarea(有必要的话) editor.updateValue(); // onchange 事件 if (editor.onchange && typeof editor.onchange === 'function') { editor.onchange.call(editor); } // ????? // 注释:$txt 被重新赋值之后,range会被重置,cloneRange() 也不好使 // // 重置选区 // if (range2) { // editor.restoreSelection(range2); // } } // 记录 E.fn.undoRecord = function () { var editor = this; var $txt = editor.txt.$txt; var val = $txt.html(); var undoList = _getUndoList(editor); var redoList = _getRedoList(editor); var currentVal = undoList.length ? undoList[0] : ''; if (val === currentVal.val) { return; } // 清空 redolist if (redoList.length) { redoList = []; } // 添加数据到 undoList undoList.unshift({ range: editor.currentRange(), // 将当前的range也记录下 val: val }); // 限制 undoList 长度 if (undoList.length > length) { undoList.pop(); } }; // undo 操作 E.fn.undo = function () { var editor = this; var undoList = _getUndoList(editor); var redoList = _getRedoList(editor); if (!undoList.length) { return; } // 取出 undolist 第一个值,加入 redolist var data = undoList.shift(); redoList.unshift(data); // 并修改编辑器的内容 _handle(this, data, 'undo'); }; // redo 操作 E.fn.redo = function () { var editor = this; var undoList = _getUndoList(editor); var redoList = _getRedoList(editor); if (!redoList.length) { return; } // 取出 redolist 第一个值,加入 undolist var data = redoList.shift(); undoList.unshift(data); // 并修改编辑器的内容 _handle(this, data, 'redo'); }; }); // 暴露给用户的 API _e(function (E, $) { // 创建编辑器 E.fn.create = function () { var editor = this; // 检查 E.$body 是否有值 // 如果在 body 之前引用了 js 文件,body 尚未加载,可能没有值 if (!E.$body || E.$body.length === 0) { E.$body = $('body'); E.$document = $(document); E.$window = $(window); } // 执行 addMenus 之前: // 1. 允许用户修改 editor.UI 自定义配置UI // 2. 允许用户通过修改 editor.menus 来自定义配置菜单 // 因此要在 create 时执行,而不是 init editor.addMenus(); // 渲染 editor.renderMenus(); editor.renderMenuContainer(); editor.renderTxt(); editor.renderEditorContainer(); // 绑定事件 editor.eventMenus(); editor.eventMenuContainer(); editor.eventTxt(); // 处理ready事件 editor.readyHeadler(); // 初始化选区 editor.initSelection(); // $txt 快捷方式 editor.$txt = editor.txt.$txt; // 执行用户自定义事件,通过 E.ready() 添加 var _plugins = E._plugins; if (_plugins && _plugins.length) { $.each(_plugins, function (k, val) { val.call(editor); }); } }; // 禁用编辑器 E.fn.disable = function () { this.txt.$txt.removeAttr('contenteditable'); this.disableMenusExcept(); // 先禁用,再记录状态 this._disabled = true; }; // 启用编辑器 E.fn.enable = function () { // 先解除状态记录,再启用 this._disabled = false; this.txt.$txt.attr('contenteditable', 'true'); this.enableMenusExcept(); }; // 销毁编辑器 E.fn.destroy = function () { var self = this; var $valueContainer = self.$valueContainer; var $editorContainer = self.$editorContainer; var valueNodeName = self.valueNodeName; if (valueNodeName === 'div') { // div 生成的编辑器 $valueContainer.removeAttr('contenteditable'); $editorContainer.after($valueContainer); $editorContainer.hide(); } else { // textarea 生成的编辑器 $valueContainer.show(); $editorContainer.hide(); } }; // 撤销 销毁编辑器 E.fn.undestroy = function () { var self = this; var $valueContainer = self.$valueContainer; var $editorContainer = self.$editorContainer; var $menuContainer = self.menuContainer.$menuContainer; var valueNodeName = self.valueNodeName; if (valueNodeName === 'div') { // div 生成的编辑器 $valueContainer.attr('contenteditable', 'true'); $menuContainer.after($valueContainer); $editorContainer.show(); } else { // textarea 生成的编辑器 $valueContainer.hide(); $editorContainer.show(); } }; // 清空内容的快捷方式 E.fn.clear = function () { var editor = this; var $txt = editor.txt.$txt; $txt.html('
' + title + '
'); // 先添加到body,计算完再 remove E.$body.append($tempDiv); editor.ready(function () { var editor = this; var titleWidth = $tempDiv.outerWidth() + 5; // 多出 5px 的冗余 var currentWidth = $tip.outerWidth(); var currentMarginLeft = parseFloat($tip.css('margin-left'), 10); // 计算完,拿到数据,则弃用 $tempDiv.remove(); $tempDiv = null; // 重新设置样式 $tip.css({ width: titleWidth, 'margin-left': currentMarginLeft + (currentWidth - titleWidth)/2 }); // 存储 self.tipWidth = titleWidth; }); } // $tip.append($triangle); $tip.append(title); $menuItem.append($tip); function show() { $tip.show(); } function hide() { $tip.hide(); } var timeoutId; $menuItem.find('a').on('mouseenter', function (e) { if (!self.active() && !self.disabled()) { timeoutId = setTimeout(show, 200); } }).on('mouseleave', function (e) { timeoutId && clearTimeout(timeoutId); hide(); }).on('click', hide); }; // 绑定事件 Menu.fn.bindEvent = function () { var self = this; var $domNormal = self.$domNormal; var $domSelected = self.$domSelected; // 试图获取该菜单定义的事件(未selected),没有则自己定义 var clickEvent = self.clickEvent; if (!clickEvent) { clickEvent = function (e) { // -----------dropPanel dropList modal----------- var dropObj = self.dropPanel || self.dropList || self.modal; if (dropObj && dropObj.show) { if (dropObj.isShowing) { dropObj.hide(); } else { dropObj.show(); } return; } // -----------command----------- var editor = self.editor; var commandName; var commandValue; var selected = self.selected; if (selected) { commandName = self.commandNameSelected; commandValue = self.commandValueSelected; } else { commandName = self.commandName; commandValue = self.commandValue; } if (commandName) { // 执行命令 editor.command(e, commandName, commandValue); } else { // 提示 E.warn('菜单 "' + self.id + '" 未定义click事件'); e.preventDefault(); } }; } // 获取菜单定义的selected情况下的点击事件 var clickEventSelected = self.clickEventSelected || clickEvent; // 将事件绑定到菜单dom上 $domNormal.click(function (e) { if (!self.disabled()) { clickEvent.call(self, e); self.updateSelected(); } e.preventDefault(); }); $domSelected.click(function (e) { if (!self.disabled()) { clickEventSelected.call(self, e); self.updateSelected(); } e.preventDefault(); }); }; // 更新选中状态 Menu.fn.updateSelected = function () { var self = this; var editor = self.editor; // 试图获取用户自定义的判断事件 var updateSelectedEvent = self.updateSelectedEvent; if (!updateSelectedEvent) { // 用户未自定义,则设置默认值 updateSelectedEvent = function () { var self = this; var editor = self.editor; var commandName = self.commandName; var commandValue = self.commandValue; if (commandValue) { if (editor.queryCommandValue(commandName).toLowerCase() === commandValue.toLowerCase()) { return true; } } else if (editor.queryCommandState(commandName)) { return true; } return false; }; } // 获取结果 var result = updateSelectedEvent.call(self); result = !!result; // 存储结果、显示效果 self.changeSelectedState(result); }; // 切换选中状态、显示效果 Menu.fn.changeSelectedState = function (state) { var self = this; var selected = self.selected; if (state != null && typeof state === 'boolean') { if (selected === state) { // 计算结果和当前的状态一样 return; } // 存储结果 self.selected = state; // 切换菜单的显示 if (state) { // 选中 self.$domNormal.hide(); self.$domSelected.show(); } else { // 未选中 self.$domNormal.show(); self.$domSelected.hide(); } } // if }; // 点击菜单,显示了 dropPanel modal 时,菜单的状态 Menu.fn.active = function (active) { if (active == null) { return this._activeState; } this._activeState = active; }; Menu.fn.activeStyle = function (active) { var selected = this.selected; var $dom = this.$domNormal; var $domSelected = this.$domSelected; if (active) { $dom.addClass('active'); $domSelected.addClass('active'); } else { $dom.removeClass('active'); $domSelected.removeClass('active'); } // 记录状态 ( menu hover 时会取状态用 ) this.active(active); }; // 菜单的启用和禁用 Menu.fn.disabled = function (opt) { // 参数为空,取值 if (opt == null) { return !!this._disabled; } if (this._disabled === opt) { // 要设置的参数值和当前参数只一样,无需再次设置 return; } var $dom = this.$domNormal; var $domSelected = this.$domSelected; // 设置样式 if (opt) { $dom.addClass('disable'); $domSelected.addClass('disable'); } else { $dom.removeClass('disable'); $domSelected.removeClass('disable'); } // 存储 this._disabled = opt; }; }); // dropList 构造函数 _e(function (E, $) { // 定义构造函数 var DropList = function (editor, menu, opt) { this.editor = editor; this.menu = menu; // list 的数据源,格式 {'commandValue': 'title', ...} this.data = opt.data; // 要为每个item自定义的模板 this.tpl = opt.tpl; // 为了执行 editor.commandForElem 而传入的elem查询方式 this.selectorForELemCommand = opt.selectorForELemCommand; // 执行事件前后的钩子 this.beforeEvent = opt.beforeEvent; this.afterEvent = opt.afterEvent; // 初始化 this.init(); }; DropList.fn = DropList.prototype; // 暴露给 E 即 window.wangEditor E.DropList = DropList; }); // dropList fn bind _e(function (E, $) { var DropList = E.DropList; // init DropList.fn.init = function () { var self = this; // 生成dom对象 self.initDOM(); // 绑定command事件 self.bindEvent(); // 声明隐藏的事件 self.initHideEvent(); }; // 初始化dom结构 DropList.fn.initDOM = function () { var self = this; var data = self.data; var tpl = self.tpl || '{#title}'; var $list = $(''); var itemContent; var $item; $.each(data, function (commandValue, title) { itemContent = tpl.replace(/{#commandValue}/ig, commandValue).replace(/{#title}/ig, title); $item = $(''); $item.append(itemContent); $list.append($item); }); self.$list = $list; }; // 绑定事件 DropList.fn.bindEvent = function () { var self = this; var editor = self.editor; var menu = self.menu; var commandName = menu.commandName; var selectorForELemCommand = self.selectorForELemCommand; var $list = self.$list; // 执行事件前后的钩子函数 var beforeEvent = self.beforeEvent; var afterEvent = self.afterEvent; $list.on('click', 'a[commandValue]', function (e) { // 正式命令执行之前 if (beforeEvent && typeof beforeEvent === 'function') { beforeEvent.call(e); } // 执行命令 var commandValue = $(e.currentTarget).attr('commandValue'); if (menu.selected && editor.isRangeEmpty() && selectorForELemCommand) { // 当前处于选中状态,并且选中内容为空 editor.commandForElem(selectorForELemCommand, e, commandName, commandValue); } else { // 当前未处于选中状态,或者有选中内容。则执行默认命令 editor.command(e, commandName, commandValue); } // 正式命令之后的钩子 if (afterEvent && typeof afterEvent === 'function') { afterEvent.call(e); } }); }; // 点击其他地方,立即隐藏 droplist DropList.fn.initHideEvent = function () { var self = this; // 获取 list elem var thisList = self.$list.get(0); E.$body.on('click', function (e) { if (!self.isShowing) { return; } var trigger = e.target; // 获取菜单elem var menu = self.menu; var menuDom; if (menu.selected) { menuDom = menu.$domSelected.get(0); } else { menuDom = menu.$domNormal.get(0); } if (menuDom === trigger || $.contains(menuDom, trigger)) { // 说明由本菜单点击触发的 return; } if (thisList === trigger || $.contains(thisList, trigger)) { // 说明由本list点击触发的 return; } // 其他情况,隐藏 list self.hide(); }); E.$window.scroll(function () { self.hide(); }); E.$window.on('resize', function () { self.hide(); }); }; }); // dropListfn api _e(function (E, $) { var DropList = E.DropList; // 渲染 DropList.fn._render = function () { var self = this; var editor = self.editor; var $list = self.$list; // 渲染到页面 editor.$editorContainer.append($list); // 记录状态 self.rendered = true; }; // 定位 DropList.fn._position = function () { var self = this; var $list = self.$list; var editor = self.editor; var menu = self.menu; var $menuContainer = editor.menuContainer.$menuContainer; var $menuDom = menu.selected ? menu.$domSelected : menu.$domNormal; // 注意这里的 offsetParent() 要返回 .menu-item 的 position // 因为 .menu-item 是 position:relative var menuPosition = $menuDom.offsetParent().position(); // 取得 menu 的位置、尺寸属性 var menuTop = menuPosition.top; var menuLeft = menuPosition.left; var menuHeight = $menuDom.offsetParent().height(); var menuWidth = $menuDom.offsetParent().width(); // 取得 list 的尺寸属性 var listWidth = $list.outerWidth(); // var listHeight = $list.outerHeight(); // 取得 $txt 的尺寸 var txtWidth = editor.txt.$txt.outerWidth(); // ------------开始计算------------- // 初步计算 list 位置属性 var top = menuTop + menuHeight; var left = menuLeft + menuWidth/2; var marginLeft = 0 - menuWidth/2; // 如果超出了有边界,则要左移(且和右侧有间隙) var valWithTxt = (left + listWidth) - txtWidth; if (valWithTxt > -10) { marginLeft = marginLeft - valWithTxt - 10; } // 设置样式 $list.css({ top: top, left: left, 'margin-left': marginLeft }); // 如果因为向下滚动而导致菜单fixed,则再加一步处理 if (editor._isMenufixed) { top = top + (($menuContainer.offset().top + $menuContainer.outerHeight()) - $list.offset().top); // 重新设置top $list.css({ top: top }); } }; // 显示 DropList.fn.show = function () { var self = this; var menu = self.menu; if (!self.rendered) { // 第一次show之前,先渲染 self._render(); } if (self.isShowing) { return; } var $list = self.$list; $list.show(); // 定位 self._position(); // 记录状态 self.isShowing = true; // 菜单状态 menu.activeStyle(true); }; // 隐藏 DropList.fn.hide = function () { var self = this; var menu = self.menu; if (!self.isShowing) { return; } var $list = self.$list; $list.hide(); // 记录状态 self.isShowing = false; // 菜单状态 menu.activeStyle(false); }; }); // dropPanel 构造函数 _e(function (E, $) { // 定义构造函数 var DropPanel = function (editor, menu, opt) { this.editor = editor; this.menu = menu; this.$content = opt.$content; this.width = opt.width || 200; this.height = opt.height; this.onRender = opt.onRender; // init this.init(); }; DropPanel.fn = DropPanel.prototype; // 暴露给 E 即 window.wangEditor E.DropPanel = DropPanel; }); // dropPanel fn bind _e(function (E, $) { var DropPanel = E.DropPanel; // init DropPanel.fn.init = function () { var self = this; // 生成dom对象 self.initDOM(); // 声明隐藏的事件 self.initHideEvent(); }; // init DOM DropPanel.fn.initDOM = function () { var self = this; var $content = self.$content; var width = self.width; var height = self.height; var $panel = $(''); var $triangle = $(''); $panel.css({ width: width, height: height ? height : 'auto' }); $panel.append($triangle); $panel.append($content); // 添加对象数据 self.$panel = $panel; self.$triangle = $triangle; }; // 点击其他地方,立即隐藏 dropPanel DropPanel.fn.initHideEvent = function () { var self = this; // 获取 panel elem var thisPanle = self.$panel.get(0); E.$body.on('click', function (e) { if (!self.isShowing) { return; } var trigger = e.target; // 获取菜单elem var menu = self.menu; var menuDom; if (menu.selected) { menuDom = menu.$domSelected.get(0); } else { menuDom = menu.$domNormal.get(0); } if (menuDom === trigger || $.contains(menuDom, trigger)) { // 说明由本菜单点击触发的 return; } if (thisPanle === trigger || $.contains(thisPanle, trigger)) { // 说明由本panel点击触发的 return; } // 其他情况,隐藏 panel self.hide(); }); E.$window.scroll(function (e) { self.hide(); }); E.$window.on('resize', function () { self.hide(); }); }; }); // dropPanel fn api _e(function (E, $) { var DropPanel = E.DropPanel; // 渲染 DropPanel.fn._render = function () { var self = this; var onRender = self.onRender; var editor = self.editor; var $panel = self.$panel; // 渲染到页面 editor.$editorContainer.append($panel); // 渲染后的回调事件 onRender && onRender.call(self); // 记录状态 self.rendered = true; }; // 定位 DropPanel.fn._position = function () { var self = this; var $panel = self.$panel; var $triangle = self.$triangle; var editor = self.editor; var $menuContainer = editor.menuContainer.$menuContainer; var menu = self.menu; var $menuDom = menu.selected ? menu.$domSelected : menu.$domNormal; // 注意这里的 offsetParent() 要返回 .menu-item 的 position // 因为 .menu-item 是 position:relative var menuPosition = $menuDom.offsetParent().position(); // 取得 menu 的位置、尺寸属性 var menuTop = menuPosition.top; var menuLeft = menuPosition.left; var menuHeight = $menuDom.offsetParent().height(); var menuWidth = $menuDom.offsetParent().width(); // 取得 panel 的尺寸属性 var panelWidth = $panel.outerWidth(); // var panelHeight = $panel.outerHeight(); // 取得 $txt 的尺寸 var txtWidth = editor.txt.$txt.outerWidth(); // ------------开始计算------------- // 初步计算 panel 位置属性 var top = menuTop + menuHeight; var left = menuLeft + menuWidth/2; var marginLeft = 0 - panelWidth/2; var marginLeft2 = marginLeft; // 下文用于和 marginLeft 比较,来设置三角形tip的位置 // 如果超出了左边界,则移动回来(要和左侧有10px间隙) if ((0 - marginLeft) > (left - 10)) { marginLeft = 0 - (left - 10); } // 如果超出了有边界,则要左移(且和右侧有10px间隙) var valWithTxt = (left + panelWidth + marginLeft) - txtWidth; if (valWithTxt > -10) { marginLeft = marginLeft - valWithTxt - 10; } // 设置样式 $panel.css({ top: top, left: left, 'margin-left': marginLeft }); // 如果因为向下滚动而导致菜单fixed,则再加一步处理 if (editor._isMenufixed) { top = top + (($menuContainer.offset().top + $menuContainer.outerHeight()) - $panel.offset().top); // 重新设置top $panel.css({ top: top }); } // 设置三角形 tip 的位置 $triangle.css({ 'margin-left': marginLeft2 - marginLeft - 5 }); }; // focus 第一个 input DropPanel.fn.focusFirstInput = function () { var self = this; var $panel = self.$panel; $panel.find('input[type=text],textarea').each(function () { var $input = $(this); if ($input.attr('disabled') == null) { $input.focus(); return false; } }); }; // 显示 DropPanel.fn.show = function () { var self = this; var menu = self.menu; if (!self.rendered) { // 第一次show之前,先渲染 self._render(); } if (self.isShowing) { return; } var $panel = self.$panel; $panel.show(); // 定位 self._position(); // 记录状态 self.isShowing = true; // 菜单状态 menu.activeStyle(true); if (E.w3cRange) { // 高级浏览器 self.focusFirstInput(); } else { // 兼容 IE8 input placeholder E.placeholderForIE8($panel); } }; // 隐藏 DropPanel.fn.hide = function () { var self = this; var menu = self.menu; if (!self.isShowing) { return; } var $panel = self.$panel; $panel.hide(); // 记录状态 self.isShowing = false; // 菜单状态 menu.activeStyle(false); }; }); // modal 构造函数 _e(function (E, $) { // 定义构造函数 var Modal = function (editor, menu, opt) { this.editor = editor; this.menu = menu; this.$content = opt.$content; this.init(); }; Modal.fn = Modal.prototype; // 暴露给 E 即 window.wangEditor E.Modal = Modal; }); // modal fn bind _e(function (E, $) { var Modal = E.Modal; Modal.fn.init = function () { var self = this; // 初始化dom self.initDom(); // 初始化隐藏事件 self.initHideEvent(); }; // 初始化dom Modal.fn.initDom = function () { var self = this; var $content = self.$content; var $modal = $(''); var $close = $('' + $keydownDivElem.html() + '
'); $keydownDivElem.after($pElem); $keydownDivElem.remove(); } $txt.on('keydown keyup', function (e) { if (e.keyCode !== 13) { return; } // 查找合法标签 var rangeElem = editor.getRangeElem(); var targetElem = editor.getLegalTags(rangeElem); var $targetElem; var $pElem; if (!targetElem) { // 没找到合法标签,就去查找 div targetElem = editor.getSelfOrParentByName(rangeElem, 'div'); if (!targetElem) { return; } $targetElem = $(targetElem); if (e.type === 'keydown') { // 异步执行(同步执行会出现问题) $keydownDivElem = $targetElem; setTimeout(divHandler, 0); } if (e.type === 'keyup') { // 将 div 的内容移动到 p 里面,并移除 div $pElem = $('' + $targetElem.html() + '
'); $targetElem.after($pElem); $targetElem.remove(); // 如果是回车结束,将选区定位到行首 editor.restoreSelectionByElem($pElem.get(0), 'start'); } } }); }; // enter时,用 p 包裹 text Txt.fn.bindEnterForText = function () { var self = this; var $txt = self.$txt; var handle; $txt.on('keyup', function (e) { if (e.keyCode !== 13) { return; } if (!handle) { handle = function() { self.wrapImgAndText(); }; } setTimeout(handle); }); }; // tab 时,插入4个空格 Txt.fn.bindTabEvent = function () { var self = this; var editor = self.editor; var $txt = self.$txt; $txt.on('keydown', function (e) { if (e.keyCode !== 9) { // 只监听 tab 按钮 return; } // 如果浏览器支持 insertHtml 则插入4个空格。如果不支持,就不管了 if (editor.queryCommandSupported('insertHtml')) { editor.command(e, 'insertHtml', ' '); } }); }; // 处理粘贴内容 Txt.fn.bindPasteFilter = function () { var self = this; var editor = self.editor; var resultHtml = ''; //存储最终的结果 var $txt = self.$txt; var legalTags = editor.config.legalTags; var legalTagArr = legalTags.split(','); $txt.on('paste', function (e) { if (!editor.config.pasteFilter) { // 配置中取消了粘贴过滤 return; } var currentNodeName = editor.getRangeElem().nodeName; if (currentNodeName === 'TD' || currentNodeName === 'TH') { // 在表格的单元格中粘贴,忽略所有内容。否则会出现异常情况 return; } resultHtml = ''; // 先清空 resultHtml var pasteHtml, $paste, docSplitHtml; var data = e.clipboardData || e.originalEvent.clipboardData; var ieData = window.clipboardData; if (editor.config.pasteText) { // 只粘贴纯文本 if (data && data.getData) { // w3c pasteHtml = data.getData('text/plain'); } else if (ieData && ieData.getData) { // IE pasteHtml = ieData.getData('text'); } else { // 其他情况 return; } // 拼接为标签 if (pasteHtml) { resultHtml = '
' + pasteHtml + '
'; } } else { // 粘贴过滤了样式的、只有标签的 html if (data && data.getData) { // w3c // 获取粘贴过来的html pasteHtml = data.getData('text/html'); // 过滤从 word excel 粘贴过来的乱码 docSplitHtml = pasteHtml.split('