设置标题
'), + type: 'list', // droplist 以列表形式展示 + list: [{ $elem: $('正文
'), value: '' }], + onClick: function onClick(value) { + // 注意 this 是指向当前的 Head 对象 + _this._command(value); + } + }); +} + + +Head.prototype = { + constructor: Head, + + + _command: function _command(value) { + // 获取编辑器实例 + var editor = this.editor; + + // 获取当前选中的容器元素 + var $selectionElem = editor.selection.getSelectionContainerElem(); + + // 如果选中的元素是文本元素,则不进行任何操作 + if (editor.$textElem.equal($selectionElem)) { + return; + } + + // 执行格式化命令,将选中的内容格式化为指定的块类型 + editor.cmd.do('formatBlock', value); + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + // 获取编辑器实例 + var editor = this.editor; + // 获取当前元素 + var $elem = this.$elem; + // 定义正则表达式,用于匹配以 'h' 开头的字符串(忽略大小写) + var reg = /^h/i; + // 查询当前命令的值,判断是否为标题格式 + var cmdValue = editor.cmd.queryCommandValue('formatBlock'); + // 如果命令值匹配正则表达式,表示当前是标题格式 + if (reg.test(cmdValue)) { + // 设置活动状态为 true + this._active = true; + // 为当前元素添加 'w-e-active' 类,表示激活状态 + $elem.addClass('w-e-active'); + } else { + // 设置活动状态为 false + this._active = false; + // 移除当前元素的 'w-e-active' 类,表示非激活状态 + $elem.removeClass('w-e-active'); + } + } + + +function FontSize(editor) { + var _this = this; // 保存当前对象的引用,以便在嵌套函数中使用 + + this.editor = editor; // 将传入的编辑器对象赋值给当前实例的 editor 属性 + this.$elem = $('
'); // 创建菜单元素并添加到当前实例的 $elem 属性中 + this.type = 'droplist'; // 设置菜单类型为下拉列表 + + // 当前是否 active 状态 + this._active = false; // 初始化 active 状态为 false + + // 初始化 droplist + this.droplist = new DropList(this, { + width: 160, // 设置下拉列表的宽度 + $title: $('字号
'), // 设置下拉列表的标题 + type: 'list', // 设置下拉列表的类型为列表形式展示 + list: [ + { $elem: $('x-small'), value: '1' }, // 定义第一个选项:字体大小为 x-small + { $elem: $('small'), value: '2' }, // 定义第二个选项:字体大小为 small + { $elem: $('normal'), value: '3' }, // 定义第三个选项:字体大小为 normal + { $elem: $('large'), value: '4' }, // 定义第四个选项:字体大小为 large + { $elem: $('x-large'), value: '5' }, // 定义第五个选项:字体大小为 x-large + { $elem: $('xx-large'), value: '6' } // 定义第六个选项:字体大小为 xx-large + ], + onClick: function onClick(value) { + // 注意 this 是指向当前的 FontSize 对象 + _this._command(value); // 调用当前对象的 _command 方法,并传递选中的值 + } + }); +} + + +FontSize.prototype = { + constructor: FontSize, + + + _command: function _command(value) { + // 获取当前编辑器实例 + var editor = this.editor; + // 调用编辑器的命令接口,设置字体大小 + editor.cmd.do('fontSize', value); + } +}; + + +function FontName(editor) { + var _this = this; + + // 保存编辑器实例 + this.editor = editor; + // 创建菜单元素,包含一个图标 + this.$elem = $(' '); + // 设置菜单类型为下拉列表 + this.type = 'droplist'; + + // 当前是否 active 状态 + this._active = false; + + // 获取配置的字体 + var config = editor.config; + var fontNames = config.fontNames || []; + + // 初始化 droplist + this.droplist = new DropList(this, { + width: 100, // 设置下拉列表宽度 + $title: $('字体
'), // 设置下拉列表标题 + type: 'list', // 设置下拉列表类型为列表形式展示 + list: fontNames.map(function (fontName) { + // 将每个字体名称映射为一个对象,包含显示的元素和值 + return { $elem: $('' + fontName + ''), value: fontName }; + }), + onClick: function onClick(value) { + // 注意 this 是指向当前的 FontName 对象 + // 当点击某个字体时,执行命令 + _this._command(value); + } + }); +} + + +FontName.prototype = { + // 构造函数,指向FontName类 + constructor: FontName, + + + _command: function _command(value) { + // 获取当前编辑器实例 + var editor = this.editor; + // 调用编辑器的命令接口,设置字体名称 + editor.cmd.do('fontName', value); + } +}; + + + +var emptyFn = function emptyFn() {}; +// 定义一个空函数,用于占位或默认回调 + +// 记录已经显示 panel 的菜单 +var _isCreatedPanelMenus = []; +// 初始化一个数组,用于存储已经创建的 panel 菜单 + +// 构造函数 +function Panel(menu, opt) { + this.menu = menu; + // 将传入的 menu 参数赋值给实例的 menu 属性 + this.opt = opt; + // 将传入的 opt 参数赋值给实例的 opt 属性 +} + + +Panel.prototype = { + constructor: Panel, + + + show: function show() { + // 保存当前对象的引用 + var _this = this; + + // 获取当前面板的菜单 + var menu = this.menu; + // 如果菜单已经创建过,则直接返回 + if (_isCreatedPanelMenus.indexOf(menu) >= 0) { + // 如果菜单已经创建,则直接返回 + return; + } + var editor = menu.editor; // 获取编辑器实例 + var $body = $('body'); // 获取 body 元素 + var $textContainerElem = editor.$textContainerElem; // 获取文本容器元素 + var opt = this.opt; // 获取配置选项 + + // panel 的容器 + var $container = $(''); // 创建一个新的 div 作为面板容器 + var width = opt.width || 300; // 默认宽度为 300px,如果配置中有指定宽度则使用配置中的宽度 + $container.css('width', width + 'px').css('margin-left', (0 - width) / 2 + 'px'); // 设置容器的宽度和左边距 + + // 添加关闭按钮 + var $closeBtn = $(''); // 创建一个关闭按钮 + $container.append($closeBtn); // 将关闭按钮添加到容器中 + $closeBtn.on('click', function () { + // 绑定点击事件,当点击关闭按钮时隐藏面板 + _this.hide(); + }); + + + // 创建一个包含类名 'w-e-panel-tab-title' 的设置列表
'), // 设置下拉列表标题 + type: 'list', // 设置下拉列表展示形式为列表 + list: [ + { $elem: $(' 有序列表'), value: 'insertOrderedList' }, // 添加有序列表选项 + { $elem: $(' 无序列表'), value: 'insertUnorderedList' } // 添加无序列表选项 + ], + onClick: function onClick(value) { + // 注意 this 是指向当前的 List 对象 + _this._command(value); // 调用命令方法执行相应的操作 + } + }); +} + + +ist.prototype = { + constructor: List, + + // 执行命令的方法 + _command: function _command(value) { + // 获取编辑器实例 + var editor = this.editor; + // 获取文本元素 + var $textElem = editor.$textElem; + // 恢复之前的选区 + editor.selection.restoreSelection(); + // 如果命令已经处于激活状态,则直接返回 + if (editor.cmd.queryCommandState(value)) { + return; + } + // 否则执行该命令 + editor.cmd.do(value); + }, + + var $selectionElem = editor.selection.getSelectionContainerElem(); // 获取当前选中的元素 + if ($selectionElem.getNodeName() === 'LI') { // 如果选中的元素是列表项 + $selectionElem = $selectionElem.parent(); // 将选中元素设置为其父元素 + } + if (/^ol|ul$/i.test($selectionElem.getNodeName()) === false) { // 如果选中的元素不是有序或无序列表 + return; // 退出函数 + } + if ($selectionElem.equal($textElem)) { // 如果选中的元素等于文本元素 + // 如果是根节点,则不处理 + return; // 退出函数 + } + var $parent = $selectionElem.parent(); // 获取选中元素的父元素 + if ($parent.equal($textElem)) { // 如果父元素等于文本元素 + // 如果是根节点,则不处理 + return; // 退出函数 + } + + $selectionElem.insertAfter($parent); // 将选中元素插入到父元素的后面 + $parent.remove(); // 移除父元素 + }, + + + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; // 获取编辑器实例 + var $elem = this.$elem; // 获取当前元素 + if (editor.cmd.queryCommandState('insertUnOrderedList') || editor.cmd.queryCommandState('insertOrderedList')) { + // 如果命令状态为无序列表或有序列表 + this._active = true; // 设置活动状态为真 + $elem.addClass('w-e-active'); // 添加活动样式类 + } else { + this._active = false; // 设置活动状态为假 + $elem.removeClass('w-e-active'); // 移除活动样式类 + } + } + }; + + +function Justify(editor) { + // 保存当前对象的引用 + var _this = this; + + // 将传入的编辑器对象赋值给当前实例的 editor 属性 + this.editor = editor; + // 创建一个包含图标和样式的 div 元素,并赋值给当前实例的 $elem 属性 + this.$elem = $(' '); + // 设置类型为 'droplist' + this.type = 'droplist'; + + // 初始化一个布尔值,表示当前是否处于 active 状态 + this._active = false; + + // 初始化 droplist 组件 + this.droplist = new DropList(this, { + // 设置 droplist 的宽度 + width: 100, + // 设置 droplist 的标题 + $title: $('对齐方式
'), + // 设置 droplist 的类型为列表形式展示 + type: 'list', + // 定义列表项及其对应的值 + list: [ + { $elem: $(' 靠左'), value: 'justifyLeft' }, + { $elem: $(' 居中'), value: 'justifyCenter' }, + { $elem: $(' 靠右'), value: 'justifyRight' } + ], + // 定义点击列表项时的回调函数 + onClick: function onClick(value) { + // 注意 this 是指向当前的 List 对象 + _this._command(value); + } + }); +} + + +Justify.prototype = { + constructor: Justify, + + + _command: function _command(value) { + // 获取编辑器实例 + var editor = this.editor; + // 调用编辑器的cmd对象的do方法,执行传入的命令 + editor.cmd.do(value); + } +}; + + +function ForeColor(editor) { + var _this = this; + + // 保存编辑器实例 + this.editor = editor; + // 创建菜单元素,包含一个图标 + this.$elem = $(' '); + // 设置菜单类型为下拉列表 + this.type = 'droplist'; + + // 获取配置的颜色 + var config = editor.config; + var colors = config.colors || []; + + // 当前是否 active 状态 + this._active = false; + + // 初始化 droplist + this.droplist = new DropList(this, { + width: 120, // 设置下拉列表宽度 + $title: $('文字颜色
'), // 设置下拉列表标题 + type: 'inline-block', // 设置下拉列表内容以 inline-block 形式展示 + list: colors.map(function (color) { + // 将颜色数组映射为包含颜色样式和值的对象数组 + return { $elem: $(''), value: color }; + }), + onClick: function onClick(value) { + // 注意 this 是指向当前的 ForeColor 对象 + // 当点击某个颜色时执行命令 + _this._command(value); + } + }); +} + + +ForeColor.prototype = { + constructor: ForeColor, + + // 私有方法,用于执行设置前景色的命令 + _command: function _command(value) { + // 获取编辑器实例 + var editor = this.editor; + // 调用编辑器命令接口,执行设置前景色的命令 + editor.cmd.do('foreColor', value); + } +}; + + +function BackColor(editor) { + var _this = this; // 保存当前对象的引用,以便在回调函数中使用 + + this.editor = editor; // 将传入的编辑器对象赋值给当前实例的 editor 属性 + this.$elem = $(' '); // 创建菜单元素并添加到当前实例的 $elem 属性中 + this.type = 'droplist'; // 设置菜单类型为下拉列表 + + // 获取配置的颜色 + var config = editor.config; // 从编辑器配置中获取配置对象 + var colors = config.colors || []; // 获取配置中的颜色数组,如果没有则使用空数组 + + // 当前是否 active 状态 + this._active = false; // 初始化 active 状态为 false + + // 初始化 droplist + this.droplist = new DropList(this, { + width: 120, // 设置下拉列表的宽度 + $title: $('背景色
'), // 设置下拉列表的标题 + type: 'inline-block', // 设置下拉列表内容以 inline-block 形式展示 + list: colors.map(function (color) { + return { $elem: $(''), value: color }; // 生成颜色选项列表 + }), + onClick: function onClick(value) { + // 注意 this 是指向当前的 BackColor 对象 + _this._command(value); // 调用命令方法,应用选中的背景色 + } + }); +} + + +BackColor.prototype = { + constructor: BackColor, + + + _command: function _command(value) { + // 获取编辑器实例 + var editor = this.editor; + // 调用编辑器的命令接口,设置背景颜色 + editor.cmd.do('backColor', value); + } +}; + + +function Quote(editor) { + // 将传入的编辑器实例赋值给当前对象的 editor 属性 + this.editor = editor; + + // 创建一个包含特定 HTML 结构的 jQuery 对象,并赋值给当前对象的 $elem 属性 + this.$elem = $(' '); + + // 设置事件类型为 'click' + this.type = 'click'; + + // 初始化当前对象的 active 状态为 false + this._active = false; +} + +// 原型 +Quote.prototype = { + constructor: Quote, + + onClick: function onClick(e) { + // 获取编辑器实例 + var editor = this.editor; + // 获取当前选中的容器元素 + var $selectionElem = editor.selection.getSelectionContainerElem(); + // 获取选中元素的节点名称 + var nodeName = $selectionElem.getNodeName(); + + // 判断是否为非IE浏览器 + if (!UA.isIE()) { + // 如果选中的元素是 BLOCKQUOTE,则撤销引用格式 + if (nodeName === 'BLOCKQUOTE') { + editor.cmd.do('formatBlock', ''); + } else { + // 否则,将选中的元素转换为引用格式 + editor.cmd.do('formatBlock', '
'); + } + return; + } + + // 定义变量 content 和 $targetELem,初始值为 undefined + var content = void 0, + $targetELem = void 0; + if (nodeName === 'P') { + // 如果当前节点是 P 标签 + // 将 P 标签的内容赋值给 content + content = $selectionElem.text(); + // 创建一个新的 blockquote 元素,并将 P 标签的内容插入其中 + $targetELem = $(''); + + // 设置事件类型为点击事件 + this.type = 'click'; + + // 初始化当前对象是否处于 active 状态的属性,默认值为 false + this._active = false; +} + + +Italic.prototype = { + constructor: Italic, + + + onClick: function onClick(e) { + // 获取编辑器实例 + var editor = this.editor; + // 检查当前选区是否为空 + var isSeleEmpty = editor.selection.isSelectionEmpty(); + + if (isSeleEmpty) { + // 如果选区为空,则创建一个空的选区 + editor.selection.createEmptyRange(); + } + + // 执行斜体命令 + editor.cmd.do('italic'); + } +}; + + if (isSeleEmpty) { + // 如果选择为空,则折叠选区并恢复选区 + editor.selection.collapseRange(); + editor.selection.restoreSelection(); + } + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; // 获取编辑器实例 + var $elem = this.$elem; // 获取当前元素的 jQuery 对象 + if (editor.cmd.queryCommandState('italic')) { + // 如果编辑器命令状态为斜体,则设置 active 状态为 true 并添加 'w-e-active' 类 + this._active = true; + $elem.addClass('w-e-active'); + } else { + // 否则设置 active 状态为 false 并移除 'w-e-active' 类 + this._active = false; + $elem.removeClass('w-e-active'); + } + } + }; + + +function Redo(editor) { + // 将传入的编辑器实例赋值给当前对象的 editor 属性 + this.editor = editor; + + // 创建一个包含重做图标的 div 元素,并赋值给当前对象的 $elem 属性 + this.$elem = $(' '); + + // 设置事件类型为 'click' + this.type = 'click'; + + // 初始化当前是否处于 active 状态的属性,默认值为 false + this._active = false; +} + + +Redo.prototype = { + constructor: Redo, + + + onClick: function onClick(e) { + // 获取当前编辑器实例 + var editor = this.editor; + + // 执行重做命令 + editor.cmd.do('redo'); + } +}; + + +function StrikeThrough(editor) { + // 初始化编辑器实例 + this.editor = editor; + // 创建菜单元素并设置类名和图标 + this.$elem = $(' '); + // 设置事件类型为点击 + this.type = 'click'; + + // 当前是否 active 状态 + this._active = false; +} + +StrikeThrough.prototype = { + constructor: StrikeThrough, + + // 点击事件处理函数 + onClick: function onClick(e) { + // 获取编辑器实例 + var editor = this.editor; + // 判断当前选区是否为空 + var isSeleEmpty = editor.selection.isSelectionEmpty(); + + if (isSeleEmpty) { + // 选区是空的,插入并选中一个“空白” + editor.selection.createEmptyRange(); + } + + // 执行 strikeThrough 命令 + editor.cmd.do('strikeThrough'); + + if (isSeleEmpty) { + // 需要将选取折叠起来 + editor.selection.collapseRange(); + // 恢复之前的选取范围 + editor.selection.restoreSelection(); + } + }, + + + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; // 获取编辑器实例 + var $elem = this.$elem; // 获取当前元素 + if (editor.cmd.queryCommandState('strikeThrough')) { + // 如果命令状态为删除线,则设置 active 状态为 true + this._active = true; + // 添加 'w-e-active' 类以表示激活状态 + $elem.addClass('w-e-active'); + } else { + // 否则设置 active 状态为 false + this._active = false; + // 移除 'w-e-active' 类以表示非激活状态 + $elem.removeClass('w-e-active'); + } + } + }; + + +function Underline(editor) { + // 初始化Underline对象,并设置编辑器实例 + this.editor = editor; + // 创建菜单元素,包含下划线图标 + this.$elem = $(' '); + // 设置事件类型为点击 + this.type = 'click'; + + // 当前是否激活状态 + this._active = false; +} + +Underline.prototype = { + constructor: Underline, + + + onClick: function onClick(e) { + // 阻止默认行为和冒泡 + e.preventDefault(); + e.stopPropagation(); + + // 获取编辑器实例 + var editor = this.editor; + // 判断当前选区是否为空 + var isSeleEmpty = editor.selection.isSelectionEmpty(); + if (isSeleEmpty) { + // 如果当前选择为空,则创建一个空的选择范围 + editor.selection.createEmptyRange(); + } + + // 执行下划线命令 + editor.cmd.do('underline'); + + if (isSeleEmpty) { + // 如果当前选择为空,则将光标折叠到起始位置并恢复之前的选择 + editor.selection.collapseRange(); + editor.selection.restoreSelection(); + } + }, + + + tryChangeActive: function tryChangeActive(e) { + // 获取编辑器实例 + var editor = this.editor; + // 获取当前元素的jQuery对象 + var $elem = this.$elem; + // 检查编辑器命令'underline'的状态 + if (editor.cmd.queryCommandState('underline')) { + // 如果命令状态为激活,设置_active属性为true + this._active = true; + // 给元素添加'w-e-active'类 + $elem.addClass('w-e-active'); + } else { + // 如果命令状态为未激活,设置_active属性为false + this._active = false; + // 从元素中移除'w-e-active'类 + $elem.removeClass('w-e-active'); + } + } + +function Undo(editor) { + // 保存编辑器实例 + this.editor = editor; + // 创建菜单元素并设置类名和图标 + this.$elem = $(' '); + // 设置事件类型为点击 + this.type = 'click'; + + // 当前是否 active 状态 + this._active = false; +} + +Undo.prototype = { + constructor: Undo, + + + onClick: function onClick(e) { + // 获取编辑器实例 + var editor = this.editor; + + // 执行撤销命令 + editor.cmd.do('undo'); + } +}; + + +function List(editor) { + var _this = this; // 保存当前对象的引用,以便在嵌套函数中使用 + + this.editor = editor; // 将传入的编辑器实例赋值给当前对象 + this.$elem = $(' '); // 创建菜单元素并添加到当前对象 + this.type = 'droplist'; // 设置菜单类型为下拉列表 + + // 当前是否 active 状态 + this._active = false; // 初始化 active 状态为 false + + // 初始化 droplist + this.droplist = new DropList(this, { + width: 120, // 设置下拉列表宽度为 120 + $title: $('' + content + ''); + // 将新的 blockquote 元素插入到 P 标签之后 + $targetELem.insertAfter($selectionElem); + // 移除原来的 P 标签 + $selectionElem.remove(); + // 结束函数执行 + return; + } + if (nodeName === 'BLOCKQUOTE') { + // 如果当前节点是 BLOCKQUOTE 标签 + // 将 BLOCKQUOTE 标签的内容赋值给 content + content = $selectionElem.text(); + // 创建一个新的 P 元素,并将 BLOCKQUOTE 标签的内容插入其中 + $targetELem = $('' + content + '
'); + // 将新的 P 元素插入到 BLOCKQUOTE 标签之后 + $targetELem.insertAfter($selectionElem); + // 移除原来的 BLOCKQUOTE 标签 + $selectionElem.remove(); + } + }, + + tryChangeActive: function tryChangeActive(e) { + // 获取编辑器实例 + var editor = this.editor; + // 获取当前元素的jQuery对象 + var $elem = this.$elem; + // 定义正则表达式,用于匹配BLOCKQUOTE标签 + var reg = /^BLOCKQUOTE$/i; + // 查询当前命令的值,判断是否为BLOCKQUOTE格式 + var cmdValue = editor.cmd.queryCommandValue('formatBlock'); + // 如果当前命令值为BLOCKQUOTE格式 + if (reg.test(cmdValue)) { + // 设置_active属性为true + this._active = true; + // 给元素添加'w-e-active'类 + $elem.addClass('w-e-active'); + } else { + // 设置_active属性为false + this._active = false; + // 移除元素的'w-e-active'类 + $elem.removeClass('w-e-active'); + } + } + + + +function Code(editor) { + // 将传入的编辑器实例赋值给当前对象的editor属性 + this.editor = editor; + // 创建一个包含特定HTML结构的jQuery元素,并赋值给当前对象的$elem属性 + this.$elem = $(' '); + // 设置当前对象的类型为'panel' + this.type = 'panel'; + + // 当前未使用 + // 初始化_active属性为false,表示当前状态未激活 + this._active = false; +} + +Code.prototype = { + constructor: Code, + + onClick: function onClick(e) { + // 获取编辑器实例 + var editor = this.editor; + // 获取选择区域的起始元素 + var $startElem = editor.selection.getSelectionStartElem(); + // 获取选择区域的结束元素 + var $endElem = editor.selection.getSelectionEndElem(); + // 判断当前选择区域是否为空 + var isSeleEmpty = editor.selection.isSelectionEmpty(); + // 获取当前选择的文本内容 + var selectionText = editor.selection.getSelectionText(); + // 定义一个变量用于存储代码块元素 + var $code = void 0; + + if (!$startElem.equal($endElem)) { + // 如果起始元素和结束元素不相等,则恢复选区并返回 + editor.selection.restoreSelection(); + return; + } + if (!isSeleEmpty) { + // 如果选区不为空,则将选中的文本包裹在标签中并插入到编辑器中 + $code = $('
' + selectionText + '
'); + editor.cmd.do('insertElem', $code); + // 创建一个新的选区范围,并将其设置为包含新插入的元素 + editor.selection.createRangeByElem($code, false); + // 恢复选区 + editor.selection.restoreSelection(); + return; + } + + // 如果选区为空,则显示代码块面板 + if (this._active) { + // 如果当前插件已经激活,则显示带有当前HTML内容的代码块面板 + this._createPanel($startElem.html()); + } else { + // 如果当前插件未激活,则显示一个空的代码块面板 + this._createPanel(); + } + }, + + _createPanel: function _createPanel(value) { + var _this = this; + + // 如果 value 为空,则设置为空字符串 + value = value || ''; + // 根据 value 是否为空判断是新建还是编辑模式 + var type = !value ? 'new' : 'edit'; + // 生成随机的文本框 ID + var textId = getRandom('texxt'); + // 生成随机的按钮 ID + var btnId = getRandom('btn'); + + // 创建一个新的 Panel 对象 + var panel = new Panel(this, { + width: 500, + // 一个 Panel 包含多个 tab + tabs: [{ + // 标题 + title: '插入代码', + // 模板,包含一个文本框和一个按钮 + tpl: '
\n \n\n', + // 事件绑定 + events: [ + // 插入代码的事件处理函数 + { + selector: '#' + btnId, + type: 'click', + fn: function fn() { + // 获取文本框的值或 HTML 内容 + var $text = $('#' + textId); + var text = $text.val() || $text.html(); + // 替换 HTML 特殊字符 + text = replaceHtmlSymbol(text); + if (type === 'new') { + // 新插入操作 + _this._insertCode(text); + } else { + // 编辑更新操作 + _this._updateCode(text); + } + + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + } + ] + }); + + // 显示 panel + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + // 插入代码 + _insertCode: function _insertCode(value) { + // 获取当前编辑器实例 + var editor = this.editor; + // 执行插入HTML命令,将传入的代码值包裹在标签中并插入到编辑器中 + editor.cmd.do('insertHTML', '
' + value + '
'); + }, + + // 更新代码 + _updateCode: function _updateCode(value) { + // 获取编辑器实例 + var editor = this.editor; + // 获取当前选中的DOM元素 + var $selectionELem = editor.selection.getSelectionContainerElem(); + // 如果未选中任何元素,则直接返回 + if (!$selectionELem) { + return; + } + // 将选中元素的HTML内容更新为传入的值 + $selectionELem.html(value); + // 恢复选区到之前的位置 + editor.selection.restoreSelection(); + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + // 获取编辑器实例 + var editor = this.editor; + // 获取当前元素的 jQuery 对象 + var $elem = this.$elem; + // 获取当前选中的容器元素 + var $selectionELem = editor.selection.getSelectionContainerElem(); + // 如果未选中任何元素,则直接返回 + if (!$selectionELem) { + return; + } + // 获取选中元素的父元素 + var $parentElem = $selectionELem.parent(); + // 判断选中的元素是否为
且其父元素是否为
+ if ($selectionELem.getNodeName() === 'CODE' && $parentElem.getNodeName() === 'PRE') { + // 设置 active 状态为 true + this._active = true; + // 给当前元素添加 'w-e-active' 类 + $elem.addClass('w-e-active'); + } else { + // 设置 active 状态为 false + this._active = false; + // 移除当前元素的 'w-e-active' 类 + $elem.removeClass('w-e-active'); + } + } + + +function Emoticon(editor) { + // 初始化表情对象,传入编辑器实例 + this.editor = editor; + // 创建表情菜单的DOM元素 + this.$elem = $('' + item + ''; + } + }); + } + // 图片表情 + if (emotType === 'image') { + content.forEach(function (item) { + var src = item.src; + var alt = item.alt; + if (src) { + // 加一个 data-w-e 属性,点击图片的时候不再提示编辑图片 + faceHtml += ''); + // 设置菜单类型为面板 + this.type = 'panel'; + + // 当前是否激活状态 + this._active = false; +} + +Emoticon.prototype = { + constructor: Emoticon, + + + onClick: function onClick() { + // 调用创建面板的方法 + this._createPanel(); + }, + + + _createPanel: function _createPanel() { + var _this = this; // 保存当前上下文引用 + + var editor = this.editor; // 获取编辑器实例 + var config = editor.config; // 获取编辑器配置 + // 获取表情配置 + var emotions = config.emotions || []; + + // 创建表情 dropPanel 的配置 + var tabConfig = []; + emotions.forEach(function (emotData) { + var emotType = emotData.type; // 表情类型 + var content = emotData.content || []; // 表情内容数组 + + // 这一组表情最终拼接出来的 html + var faceHtml = ''; + + // emoji 表情 + if (emotType === 'emoji') { + content.forEach(function (item) { + if (item) { + faceHtml += ''; + } + }); + } + + tabConfig.push({ + title: emotData.title, + tpl: '
' + faceHtml + '', + events: [{ + selector: 'span.w-e-item', + type: 'click', + fn: function fn(e) { + var target = e.target; + var $target = $(target); + var nodeName = $target.getNodeName(); + + var insertHtml = void 0; + if (nodeName === 'IMG') { + // 插入图片 + insertHtml = $target.parent().html(); + } else { + // 插入 emoji + insertHtml = '' + $target.html() + ''; + } + + _this._insert(insertHtml); + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + }); + }); + + var panel = new Panel(this, { + width: 300, + height: 200, + // 一个 Panel 包含多个 tab + tabs: tabConfig + }); + + // 显示 panel + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + // 插入表情 + _insert: function _insert(emotHtml) { + var editor = this.editor; + editor.cmd.do('insertHTML', emotHtml); + } +}; + +/* + menu - table +*/ +// 构造函数 +function Table(editor) { + this.editor = editor; + this.$elem = $(' '); + this.type = 'panel'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Table.prototype = { + constructor: Table, + + onClick: function onClick() { + if (this._active) { + // 编辑现有表格 + this._createEditPanel(); + } else { + // 插入新表格 + this._createInsertPanel(); + } + }, + + // 创建插入新表格的 panel + _createInsertPanel: function _createInsertPanel() { + var _this = this; + + // 用到的 id + var btnInsertId = getRandom('btn'); + var textRowNum = getRandom('row'); + var textColNum = getRandom('col'); + + var panel = new Panel(this, { + width: 250, + // panel 包含多个 tab + tabs: [{ + // 标题 + title: '插入表格', + // 模板 + tpl: '\n', + // 事件绑定 + events: [{ + // 点击按钮,插入表格 + selector: '#' + btnInsertId, + type: 'click', + fn: function fn() { + var rowNum = parseInt($('#' + textRowNum).val()); + var colNum = parseInt($('#' + textColNum).val()); + + if (rowNum && colNum && rowNum > 0 && colNum > 0) { + // form 数据有效 + _this._insert(rowNum, colNum); + } + + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + } // first tab end + ] // tabs end + }); // panel end + + // 展示 panel + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + // 插入表格 + _insert: function _insert(rowNum, colNum) { + // 拼接 table 模板 + var r = void 0, + c = void 0; + var html = '\n \u521B\u5EFA\n \n \u884C\n \n \u5217\u7684\u8868\u683C\n
\n \n'; + for (r = 0; r < rowNum; r++) { + html += '
'; + if (r === 0) { + for (c = 0; c < colNum; c++) { + html += ' '; + } + html += ''; + } + } else { + for (c = 0; c < colNum; c++) { + html += ' '; + } + } + html += ' '; + + // 执行命令 + var editor = this.editor; + editor.cmd.do('insertHTML', html); + + // 防止 firefox 下出现 resize 的控制点 + editor.cmd.do('enableObjectResizing', false); + editor.cmd.do('enableInlineTableEditing', false); + }, + + // 创建编辑表格的 panel + _createEditPanel: function _createEditPanel() { + var _this2 = this; + + // 可用的 id + var addRowBtnId = getRandom('add-row'); + var addColBtnId = getRandom('add-col'); + var delRowBtnId = getRandom('del-row'); + var delColBtnId = getRandom('del-col'); + var delTableBtnId = getRandom('del-table'); + + // 创建 panel 对象 + var panel = new Panel(this, { + width: 320, + // panel 包含多个 tab + tabs: [{ + // 标题 + title: '编辑表格', + // 模板 + tpl: '
\n\n ', + // 事件绑定 + events: [{ + // 增加行 + selector: '#' + addRowBtnId, + type: 'click', + fn: function fn() { + _this2._addRow(); + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + // 增加列 + selector: '#' + addColBtnId, + type: 'click', + fn: function fn() { + _this2._addCol(); + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + // 删除行 + selector: '#' + delRowBtnId, + type: 'click', + fn: function fn() { + _this2._delRow(); + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + // 删除列 + selector: '#' + delColBtnId, + type: 'click', + fn: function fn() { + _this2._delCol(); + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + // 删除表格 + selector: '#' + delTableBtnId, + type: 'click', + fn: function fn() { + _this2._delTable(); + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + }] + }); + // 显示 panel + panel.show(); + }, + + // 获取选中的单元格的位置信息 + _getLocationData: function _getLocationData() { + var result = {}; + var editor = this.editor; + var $selectionELem = editor.selection.getSelectionContainerElem(); + if (!$selectionELem) { + return; + } + var nodeName = $selectionELem.getNodeName(); + if (nodeName !== 'TD' && nodeName !== 'TH') { + return; + } + + // 获取 td index + var $tr = $selectionELem.parent(); + var $tds = $tr.children(); + var tdLength = $tds.length; + $tds.forEach(function (td, index) { + if (td === $selectionELem[0]) { + // 记录并跳出循环 + result.td = { + index: index, + elem: td, + length: tdLength + }; + return false; + } + }); + + // 获取 tr index + var $tbody = $tr.parent(); + var $trs = $tbody.children(); + var trLength = $trs.length; + $trs.forEach(function (tr, index) { + if (tr === $tr[0]) { + // 记录并跳出循环 + result.tr = { + index: index, + elem: tr, + length: trLength + }; + return false; + } + }); + + // 返回结果 + return result; + }, + + // 增加行 + _addRow: function _addRow() { + // 获取当前单元格的位置信息 + var locationData = this._getLocationData(); + if (!locationData) { + return; + } + var trData = locationData.tr; + var $currentTr = $(trData.elem); + var tdData = locationData.td; + var tdLength = tdData.length; + + // 拼接即将插入的字符串 + var newTr = document.createElement('tr'); + var tpl = '', + i = void 0; + for (i = 0; i < tdLength; i++) { + tpl += ''; + } + newTr.innerHTML = tpl; + // 插入 + $(newTr).insertAfter($currentTr); + }, + + // 增加列 + _addCol: function _addCol() { + // 获取当前单元格的位置信息 + var locationData = this._getLocationData(); + if (!locationData) { + return; + } + var trData = locationData.tr; + var tdData = locationData.td; + var tdIndex = tdData.index; + var $currentTr = $(trData.elem); + var $trParent = $currentTr.parent(); + var $trs = $trParent.children(); + + // 遍历所有行 + $trs.forEach(function (tr) { + var $tr = $(tr); + var $tds = $tr.children(); + var $currentTd = $tds.get(tdIndex); + var name = $currentTd.getNodeName().toLowerCase(); + + // new 一个 td,并插入 + var newTd = document.createElement(name); + $(newTd).insertAfter($currentTd); + }); + }, + + // 删除行 + _delRow: function _delRow() { + // 获取当前单元格的位置信息 + var locationData = this._getLocationData(); + if (!locationData) { + return; + } + var trData = locationData.tr; + var $currentTr = $(trData.elem); + $currentTr.remove(); + }, + + // 删除列 + _delCol: function _delCol() { + // 获取当前单元格的位置信息 + var locationData = this._getLocationData(); + if (!locationData) { + return; + } + var trData = locationData.tr; + var tdData = locationData.td; + var tdIndex = tdData.index; + var $currentTr = $(trData.elem); + var $trParent = $currentTr.parent(); + var $trs = $trParent.children(); + + // 遍历所有行 + $trs.forEach(function (tr) { + var $tr = $(tr); + var $tds = $tr.children(); + var $currentTd = $tds.get(tdIndex); + // 删除 + $currentTd.remove(); + }); + }, + + // 删除表格 + _delTable: function _delTable() { + var editor = this.editor; + var $selectionELem = editor.selection.getSelectionContainerElem(); + if (!$selectionELem) { + return; + } + var $table = $selectionELem.parentUntil('table'); + if (!$table) { + return; + } + $table.remove(); + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + var $selectionELem = editor.selection.getSelectionContainerElem(); + if (!$selectionELem) { + return; + } + var nodeName = $selectionELem.getNodeName(); + if (nodeName === 'TD' || nodeName === 'TH') { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + menu - video +*/ +// 构造函数 +function Video(editor) { + this.editor = editor; + this.$elem = $(' '); + this.type = 'panel'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Video.prototype = { + constructor: Video, + + onClick: function onClick() { + this._createPanel(); + }, + + _createPanel: function _createPanel() { + var _this = this; + + // 创建 id + var textValId = getRandom('text-val'); + var btnId = getRandom('btn'); + + // 创建 panel + var panel = new Panel(this, { + width: 350, + // 一个 panel 多个 tab + tabs: [{ + // 标题 + title: '插入视频', + // 模板 + tpl: ' \n \n', + // 事件绑定 + events: [{ + selector: '#' + btnId, + type: 'click', + fn: function fn() { + var $text = $('#' + textValId); + var val = $text.val().trim(); + + // 测试用视频地址 + // + + if (val) { + // 插入视频 + _this._insert(val); + } + + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + } // first tab end + ] // tabs end + }); // panel end + + // 显示 panel + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + // 插入视频 + _insert: function _insert(val) { + var editor = this.editor; + editor.cmd.do('insertHTML', val + ' \n'); + } +}; + +/* + menu - img +*/ +// 构造函数 +function Image(editor) { + this.editor = editor; + var imgMenuId = getRandom('w-e-img'); + this.$elem = $(' '); + editor.imgMenuId = imgMenuId; + this.type = 'panel'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Image.prototype = { + constructor: Image, + + onClick: function onClick() { + var editor = this.editor; + var config = editor.config; + if (config.qiniu) { + return; + } + if (this._active) { + this._createEditPanel(); + } else { + this._createInsertPanel(); + } + }, + + _createEditPanel: function _createEditPanel() { + var editor = this.editor; + + // id + var width30 = getRandom('width-30'); + var width50 = getRandom('width-50'); + var width100 = getRandom('width-100'); + var delBtn = getRandom('del-btn'); + + // tab 配置 + var tabsConfig = [{ + title: '编辑图片', + tpl: '
\n\n ', + events: [{ + selector: '#' + width30, + type: 'click', + fn: function fn() { + var $img = editor._selectedImg; + if ($img) { + $img.css('max-width', '30%'); + } + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + selector: '#' + width50, + type: 'click', + fn: function fn() { + var $img = editor._selectedImg; + if ($img) { + $img.css('max-width', '50%'); + } + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + selector: '#' + width100, + type: 'click', + fn: function fn() { + var $img = editor._selectedImg; + if ($img) { + $img.css('max-width', '100%'); + } + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + selector: '#' + delBtn, + type: 'click', + fn: function fn() { + var $img = editor._selectedImg; + if ($img) { + $img.remove(); + } + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + }]; + + // 创建 panel 并显示 + var panel = new Panel(this, { + width: 300, + tabs: tabsConfig + }); + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + _createInsertPanel: function _createInsertPanel() { + var editor = this.editor; + var uploadImg = editor.uploadImg; + var config = editor.config; + + // id + var upTriggerId = getRandom('up-trigger'); + var upFileId = getRandom('up-file'); + var linkUrlId = getRandom('link-url'); + var linkBtnId = getRandom('link-btn'); + + // tabs 的配置 + var tabsConfig = [{ + title: '上传图片', + tpl: '\n', + events: [{ + // 触发选择图片 + selector: '#' + upTriggerId, + type: 'click', + fn: function fn() { + var $file = $('#' + upFileId); + var fileElem = $file[0]; + if (fileElem) { + fileElem.click(); + } else { + // 返回 true 可关闭 panel + return true; + } + } + }, { + // 选择图片完毕 + selector: '#' + upFileId, + type: 'change', + fn: function fn() { + var $file = $('#' + upFileId); + var fileElem = $file[0]; + if (!fileElem) { + // 返回 true 可关闭 panel + return true; + } + + // 获取选中的 file 对象列表 + var fileList = fileElem.files; + if (fileList.length) { + uploadImg.uploadImg(fileList); + } + + // 返回 true 可关闭 panel + return true; + } + }] + }, // first tab end + { + title: '网络图片', + tpl: '\n \n\n \n\n \n', + events: [{ + selector: '#' + linkBtnId, + type: 'click', + fn: function fn() { + var $linkUrl = $('#' + linkUrlId); + var url = $linkUrl.val().trim(); + + if (url) { + uploadImg.insertLinkImg(url); + } + + // 返回 true 表示函数执行结束之后关闭 panel + return true; + } + }] + } // second tab end + ]; // tabs end + + // 判断 tabs 的显示 + var tabsConfigResult = []; + if ((config.uploadImgShowBase64 || config.uploadImgServer || config.customUploadImg) && window.FileReader) { + // 显示“上传图片” + tabsConfigResult.push(tabsConfig[0]); + } + if (config.showLinkImg) { + // 显示“网络图片” + tabsConfigResult.push(tabsConfig[1]); + } + + // 创建 panel 并显示 + var panel = new Panel(this, { + width: 300, + tabs: tabsConfigResult + }); + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + if (editor._selectedImg) { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + 所有菜单的汇总 +*/ + +// 存储菜单的构造函数 +var MenuConstructors = {}; + +MenuConstructors.bold = Bold; + +MenuConstructors.head = Head; + +MenuConstructors.fontSize = FontSize; + +MenuConstructors.fontName = FontName; + +MenuConstructors.link = Link; + +MenuConstructors.italic = Italic; + +MenuConstructors.redo = Redo; + +MenuConstructors.strikeThrough = StrikeThrough; + +MenuConstructors.underline = Underline; + +MenuConstructors.undo = Undo; + +MenuConstructors.list = List; + +MenuConstructors.justify = Justify; + +MenuConstructors.foreColor = ForeColor; + +MenuConstructors.backColor = BackColor; + +MenuConstructors.quote = Quote; + +MenuConstructors.code = Code; + +MenuConstructors.emoticon = Emoticon; + +MenuConstructors.table = Table; + +MenuConstructors.video = Video; + +MenuConstructors.image = Image; + +/* + 菜单集合 +*/ +// 构造函数 +function Menus(editor) { + this.editor = editor; + this.menus = {}; +} + +// 修改原型 +Menus.prototype = { + constructor: Menus, + + // 初始化菜单 + init: function init() { + var _this = this; + + var editor = this.editor; + var config = editor.config || {}; + var configMenus = config.menus || []; // 获取配置中的菜单 + + // 根据配置信息,创建菜单 + configMenus.forEach(function (menuKey) { + var MenuConstructor = MenuConstructors[menuKey]; + if (MenuConstructor && typeof MenuConstructor === 'function') { + // 创建单个菜单 + _this.menus[menuKey] = new MenuConstructor(editor); + } + }); + + // 添加到菜单栏 + this._addToToolbar(); + + // 绑定事件 + this._bindEvent(); + }, + + // 添加到菜单栏 + _addToToolbar: function _addToToolbar() { + var editor = this.editor; + var $toolbarElem = editor.$toolbarElem; + var menus = this.menus; + var config = editor.config; + // config.zIndex 是配置的编辑区域的 z-index,菜单的 z-index 得在其基础上 +1 + var zIndex = config.zIndex + 1; + objForEach(menus, function (key, menu) { + var $elem = menu.$elem; + if ($elem) { + // 设置 z-index + $elem.css('z-index', zIndex); + $toolbarElem.append($elem); + } + }); + }, + + // 绑定菜单 click mouseenter 事件 + _bindEvent: function _bindEvent() { + var menus = this.menus; + var editor = this.editor; + objForEach(menus, function (key, menu) { + var type = menu.type; + if (!type) { + return; + } + var $elem = menu.$elem; + var droplist = menu.droplist; + var panel = menu.panel; + + // 点击类型,例如 bold + if (type === 'click' && menu.onClick) { + $elem.on('click', function (e) { + if (editor.selection.getRange() == null) { + return; + } + menu.onClick(e); + }); + } + + // 下拉框,例如 head + if (type === 'droplist' && droplist) { + $elem.on('mouseenter', function (e) { + if (editor.selection.getRange() == null) { + return; + } + // 显示 + droplist.showTimeoutId = setTimeout(function () { + droplist.show(); + }, 200); + }).on('mouseleave', function (e) { + // 隐藏 + droplist.hideTimeoutId = setTimeout(function () { + droplist.hide(); + }, 0); + }); + } + + // 弹框类型,例如 link + if (type === 'panel' && menu.onClick) { + $elem.on('click', function (e) { + e.stopPropagation(); + if (editor.selection.getRange() == null) { + return; + } + // 在自定义事件中显示 panel + menu.onClick(e); + }); + } + }); + }, + + // 尝试修改菜单状态 + changeActive: function changeActive() { + var menus = this.menus; + objForEach(menus, function (key, menu) { + if (menu.tryChangeActive) { + setTimeout(function () { + menu.tryChangeActive(); + }, 100); + } + }); + } +}; + +/* + 粘贴信息的处理 +*/ + +// 获取粘贴的纯文本 +function getPasteText(e) { + var clipboardData = e.clipboardData || e.originalEvent && e.originalEvent.clipboardData; + var pasteText = void 0; + if (clipboardData == null) { + pasteText = window.clipboardData && window.clipboardData.getData('text'); + } else { + pasteText = clipboardData.getData('text/plain'); + } + + return replaceHtmlSymbol(pasteText); +} + +// 获取粘贴的html +function getPasteHtml(e, filterStyle, ignoreImg) { + var clipboardData = e.clipboardData || e.originalEvent && e.originalEvent.clipboardData; + var pasteText = void 0, + pasteHtml = void 0; + if (clipboardData == null) { + pasteText = window.clipboardData && window.clipboardData.getData('text'); + } else { + pasteText = clipboardData.getData('text/plain'); + pasteHtml = clipboardData.getData('text/html'); + } + if (!pasteHtml && pasteText) { + pasteHtml = ' \n' + replaceHtmlSymbol(pasteText) + '
'; + } + if (!pasteHtml) { + return; + } + + // 过滤word中状态过来的无用字符 + var docSplitHtml = pasteHtml.split('