(function (global, factory) {
// 检查是否在Node.js环境中,如果是则使用CommonJS模块规范导出
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
// 检查是否在AMD环境中,如果是则使用AMD模块定义
typeof define === 'function' && define.amd ? define(factory) :
// 否则将wangEditor挂载到全局对象上
(global.wangEditor = factory());
}(this, (function () { 'use strict';
var polyfill = function () {
// Object.assign的polyfill实现
if (typeof Object.assign != 'function') {
Object.assign = function (target, varArgs) {
// 如果目标对象为null或undefined,抛出TypeError异常
if (target == null) {
// 如果目标对象为null或undefined,抛出类型错误异常
throw new TypeError('Cannot convert undefined or null to object');
}
// 将目标对象转换为一个普通对象
var to = Object(target);
// 遍历所有传入的源对象
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
// 如果当前源对象不为null或undefined
if (nextSource != null) {
// 遍历当前源对象的所有属性
for (var nextKey in nextSource) {
// 确保只复制源对象自身的属性,而不是继承的属性
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
// 将当前属性赋值给目标对象
to[nextKey] = nextSource[nextKey];
}
}
}
}
// 返回合并后的目标对象
return to;
};
// 检查浏览器是否支持 matches 方法,如果不支持则进行扩展
if (!Element.prototype.matches) {
// 将 matches 方法定义为 matchesSelector、mozMatchesSelector、msMatchesSelector、oMatchesSelector 或 webkitMatchesSelector 中的一种
Element.prototype.matches = Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector || function (s) {
// 使用 querySelectorAll 获取所有匹配选择器的元素
var matches = (this.document || this.ownerDocument).querySelectorAll(s),
i = matches.length;
// 遍历匹配的元素列表,查找当前元素是否存在于其中
while (--i >= 0 && matches.item(i) !== this) {}
// 如果找到当前元素,返回 true;否则返回 false
return i > -1;
};
}
// 根据 html 代码片段创建 dom 对象
function createElemByHTML(html) {
// 声明一个变量div,用于存储创建的div元素
var div = void 0;
// 创建一个新的div元素并赋值给div变量
div = document.createElement('div');
// 将传入的HTML字符串设置为div元素的innerHTML
div.innerHTML = html;
// 返回div元素的子元素集合
return div.children;
}
function isDOMList(selector) {
// 如果选择器为空,返回false
if (!selector) {
return false;
}
// 如果选择器是HTMLCollection或NodeList的实例,返回true
if (selector instanceof HTMLCollection || selector instanceof NodeList) {
return true;
}
// 默认情况下,返回false
return false;
}
function querySelectorAll(selector) {
// 使用document.querySelectorAll方法根据选择器查找元素
var result = document.querySelectorAll(selector);
// 判断result是否为DOMList类型
if (isDOMList(result)) {
// 如果是DOMList类型,直接返回结果
return result;
} else {
// 如果不是DOMList类型,将结果包装成数组后返回
return [result];
}
}
// 记录所有的事件绑定
var eventList = [];
// 创建构造函数
function DomElement(selector) {
// 如果未传入选择器,则直接返回
if (!selector) {
return;
}
// 如果传入的是DomElement实例,则直接返回该实例
if (selector instanceof DomElement) {
return selector;
}
// 将选择器赋值给当前实例的selector属性
this.selector = selector;
// 获取选择器的节点类型
var nodeType = selector.nodeType;
// 定义一个空数组用于存储选择结果
var selectorResult = [];
// 如果选择器是文档节点(nodeType为9),则将其放入数组中
if (nodeType === 9) {
selectorResult = [selector];
// 如果选择器是元素节点(nodeType为1),则将其放入数组中
} else if (nodeType === 1) {
selectorResult = [selector];
// 如果选择器是DOM列表或数组,则直接赋值给selectorResult
} else if (isDOMList(selector) || selector instanceof Array) {
selectorResult = selector;
// 如果选择器是字符串类型
} else if (typeof selector === 'string') {
// 去除选择器中的换行符并去除首尾空格
selector = selector.replace('/\n/mg', '').trim();
// 如果选择器以'<'开头,表示HTML字符串,创建对应的DOM元素
if (selector.indexOf('<') === 0) {
selectorResult = createElemByHTML(selector);
// 否则,使用querySelectorAll方法查找匹配的元素
} else {
selectorResult = querySelectorAll(selector);
}
}
// 获取选择器结果的长度
var length = selectorResult.length;
// 如果长度为0,则直接返回当前对象
if (!length) {
return this;
}
// 声明变量i并初始化为undefined
var i = void 0;
// 循环遍历从0到length的每一个索引
for (i = 0; i < length; i++) {
// 将selectorResult数组中对应索引的值赋给当前对象的相应位置
this[i] = selectorResult[i];
}
// 设置当前对象的长度属性为传入的length值
this.length = length;
omElement.prototype = {
constructor: DomElement,
forEach: function forEach(fn) {
// 声明变量i用于循环计数
var i = void 0;
// 遍历当前对象的每一个元素
for (i = 0; i < this.length; i++) {
// 获取当前元素
var elem = this[i];
// 调用传入的函数,并将当前元素和索引作为参数传递
var result = fn.call(elem, elem, i);
// 如果传入的函数返回false,则中断循环
if (result === false) {
break;
}
}
// 返回当前对象以支持链式调用
return this;
},
clone: function clone(deep) {
// 创建一个空数组用于存储克隆的节点
var cloneList = [];
// 遍历当前对象的每个元素
this.forEach(function (elem) {
// 将克隆的节点(根据deep参数决定是否深度克隆)添加到cloneList中
cloneList.push(elem.cloneNode(!!deep));
});
// 返回包含克隆节点的新对象
return $(cloneList);
},
get: function get(index) {
// 获取当前对象的长度
var length = this.length;
// 如果索引大于等于长度,则取模以循环索引
if (index >= length) {
index = index % length;
}
// 返回指定索引处的元素,并包装成jQuery对象
return $(this[index]);
},
first: function first() {
// 调用get方法获取第一个元素
return this.get(0);
},
last: function last() {
// 获取当前对象的长度
var length = this.length;
// 调用get方法获取最后一个元素
return this.get(length - 1);
},
on: function on(type, selector, fn) {
// 如果fn未定义,则将selector赋值给fn,并将selector置为空
if (!fn) {
fn = selector;
selector = null;
}
// 初始化一个空数组,用于存储事件类型
var types = [];
// 将传入的事件类型字符串按空格分割成数组
types = type.split(/\s+/);
// 遍历当前对象的每一个元素
return this.forEach(function (elem) {
// 遍历每一个事件类型
types.forEach(function (type) {
// 如果事件类型为空,则跳过本次循环
if (!type) {
return;
}
// 将事件信息(元素、事件类型、处理函数)推入事件列表
eventList.push({
elem: elem,
type: type,
fn: fn
});
// 如果没有选择器,直接在元素上添加事件监听器
if (!selector) {
elem.addEventListener(type, fn);
return;
}
elem.addEventListener(type, function (e) {
// 获取事件的目标元素
var target = e.target;
// 如果目标元素匹配选择器
if (target.matches(selector)) {
// 调用传入的函数,并将目标元素和事件对象作为参数传递
fn.call(target, e);
}
});
off: function off(type, fn) {
// 遍历当前对象中的每一个元素,移除指定类型的事件监听器
return this.forEach(function (elem) {
elem.removeEventListener(type, fn);
});
},
attr: function attr(key, val) {
if (val == null) {
// 如果val为null或undefined,则获取第一个元素的指定属性值
return this[0].getAttribute(key);
} else {
// 如果val不为null或undefined,则设置每个元素的指定属性值
return this.forEach(function (elem) {
elem.setAttribute(key, val);
});
}
},
addClass: function addClass(className) {
// 如果 className 为空,直接返回当前对象
if (!className) {
return this;
}
// 遍历当前对象中的每个元素
return this.forEach(function (elem) {
var arr = void 0;
// 如果元素已经有 className
if (elem.className) {
// 解析当前 className 转换为数组
arr = elem.className.split(/\s/);
// 过滤掉空白的 class 名称
arr = arr.filter(function (item) {
return !!item.trim();
});
// 如果数组中不包含新的 className,则添加它
if (arr.indexOf(className) < 0) {
arr.push(className);
}
// 将数组重新拼接成字符串并赋值给元素的 className
elem.className = arr.join(' ');
} else {
// 如果元素没有 className,直接赋值新的 className
elem.className = className;
}
});
},
// 删除 class
removeClass: function removeClass(className) {
// 如果 className 为空,直接返回当前对象
if (!className) {
return this;
}
// 遍历每一个元素
return this.forEach(function (elem) {
var arr = void 0;
// 如果元素有 className 属性
if (elem.className) {
// 解析当前 className 转换为数组
arr = elem.className.split(/\s/);
// 过滤掉要删除的 className
arr = arr.filter(function (item) {
item = item.trim();
// 删除 class
if (!item || item === className) {
return false;
}
return true;
});
// 修改 elem.class
elem.className = arr.join(' ');
}
});
},
css: function css(key, val) {
// 将传入的键值对转换为CSS样式字符串
var currentStyle = key + ':' + val + ';';
// 遍历当前对象中的每一个元素
return this.forEach(function (elem) {
// 获取元素的style属性,并去除首尾空格
var style = (elem.getAttribute('style') || '').trim();
var styleArr = void 0,
resultArr = [];
// 如果style属性不为空
if (style) {
// 将style属性按分号分割成数组
styleArr = style.split(';');
// 遍历style数组中的每一个项
styleArr.forEach(function (item) {
// 将每一项按冒号分割成键值对数组,并去除首尾空格
var arr = item.split(':').map(function (i) {
return i.trim();
});
// 如果键值对数组长度为2,则将其重新组合成字符串并加入结果数组
if (arr.length === 2) {
resultArr.push(arr[0] + ':' + arr[1]);
}
});
// 遍历resultArr数组,对每个元素进行处理
resultArr = resultArr.map(function (item) {
// 如果当前元素的开头部分与key相同,则替换为currentStyle
if (item.indexOf(key) === 0) {
return currentStyle;
} else {
// 否则保持原样
return item;
}
});
// 如果resultArr中不包含currentStyle,则将其添加到resultArr末尾
if (resultArr.indexOf(currentStyle) < 0) {
resultArr.push(currentStyle);
}
// 将处理后的样式数组转换为字符串并设置为元素的style属性
elem.setAttribute('style', resultArr.join('; '));
} else {
// 如果不需要合并样式,直接设置元素的style属性为currentStyle
elem.setAttribute('style', currentStyle);
}
show: function show() {
// 将元素的CSS display属性设置为'block',使其显示
return this.css('display', 'block');
},
hide: function hide() {
// 将元素的CSS display属性设置为'none',使其隐藏
return this.css('display', 'none');
},
children: function children() {
// 获取当前元素的第一个子元素
var elem = this[0];
if (!elem) {
// 如果当前元素不存在,返回null
return null;
}
// 返回当前元素的所有子元素,使用$函数包装成jQuery对象
return $(elem.children);
},
childNodes: function childNodes() {
// 获取当前对象的第一个元素
var elem = this[0];
// 如果元素不存在,返回null
if (!elem) {
return null;
}
// 返回该元素的所有子节点
return $(elem.childNodes);
},
append: function append($children) {
// 遍历当前对象中的每个元素
return this.forEach(function (elem) {
// 遍历要添加的子节点
$children.forEach(function (child) {
// 将子节点添加到当前元素中
elem.appendChild(child);
});
});
},
remove: function remove() {
// 遍历当前对象中的每一个元素
return this.forEach(function (elem) {
// 如果元素有remove方法,则调用该方法移除元素
if (elem.remove) {
elem.remove();
} else {
// 否则获取元素的父节点并从父节点中移除该元素
var parent = elem.parentElement;
parent && parent.removeChild(elem);
}
});
},
isContain: function isContain($child) {
// 获取当前对象的第一个元素
var elem = this[0];
// 获取传入子元素的第一个元素
var child = $child[0];
// 判断当前元素是否包含传入的子元素
return elem.contains(child);
},
getSizeData: function getSizeData() {
// 获取当前对象的第一个元素
var elem = this[0];
// 返回元素的边界矩形数据,包括 bottom, height, left, right, top, width 等属性
return elem.getBoundingClientRect(); // 可得到 bottom height left right top width 的数据
},
getNodeName: function getNodeName() {
// 获取当前对象的第一个元素
var elem = this[0];
// 返回元素的节点名称
return elem.nodeName;
},
find: function find(selector) {
// 获取当前对象的第一个元素
var elem = this[0];
// 使用querySelectorAll查找子元素,并返回包装后的结果
return $(elem.querySelectorAll(selector));
},
text: function text(val) {
if (!val) {
// 如果未传入参数,则获取第一个元素的文本内容
var elem = this[0];
// 移除HTML标签,只保留纯文本
return elem.innerHTML.replace(/<.*?>/g, function () {
return '';
});
} else {
// 如果传入了参数,则设置每个元素的文本内容
return this.forEach(function (elem) {
elem.innerHTML = val;
});
}
},
html: function html(value) {
// 获取当前对象的第一个元素
var elem = this[0];
// 如果传入的值为null或undefined,则返回元素的innerHTML内容
if (value == null) {
return elem.innerHTML;
} else {
// 否则,将传入的值设置为元素的innerHTML内容
elem.innerHTML = value;
// 返回当前对象以支持链式调用
return this;
}
},
val: function val() {
// 获取当前对象的第一个元素
var elem = this[0];
// 返回该元素的值,并去除前后空格
return elem.value.trim();
},
focus: function focus() {
// 遍历当前对象的每一个元素
return this.forEach(function (elem) {
// 将焦点设置到当前元素上
elem.focus();
});
},
parent: function parent() {
// 获取当前对象的第一个元素
var elem = this[0];
// 返回该元素的父元素,并包装成jQuery对象
return $(elem.parentElement);
},
parentUntil: function parentUntil(selector, _currentElem) {
// 使用querySelectorAll查找所有匹配选择器的元素
var results = document.querySelectorAll(selector);
// 获取匹配元素的数量
var length = results.length;
// 如果没有匹配的元素
if (!length) {
// 返回null
return null;
}
var elem = _currentElem || this[0]; // 获取当前元素,如果未定义则使用this的第一个元素
if (elem.nodeName === 'BODY') { // 如果当前元素的节点名称是'BODY'
return null; // 返回null
}
var parent = elem.parentElement; // 获取当前元素的父元素
var i = void 0; // 声明变量i并初始化为undefined
for (i = 0; i < length; i++) { // 遍历长度为length的数组
if (parent === results[i]) { // 如果父元素等于results数组中的某个元素
// 找到,并返回
return $(parent); // 返回父元素的jQuery对象
}
}
// 继续查找父元素,直到找到匹配选择器的父元素
return this.parentUntil(selector, parent);
},
// 判断两个 elem 是否相等
equal: function equal($elem) {
// 如果传入的 $elem 是一个节点类型为1的元素(即元素节点)
if ($elem.nodeType === 1) {
// 比较当前对象的第一个元素和传入的 $elem 是否相同
return this[0] === $elem;
} else {
// 否则,比较当前对象的第一个元素和传入的 $elem 的第一个元素是否相同
return this[0] === $elem[0];
}
},
// 将该元素插入到某个元素前面
insertBefore: function insertBefore(selector) {
// 获取参考节点的jQuery对象
var $referenceNode = $(selector);
// 获取参考节点的DOM对象
var referenceNode = $referenceNode[0];
// 如果参考节点不存在,直接返回当前对象
if (!referenceNode) {
return this;
}
// 遍历当前对象中的每个元素
return this.forEach(function (elem) {
// 获取参考节点的父节点
var parent = referenceNode.parentNode;
// 将当前元素插入到参考节点之前
parent.insertBefore(elem, referenceNode);
});
},
// 将该元素插入到某个元素后面
insertAfter: function insertAfter(selector) {
// 获取参考节点的jQuery对象
var $referenceNode = $(selector);
// 获取参考节点的DOM对象
var referenceNode = $referenceNode[0];
// 如果参考节点不存在,直接返回当前对象
if (!referenceNode) {
return this;
}
// 遍历当前对象中的每个元素
return this.forEach(function (elem) {
// 获取参考节点的父节点
var parent = referenceNode.parentNode;
// 如果参考节点是父节点的最后一个子节点
if (parent.lastChild === referenceNode) {
// 将当前元素追加到父节点的末尾
parent.appendChild(elem);
} else {
// 否则,将当前元素插入到参考节点的下一个兄弟节点之前
parent.insertBefore(elem, referenceNode.nextSibling);
}
});
}
function $(selector) {
// 创建一个新的DomElement对象并返回
return new DomElement(selector);
}
$.offAll = function () {
// 遍历事件列表
eventList.forEach(function (item) {
// 获取元素、事件类型和处理函数
var elem = item.elem;
var type = item.type;
var fn = item.fn;
// 解绑事件监听器
elem.removeEventListener(type, fn);
});
};
var config = {
// 默认菜单配置,包含各种编辑功能按钮
menus: ['head', 'bold', 'fontSize', 'fontName', 'italic', 'underline', 'strikeThrough', 'foreColor', 'backColor', 'link', 'list', 'justify', 'quote', 'emoticon', 'image', 'table', 'video', 'code', 'undo', 'redo'],
// 字体名称列表
fontNames: ['宋体', '微软雅黑', 'Arial', 'Tahoma', 'Verdana'],
// 颜色代码数组,用于设置文本和背景颜色
colors: ['#000000', '#eeece0', '#1c487f', '#4d80bf', '#c24f4a', '#8baa4a', '#7b5ba1', '#46acc8', '#f9963b', '#ffffff'],
// 表情配置
emotions: [{
// tab 的标题
title: '默认',
// type -> 'emoji' / 'image',表示表情的类型是图片
type: 'image',
// content -> 数组,包含表情的具体信息
content: [{
// alt 属性为表情的描述文字
alt: '[坏笑]',
// src 属性为表情图片的 URL
src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/50/pcmoren_huaixiao_org.png'
}, {
alt: '[舔屏]',
src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/40/pcmoren_tian_org.png'
}, {
alt: '[污]',
src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/3c/pcmoren_wu_org.png'
}]
}, {
// tab 的标题
title: '新浪',
// type -> 'emoji' / 'image'
type: 'image',
// content -> 数组,包含多个表情图片对象
content: [{
// 表情图片的 URL 地址
src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/7a/shenshou_thumb.gif',
// 表情图片的替代文本
alt: '[草泥马]'
}, {
// 表情图片的 URL 地址
src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/60/horse2_thumb.gif',
// 表情图片的替代文本
alt: '[神马]'
}, {
// 表情图片的 URL 地址
src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/bc/fuyun_thumb.gif',
// 表情图片的替代文本
alt: '[浮云]'
}]
}, {
// tab 的标题
title: 'emoji',
// type -> 'emoji' / 'image'
type: 'emoji',
// content -> 数组,包含多个 emoji 字符
content: '😀 😃 😄 😁 😆 😅 😂 😊 😇 🙂 🙃 😉 😓 😪 😴 🙄 🤔 😬 🤐'.split(/\s/)
}],
title: 'emoji',
// type -> 'emoji' / 'image'
type: 'emoji',
// content -> 数组,包含多个 emoji 字符
content: '😀 😃 😄 😁 😆 😅 😂 😊 😇 🙂 🙃 😉 😓 😪 😴 🙄 🤔 😬 🤐'.split(/\s/)
}],
// 编辑区域的 z-index
zIndex: 10000,
// 是否开启 debug 模式(debug 模式下错误会 throw error 形式抛出)
debug: false,
// 插入链接时候的格式校验
linkCheck: function linkCheck(text, link) {
// text 是插入的文字
// link 是插入的链接
return true; // 返回 true 即表示成功
// return '校验失败' // 返回字符串即表示失败的提示信息
},
// 插入网络图片的校验
linkImgCheck: function linkImgCheck(src) {
// src 即图片的地址
return true; // 返回 true 即表示成功
// return '校验失败' // 返回字符串即表示失败的提示信息
},
// 粘贴过滤样式,默认开启
pasteFilterStyle: true,
// 粘贴内容时,忽略图片。默认关闭
pasteIgnoreImg: false,
pasteTextHandle: function pasteTextHandle(content) {
// content 即粘贴过来的内容(html 或 纯文本),可进行自定义处理然后返回
return content;
},
showLinkImg: true,
// 插入网络图片的回调
linkImgCallback: function linkImgCallback(url) {
// console.log(url) // url 即插入图片的地址
},
// 默认上传图片 max size: 5M
uploadImgMaxSize: 5 * 1024 * 1024,
// 配置一次最多上传几个图片
// uploadImgMaxLength: 5,
// 上传图片,是否显示 base64 格式
uploadImgShowBase64: false,
// 上传图片,server 地址(如果有值,则 base64 格式的配置则失效)
// uploadImgServer: '/upload',
// 自定义配置 filename
uploadFileName: '',
// 上传图片的自定义参数
uploadImgParams: {
// token: 'abcdef12345'
},
// 上传图片的自定义header
uploadImgHeaders: {
// 'Accept': 'text/x-json'
},
// 配置 XHR withCredentials
withCredentials: false,
// 自定义上传图片超时时间 ms
uploadImgTimeout: 10000,
// 上传图片 hook
uploadImgHooks: {
before: function before(xhr, editor, files) {
},
success: function success(xhr, editor, result) {
// 图片上传并返回结果,图片插入成功之后触发
},
fail: function fail(xhr, editor, result) {
// 图片上传并返回结果,但图片插入错误时触发
},
error: function error(xhr, editor) {
// 图片上传出错时触发
},
timeout: function timeout(xhr, editor) {
// 图片上传超时时触发
}
},
// 是否上传七牛云,默认为 false
qiniu: false
};
var UA = {
// 获取用户代理字符串
_ua: navigator.userAgent,
// 判断是否为 Webkit 内核的浏览器
isWebkit: function isWebkit() {
// 定义匹配 Webkit 的正则表达式
var reg = /webkit/i;
// 测试用户代理字符串是否包含 'webkit'
return reg.test(this._ua);
},
// 判断是否为 IE 浏览器
isIE: function isIE() {
// 检查 window 对象中是否存在 ActiveXObject 属性
return 'ActiveXObject' in window;
}
};
function objForEach(obj, fn) {
// 定义变量key用于存储当前遍历的属性名
var key = void 0,
// 定义变量result用于存储函数fn的返回值
result = void 0;
// 使用for...in循环遍历对象的所有可枚举属性
for (key in obj) {
// 检查属性是否是对象自身的属性(而不是继承自原型链)
if (obj.hasOwnProperty(key)) {
// 调用传入的函数fn,并将当前属性名和属性值作为参数传递
result = fn.call(obj, key, obj[key]);
// 如果函数fn返回false,则中断循环
if (result === false) {
break;
}
}
}
}
function arrForEach(fakeArr, fn) {
// 声明变量i用于循环计数,item用于存储当前元素,result用于存储函数返回值
var i = void 0,
item = void 0,
result = void 0;
// 获取伪数组的长度,如果长度为undefined则默认为0
var length = fakeArr.length || 0;
// 使用for循环遍历伪数组
for (i = 0; i < length; i++) {
// 获取当前元素
item = fakeArr[i];
// 调用传入的函数,并将当前元素、索引和数组本身作为参数传递
result = fn.call(fakeArr, item, i);
// 如果函数返回false,则中断循环
if (result === false) {
break;
}
}
}
function getRandom(prefix) {
// 使用Math.random()生成一个0到1之间的随机数,并将其转换为字符串
// 然后通过slice(2)去掉字符串的前两位(即"0."),只保留小数部分
return prefix + Math.random().toString().slice(2);
}
/function replaceHtmlSymbol(html) {
// 如果输入的HTML字符串为null,则返回空字符串
if (html == null) {
return '';
}
// 使用正则表达式替换HTML字符串中的<、>、"和换行符为对应的HTML实体
return html.replace(//gm, '>') // 替换所有的>为>
.replace(/"/gm, '"') // 替换所有的"为"
.replace(/(\r\n|\r|\n)/g, '
'); // 替换所有的换行符为
}
function isFunction(fn) {
// 使用typeof运算符检查参数的类型是否为'function'
return typeof fn === 'function';
}
function Bold(editor) {
// 将传入的编辑器实例赋值给当前对象的editor属性
this.editor = editor;
// 创建一个包含加粗图标的div元素,并赋值给当前对象的$elem属性
this.$elem = $('
设置标题
'), 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 = $(' 标签中并添加到 faceHtml 字符串中 if (item) { faceHtml += '' + item + ''; } }); } // 图片表情处理 if (emotType === 'image') { // 遍历图片内容数组 content.forEach(function (item) { // 获取图片的 src 和 alt 属性 var src = item.src; var alt = item.alt; // 如果 src 存在,则生成包含图片的 标签,并添加 data-w-e 属性 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 = ''; if (emotType === 'emoji') { // 遍历表情内容数组 content.forEach(function (item) { // 如果当前项存在,则将其包裹在'; } }); } // 加一个 data-w-e 属性,点击图片的时候不再提示编辑图片 faceHtml += '
'; } }); } tabConfig.push({ // 设置标签页的标题 title: emotData.title, // 设置标签页的内容模板,包含表情 HTML tpl: '
' + faceHtml + '', events: [{ // 选择器,用于匹配表情项 selector: 'span.w-e-item', // 事件类型,点击事件 type: 'click', fn: function fn(e) { // 获取触发事件的目标元素 var target = e.target; // 将目标元素转换为 jQuery 对象 var $target = $(target); // 获取目标元素的节点名称 var nodeName = $target.getNodeName(); var insertHtml = void 0; if (nodeName === 'IMG') { // 如果目标元素是图片,则插入图片的 HTML insertHtml = $target.parent().html(); } else { // 如果目标元素不是图片,则插入 emoji 的 HTML insertHtml = '' + $target.html() + ''; } // 调用插入方法,将生成的 HTML 插入到编辑器中 _this._insert(insertHtml); // 返回 true,表示该事件执行完之后,面板要关闭。否则面板不会关闭 return true; } }] }); }); var panel = new Panel(this, { // 设置面板的宽度为300 width: 300, // 设置面板的高度为200 height: 200, // 一个 Panel 包含多个 tab,tabConfig 是 tab 的配置信息 tabs: tabConfig }); // 显示 panel panel.show(); // 记录属性,将创建的 panel 对象赋值给当前对象的 panel 属性 this.panel = panel; }, // 插入表情 _insert: function _insert(emotHtml) { // 获取当前编辑器实例 var editor = this.editor; // 执行插入HTML命令,将表情HTML插入到编辑器中 editor.cmd.do('insertHTML', emotHtml); } function Table(editor) { // 将传入的编辑器实例赋值给当前对象的 editor 属性 this.editor = editor; // 创建一个包含特定类名和图标的 div 元素,并赋值给当前对象的 $elem 属性 this.$elem = $(' '); // 设置当前对象类型为 'panel' this.type = 'panel'; // 初始化当前对象的活动状态为 false 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'); // 创建一个新的 Panel 对象 var panel = new Panel(this, { width: 250, // panel 包含多个 tab tabs: [{ // 设置 tab 的标题 title: '插入表格', // 定义 tab 的内容模板 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) { // 如果行数和列数都有效且大于0,则执行插入操作 _this._insert(rowNum, colNum); } // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 return true; } ] } // 展示 panel panel.show(); // 记录属性 this.panel = panel; }, // 插入表格 _insert: function _insert(rowNum, colNum) { // 定义行和列的变量 var r = void 0, c = void 0; // 初始化 HTML 字符串,包含 table 标签和样式属性 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 += ' '; // 添加行结束标签 } // 关闭 table 标签并添加换行符 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'); // 生成一个随机的ID,用于添加列按钮 var addColBtnId = getRandom('add-col'); // 生成一个随机的ID,用于删除行按钮 var delRowBtnId = getRandom('del-row'); // 生成一个随机的ID,用于删除列按钮 var delColBtnId = getRandom('del-col'); // 生成一个随机的ID,用于删除表格按钮 var delTableBtnId = getRandom('del-table'); // 创建 panel 对象 var panel = new Panel(this, { width: 320, // panel 包含多个 tab tabs: [{ // 标题 title: '编辑表格', // 模板,定义了按钮的 HTML 结构 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() { // 调用当前对象的 _addCol 方法,增加列 _this2._addCol(); // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 return true; } }, { // 删除行 selector: '#' + delRowBtnId, type: 'click', fn: function fn() { // 调用 _delRow 方法,执行删除行操作 _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') { // 如果节点名称不是 TD 或 TH,则直接返回 return; } // 获取 td index var $tr = $selectionELem.parent(); // 获取当前选中元素的父元素(即 tr) var $tds = $tr.children(); // 获取 tr 的所有子元素(即所有 td 和 th) var tdLength = $tds.length; // 获取子元素的数量 $tds.forEach(function (td, index) { // 遍历所有子元素 if (td === $selectionELem[0]) { // 如果当前子元素是选中的元素 // 记录并跳出循环 result.td = { index: index, // 记录选中元素的索引 elem: td, // 记录选中元素本身 length: tdLength // 记录子元素的数量 }; return false; // 跳出循环 } }); // 获取 tr 的父元素,即 tbody var $tbody = $tr.parent(); // 获取 tbody 中的所有子元素,即所有的 tr var $trs = $tbody.children(); // 获取所有 tr 的数量 var trLength = $trs.length; // 遍历所有的 tr $trs.forEach(function (tr, index) { // 如果当前遍历到的 tr 是目标 tr if (tr === $tr[0]) { // 记录目标 tr 的索引、元素和总数量,并跳出循环 result.tr = { index: index, elem: tr, length: trLength }; return false; } }); // 返回结果 return result; }, // 增加行 _addRow: function _addRow() { // 获取当前单元格的位置信息 var locationData = this._getLocationData(); if (!locationData) { // 如果 locationData 为空,则直接返回 return; } var trData = locationData.tr; // 获取当前行的数据 var $currentTr = $(trData.elem); // 将当前行元素转换为 jQuery 对象 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; // 设置新行的 HTML 内容为生成的空白单元格字符串 // 将新行插入到当前行之后 $(newTr).insertAfter($currentTr); // 增加列 _addCol: function _addCol() { // 获取当前单元格的位置信息 var locationData = this._getLocationData(); if (!locationData) { // 如果 locationData 为空,则直接返回 return; } var trData = locationData.tr; // 获取行数据 var tdData = locationData.td; // 获取单元格数据 var tdIndex = tdData.index; // 获取单元格索引 var $currentTr = $(trData.elem); // 获取当前行的 jQuery 对象 var $trParent = $currentTr.parent(); // 获取当前行的父元素 var $trs = $trParent.children(); // 获取所有子行 // 遍历所有行 $trs.forEach(function (tr) { var $tr = $(tr); // 将当前行转换为 jQuery 对象 var $tds = $tr.children(); // 获取当前行的所有单元格 var $currentTd = $tds.get(tdIndex); // 获取指定索引的单元格 var name = $currentTd.getNodeName().toLowerCase(); // 获取单元格的标签名并转换为小写 // 创建一个新的单元格,并插入到当前单元格之后 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); // 将当前行从DOM中移除 $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) { // 将当前遍历到的 tr 元素转换为 jQuery 对象 var $tr = $(tr); // 获取当前 tr 元素的所有子元素(即所有 td 元素) var $tds = $tr.children(); // 获取指定索引位置的 td 元素 var $currentTd = $tds.get(tdIndex); // 删除当前索引位置的 td 元素 $currentTd.remove(); }); // 删除表格 _delTable: function _delTable() { // 获取当前编辑器实例 var editor = this.editor; // 获取当前选中的容器元素 var $selectionELem = editor.selection.getSelectionContainerElem(); // 如果未选中任何元素,则直接返回 if (!$selectionELem) { return; } // 查找选中元素的父级元素,直到找到表格元素为止 var $table = $selectionELem.parentUntil('table'); if (!$table) { // 如果 $table 不存在,则直接返回 return; } $table.remove(); // 移除 $table 元素 }, tryChangeActive: function tryChangeActive(e) { var editor = this.editor; // 获取当前编辑器实例 var $elem = this.$elem; // 获取当前元素的 jQuery 对象 var $selectionELem = editor.selection.getSelectionContainerElem(); // 获取当前选中的容器元素 if (!$selectionELem) { // 如果 $selectionELem 不存在,则直接返回,不执行后续代码 return; } var nodeName = $selectionELem.getNodeName(); // 获取 $selectionELem 的节点名称 if (nodeName === 'TD' || nodeName === 'TH') { // 如果节点名称是 'TD' 或 'TH' this._active = true; // 将对象的 _active 属性设置为 true $elem.addClass('w-e-active'); // 为 $elem 添加 'w-e-active' 类 } else { // 如果节点名称不是 'TD' 或 'TH' this._active = false; // 将对象的 _active 属性设置为 false $elem.removeClass('w-e-active'); // 从 $elem 移除 'w-e-active' 类 } function Video(editor) { // 将传入的编辑器实例赋值给当前对象的 editor 属性 this.editor = editor; // 创建一个包含播放图标的 div 元素,并赋值给当前对象的 $elem 属性 this.$elem = $(' '); // 设置当前对象的类型为 'panel' this.type = 'panel'; // 初始化当前对象是否处于 active 状态,默认为 false 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'); // 创建一个新的面板对象 var panel = new Panel(this, { width: 350, // 一个面板包含多个标签页 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; } }] } ] }); // 显示 panel panel.show(); // 记录属性 this.panel = panel; }, // 插入视频 _insert: function _insert(val) { var editor = this.editor; editor.cmd.do('insertHTML', val + ' \n'); } }; function Image(editor) { // 将编辑器实例赋值给当前对象 this.editor = editor; // 生成一个随机的菜单ID var imgMenuId = getRandom('w-e-img'); // 创建一个包含图标的div元素,并设置其ID为生成的菜单ID this.$elem = $(' '); // 将菜单ID存储在编辑器实例中 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) { // 如果配置中包含 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'); // 配置选项卡 var tabsConfig = [{ title: '编辑图片', tpl: '
\n' + '\n' + ' ', events: [{ selector: '#' + width30, type: 'click', fn: function fn() { // 获取选中的图片元素 var $img = editor._selectedImg; if ($img) { // 如果存在选中的图片,则将图片的最大宽度设置为30% $img.css('max-width', '30%'); } // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 return true; } }, { selector: '#' + width50, type: 'click', fn: function fn() { // 获取当前编辑器中被选中的图片元素 var $img = editor._selectedImg; if ($img) { // 如果存在选中的图片,则将图片的最大宽度设置为50% $img.css('max-width', '50%'); } // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 return true; } }, { selector: '#' + width100, type: 'click', fn: function fn() { // 获取当前编辑器中被选中的图片元素 var $img = editor._selectedImg; if ($img) { // 设置图片的最大宽度为100% $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,用于唯一标识不同的DOM元素 var upTriggerId = getRandom('up-trigger'); var upFileId = getRandom('up-file'); var linkUrlId = getRandom('link-url'); var linkBtnId = getRandom('link-btn'); // 配置选项卡 var tabsConfig = [{ title: '上传图片', // 设置选项卡模板,包含上传按钮和隐藏的文件输入框 tpl: '\n', events: [{ // 绑定点击事件到上传按钮,触发选择图片文件 selector: '#' + upTriggerId, type: 'click', fn: function fn() { // 获取文件输入框的jQuery对象 var $file = $('#' + upFileId); // 获取文件输入框的DOM元素 var fileElem = $file[0]; if (fileElem) { // 如果 fileElem 存在,则触发点击事件 fileElem.click(); } else { // 如果 fileElem 不存在,返回 true 可关闭 panel return true; } } }, { // 选择图片完毕 selector: '#' + upFileId, type: 'change', fn: function fn() { // 获取文件输入元素 var $file = $('#' + upFileId); // 获取原生的 file 元素 var fileElem = $file[0]; if (!fileElem) { // 如果 fileElem 不存在,返回 true 可关闭 panel return true; } // 获取选中的 file 对象列表 var fileList = fileElem.files; if (fileList.length) { // 如果文件列表不为空,则调用 uploadImg 方法上传图片 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); // 获取输入框中的 URL 并去除首尾空格 var url = $linkUrl.val().trim(); if (url) { // 如果 url 存在,则调用 insertLinkImg 方法插入链接图片 uploadImg.insertLinkImg(url); } // 返回 true 表示函数执行结束之后关闭 panel return true; } // second tab end ]; // tabs end // 判断 tabs 的显示 var tabsConfigResult = []; if ((config.uploadImgShowBase64 || config.uploadImgServer || config.customUploadImg) && window.FileReader) { // 如果配置允许上传图片(包括 Base64 编码、服务器上传或自定义上传)并且浏览器支持 FileReader,则显示“上传图片”选项卡 tabsConfigResult.push(tabsConfig[0]); } if (config.showLinkImg) { // 如果配置允许显示网络图片链接,则显示“网络图片”选项卡 tabsConfigResult.push(tabsConfig[1]); } // 创建一个新的 Panel 实例,并设置宽度和选项卡配置 var panel = new Panel(this, { width: 300, tabs: tabsConfigResult }); // 显示面板 panel.show(); // 将创建的面板实例保存到当前对象的 panel 属性中 this.panel = panel; }, // 试图改变 active 状态 tryChangeActive: function tryChangeActive(e) { // 获取当前编辑器实例 var editor = this.editor; // 获取当前元素的jQuery对象 var $elem = this.$elem; // 如果编辑器中选中了图片 if (editor._selectedImg) { // 将当前对象的活动状态设为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'); } } var MenuConstructors = {}; // 创建一个空对象,用于存储菜单构造函数 // 将 Bold 构造函数赋值给 MenuConstructors 对象的 bold 属性 MenuConstructors.bold = Bold; // 将 Head 构造函数赋值给 MenuConstructors 对象的 head 属性 MenuConstructors.head = Head; // 将 FontSize 构造函数赋值给 MenuConstructors 对象的 fontSize 属性 MenuConstructors.fontSize = FontSize; // 将 FontName 构造函数赋值给 MenuConstructors 对象的 fontName 属性 MenuConstructors.fontName = FontName; // 将 Link 构造函数赋值给 MenuConstructors 对象的 link 属性 MenuConstructors.link = Link; // 将 Italic 构造函数赋值给 MenuConstructors 对象的 italic 属性 MenuConstructors.italic = Italic; // 将 Redo 构造函数赋值给 MenuConstructors 对象的 redo 属性 MenuConstructors.redo = Redo; // 将 StrikeThrough 构造函数赋值给 MenuConstructors 对象的 strikeThrough 属性 MenuConstructors.strikeThrough = StrikeThrough; // 将 Underline 构造函数赋值给 MenuConstructors 对象的 underline 属性 MenuConstructors.underline = Underline; // 将 Undo 构造函数赋值给 MenuConstructors 对象的 undo 属性 MenuConstructors.undo = Undo; // 将 List 构造函数赋值给 MenuConstructors 对象的 list 属性 MenuConstructors.list = List; // 将 Justify 构造函数赋值给 MenuConstructors 对象的 justify 属性 MenuConstructors.justify = Justify; // 将 ForeColor 构造函数赋值给 MenuConstructors 的 foreColor 属性 MenuConstructors.foreColor = ForeColor; // 将 BackColor 构造函数赋值给 MenuConstructors 的 backColor 属性 MenuConstructors.backColor = BackColor; // 将 Quote 构造函数赋值给 MenuConstructors 的 quote 属性 MenuConstructors.quote = Quote; // 将 Code 构造函数赋值给 MenuConstructors 的 code 属性 MenuConstructors.code = Code; // 将 Emoticon 构造函数赋值给 MenuConstructors 的 emoticon 属性 MenuConstructors.emoticon = Emoticon; // 将 Table 构造函数赋值给 MenuConstructors 的 table 属性 MenuConstructors.table = Table; // 将 Video 构造函数赋值给 MenuConstructors 的 video 属性 MenuConstructors.video = Video; // 将 Image 构造函数赋值给 MenuConstructors 的 image 属性 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; // 计算菜单的 z-index objForEach(menus, function (key, menu) { var $elem = menu.$elem; // 获取菜单元素 if ($elem) { // 设置 z-index 属性,使元素在 z 轴上的位置发生变化 $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) { // 如果 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; } // 调用菜单的 onClick 方法并传递事件对象 menu.onClick(e); }); } // 下拉框,例如 head if (type === 'droplist' && droplist) { // 为元素绑定鼠标进入事件 $elem.on('mouseenter', function (e) { // 如果编辑器中没有选中的范围,则直接返回 if (editor.selection.getRange() == null) { return; } // 显示下拉列表,设置延迟200毫秒后执行 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() { // 获取当前对象的menus属性 var menus = this.menus; // 遍历menus对象的每一个键值对 objForEach(menus, function (key, menu) { // 如果menu对象有tryChangeActive方法 if (menu.tryChangeActive) { // 设置一个定时器,在100毫秒后调用menu的tryChangeActive方法 setTimeout(function () { menu.tryChangeActive(); }, 100); } }); } /** * 获取粘贴的文本内容 * @param {Event} e - 事件对象,包含剪贴板数据 * @returns {string} - 处理后的粘贴文本内容 */ function getPasteText(e) { // 获取剪贴板数据,如果事件对象中没有则尝试从原始事件中获取 var clipboardData = e.clipboardData || e.originalEvent && e.originalEvent.clipboardData; var pasteText = void 0; // 如果剪贴板数据为空,则尝试从window.clipboardData中获取 if (clipboardData == null) { pasteText = window.clipboardData && window.clipboardData.getData('text'); } else { // 否则从剪贴板数据中获取纯文本格式的数据 pasteText = clipboardData.getData('text/plain'); } // 返回替换HTML符号后的文本内容 return replaceHtmlSymbol(pasteText); } function getPasteHtml(e, filterStyle, ignoreImg) { // 获取剪贴板数据,如果事件对象中没有则尝试从原始事件中获取 var clipboardData = e.clipboardData || e.originalEvent && e.originalEvent.clipboardData; // 定义变量用于存储粘贴的文本和HTML内容 var pasteText = void 0, pasteHtml = void 0; // 如果剪贴板数据为空,则尝试从window.clipboardData中获取文本数据 if (clipboardData == null) { // 如果 clipboardData 为空,则尝试从 window.clipboardData 获取纯文本数据 pasteText = window.clipboardData && window.clipboardData.getData('text'); } else { // 否则从剪贴板数据中获取纯文本和HTML内容 pasteText = clipboardData.getData('text/plain'); pasteHtml = clipboardData.getData('text/html'); } if (!pasteHtml && pasteText) { // 如果只有纯文本没有HTML,则将纯文本转换为简单的HTML段落 pasteHtml = ' \n' + replaceHtmlSymbol(pasteText) + '
'; } if (!pasteHtml) { // 如果没有获取到任何HTML内容,则直接返回 return; } // 过滤word中状态过来的无用字符 var docSplitHtml = pasteHtml.split('