You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5452 lines
222 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

(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, '&lt;') // 替换所有的<为&lt;
.replace(/>/gm, '&gt;') // 替换所有的>为&gt;
.replace(/"/gm, '&quot;') // 替换所有的"为&quot;
.replace(/(\r\n|\r|\n)/g, '<br/>'); // 替换所有的换行符为<br/>
}
function isFunction(fn) {
// 使用typeof运算符检查参数的类型是否为'function'
return typeof fn === 'function';
}
function Bold(editor) {
// 将传入的编辑器实例赋值给当前对象的editor属性
this.editor = editor;
// 创建一个包含加粗图标的div元素并赋值给当前对象的$elem属性
this.$elem = $('<div class="w-e-menu">\n <i class="w-e-icon-bold"></i>\n </div>');
// 设置按钮类型为点击事件
this.type = 'click';
}
// 当前是否 active 状态
this._active = false;
}
// 原型
Bold.prototype = {
constructor: Bold,
// 点击事件处理函数
onClick: function onClick(e) {
// 获取编辑器实例
var editor = this.editor;
// 检查当前选区是否为空
var isSeleEmpty = editor.selection.isSelectionEmpty();
if (isSeleEmpty) {
// 如果选区为空,创建一个空的选区并选中一个“空白”
editor.selection.createEmptyRange();
}
// 执行加粗命令
editor.cmd.do('bold');
if (isSeleEmpty) {
// 如果之前选区为空,将选区折叠起来并恢复之前的选区状态
editor.selection.collapseRange();
editor.selection.restoreSelection();
}
},
// 试图改变 active 状态
tryChangeActive: function tryChangeActive(e) {
// 获取编辑器实例
var editor = this.editor;
// 获取当前元素的jQuery对象
var $elem = this.$elem;
// 检查编辑器中的'bold'命令是否处于激活状态
if (editor.cmd.queryCommandState('bold')) {
// 如果'bold'命令激活设置_active为true
this._active = true;
// 给元素添加'w-e-active'类,表示激活状态
$elem.addClass('w-e-active');
} else {
// 如果'bold'命令未激活设置_active为false
this._active = false;
// 从元素中移除'w-e-active'类,表示非激活状态
$elem.removeClass('w-e-active');
}
}
var replaceLang = function (editor, str) {
// 获取编辑器配置中的语言参数数组,如果没有则默认为空数组
var langArgs = editor.config.langArgs || [];
// 初始化结果字符串为输入字符串
var result = str;
// 遍历语言参数数组
langArgs.forEach(function (item) {
// 获取当前语言参数的正则表达式
var reg = item.reg;
// 获取当前语言参数的值
var val = item.val;
// 如果结果字符串匹配当前正则表达式
if (reg.test(result)) {
// 使用当前值替换匹配的部分
result = result.replace(reg, function () {
return val;
});
}
});
// 返回处理后的结果字符串
return result;
};
var _emptyFn = function _emptyFn() {}; // 定义一个空函数,用于默认的点击事件处理
function DropList(menu, opt) {
var _this = this; // 保存当前上下文
var editor = menu.editor; // 获取编辑器实例
this.menu = menu; // 保存菜单对象
this.opt = opt; // 保存配置选项
// 创建容器元素
var $container = $('<div class="w-e-droplist"></div>');
var $title = opt.$title; // 获取标题元素
var titleHtml = void 0; // 初始化标题 HTML
if ($title) {
// 替换多语言
titleHtml = $title.html(); // 获取标题 HTML
titleHtml = replaceLang(editor, titleHtml); // 替换多语言文本
$title.html(titleHtml); // 设置替换后的标题 HTML
$title.addClass('w-e-dp-title'); // 添加标题样式类
$container.append($title); // 将标题添加到容器中
}
var list = opt.list || []; // 获取列表项数组,默认为空数组
var type = opt.type || 'list'; // 获取类型,默认为 'list'
var onClick = opt.onClick || _emptyFn; // 获取点击事件处理函数,默认为空函数
// 创建列表元素并绑定事件
var $list = $('<ul class="' + (type === 'list' ? 'w-e-list' : 'w-e-block') + '"></ul>'); // 根据类型选择样式类
$container.append($list); // 将列表添加到容器中
list.forEach(function (item) {
var $elem = item.$elem; // 获取每个列表项的元素
// 获取元素的HTML内容
var elemHtml = $elem.html();
// 替换语言标记
elemHtml = replaceLang(editor, elemHtml);
// 将替换后的内容重新设置到元素中
$elem.html(elemHtml);
// 获取item的值
var value = item.value;
// 创建一个新的<li>元素,并添加类名"w-e-item"
var $li = $('<li class="w-e-item"></li>');
if ($elem) {
// 如果$elem存在则将其添加到<li>元素中
$li.append($elem);
// 将<li>元素添加到列表中
$list.append($li);
// 为<li>元素绑定点击事件
$li.on('click', function (e) {
// 调用点击处理函数
onClick(value);
// 隐藏操作
_this.hideTimeoutId = setTimeout(function () {
// 调用隐藏方法
_this.hide();
}, 0);
});
}
// 绑定隐藏事件
$container.on('mouseleave', function (e) {
// 设置一个定时器,在鼠标离开容器时延迟执行隐藏操作
_this.hideTimeoutId = setTimeout(function () {
// 调用隐藏方法
_this.hide();
}, 0);
});
// 记录属性
this.$container = $container;
// 基本属性
// 标记是否已经渲染
this._rendered = false;
// 标记是否显示
this._show = false;
}
DropList.prototype = {
constructor: DropList,
show: function show() {
// 如果存在隐藏超时ID则清除该超时
if (this.hideTimeoutId) {
clearTimeout(this.hideTimeoutId);
}
var menu = this.menu; // 获取当前菜单对象
var $menuELem = menu.$elem; // 获取菜单的DOM元素
var $container = this.$container; // 获取容器的DOM元素
// 如果菜单已经显示,则直接返回
if (this._show) {
return;
}
// 如果菜单已经渲染过,则直接显示容器
if (this._rendered) {
$container.show();
} else {
// 获取菜单的高度如果未定义则默认为0
var menuHeight = $menuELem.getSizeData().height || 0;
// 获取宽度如果未定义则默认为100
var width = this.opt.width || 100;
// 设置容器的上边距和宽度
$container.css('margin-top', menuHeight + 'px').css('width', width + 'px');
// 将容器添加到菜单的DOM元素中
$menuELem.append($container);
// 标记菜单已渲染
this._rendered = true;
}
}
};
// 修改属性
this._show = true;
},
// 隐藏移除DOM
hide: function hide() {
// 如果存在显示定时器ID则清除之前的定时显示
if (this.showTimeoutId) {
clearTimeout(this.showTimeoutId);
}
var $container = this.$container;
// 如果当前状态为不显示,则直接返回
if (!this._show) {
return;
}
// 隐藏容器并修改显示状态属性
$container.hide();
this._show = false;
}
function Head(editor) {
// 保存当前上下文的引用
var _this = this;
// 将传入的编辑器实例赋值给当前对象的editor属性
this.editor = editor;
// 创建一个包含头部菜单图标的div元素并赋值给当前对象的$elem属性
this.$elem = $('<div class="w-e-menu"><i class="w-e-icon-header"></i></div>');
// 设置当前对象的类型为下拉列表
this.type = 'droplist';
// 当前是否 active 状态
this._active = false;
// 初始化 droplist
this.droplist = new DropList(this, {
width: 100,
$title: $('<p>设置标题</p>'),
type: 'list', // droplist 以列表形式展示
list: [{ $elem: $('<h1>H1</h1>'), value: '<h1>' }, { $elem: $('<h2>H2</h2>'), value: '<h2>' }, { $elem: $('<h3>H3</h3>'), value: '<h3>' }, { $elem: $('<h4>H4</h4>'), value: '<h4>' }, { $elem: $('<h5>H5</h5>'), value: '<h5>' }, { $elem: $('<p>正文</p>'), value: '<p>' }],
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 = $('<div class="w-e-menu"><i class="w-e-icon-text-heigh"></i></div>'); // 创建菜单元素并添加到当前实例的 $elem 属性中
this.type = 'droplist'; // 设置菜单类型为下拉列表
// 当前是否 active 状态
this._active = false; // 初始化 active 状态为 false
// 初始化 droplist
this.droplist = new DropList(this, {
width: 160, // 设置下拉列表的宽度
$title: $('<p>字号</p>'), // 设置下拉列表的标题
type: 'list', // 设置下拉列表的类型为列表形式展示
list: [
{ $elem: $('<span style="font-size: x-small;">x-small</span>'), value: '1' }, // 定义第一个选项:字体大小为 x-small
{ $elem: $('<span style="font-size: small;">small</span>'), value: '2' }, // 定义第二个选项:字体大小为 small
{ $elem: $('<span>normal</span>'), value: '3' }, // 定义第三个选项:字体大小为 normal
{ $elem: $('<span style="font-size: large;">large</span>'), value: '4' }, // 定义第四个选项:字体大小为 large
{ $elem: $('<span style="font-size: x-large;">x-large</span>'), value: '5' }, // 定义第五个选项:字体大小为 x-large
{ $elem: $('<span style="font-size: xx-large;">xx-large</span>'), 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 = $('<div class="w-e-menu"><i class="w-e-icon-font"></i></div>');
// 设置菜单类型为下拉列表
this.type = 'droplist';
// 当前是否 active 状态
this._active = false;
// 获取配置的字体
var config = editor.config;
var fontNames = config.fontNames || [];
// 初始化 droplist
this.droplist = new DropList(this, {
width: 100, // 设置下拉列表宽度
$title: $('<p>字体</p>'), // 设置下拉列表标题
type: 'list', // 设置下拉列表类型为列表形式展示
list: fontNames.map(function (fontName) {
// 将每个字体名称映射为一个对象,包含显示的元素和值
return { $elem: $('<span style="font-family: ' + fontName + ';">' + fontName + '</span>'), 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 class="w-e-panel-container"></div>'); // 创建一个新的 div 作为面板容器
var width = opt.width || 300; // 默认宽度为 300px如果配置中有指定宽度则使用配置中的宽度
$container.css('width', width + 'px').css('margin-left', (0 - width) / 2 + 'px'); // 设置容器的宽度和左边距
// 添加关闭按钮
var $closeBtn = $('<i class="w-e-icon-close w-e-panel-close"></i>'); // 创建一个关闭按钮
$container.append($closeBtn); // 将关闭按钮添加到容器中
$closeBtn.on('click', function () {
// 绑定点击事件,当点击关闭按钮时隐藏面板
_this.hide();
});
// 创建一个包含类名 'w-e-panel-tab-title' 的 <ul> 元素,并赋值给 $tabTitleContainer
var $tabTitleContainer = $('<ul class="w-e-panel-tab-title"></ul>');
// 创建一个包含类名 'w-e-panel-tab-content' 的 <div> 元素,并赋值给 $tabContentContainer
var $tabContentContainer = $('<div class="w-e-panel-tab-content"></div>');
// 将 $tabTitleContainer 和 $tabContentContainer 添加到 $container 中
$container.append($tabTitleContainer).append($tabContentContainer);
// 获取配置项中的 height 值
var height = opt.height;
// 如果 height 存在,则设置 $tabContentContainer 的高度,并启用垂直滚动条
if (height) {
$tabContentContainer.css('height', height + 'px').css('overflow-y', 'auto');
}
// 获取配置项中的 tabs 数组,如果不存在则默认为空数组
var tabs = opt.tabs || [];
// 初始化一个空数组,用于存储每个 tab 的标题
var tabTitleArr = [];
// 初始化一个空数组,用于存储每个 tab 的内容
var tabContentArr = [];
// 遍历 tabs 数组,对每一个 tab 进行处理
tabs.forEach(function (tab, tabIndex) {
if (!tab) {
// 如果 tab 对象不存在,则直接返回
return;
}
var title = tab.title || ''; // 获取 tab 的标题,如果不存在则为空字符串
var tpl = tab.tpl || ''; // 获取 tab 的模板内容,如果不存在则为空字符串
// 替换语言变量
title = replaceLang(editor, title);
tpl = replaceLang(editor, tpl);
// 创建一个新的 <li> 元素,并设置其类名为 "w-e-item",内容为处理后的标题
var $title = $('<li class="w-e-item">' + title + '</li>');
// 将新创建的标题元素添加到 tab 标题容器中
$tabTitleContainer.append($title);
// 创建一个新的元素,内容为处理后的模板内容
var $content = $(tpl);
// 将新创建的内容元素添加到 tab 内容容器中
$tabContentContainer.append($content);
// 记录到内存
$title._index = tabIndex; // 将当前 tab 索引赋值给标题元素的 _index 属性
tabTitleArr.push($title); // 将标题元素添加到 tabTitleArr 数组中
tabContentArr.push($content); // 将内容元素添加到 tabContentArr 数组中
// 判断当前选项卡索引是否为0
if (tabIndex === 0) {
// 将标题的_active属性设置为true表示该标题处于激活状态
$title._active = true;
// 给标题添加'w-e-active'类,用于样式控制
$title.addClass('w-e-active');
} else {
// 如果当前选项卡索引不为0隐藏内容区域
$content.hide();
}
$title.on('click', function (e) {
// 如果当前标题已经是激活状态,则直接返回,不进行后续操作
if ($title._active) {
return;
}
// 遍历所有标题将它们的激活状态设为false并移除激活样式
tabTitleArr.forEach(function ($title) {
$title._active = false;
$title.removeClass('w-e-active');
});
// 隐藏所有内容区域
tabContentArr.forEach(function ($content) {
$content.hide();
});
// 设置当前点击的标题为激活状态,并添加激活样式
$title._active = true;
$title.addClass('w-e-active');
// 显示对应的内容区域
$content.show();
});
$container.on('click', function (e) {
// 点击时阻止冒泡,防止事件传播到父元素
e.stopPropagation();
});
$body.on('click', function (e) {
// 隐藏当前对象
_this.hide();
});
// 将容器元素添加到 DOM 中
$textContainerElem.append($container);
// 绑定 tabs 的事件,只有在添加到 DOM 之后才能成功绑定
tabs.forEach(function (tab, index) {
if (!tab) {
// 如果 tab 不存在,则跳过本次循环
return;
}
var events = tab.events || [];
events.forEach(function (event) {
var selector = event.selector; // 获取事件选择器
var type = event.type; // 获取事件类型
var fn = event.fn || emptyFn; // 获取事件处理函数,如果没有则使用空函数
var $content = tabContentArr[index]; // 获取对应的内容元素
$content.find(selector).on(type, function (e) {
// 在内容元素上绑定事件处理函数
e.stopPropagation(); // 阻止事件冒泡
var needToHide = fn(e); // 执行事件处理函数并获取是否需要隐藏的结果
// 执行完事件之后,是否要关闭 panel
if (needToHide) {
// 如果需要隐藏,则调用 hide 方法
_this.hide();
}
// 查找容器中的所有文本输入框和文本区域
var $inputs = $container.find('input[type=text],textarea');
if ($inputs.length) {
// 如果找到输入框或文本区域,则将焦点设置到第一个元素上
$inputs.get(0).focus();
}
// 将当前容器赋值给实例的 $container 属性
this.$container = $container;
// 调用私有方法隐藏其他面板
this._hideOtherPanels();
// 记录该菜单已经创建了面板
_isCreatedPanelMenus.push(menu);
hide: function hide() {
// 获取当前对象的menu属性
var menu = this.menu;
// 获取当前对象的$container属性
var $container = this.$container;
// 如果$container存在则移除该元素
if ($container) {
$container.remove();
}
// 过滤_isCreatedPanelMenus数组移除与当前menu匹配的元素
_isCreatedPanelMenus = _isCreatedPanelMenus.filter(function (item) {
// 如果item等于menu返回false表示移除该元素
if (item === menu) {
return false;
} else {
// 否则保留该元素
return true;
}
});
},
_hideOtherPanels: function _hideOtherPanels() {
// 如果未创建任何面板菜单,则直接返回
if (!_isCreatedPanelMenus.length) {
return;
}
// 遍历所有已创建的面板菜单
_isCreatedPanelMenus.forEach(function (menu) {
// 获取当前菜单对应的面板对象,如果不存在则为空对象
var panel = menu.panel || {};
// 如果面板对象存在且具有 hide 方法,则调用该方法隐藏面板
if (panel.hide) {
panel.hide();
}
});
}
};
function Link(editor) {
// 将传入的编辑器实例赋值给当前对象的 editor 属性
this.editor = editor;
// 创建一个包含链接图标的 div 元素,并赋值给当前对象的 $elem 属性
this.$elem = $('<div class="w-e-menu"><i class="w-e-icon-link"></i></div>');
// 设置当前对象类型为 'panel'
this.type = 'panel';
// 初始化当前对象的 active 状态为 false
this._active = false;
}
Link.prototype = {
constructor: Link,
onClick: function onClick(e) {
// 获取当前编辑器实例
var editor = this.editor;
// 定义一个变量用于存储链接元素
var $linkelem = void 0;
if (this._active) {
// 当前选区在链接里面
$linkelem = editor.selection.getSelectionContainerElem();
if (!$linkelem) {
// 如果选区没有包含任何元素,直接返回
return;
}
// 将该元素都包含在选取之内,以便后面整体替换
editor.selection.createRangeByElem($linkelem);
editor.selection.restoreSelection();
// 显示 panel并传入链接文本和链接地址
this._createPanel($linkelem.text(), $linkelem.attr('href'));
} else {
// 当前选区不在链接里面
if (editor.selection.isSelectionEmpty()) {
// 选区是空的,未选中内容
this._createPanel('', '');
} else {
// 选中内容了
this._createPanel(editor.selection.getSelectionText(), '');
}
}
_createPanel: function _createPanel(text, link) {
var _this = this;
// 生成一个随机的ID用于输入链接的input元素
var inputLinkId = getRandom('input-link');
// 生成一个随机的ID用于输入文本的input元素
var inputTextId = getRandom('input-text');
// 生成一个随机的ID用于确认按钮的button元素
var btnOkId = getRandom('btn-ok');
// 生成一个随机的ID用于删除按钮的button元素
var btnDelId = getRandom('btn-del');
// 是否显示“删除链接”按钮,根据当前状态决定
var delBtnDisplay = this._active ? 'inline-block' : 'none';
// 初始化并显示 panel
var panel = new Panel(this, {
width: 300,
// panel 中可包含多个 tab
tabs: [{
// tab 的标题
title: '链接',
// 模板,定义了输入框和按钮的 HTML 结构
tpl: '<div>\n <input id="' + inputTextId + '" type="text" class="block" value="' + text + '" placeholder="\u94FE\u63A5\u6587\u5B57"/></td>\n <input id="' + inputLinkId + '" type="text" class="block" value="' + link + '" placeholder="http://..."/></td>\n <div class="w-e-button-container">\n <button id="' + btnOkId + '" class="right">\u63D2\u5165</button>\n <button id="' + btnDelId + '" class="gray right" style="display:' + delBtnDisplay + '">\u5220\u9664\u94FE\u63A5</button>\n </div>\n </div>',
// 事件绑定
events: [
// 插入链接
{
selector: '#' + btnOkId, // 选择器,用于绑定点击事件到按钮上
type: 'click', // 事件类型为点击事件
fn: function fn() {
// 执行插入链接
var $link = $('#' + inputLinkId); // 获取输入框中的链接值
var $text = $('#' + inputTextId); // 获取输入框中的文本值
var link = $link.val(); // 获取链接的实际值
var text = $text.val(); // 获取文本的实际值
_this._insertLink(text, link); // 调用方法插入链接和文本
// 返回 true表示该事件执行完之后panel 要关闭。否则 panel 不会关闭
return true;
}
{
selector: '#' + btnDelId, // 选择器用于绑定点击事件的元素ID
type: 'click', // 事件类型,这里是点击事件
fn: function fn() { // 事件处理函数
// 执行删除链接操作
_this._delLink();
// 返回 true表示该事件执行完之后panel 要关闭。否则 panel 不会关闭
return true;
}
}]
}
// 显示 panel
panel.show();
// 记录属性
this.panel = panel;
},
_delLink: function _delLink() {
// 如果当前对象未激活,则直接返回
if (!this._active) {
return;
}
// 获取编辑器实例
var editor = this.editor;
// 获取当前选中的容器元素
var $selectionELem = editor.selection.getSelectionContainerElem();
// 如果没有选中的容器元素,则直接返回
if (!$selectionELem) {
return;
}
// 获取当前选中的文本内容
var selectionText = editor.selection.getSelectionText();
// 使用插入HTML命令将选中的文本包裹在<span>标签中
editor.cmd.do('insertHTML', '<span>' + selectionText + '</span>');
},
_insertLink: function _insertLink(text, link) {
// 获取编辑器实例
var editor = this.editor;
// 获取编辑器配置
var config = editor.config;
// 获取链接检查函数
var linkCheck = config.linkCheck;
// 初始化检查结果为 true
var checkResult = true; // 默认为 true
// 如果存在链接检查函数且其类型为函数,则执行检查
if (linkCheck && typeof linkCheck === 'function') {
// 调用链接检查函数并获取结果
checkResult = linkCheck(text, link);
}
// 如果检查结果为 true插入 HTML 链接
if (checkResult === true) {
// 使用编辑器命令插入 HTML 链接
editor.cmd.do('insertHTML', '<a href="' + link + '" target="_blank">' + text + '</a>');
} else {
// 如果检查结果不为 true弹出警告信息
alert(checkResult);
}
},
tryChangeActive: function tryChangeActive(e) {
// 获取编辑器实例
var editor = this.editor;
// 获取当前元素的jQuery对象
var $elem = this.$elem;
// 获取当前选中的容器元素
var $selectionELem = editor.selection.getSelectionContainerElem();
// 如果未选中任何元素,则直接返回
if (!$selectionELem) {
return;
}
// 判断选中的元素是否为链接(<a>标签)
if ($selectionELem.getNodeName() === 'A') {
// 如果是链接设置_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 Italic(editor) {
// 将传入的编辑器实例赋值给当前对象的 editor 属性
this.editor = editor;
// 创建一个包含斜体图标的 div 元素,并赋值给当前对象的 $elem 属性
this.$elem = $('<div class="w-e-menu">\n <i class="w-e-icon-italic"></i>\n </div>');
// 设置事件类型为点击事件
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 = $('<div class="w-e-menu">\n <i class="w-e-icon-redo"></i>\n </div>');
// 设置事件类型为 '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 = $('<div class="w-e-menu">\n <i class="w-e-icon-strikethrough"></i>\n </div>');
// 设置事件类型为点击
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 = $('<div class="w-e-menu">\n <i class="w-e-icon-underline"></i>\n </div>');
// 设置事件类型为点击
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 = $('<div class="w-e-menu">\n <i class="w-e-icon-undo"></i>\n </div>');
// 设置事件类型为点击
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 = $('<div class="w-e-menu"><i class="w-e-icon-list2"></i></div>'); // 创建菜单元素并添加到当前对象
this.type = 'droplist'; // 设置菜单类型为下拉列表
// 当前是否 active 状态
this._active = false; // 初始化 active 状态为 false
// 初始化 droplist
this.droplist = new DropList(this, {
width: 120, // 设置下拉列表宽度为 120
$title: $('<p>设置列表</p>'), // 设置下拉列表标题
type: 'list', // 设置下拉列表展示形式为列表
list: [
{ $elem: $('<span><i class="w-e-icon-list-numbered"></i> 有序列表</span>'), value: 'insertOrderedList' }, // 添加有序列表选项
{ $elem: $('<span><i class="w-e-icon-list2"></i> 无序列表</span>'), 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 = $('<div class="w-e-menu"><i class="w-e-icon-paragraph-left"></i></div>');
// 设置类型为 'droplist'
this.type = 'droplist';
// 初始化一个布尔值,表示当前是否处于 active 状态
this._active = false;
// 初始化 droplist 组件
this.droplist = new DropList(this, {
// 设置 droplist 的宽度
width: 100,
// 设置 droplist 的标题
$title: $('<p>对齐方式</p>'),
// 设置 droplist 的类型为列表形式展示
type: 'list',
// 定义列表项及其对应的值
list: [
{ $elem: $('<span><i class="w-e-icon-paragraph-left"></i> 靠左</span>'), value: 'justifyLeft' },
{ $elem: $('<span><i class="w-e-icon-paragraph-center"></i> 居中</span>'), value: 'justifyCenter' },
{ $elem: $('<span><i class="w-e-icon-paragraph-right"></i> 靠右</span>'), value: 'justifyRight' }
],
// 定义点击列表项时的回调函数
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 = $('<div class="w-e-menu"><i class="w-e-icon-pencil2"></i></div>');
// 设置菜单类型为下拉列表
this.type = 'droplist';
// 获取配置的颜色
var config = editor.config;
var colors = config.colors || [];
// 当前是否 active 状态
this._active = false;
// 初始化 droplist
this.droplist = new DropList(this, {
width: 120, // 设置下拉列表宽度
$title: $('<p>文字颜色</p>'), // 设置下拉列表标题
type: 'inline-block', // 设置下拉列表内容以 inline-block 形式展示
list: colors.map(function (color) {
// 将颜色数组映射为包含颜色样式和值的对象数组
return { $elem: $('<i style="color:' + color + ';" class="w-e-icon-pencil2"></i>'), 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 = $('<div class="w-e-menu"><i class="w-e-icon-paint-brush"></i></div>'); // 创建菜单元素并添加到当前实例的 $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: $('<p>背景色</p>'), // 设置下拉列表的标题
type: 'inline-block', // 设置下拉列表内容以 inline-block 形式展示
list: colors.map(function (color) {
return { $elem: $('<i style="color:' + color + ';" class="w-e-icon-paint-brush"></i>'), 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 = $('<div class="w-e-menu">\n <i class="w-e-icon-quotes-left"></i>\n </div>');
// 设置事件类型为 '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', '<P>');
} else {
// 否则,将选中的元素转换为引用格式
editor.cmd.do('formatBlock', '<BLOCKQUOTE>');
}
return;
}
// 定义变量 content 和 $targetELem初始值为 undefined
var content = void 0,
$targetELem = void 0;
if (nodeName === 'P') {
// 如果当前节点是 P 标签
// 将 P 标签的内容赋值给 content
content = $selectionElem.text();
// 创建一个新的 blockquote 元素,并将 P 标签的内容插入其中
$targetELem = $('<blockquote>' + content + '</blockquote>');
// 将新的 blockquote 元素插入到 P 标签之后
$targetELem.insertAfter($selectionElem);
// 移除原来的 P 标签
$selectionElem.remove();
// 结束函数执行
return;
}
if (nodeName === 'BLOCKQUOTE') {
// 如果当前节点是 BLOCKQUOTE 标签
// 将 BLOCKQUOTE 标签的内容赋值给 content
content = $selectionElem.text();
// 创建一个新的 P 元素,并将 BLOCKQUOTE 标签的内容插入其中
$targetELem = $('<p>' + content + '</p>');
// 将新的 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 = $('<div class="w-e-menu">\n <i class="w-e-icon-terminal"></i>\n </div>');
// 设置当前对象的类型为'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>标签中并插入到编辑器中
$code = $('<code>' + selectionText + '</code>');
editor.cmd.do('insertElem', $code);
// 创建一个新的选区范围,并将其设置为包含新插入的<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: '<div>\n <textarea id="' + textId + '" style="height:145px;;">' + value + '</textarea>\n <div class="w-e-button-container">\n <button id="' + btnId + '" class="right">\u63D2\u5165</button>\n </div>\n <div>',
// 事件绑定
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命令将传入的代码值包裹在<pre><code>标签中并插入到编辑器中
editor.cmd.do('insertHTML', '<pre><code>' + value + '</code></pre><p><br></p>');
},
// 更新代码
_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();
// 判断选中的元素是否为 <code> 且其父元素是否为 <pre>
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 = $('<div class="w-e-menu">\n <i class="w-e-icon-happy"></i>\n </div>');
// 设置菜单类型为面板
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) {
// 如果当前项存在,则将其包裹在 <span> 标签中并添加到 faceHtml 字符串中
if (item) {
faceHtml += '<span class="w-e-item">' + item + '</span>';
}
});
}
// 图片表情处理
if (emotType === 'image') {
// 遍历图片内容数组
content.forEach(function (item) {
// 获取图片的 src 和 alt 属性
var src = item.src;
var alt = item.alt;
// 如果 src 存在,则生成包含图片的 <span> 标签,并添加 data-w-e 属性
if (src) {
// 加一个 data-w-e 属性,点击图片的时候不再提示编辑图片
faceHtml += '<span class="w-e-item"><img src="' + src + '" alt="' + alt + '" data-w-e="1"/></span>';
}
});
}
// 加一个 data-w-e 属性,点击图片的时候不再提示编辑图片
faceHtml += '<span class="w-e-item"><img src="' + src + '" alt="' + alt + '" data-w-e="1"/></span>';
}
});
}
tabConfig.push({
// 设置标签页的标题
title: emotData.title,
// 设置标签页的内容模板,包含表情 HTML
tpl: '<div class="w-e-emoticon-container">' + faceHtml + '</div>',
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 = '<span>' + $target.html() + '</span>';
}
// 调用插入方法,将生成的 HTML 插入到编辑器中
_this._insert(insertHtml);
// 返回 true表示该事件执行完之后面板要关闭。否则面板不会关闭
return true;
}
}]
});
});
var panel = new Panel(this, {
// 设置面板的宽度为300
width: 300,
// 设置面板的高度为200
height: 200,
// 一个 Panel 包含多个 tabtabConfig 是 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 = $('<div class="w-e-menu"><i class="w-e-icon-table2"></i></div>');
// 设置当前对象类型为 '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: '<div>\n <p style="text-align:left; padding:5px 0;">\n \u521B\u5EFA\n <input id="' + textRowNum + '" type="text" value="5" style="width:40px;text-align:center;"/>\n \u884C\n <input id="' + textColNum + '" type="text" value="5" style="width:40px;text-align:center;"/>\n \u5217\u7684\u8868\u683C\n </p>\n <div class="w-e-button-container">\n <button id="' + btnInsertId + '" class="right">\u63D2\u5165</button>\n </div>\n </div>',
// 绑定事件处理函数
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 = '<table border="0" width="100%" cellpadding="0" cellspacing="0">';
// 循环生成每一行
for (r = 0; r < rowNum; r++) {
html += '<tr>'; // 添加行开始标签
if (r === 0) { // 如果是第一行
for (c = 0; c < colNum; c++) {
html += '<th>&nbsp;</th>'; // 添加表头单元格
}
} else { // 如果不是第一行
for (c = 0; c < colNum; c++) {
html += '<td>&nbsp;</td>'; // 添加普通单元格
}
}
html += '</tr>'; // 添加行结束标签
}
// 关闭 table 标签并添加换行符
html += '</table><p><br></p>';
// 执行命令
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: '<div>\n <div class="w-e-button-container" style="border-bottom:1px solid #f1f1f1;padding-bottom:5px;margin-bottom:5px;">\n <button id="' + addRowBtnId + '" class="left">\u589E\u52A0\u884C</button>\n <button id="' + delRowBtnId + '" class="red left">\u5220\u9664\u884C</button>\n <button id="' + addColBtnId + '" class="left">\u589E\u52A0\u5217</button>\n <button id="' + delColBtnId + '" class="red left">\u5220\u9664\u5217</button>\n </div>\n <div class="w-e-button-container">\n <button id="' + delTableBtnId + '" class="gray left">\u5220\u9664\u8868\u683C</button>\n </dv>\n </div>',
// 事件绑定
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 += '<td>&nbsp;</td>';
}
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 = $('<div class="w-e-menu"><i class="w-e-icon-play"></i></div>');
// 设置当前对象的类型为 '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: '<div>\n <input id="' + textValId + '" type="text" class="block" placeholder="\u683C\u5F0F\u5982\uFF1A<iframe src=... ></iframe>"/>\n <div class="w-e-button-container">\n <button id="' + btnId + '" class="right">\u63D2\u5165</button>\n </div>\n </div>',
// 绑定事件处理函数
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 + '<p><br></p>');
}
};
function Image(editor) {
// 将编辑器实例赋值给当前对象
this.editor = editor;
// 生成一个随机的菜单ID
var imgMenuId = getRandom('w-e-img');
// 创建一个包含图标的div元素并设置其ID为生成的菜单ID
this.$elem = $('<div class="w-e-menu" id="' + imgMenuId + '"><i class="w-e-icon-image"></i></div>');
// 将菜单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: '<div>\n' +
' <div class="w-e-button-container" style="border-bottom:1px solid #f1f1f1;padding-bottom:5px;margin-bottom:5px;">\n' +
' <span style="float:left;font-size:14px;margin:4px 5px 0 5px;color:#333;">最大宽度:</span>\n' +
' <button id="' + width30 + '" class="left">30%</button>\n' +
' <button id="' + width50 + '" class="left">50%</button>\n' +
' <button id="' + width100 + '" class="left">100%</button>\n' +
' </div>\n' +
' <div class="w-e-button-container">\n' +
' <button id="' + delBtn + '" class="gray left">删除图片</button>\n' +
' </dv>\n' +
' </div>',
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: '<div class="w-e-up-img-container">\n <div id="' + upTriggerId + '" class="w-e-up-btn">\n <i class="w-e-icon-upload2"></i>\n </div>\n <div style="display:none;">\n <input id="' + upFileId + '" type="file" multiple="multiple" accept="image/jpg,image/jpeg,image/png,image/gif,image/bmp"/>\n </div>\n </div>',
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: '<div>\n <input id="' + linkUrlId + '" type="text" class="block" placeholder="\u56FE\u7247\u94FE\u63A5"/></td>\n <div class="w-e-button-container">\n <button id="' + linkBtnId + '" class="right">\u63D2\u5165</button>\n </div>\n </div>',
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 = '<p>' + replaceHtmlSymbol(pasteText) + '</p>';
}
if (!pasteHtml) {
// 如果没有获取到任何HTML内容则直接返回
return;
}
// 过滤word中状态过来的无用字符
var docSplitHtml = pasteHtml.split('</html>');
if (docSplitHtml.length === 2) {
// 如果 docSplitHtml 数组的长度为 2则将 pasteHtml 设置为数组的第一个元素
pasteHtml = docSplitHtml[0];
}
// 过滤无用标签,移除 <meta>、<script> 和 <link> 标签
pasteHtml = pasteHtml.replace(/<(meta|script|link).+?>/igm, '');
// 去掉 HTML 注释
pasteHtml = pasteHtml.replace(/<!--.*?-->/mg, '');
// 过滤 data-xxx 属性,移除所有以 data- 开头的属性
pasteHtml = pasteHtml.replace(/\s?data-.+?=('|").+?('|")/igm, '');
if (ignoreImg) {
// 忽略图片将HTML中的<img>标签替换为空字符串
pasteHtml = pasteHtml.replace(/<img.+?>/igm, '');
}
if (filterStyle) {
// 过滤样式将HTML中的class和style属性移除
pasteHtml = pasteHtml.replace(/\s?(class|style)=('|").*?('|")/igm, '');
} else {
// 保留样式仅移除HTML中的class属性
pasteHtml = pasteHtml.replace(/\s?class=('|").*?('|")/igm, '');
}
// 返回处理后的HTML字符串
return pasteHtml;
}
function getPasteImgs(e) {
// 初始化结果数组,用于存储粘贴的图片文件
var result = [];
// 获取粘贴的文本内容
var txt = getPasteText(e);
if (txt) {
// 如果存在文本内容,则忽略图片并返回空结果数组
return result;
}
// 获取剪贴板数据对象,兼容不同浏览器的事件对象
var clipboardData = e.clipboardData || e.originalEvent && e.originalEvent.clipboardData || {};
// 获取剪贴板中的项目列表
var items = clipboardData.items;
if (!items) {
// 如果没有项目列表,则返回空结果数组
return result;
}
// 遍历剪贴板中的每个项目
objForEach(items, function (key, value) {
// 获取项目的MIME类型
var type = value.type;
// 如果项目是图片类型
if (/image/i.test(type)) {
// 将图片文件添加到结果数组中
result.push(value.getAsFile());
}
});
// 返回包含所有粘贴图片文件的结果数组
return result;
}
}
function getChildrenJSON($elem) {
// 初始化结果数组
var result = [];
// 获取子节点,如果不存在则返回空数组
var $children = $elem.childNodes() || []; // 注意 childNodes() 可以获取文本节点
// 遍历每一个子节点
$children.forEach(function (curElem) {
// 初始化当前元素的结果变量
var elemResult = void 0;
// 获取当前节点的类型
var nodeType = curElem.nodeType;
// 如果当前节点是文本节点nodeType === 3
if (nodeType === 3) {
// 获取文本内容
elemResult = curElem.textContent;
// 替换 HTML 符号
elemResult = replaceHtmlSymbol(elemResult);
}
// 普通 DOM 节点
if (nodeType === 1) {
// 如果节点类型为元素节点nodeType === 1
elemResult = {};
// 获取元素的标签名,并转换为小写字母
elemResult.tag = curElem.nodeName.toLowerCase();
// 初始化属性数据数组
var attrData = [];
// 获取当前元素的属性列表,如果没有则设置为空对象
var attrList = curElem.attributes || {};
// 获取属性列表的长度如果没有则为0
var attrListLength = attrList.length || 0;
// 遍历属性列表
for (var i = 0; i < attrListLength; i++) {
var attr = attrList[i];
// 将每个属性的名称和值存入attrData数组中
attrData.push({
name: attr.name,
value: attr.value
});
}
// 将属性数据数组赋值给elemResult的attrs属性
elemResult.attrs = attrData;
// 递归获取子元素的数据并赋值给elemResult的children属性
elemResult.children = getChildrenJSON($(curElem));
}
// 将当前元素的结果对象推入结果数组中
result.push(elemResult);
return result;
function Text(editor) {
this.editor = editor;
}
// 修改原型
Text.prototype = {
constructor: Text,
// 初始化
init: function init() {
// 绑定事件
this._bindEvent();
},
// 清空内容
clear: function clear() {
this.html('<p><br></p>');
},
// 获取 设置 html
html: function html(val) {
// 获取编辑器实例
var editor = this.editor;
// 获取编辑器的文本元素
var $textElem = editor.$textElem;
var html = void 0;
// 如果传入的值为空则获取当前文本元素的HTML内容
if (val == null) {
// 获取文本元素的HTML内容
html = $textElem.html();
// 替换掉所有的零宽度空格字符(&#8203
html = html.replace(/\u200b/gm, '');
// 返回处理后的HTML内容
return html;
} else {
// 如果传入的值不为空则设置文本元素的HTML内容为传入的值
$textElem.html(val);
// 初始化选取,将光标定位到内容尾部
editor.initSelection();
}
},
getJSON: function getJSON() {
// 获取当前编辑器实例
var editor = this.editor;
// 获取编辑器的文本元素
var $textElem = editor.$textElem;
// 调用getChildrenJSON函数将文本元素转换为JSON并返回
return getChildrenJSON($textElem);
},
// 获取 设置 text
text: function text(val) {
// 获取编辑器实例
var editor = this.editor;
// 获取编辑器中的文本元素
var $textElem = editor.$textElem;
var text = void 0;
// 如果传入的值为空,则获取当前文本内容
if (val == null) {
// 获取文本元素的文本内容
text = $textElem.text();
// 替换掉文本中的零宽度空格字符(&#8203
text = text.replace(/\u200b/gm, '');
return text;
} else {
// 设置文本元素的HTML内容为传入的值并包裹在<p>标签中
$textElem.text('<p>' + val + '</p>');
// 初始化选取,将光标定位到内容尾部
editor.initSelection();
}
},
// 追加内容
append: function append(html) {
var editor = this.editor;
var $textElem = editor.$textElem;
$textElem.append($(html));
// 初始化选取,将光标定位到内容尾部
editor.initSelection();
},
// 绑定事件
_bindEvent: function _bindEvent() {
// 实时保存选取
this._saveRangeRealTime();
// 按回车建时的特殊处理
this._enterKeyHandle();
// 清空时保留 <p><br></p>
this._clearHandle();
// 粘贴事件(粘贴文字,粘贴图片)
this._pasteHandle();
// tab 特殊处理
this._tabHandle();
// img 点击
this._imgHandle();
// 拖拽事件
this._dragHandle();
},
// 实时保存选取
_saveRangeRealTime: function _saveRangeRealTime() {
var editor = this.editor;
var $textElem = editor.$textElem;
// 保存当前的选区
function saveRange(e) {
// 随时保存选区
editor.selection.saveRange();
// 更新按钮 active 状态
editor.menus.changeActive();
}
// 按键后保存
$textElem.on('keyup', saveRange);
$textElem.on('mousedown', function (e) {
// mousedown 状态下,鼠标滑动到编辑区域外面,也需要保存选区
$textElem.on('mouseleave', saveRange);
});
$textElem.on('mouseup', function (e) {
saveRange();
// 在编辑器区域之内完成点击,取消鼠标滑动到编辑区外面的事件
$textElem.off('mouseleave', saveRange);
});
},
// 按回车键时的特殊处理
_enterKeyHandle: function _enterKeyHandle() {
// 获取编辑器实例
var editor = this.editor;
// 获取编辑器中的文本元素
var $textElem = editor.$textElem;
function insertEmptyP($selectionElem) {
// 创建一个新的<p>标签,其中包含一个<br>标签
var $p = $('<p><br></p>');
// 将新的<p>标签插入到当前选中元素的前面
$p.insertBefore($selectionElem);
// 创建一个范围,选中新插入的<p>标签
editor.selection.createRangeByElem($p, true);
// 恢复选区
editor.selection.restoreSelection();
// 移除当前选中的元素
$selectionElem.remove();
}
// 将回车之后生成的非 <p> 的顶级标签,改为 <p>
function pHandle(e) {
// 获取当前选中的容器元素
var $selectionElem = editor.selection.getSelectionContainerElem();
// 获取选中元素的父元素
var $parentElem = $selectionElem.parent();
// 检查父元素的HTML内容是否为 '<code><br></code>'
if ($parentElem.html() === '<code><br></code>') {
// 如果光标之前在一个 <p><code>.....</code></p> 标签内,回车后会生成一个空的 <p><code><br></code></p>
// 并且继续回车无法跳出这个状态,因此需要特殊处理
insertEmptyP($selectionElem);
return;
}
}
if (!$parentElem.equal($textElem)) {
// 检查父元素是否等于文本元素,如果不是顶级标签则返回
return;
}
var nodeName = $selectionElem.getNodeName();
if (nodeName === 'P') {
// 如果当前选中的元素是 <p> 标签,则无需处理,直接返回
return;
}
if ($selectionElem.text()) {
// 如果当前选中的元素有内容,则无需处理,直接返回
return;
}
// 插入一个空的 <p> 标签,并将光标定位到新插入的 <p> 标签,同时删除当前选中的标签
insertEmptyP($selectionElem);
$textElem.on('keyup', function (e) {
// 检查按键是否为回车键keyCode 13
if (e.keyCode !== 13) {
// 如果不是回车键,则直接返回,不执行后续操作
return;
}
// 调用 pHandle 函数处理回车事件
pHandle(e);
});
// <pre><code></code></pre> 回车时 特殊处理
function codeHandle(e) {
// 获取当前选中的容器元素
var $selectionElem = editor.selection.getSelectionContainerElem();
// 如果未选中任何元素,则直接返回
if (!$selectionElem) {
return;
}
// 获取选中元素的父元素
var $parentElem = $selectionElem.parent();
// 获取选中元素和父元素的节点名称
var selectionNodeName = $selectionElem.getNodeName();
var parentNodeName = $parentElem.getNodeName();
// 如果选中的元素不是<code>标签或其父元素不是<pre>标签,则忽略处理
if (selectionNodeName !== 'CODE' || parentNodeName !== 'PRE') {
// 不符合要求 忽略
return;
}
// 检查编辑器是否支持原生的insertHTML命令
if (!editor.cmd.queryCommandSupported('insertHTML')) {
// 必须原生支持 insertHTML 命令
return;
}
// 处理:光标定位到代码末尾,联系点击两次回车,即跳出代码块
if (editor._willBreakCode === true) {
// 此时可以跳出代码块
// 插入 <p> ,并将选取定位到 <p>
var $p = $('<p><br></p>');
$p.insertAfter($parentElem);
editor.selection.createRangeByElem($p, true);
editor.selection.restoreSelection();
// 修改状态
editor._willBreakCode = false;
e.preventDefault();
return;
}
var _startOffset = editor.selection.getRange().startOffset;
// 处理:回车时,不能插入 <br> 而是插入 \n ,因为是在 pre 标签里面
editor.cmd.do('insertHTML', '\n');
editor.selection.saveRange();
if (editor.selection.getRange().startOffset === _startOffset) {
// 没起作用,再来一遍
editor.cmd.do('insertHTML', '\n');
}
var codeLength = $selectionElem.html().length;
if (editor.selection.getRange().startOffset + 1 === codeLength) {
// 说明光标在代码最后的位置,执行了回车操作
// 记录下来,以便下次回车时候跳出 code
editor._willBreakCode = true;
}
// 阻止默认行为
e.preventDefault();
}
$textElem.on('keydown', function (e) {
if (e.keyCode !== 13) {
// 不是回车键
// 取消即将跳转代码块的记录
editor._willBreakCode = false;
return;
}
// <pre><code></code></pre> 回车时 特殊处理
codeHandle(e);
});
},
// 清空时保留 <p><br></p>
_clearHandle: function _clearHandle() {
// 获取编辑器实例
var editor = this.editor;
// 获取编辑器的文本元素
var $textElem = editor.$textElem;
// 为文本元素绑定键盘按下事件
$textElem.on('keydown', function (e) {
// 如果按下的键不是退格键,则直接返回
if (e.keyCode !== 8) {
return;
}
// 获取文本元素的HTML内容并转换为小写和去除首尾空格
var txtHtml = $textElem.html().toLowerCase().trim();
// 如果文本内容只剩下一个空行,则阻止默认行为,不再删除
if (txtHtml === '<p><br></p>') {
// 最后剩下一个空行,就不再删除了
e.preventDefault();
return;
}
});
$textElem.on('keyup', function (e) {
// 当按键抬起时触发事件处理函数
if (e.keyCode !== 8) {
// 如果按下的键不是退格键,则直接返回
return;
}
var $p = void 0; // 定义一个未初始化的变量 $p
var txtHtml = $textElem.html().toLowerCase().trim(); // 获取文本元素的 HTML 内容并转换为小写和去除首尾空格
// 在 Firefox 浏览器中用 '<br>' 判断,其他浏览器用空字符串判断
if (!txtHtml || txtHtml === '<br>') {
// 如果文本内容为空或等于 '<br>'
$p = $('<p><br/></p>'); // 创建一个新的 <p> 元素,包含一个 <br/>
$textElem.html(''); // 清空文本元素的内容,否则在 Firefox 下会有问题
$textElem.append($p); // 将新的 <p> 元素添加到文本元素中
editor.selection.createRangeByElem($p, false, true); // 创建一个范围选择器,选中新添加的 <p> 元素
editor.selection.restoreSelection(); // 恢复选区
}
});
// 粘贴事件(粘贴文字 粘贴图片)
_pasteHandle: function _pasteHandle() {
// 获取编辑器实例
var editor = this.editor;
// 获取编辑器配置
var config = editor.config;
// 获取粘贴过滤样式的配置
var pasteFilterStyle = config.pasteFilterStyle;
// 获取粘贴文本处理函数的配置
var pasteTextHandle = config.pasteTextHandle;
// 获取是否忽略图片的配置
var ignoreImg = config.pasteIgnoreImg;
// 获取编辑器的文本元素
var $textElem = editor.$textElem;
// 粘贴图片、文本的事件,每次只能执行一个
// 判断该次粘贴事件是否可以执行
var pasteTime = 0; // 初始化粘贴时间变量为0
function canDo() {
var now = Date.now(); // 获取当前时间的时间戳(毫秒)
var flag = false; // 初始化标志位为false
if (now - pasteTime >= 100) {
// 如果当前时间与上次粘贴时间的间隔大于等于100毫秒可以执行操作
flag = true; // 设置标志位为true
}
pasteTime = now; // 更新粘贴时间为当前时间
return flag; // 返回标志位
}
function resetTime() {
pasteTime = 0; // 将粘贴时间重置为0
}
// 粘贴文字
$textElem.on('paste', function (e) {
if (UA.isIE()) {
return; // 如果是IE浏览器直接返回不执行后续代码
} else {
// 阻止默认行为,使用 execCommand 的粘贴命令
e.preventDefault();
}
// 粘贴图片和文本,只能同时使用一个
if (!canDo()) {
// 如果当前不能执行操作,则直接返回
return;
}
// 获取粘贴的文字
var pasteHtml = getPasteHtml(e, pasteFilterStyle, ignoreImg);
var pasteText = getPasteText(e);
// 将换行符替换为HTML的<br>标签
pasteText = pasteText.replace(/\n/gm, '<br>');
// 获取当前选中的元素
var $selectionElem = editor.selection.getSelectionContainerElem();
if (!$selectionElem) {
// 如果没有选中的元素,则直接返回
return;
}
var nodeName = $selectionElem.getNodeName();
// code 中只能粘贴纯文本
if (nodeName === 'CODE' || nodeName === 'PRE') {
if (pasteTextHandle && isFunction(pasteTextHandle)) {
// 用户自定义过滤处理粘贴内容
pasteText = '' + (pasteTextHandle(pasteText) || '');
}
// 插入处理后的纯文本到编辑器中
editor.cmd.do('insertHTML', '<p>' + pasteText + '</p>');
return;
}
if (!pasteHtml) {
// 如果没有粘贴的 HTML 内容,重置时间并返回
resetTime();
return;
}
try {
// 在 Firefox 中,获取的 pasteHtml 可能是没有 <ul> 包裹的 <li>
// 因此执行 insertHTML 会报错
if (pasteTextHandle && isFunction(pasteTextHandle)) {
// 如果用户定义了粘贴文本处理函数,则使用该函数过滤粘贴内容
pasteHtml = '' + (pasteTextHandle(pasteHtml) || '');
}
// 插入过滤后的 HTML 内容到编辑器中
editor.cmd.do('insertHTML', pasteHtml);
} catch (ex) {
// 如果发生错误,使用 pasteText 来兼容一下
if (pasteTextHandle && isFunction(pasteTextHandle)) {
// 如果用户定义了粘贴文本处理函数,则使用该函数过滤粘贴内容
pasteText = '' + (pasteTextHandle(pasteText) || '');
}
// 将过滤后的文本插入到编辑器中,用 <p> 标签包裹
editor.cmd.do('insertHTML', '<p>' + pasteText + '</p>');
}
// 粘贴图片
$textElem.on('paste', function (e) {
// 检查是否为IE浏览器如果是则直接返回
if (UA.isIE()) {
return;
} else {
// 阻止默认的粘贴行为
e.preventDefault();
}
// 检查是否可以执行粘贴操作,如果不允许则直接返回
if (!canDo()) {
return;
}
// 获取粘贴的图片文件
var pasteFiles = getPasteImgs(e);
// 如果未获取到图片文件或文件列表为空,则直接返回
if (!pasteFiles || !pasteFiles.length) {
return;
}
// 获取当前的元素
var $selectionElem = editor.selection.getSelectionContainerElem(); // 获取当前选中的容器元素
if (!$selectionElem) { // 如果未选中任何元素,则直接返回
return;
}
var nodeName = $selectionElem.getNodeName(); // 获取选中元素的节点名称
// 如果选中的元素是代码块或预格式化文本,则忽略粘贴操作
if (nodeName === 'CODE' || nodeName === 'PRE') {
return;
}
// 上传图片功能
var uploadImg = editor.uploadImg; // 获取编辑器的图片上传功能
uploadImg.uploadImg(pasteFiles); // 调用上传图片方法,并传入粘贴的文件
});
},
// tab 特殊处理
_tabHandle: function _tabHandle() {
// 获取编辑器实例
var editor = this.editor;
// 获取编辑器的文本元素
var $textElem = editor.$textElem;
// 为文本元素绑定键盘按下事件
$textElem.on('keydown', function (e) {
// 如果按下的键不是Tab键则直接返回
if (e.keyCode !== 9) {
return;
}
// 如果编辑器不支持插入HTML命令则直接返回
if (!editor.cmd.queryCommandSupported('insertHTML')) {
// 必须原生支持 insertHTML 命令
return;
}
// 获取当前选中的元素容器
var $selectionElem = editor.selection.getSelectionContainerElem();
if (!$selectionElem) {
// 如果未选择元素,则直接返回
return;
}
var $parentElem = $selectionElem.parent();
// 获取选中元素的父元素
var selectionNodeName = $selectionElem.getNodeName();
// 获取选中元素的节点名称
var parentNodeName = $parentElem.getNodeName();
// 获取父元素的节点名称
if (selectionNodeName === 'CODE' && parentNodeName === 'PRE') {
// 如果选中的元素是<code>且其父元素是<pre>
// <pre><code> 里面
editor.cmd.do('insertHTML', ' ');
// 插入四个空格的HTML代码
} else {
// 普通文字
editor.cmd.do('insertHTML', '&nbsp;&nbsp;&nbsp;&nbsp;');
// 插入四个不间断空格的HTML代码
}
e.preventDefault();
// 阻止默认事件行为
});
},
// img 点击
_imgHandle: function _imgHandle() {
// 获取编辑器实例
var editor = this.editor;
// 获取编辑器的文本元素
var $textElem = editor.$textElem;
// 为图片增加 selected 样式
$textElem.on('click', 'img', function (e) {
// 获取当前点击的图片元素
var img = this;
// 将图片元素转换为 jQuery 对象
var $img = $(img);
if ($img.attr('data-w-e') === '1') {
// 是表情图片,忽略
return;
}
// 记录当前点击过的图片
editor._selectedImg = $img;
// 修改选区并 restore ,防止用户此时点击退格键,会删除其他内容
editor.selection.createRangeByElem($img);
editor.selection.restoreSelection();
});
// 去掉图片的 selected 样式
$textElem.on('click keyup', function (e) {
// 检查点击或键盘事件的目标是否是图片元素
if (e.target.matches('img')) {
// 如果是图片,忽略该事件
return;
}
// 清空编辑器中选中的图片记录
editor._selectedImg = null;
});
},
// 拖拽事件处理函数
_dragHandle: function _dragHandle() {
var editor = this.editor;
// 获取 document 对象
var $document = $(document);
// 禁用 document 上的拖拽相关事件
$document.on('dragleave drop dragenter dragover', function (e) {
// 阻止默认行为,防止浏览器处理拖拽事件
e.preventDefault();
});
// 添加编辑区域拖拽事件
var $textElem = editor.$textElem; // 获取编辑器的文本元素
$textElem.on('drop', function (e) { // 为文本元素绑定'drop'事件处理函数
e.preventDefault(); // 阻止默认的拖放行为
var files = e.dataTransfer && e.dataTransfer.files; // 获取拖放的文件对象
if (!files || !files.length) { // 如果文件对象不存在或文件长度为0则返回
return;
}
// 上传图片
var uploadImg = editor.uploadImg; // 获取编辑器的图片上传功能
uploadImg.uploadImg(files); // 调用上传图片的方法,传入文件对象
});
function Command(editor) {
// 初始化Command对象并设置编辑器实例
this.editor = editor;
}
// 修改原型
Command.prototype = {
constructor: Command,
// 执行命令
do: function _do(name, value) {
// 获取当前编辑器实例
var editor = this.editor;
// 使用 styleWithCSS
if (!editor._useStyleWithCSS) {
// 如果编辑器未使用 CSS 样式,则启用它
document.execCommand('styleWithCSS', null, true);
// 标记编辑器已使用 CSS 样式
editor._useStyleWithCSS = true;
}
// 如果无选区,忽略
if (!editor.selection.getRange()) {
return;
}
// 恢复选取
editor.selection.restoreSelection();
// 执行命令或自定义事件
var _name = '_' + name;
if (this[_name]) {
// 如果有自定义事件,调用自定义事件处理函数
this[_name](value);
} else {
// 否则,执行默认的 command
this._execCommand(name, value);
}
// 修改菜单状态
editor.menus.changeActive();
// 最后,恢复选取保证光标在原来的位置闪烁
editor.selection.saveRange();
editor.selection.restoreSelection();
// 触发 onchange
editor.change && editor.change();
},
// 自定义 insertHTML 事件
_insertHTML: function _insertHTML(html) {
var editor = this.editor;
var range = editor.selection.getRange();
if (this.queryCommandSupported('insertHTML')) {
// 检查浏览器是否支持 'insertHTML' 命令,如果支持则使用 W3C 标准方法插入 HTML
this._execCommand('insertHTML', html);
} else if (range.insertNode) {
// 如果不支持 'insertHTML' 命令,但支持 insertNode 方法IE 浏览器)
range.deleteContents(); // 删除当前选区的内容
range.insertNode($(html)[0]); // 将新的 HTML 节点插入到选区中
} else if (range.pasteHTML) {
// 如果不支持 insertNode 方法,但支持 pasteHTML 方法IE <= 10 浏览器)
range.pasteHTML(html); // 使用 pasteHTML 方法插入 HTML
}
// 插入 elem
_insertElem: function _insertElem($elem) {
var editor = this.editor;
var range = editor.selection.getRange();
if (range.insertNode) {
// 如果 range 对象支持 insertNode 方法
range.deleteContents();
// 删除当前选区的内容
range.insertNode($elem[0]);
// 在当前选区插入指定的节点
}
},
// 封装 execCommand 方法,用于执行文档命令
_execCommand: function _execCommand(name, value) {
document.execCommand(name, false, value);
// 调用 document.execCommand 方法执行指定命令
},
// 封装 queryCommandValue 方法,用于查询命令的值
queryCommandValue: function queryCommandValue(name) {
return document.queryCommandValue(name);
// 返回指定命令的当前值
},
// 封装 queryCommandState 方法,用于查询命令的状态
queryCommandState: function queryCommandState(name) {
return document.queryCommandState(name);
// 返回指定命令的启用状态
},
// 封装 queryCommandSupported 方法,用于查询是否支持某命令
queryCommandSupported: function queryCommandSupported(name) {
return document.queryCommandSupported(name);
// 返回指定命令是否被支持
}
};
function API(editor) {
// 初始化 API 对象,并设置编辑器实例
this.editor = editor;
// 初始化当前选区为 null
this._currentRange = null;
}
// 修改原型
API.prototype = {
constructor: API,
// 获取 range 对象
getRange: function getRange() {
// 返回当前的选区对象
return this._currentRange;
},
// 保存选区
saveRange: function saveRange(_range) {
// 将传入的选区对象保存到当前对象的 _currentRange 属性中
if (_range) {
// 保存已有选区
this._currentRange = _range;
return;
}
// 获取当前的选区
var selection = window.getSelection();
if (selection.rangeCount === 0) {
// 如果当前没有选区,则直接返回
return;
}
var range = selection.getRangeAt(0);
// 判断选区内容是否在编辑内容之内
var $containerElem = this.getSelectionContainerElem(range);
if (!$containerElem) {
// 如果选区内容不在编辑内容之内,则直接返回
return;
}
// 判断选区内容是否在不可编辑区域之内
if ($containerElem.attr('contenteditable') === 'false' || $containerElem.parentUntil('[contenteditable=false]')) {
// 如果容器元素不可编辑或其父级元素中存在不可编辑的元素,则直接返回
return;
}
var editor = this.editor;
var $textElem = editor.$textElem;
if ($textElem.isContain($containerElem)) {
// 判断容器元素是否在编辑器的文本内容之内
this._currentRange = range;
// 如果是,则将当前选区设置为该范围
}
},
collapseRange: function collapseRange(toStart) {
if (toStart == null) {
// 如果未传入参数,则默认为 false
toStart = false;
}
var range = this._currentRange;
if (range) {
// 如果当前有选区,则根据参数折叠选区
range.collapse(toStart);
}
},
// 获取当前选中区域的文字
getSelectionText: function getSelectionText() {
// 获取当前的选区对象
var range = this._currentRange;
// 如果存在选区对象,返回选区中的文本内容
if (range) {
return this._currentRange.toString();
} else {
// 如果不存在选区对象,返回空字符串
return '';
}
},
// 选区的 $Elem
getSelectionContainerElem: function getSelectionContainerElem(range) {
// 如果没有传入 range则使用当前范围
range = range || this._currentRange;
var elem = void 0;
// 如果 range 存在
if (range) {
// 获取公共祖先容器
elem = range.commonAncestorContainer;
// 返回包装后的节点如果节点类型为元素节点1则直接返回否则返回其父节点
return $(elem.nodeType === 1 ? elem : elem.parentNode);
}
},
getSelectionStartElem: function getSelectionStartElem(range) {
// 如果没有传入 range则使用当前范围
range = range || this._currentRange;
var elem = void 0;
// 如果 range 存在
if (range) {
// 获取起始容器
elem = range.startContainer;
// 返回包装后的节点如果节点类型为元素节点1则直接返回否则返回其父节点
return $(elem.nodeType === 1 ? elem : elem.parentNode);
}
},
getSelectionEndElem: function getSelectionEndElem(range) {
// 如果没有传入 range则使用当前范围
range = range || this._currentRange;
var elem = void 0;
// 如果 range 存在
if (range) {
// 获取结束容器
elem = range.endContainer;
// 返回包装后的节点如果节点类型为元素节点1则直接返回否则返回其父节点
return $(elem.nodeType === 1 ? elem : elem.parentNode);
}
},
isSelectionEmpty: function isSelectionEmpty() {
// 获取当前的选区对象
var range = this._currentRange;
// 检查选区对象和起始容器是否存在
if (range && range.startContainer) {
// 检查起始容器和结束容器是否相同
if (range.startContainer === range.endContainer) {
// 检查起始偏移量和结束偏移量是否相同
if (range.startOffset === range.endOffset) {
// 如果起始和结束位置相同,则选区为空
return true;
}
}
}
// 如果上述条件不满足,则选区不为空
return false;
},
// 恢复选区
restoreSelection: function restoreSelection() {
// 获取当前窗口的选区对象
var selection = window.getSelection();
// 移除所有选区范围
selection.removeAllRanges();
// 将之前保存的选区范围添加回去
selection.addRange(this._currentRange);
},
// 创建一个空白(即 &#8203 字符)选区
createEmptyRange: function createEmptyRange() {
// 获取编辑器实例
var editor = this.editor;
// 获取当前选区范围
var range = this.getRange();
// 定义一个未初始化的元素变量
var $elem = void 0;
if (!range) {
// 如果 range 为空,则直接返回,不进行后续操作
return;
}
if (!this.isSelectionEmpty()) {
// 如果当前选区不为空,则直接返回,不进行后续操作
return;
}
try {
// 目前只支持 webkit 内核
if (UA.isWebkit()) {
// 插入 &#8203; 字符,这是一个零宽空格,用于占位
editor.cmd.do('insertHTML', '&#8203;');
// 修改 offset 位置,将光标移动到插入的零宽空格之后
range.setEnd(range.endContainer, range.endOffset + 1);
// 存储当前范围
this.saveRange(range);
} else {
// 创建一个包含零宽空格的 <strong> 元素
$elem = $('<strong>&#8203;</strong>');
// 插入该元素到编辑器中
editor.cmd.do('insertElem', $elem);
// 根据插入的元素创建一个新的范围
this.createRangeByElem($elem, true);
}
} catch (ex) {
// 部分情况下会报错,兼容一下
}
},
// 根据 $Elem 设置选区
createRangeByElem: function createRangeByElem($elem, toStart, isContent) {
// $elem - 经过封装的 elem
// toStart - true 开始位置false 结束位置
// isContent - 是否选中Elem的内容
if (!$elem.length) {
// 如果 $elem 为空,则直接返回
return;
}
var elem = $elem[0];
// 获取原生的 DOM 元素
var range = document.createRange();
// 创建一个新的 Range 对象
if (isContent) {
range.selectNodeContents(elem);
// 如果 isContent 为 true则选择 elem 的所有内容
} else {
range.selectNode(elem);
// 如果 isContent 为 false则选择整个 elem
}
if (typeof toStart === 'boolean') {
range.collapse(toStart);
// 根据 toStart 的值折叠范围到起始或结束位置
}
// 存储 range
this.saveRange(range);
// 调用 saveRange 方法保存当前的范围
}
};
function Progress(editor) {
// 初始化编辑器实例
this.editor = editor;
// 初始化时间变量
this._time = 0;
// 初始化显示状态为false
this._isShow = false;
// 初始化渲染状态为false
this._isRender = false;
// 初始化超时ID
this._timeoutId = 0;
// 获取文本容器元素
this.$textContainer = editor.$textContainerElem;
// 创建进度条的DOM元素
this.$bar = $('<div class="w-e-progress"></div>');
}
Progress.prototype = {
constructor: Progress,
show: function show(progress) {
var _this = this;
// 如果进度条已经显示,则直接返回
if (this._isShow) {
return;
}
// 设置进度条显示状态为true
this._isShow = true;
// 获取进度条的DOM元素
var $bar = this.$bar;
// 检查是否已经渲染过
if (!this._isRender) {
// 获取文本容器的引用
var $textContainer = this.$textContainer;
// 将进度条添加到文本容器中
$textContainer.append($bar);
} else {
// 如果已经渲染过,则设置渲染标志为 true
this._isRender = true;
}
// 改变进度节流100ms 渲染一次)
// 检查当前时间与上次记录时间的差值是否大于 100ms
if (Date.now() - this._time > 100) {
// 如果进度值小于等于 1
if (progress <= 1) {
// 设置进度条的宽度为当前进度的百分比
$bar.css('width', progress * 100 + '%');
// 更新上次记录时间为当前时间
this._time = Date.now();
}
}
var timeoutId = this._timeoutId;
// 获取当前对象的超时ID
if (timeoutId) {
clearTimeout(timeoutId);
// 如果存在超时ID则清除该超时
}
timeoutId = setTimeout(function () {
_this._hide();
}, 500);
// 设置一个新的超时在500毫秒后调用_hide方法
_hide: function _hide() {
var $bar = this.$bar;
$bar.remove();
// 移除进度条元素
this._time = 0;
// 重置时间变量为0
this._isShow = false;
// 设置显示状态为false
this._isRender = false;
// 设置渲染状态为false
}
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
// 定义一个函数用于判断对象类型兼容ES6的Symbol类型
function UploadImg(editor) {
// 构造函数,初始化编辑器实例
this.editor = editor;
}
UploadImg.prototype = {
constructor: UploadImg,
_alert: function _alert(alertInfo, debugInfo) {
var editor = this.editor; // 获取编辑器实例
var debug = editor.config.debug; // 获取是否处于调试模式的配置
var customAlert = editor.config.customAlert; // 获取自定义警告函数的配置
if (debug) {
// 如果处于调试模式,抛出错误并包含调试信息
throw new Error('wangEditor: ' + (debugInfo || alertInfo));
} else {
if (customAlert && typeof customAlert === 'function') {
// 如果配置了自定义警告函数且其类型为函数,调用自定义警告函数
customAlert(alertInfo);
} else {
// 否则使用默认的alert函数显示警告信息
alert(alertInfo);
}
}
},
insertLinkImg: function insertLinkImg(link) {
// 保存当前上下文的引用
var _this2 = this;
// 如果链接为空,直接返回
if (!link) {
return;
}
// 获取编辑器实例
var editor = this.editor;
// 获取编辑器配置
var config = editor.config;
// 校验链接是否合法
var linkImgCheck = config.linkImgCheck;
var checkResult = void 0;
// 如果配置了链接图片校验函数并且该函数是有效的
if (linkImgCheck && typeof linkImgCheck === 'function') {
// 执行校验函数并获取结果
checkResult = linkImgCheck(link);
// 如果校验结果是字符串类型,表示校验失败
if (typeof checkResult === 'string') {
// 弹出提示信息
alert(checkResult);
// 终止函数执行
return;
}
}
// 使用 editor.cmd.do 方法插入 HTML 代码,将图片链接嵌入到编辑器中
editor.cmd.do('insertHTML', '<img src="' + link + '" style="max-width:100%;"/>');
// 创建一个新的 img 元素用于验证图片 URL 是否有效
var img = document.createElement('img');
// 当图片加载成功时执行的回调函数
img.onload = function () {
// 获取配置中的图片链接回调函数
var callback = config.linkImgCallback;
if (callback && typeof callback === 'function') {
// 如果回调函数存在且为函数类型,则调用该回调函数并传入图片链接
callback(link);
}
// 释放 img 对象引用
img = null;
};
// 当图片加载失败时执行的回调函数
img.onerror = function () {
// 释放 img 对象引用
img = null;
// 弹出提示框,告知用户无法成功下载图片,并提供错误信息
_this2._alert('插入图片错误', 'wangEditor: \u63D2\u5165\u56FE\u7247\u51FA\u9519\uFF0C\u56FE\u7247\u94FE\u63A5\u662F "' + link + '"\uFF0C\u4E0B\u8F7D\u8BE5\u94FE\u63A5\u5931\u8D25');
return;
};
// 当图片加载被中断时执行的回调函数
img.onabort = function () {
// 释放 img 对象引用
img = null;
};
// 设置 img 元素的 src 属性为图片链接,开始加载图片
img.src = link;
},
// 上传图片
uploadImg: function uploadImg(files) {
var _this3 = this;
// 如果文件列表为空,则直接返回
if (!files || !files.length) {
return;
}
// ------------------------------ 获取配置信息 ------------------------------
// 获取编辑器实例
var editor = this.editor;
// 获取编辑器的配置对象
var config = editor.config;
// 获取上传图片的服务器地址
var uploadImgServer = config.uploadImgServer;
// 是否显示Base64编码的图片
var uploadImgShowBase64 = config.uploadImgShowBase64;
// 获取上传图片的最大尺寸(字节)
var maxSize = config.uploadImgMaxSize;
// 将最大尺寸转换为MB
var maxSizeM = maxSize / 1024 / 1024;
// 获取上传图片的最大长度默认值为10000
var maxLength = config.uploadImgMaxLength || 10000;
// 获取上传文件的名称,默认值为空字符串
var uploadFileName = config.uploadFileName || '';
// 获取上传图片时附带的参数,默认值为空对象
var uploadImgParams = config.uploadImgParams || {};
// 是否在上传图片时附带URL参数
var uploadImgParamsWithUrl = config.uploadImgParamsWithUrl;
// 获取上传图片时的请求头,默认值为空对象
var uploadImgHeaders = config.uploadImgHeaders || {};
// 获取上传图片的钩子函数,默认值为空对象
var hooks = config.uploadImgHooks || {};
// 获取上传图片的超时时间默认值为3000毫秒
var timeout = config.uploadImgTimeout || 3000;
// 是否在上传图片时携带凭证信息
var withCredentials = config.withCredentials;
if (withCredentials == null) {
// 如果 withCredentials 为空,则将其设置为 false
withCredentials = false;
}
var customUploadImg = config.customUploadImg;
if (!customUploadImg) {
// 如果没有自定义上传图片的方法,则需要以下两个配置才能继续进行图片上传
if (!uploadImgServer && !uploadImgShowBase64) {
// 如果既没有上传服务器地址也没有显示 Base64 的选项,则直接返回
return;
}
}
// ------------------------------ 验证文件信息 ------------------------------
var resultFiles = [];
var errInfo = [];
arrForEach(files, function (file) {
var name = file.name;
var size = file.size;
// chrome 低版本中 name 可能为 undefined
if (!name || !size) {
// 如果文件名或文件大小为空,直接返回
return;
}
if (/\.(jpg|jpeg|png|bmp|gif|webp)$/i.test(name) === false) {
// 检查文件后缀名是否合法,如果不是图片格式,则加入错误信息并返回
errInfo.push('\u3010' + name + '\u3011\u4E0D\u662F\u56FE\u7247');
return;
}
if (maxSize < size) {
// 检查文件大小是否超过最大限制,如果超过,则加入错误信息并返回
errInfo.push('\u3010' + name + '\u3011\u5927\u4E8E ' + maxSizeM + 'M');
return;
}
// 如果文件验证通过,将其加入结果列表
resultFiles.push(file);
// 抛出验证信息
if (errInfo.length) {
// 如果有错误信息,弹出提示框并返回
this._alert('图片验证未通过: \n' + errInfo.join('\n'));
return;
}
if (resultFiles.length > maxLength) {
// 如果上传的图片数量超过最大限制,弹出提示框并返回
this._alert('一次最多上传' + maxLength + '张图片');
return;
}
// ------------------------------ 自定义上传 ------------------------------
if (customUploadImg && typeof customUploadImg === 'function') {
// 如果存在自定义上传函数,调用该函数进行上传
customUploadImg(resultFiles, this.insertLinkImg.bind(this));
// 阻止以下代码执行
return;
}
// 添加图片数据
var formdata = new FormData();
arrForEach(resultFiles, function (file) {
// 遍历每个文件将其添加到FormData对象中
var name = uploadFileName || file.name;
formdata.append(name, file);
});
// ------------------------------ 上传图片 ------------------------------
if (uploadImgServer && typeof uploadImgServer === 'string') {
// 检查 uploadImgServer 是否存在且为字符串类型
var uploadImgServerArr = uploadImgServer.split('#');
// 将上传图片服务器地址按 '#' 分割成数组
uploadImgServer = uploadImgServerArr[0];
// 获取分割后的第一个元素,即上传图片服务器地址
var uploadImgServerHash = uploadImgServerArr[1] || '';
// 获取分割后的第二个元素(如果有),否则为空字符串
objForEach(uploadImgParams, function (key, val) {
// 遍历上传图片参数对象
// val = encodeURIComponent(val)
// 编码参数值,但因用户反馈,自定义参数不能默认编码,从 v3.1.1 版本开始注释掉
if (uploadImgParamsWithUrl) {
// 如果需要将参数拼接到 URL 中
if (uploadImgServer.indexOf('?') > 0) {
// 如果 URL 中已经包含查询参数,则添加 '&'
uploadImgServer += '&';
} else {
// 如果 URL 中不包含查询参数,则添加 '?'
uploadImgServer += '?';
}
uploadImgServer = uploadImgServer + key + '=' + val;
// 将参数键值对拼接到 URL 中
}
formdata.append(key, val);
// 将参数添加到 FormData 对象中
});
if (uploadImgServerHash) {
// 如果存在上传图片服务器的哈希值则将其添加到上传图片服务器的URL中
uploadImgServer += '#' + uploadImgServerHash;
}
// 定义 xhr 对象,用于发送 HTTP 请求
var xhr = new XMLHttpRequest();
// 初始化一个 POST 请求,目标 URL 为 uploadImgServer
xhr.open('POST', uploadImgServer);
// 设置请求超时时间
xhr.timeout = timeout;
// 当请求超时时触发的事件处理函数
xhr.ontimeout = function () {
// hook - timeout
// 如果定义了超时钩子函数且其类型为函数,则调用该钩子函数
if (hooks.timeout && typeof hooks.timeout === 'function') {
hooks.timeout(xhr, editor);
}
// 显示上传图片超时的提示信息
_this3._alert('上传图片超时');
};
// 监控 progress
if (xhr.upload) {
// 当上传进度发生变化时触发
xhr.upload.onprogress = function (e) {
var percent = void 0;
// 创建一个新的进度条实例
var progressBar = new Progress(editor);
if (e.lengthComputable) {
// 计算上传的百分比
percent = e.loaded / e.total;
// 显示进度条
progressBar.show(percent);
}
};
}
// 当请求状态变化时触发
xhr.onreadystatechange = function () {
var result = void 0;
if (xhr.readyState === 4) { // 请求完成
if (xhr.status < 200 || xhr.status >= 300) { // 请求失败
// 如果定义了错误钩子函数,则调用它
if (hooks.error && typeof hooks.error === 'function') {
hooks.error(xhr, editor);
}
// 弹出错误提示框,显示错误信息
_this3._alert('上传图片发生错误', '\u4E0A\u4F20\u56FE\u7247\u53D1\u751F\u9519\u8BEF\uFF0C\u670D\u52A1\u5668\u8FD4\u56DE\u72B6\u6001\u662F ' + xhr.status);
return;
}
// 获取响应文本
result = xhr.responseText;
// 检查 result 是否为对象,如果不是则尝试将其解析为 JSON 对象
if ((typeof result === 'undefined' ? 'undefined' : _typeof(result)) !== 'object') {
try {
// 尝试将 result 解析为 JSON 对象
result = JSON.parse(result);
} catch (ex) {
// 如果解析失败,调用 hooks.fail 方法并显示错误提示
if (hooks.fail && typeof hooks.fail === 'function') {
hooks.fail(xhr, editor, result);
}
// 显示上传图片失败的提示信息
_this3._alert('上传图片失败', '上传图片返回结果错误,返回结果是: ' + result);
return;
}
}
// 如果 hooks.customInsert 不存在且 result.errno 不为 '0',表示上传失败
if (!hooks.customInsert && result.errno != '0') {
// 调用 hooks.fail 方法并显示错误提示
if (hooks.fail && typeof hooks.fail === 'function') {
hooks.fail(xhr, editor, result);
}
// 显示数据错误的提示信息
_this3._alert('上传图片失败', '上传图片返回结果错误,返回结果 errno=' + result.errno);
} else {
// 如果存在自定义插入方法,则调用该方法
if (hooks.customInsert && typeof hooks.customInsert === 'function') {
hooks.customInsert(_this3.insertLinkImg.bind(_this3), result, editor);
} else {
// 否则,将图片链接插入编辑器
var data = result.data || [];
data.forEach(function (link) {
_this3.insertLinkImg(link);
});
}
}
// hook - success
if (hooks.success && typeof hooks.success === 'function') {
// 如果存在成功钩子函数,并且其类型为函数,则调用该钩子函数
hooks.success(xhr, editor, result);
}
// hook - before
if (hooks.before && typeof hooks.before === 'function') {
// 如果存在前置钩子函数,并且其类型为函数,则调用该钩子函数
var beforeResult = hooks.before(xhr, editor, resultFiles);
// 检查前置钩子函数的返回值是否为对象
if (beforeResult && (typeof beforeResult === 'undefined' ? 'undefined' : _typeof(beforeResult)) === 'object') {
// 如果返回的对象包含 prevent 属性且值为 true表示用户放弃上传
if (beforeResult.prevent) {
// 显示提示信息给用户
this._alert(beforeResult.msg);
// 终止后续操作
return;
}
}
}
// 遍历上传图片的自定义 headers并设置到 xhr 对象中
objForEach(uploadImgHeaders, function (key, val) {
xhr.setRequestHeader(key, val);
});
// 设置 xhr 对象的 withCredentials 属性,以支持跨域请求时传递 cookie
xhr.withCredentials = withCredentials;
// 发送包含表单数据的请求
xhr.send(formdata);
// 注意,要 return 。不去操作接下来的 base64 显示方式
return;
// ------------------------------ 显示 base64 格式 ------------------------------
if (uploadImgShowBase64) {
// 遍历文件数组
arrForEach(files, function (file) {
var _this = _this3; // 保存当前上下文的引用
var reader = new FileReader(); // 创建FileReader对象用于读取文件内容
reader.readAsDataURL(file); // 以Data URL的形式读取文件内容
reader.onload = function () {
// 当文件读取完成时调用insertLinkImg方法插入图片链接
_this.insertLinkImg(this.result);
};
});
}
// id累加
var editorId = 1;
// 构造函数
function Editor(toolbarSelector, textSelector) {
if (toolbarSelector == null) {
// 没有传入任何参数,报错
throw new Error('错误:初始化编辑器时候未传入任何参数,请查阅文档');
}
// id用以区分单个页面不同的编辑器对象
this.id = 'wangEditor-' + editorId++;
this.toolbarSelector = toolbarSelector;
this.textSelector = textSelector;
// 自定义配置
this.customConfig = {};
}
// 修改原型
Editor.prototype = {
constructor: Editor,
// 初始化配置
_initConfig: function _initConfig() {
// 创建一个空对象 target用于存储合并后的配置
var target = {};
// 将默认配置 config 和用户自定义配置 this.customConfig 合并到 target 中,并赋值给 this.config
this.config = Object.assign(target, config, this.customConfig);
// 获取语言配置,生成正则表达式
var langConfig = this.config.lang || {};
// 初始化一个数组,用于存储正则表达式和对应的替换值
var langArgs = [];
// 遍历语言配置对象,为每个规则生成正则表达式
objForEach(langConfig, function (key, val) {
// key 即需要生成正则表达式的规则,如“插入链接”
// val 即需要被替换成的语言如“insert link”
langArgs.push({
reg: new RegExp(key, 'img'), // 创建正则表达式对象
val: val // 设置替换值
});
});
// 将生成的正则表达式数组赋值给 this.config.langArgs
this.config.langArgs = langArgs;
},
// 初始化 DOM
_initDom: function _initDom() {
var _this = this;
// 获取工具栏选择器
var toolbarSelector = this.toolbarSelector;
// 将工具栏选择器转换为 jQuery 对象
var $toolbarSelector = $(toolbarSelector);
// 获取文本选择器
var textSelector = this.textSelector;
// 获取配置对象
var config$$1 = this.config;
// 从配置中获取 zIndex 值
var zIndex = config$$1.zIndex;
// 定义变量
var $toolbarElem = void 0,
$textContainerElem = void 0,
$textElem = void 0,
$children = void 0;
if (textSelector == null) {
// 如果未传入文本选择器,则创建新的工具栏和文本容器元素
$toolbarElem = $('<div></div>');
$textContainerElem = $('<div></div>');
// 将编辑器区域原有的内容暂存起来
$children = $toolbarSelector.children();
// 将新创建的工具栏和文本容器添加到 DOM 结构中
$toolbarSelector.append($toolbarElem).append($textContainerElem);
// 自行创建的,需要配置默认的样式
$toolbarElem.css('background-color', '#f1f1f1').css('border', '1px solid #ccc');
$textContainerElem.css('border', '1px solid #ccc').css('border-top', 'none').css('height', '300px');
} else {
// toolbar 和 text 的选择器都有值,记录属性
$toolbarElem = $toolbarSelector;
$textContainerElem = $(textSelector);
// 将编辑器区域原有的内容,暂存起来
$children = $textContainerElem.children();
}
// 编辑区域
$textElem = $('<div></div>');
$textElem.attr('contenteditable', 'true').css('width', '100%').css('height', '100%');
// 初始化编辑区域内容
if ($children && $children.length) {
$textElem.append($children);
} else {
$textElem.append($('<p><br></p>'));
}
// 将文本元素添加到文本容器中
$textContainerElem.append($textElem);
// 设置工具栏和文本容器的通用 class
$toolbarElem.addClass('w-e-toolbar');
$textContainerElem.addClass('w-e-text-container');
// 设置文本容器的 z-index 属性,确保层级关系
$textContainerElem.css('z-index', zIndex);
// 为文本元素添加 class
$textElem.addClass('w-e-text');
// 生成随机 ID 并赋值给工具栏和文本元素
// 生成一个随机的ID用于工具栏元素
var toolbarElemId = getRandom('toolbar-elem');
// 将生成的ID设置为工具栏元素的ID属性
$toolbarElem.attr('id', toolbarElemId);
// 生成一个随机的ID用于文本元素
var textElemId = getRandom('text-elem');
// 将生成的ID设置为文本元素的ID属性
$textElem.attr('id', textElemId);
// 记录当前实例的属性,方便后续操作
// 将传入的$toolbarElem参数赋值给当前对象的$toolbarElem属性
this.$toolbarElem = $toolbarElem;
// 将传入的$textContainerElem参数赋值给当前对象的$textContainerElem属性
this.$textContainerElem = $textContainerElem;
// 将传入的$textElem参数赋值给当前对象的$textElem属性
this.$textElem = $textElem;
// 将传入的toolbarElemId参数赋值给当前对象的toolbarElemId属性
this.toolbarElemId = toolbarElemId;
// 将传入的textElemId参数赋值给当前对象的textElemId属性
this.textElemId = textElemId;
// 记录输入法的开始和结束
var compositionEnd = true; // 初始化变量compositionEnd为true表示输入法输入结束
$textContainerElem.on('compositionstart', function () {
// 当输入法开始输入时触发此事件
compositionEnd = false; // 将compositionEnd设置为false表示输入法正在输入
});
$textContainerElem.on('compositionend', function () {
// 当输入法结束输入时触发此事件
compositionEnd = true; // 将compositionEnd设置为true表示输入法输入结束
});
// 绑定 onchange
$textContainerElem.on('click keyup', function () {
// 输入法结束才触发 onchange 事件
compositionEnd && _this.change && _this.change();
});
$toolbarElem.on('click', function () {
// 当点击工具栏时,如果存在 change 方法则调用它
this.change && this.change();
});
//绑定 onfocus 与 onblur 事件
if (config$$1.onfocus || config$$1.onblur) {
// 当前编辑器是否是焦点状态
this.isFocus = false;
$(document).on('click', function (e) {
//判断当前点击元素是否在编辑器内
var isChild = $textElem.isContain($(e.target));
//判断当前点击元素是否为工具栏
var isToolbar = $toolbarElem.isContain($(e.target));
var isMenu = $toolbarElem[0] == e.target ? true : false;
if (!isChild) {
// 若为选择工具栏中的功能则不视为成blur操作
if (isToolbar && !isMenu) {
return; // 直接返回,不执行后续代码
}
// 如果当前对象处于焦点状态
if (_this.isFocus) {
// 调用onblur方法如果存在的话
_this.onblur && _this.onblur();
}
// 将焦点状态设为false
_this.isFocus = false;
} else {
// 如果当前对象不处于焦点状态
if (!_this.isFocus) {
// 调用onfocus方法如果存在的话
_this.onfocus && _this.onfocus();
}
// 将焦点状态设为true
_this.isFocus = true;
}
// 封装 command
_initCommand: function _initCommand() {
// 创建一个新的 Command 实例并赋值给 this.cmd
this.cmd = new Command(this);
},
// 封装 selection range API
_initSelectionAPI: function _initSelectionAPI() {
// 创建一个新的 API 实例并赋值给 this.selection
this.selection = new API(this);
},
// 添加图片上传功能
_initUploadImg: function _initUploadImg() {
// 创建一个新的 UploadImg 实例并赋值给 this.uploadImg
this.uploadImg = new UploadImg(this);
},
// 初始化菜单
_initMenus: function _initMenus() {
// 创建一个新的 Menus 实例并赋值给 this.menus
this.menus = new Menus(this);
// 调用 menus 的 init 方法进行初始化
this.menus.init();
},
// 添加 text 区域
_initText: function _initText() {
this.txt = new Text(this);
this.txt.init();
},
// 初始化选区,将光标定位到内容尾部
initSelection: function initSelection(newLine) {
// 获取编辑器的文本元素
var $textElem = this.$textElem;
// 获取文本元素的子元素
var $children = $textElem.children();
// 如果文本元素没有子元素
if (!$children.length) {
// 如果编辑器区域无内容,添加一个空行,重新设置选区
$textElem.append($('<p><br></p>'));
// 递归调用自身以初始化选区
this.initSelection();
return;
}
var $last = $children.last();
// 获取最后一个子元素
if (newLine) {
// 如果需要新增一个空行
var html = $last.html().toLowerCase();
// 获取最后一个子元素的HTML内容并转换为小写
var nodeName = $last.getNodeName();
// 获取最后一个子元素的节点名称
if (html !== '<br>' && html !== '<br\/>' || nodeName !== 'P') {
// 如果最后一个元素不是 <p><br></p>,添加一个空行,重新设置选区
$textElem.append($('<p><br></p>'));
// 在文本元素中追加一个包含换行符的段落
this.initSelection();
// 初始化选区
return;
// 结束方法执行
}
}
this.selection.createRangeByElem($last, false, true);
// 根据最后一个元素创建选区范围
this.selection.restoreSelection();
// 恢复选区
// 绑定事件
_bindEvent: function _bindEvent() {
// -------- 绑定 onchange 事件 --------
var onChangeTimeoutId = 0; // 初始化 onchange 事件的定时器 ID
var beforeChangeHtml = this.txt.html(); // 获取当前文本框的 HTML 内容
var config$$1 = this.config; // 获取配置对象
// onchange 触发延迟时间
var onchangeTimeout = config$$1.onchangeTimeout; // 从配置中获取 onchange 触发延迟时间
onchangeTimeout = parseInt(onchangeTimeout, 10); // 将延迟时间转换为整数
if (!onchangeTimeout || onchangeTimeout <= 0) { // 如果延迟时间无效或小于等于 0
onchangeTimeout = 200; // 设置默认延迟时间为 200 毫秒
}
var onchange = config$$1.onchange; // 从配置中获取 onchange 回调函数
if (onchange && typeof onchange === 'function') {
this.change = function () {
// 获取当前文本内容
var currentHtml = this.txt.html();
if (currentHtml.length === beforeChangeHtml.length) {
// 如果当前文本长度与之前保存的长度相同,则需要比较每一个字符
if (currentHtml === beforeChangeHtml) {
// 如果当前文本内容与之前保存的内容完全相同,则无需处理
return;
}
}
// 如果存在 onChangeTimeoutId则清除之前的定时器
if (onChangeTimeoutId) {
clearTimeout(onChangeTimeoutId);
}
// 设置一个新的定时器,在指定的延迟后执行回调函数
onChangeTimeoutId = setTimeout(function () {
// 触发配置的 onchange 函数,并传入当前的 HTML 内容
onchange(currentHtml);
// 更新 beforeChangeHtml 为当前的 HTML 内容
beforeChangeHtml = currentHtml;
}, onchangeTimeout);
};
}
// -------- 绑定 onblur 事件 --------
// 获取配置中的onblur属性
var onblur = config$$1.onblur;
// 检查onblur是否存在且为函数类型
if (onblur && typeof onblur === 'function') {
// 定义当前对象的onblur方法
this.onblur = function () {
// 获取当前文本框的HTML内容
var currentHtml = this.txt.html();
// 调用传入的onblur函数并传递当前HTML内容作为参数
onblur(currentHtml);
};
}
// -------- 绑定 onfocus 事件 --------
var onfocus = config$$1.onfocus; // 从配置对象中获取onfocus属性
if (onfocus && typeof onfocus === 'function') { // 检查onfocus是否存在且为函数类型
this.onfocus = function () { // 定义当前对象的onfocus方法
onfocus(); // 调用传入的onfocus函数
};
}
// 创建编辑器
create: function create() {
// 初始化配置信息
this._initConfig();
// 初始化 DOM
this._initDom();
// 封装 command API
this._initCommand();
// 封装 selection range API
this._initSelectionAPI();
// 添加 text
this._initText();
// 初始化菜单
this._initMenus();
// 添加 图片上传
this._initUploadImg();
// 初始化选区,将光标定位到内容尾部
this.initSelection(true);
// 绑定事件
this._bindEvent();
},
// 解绑所有事件(暂时不对外开放)
_offAllEvent: function _offAllEvent() {
$.offAll();
}
};
// 检验是否浏览器环境
try {
document;
} catch (ex) {
throw new Error('请在浏览器环境下运行');
}
// polyfill
polyfill();
// 这里的 `inlinecss` 将被替换成 css 代码的内容,详情可去 ./gulpfile.js 中搜索 `inlinecss` 关键字
var inlinecss = '.w-e-toolbar,.w-e-text-container,.w-e-menu-panel { padding: 0; margin: 0; box-sizing: border-box;}.w-e-toolbar *,.w-e-text-container *,.w-e-menu-panel * { padding: 0; margin: 0; box-sizing: border-box;}.w-e-clear-fix:after { content: ""; display: table; clear: both;}.w-e-toolbar .w-e-droplist { position: absolute; left: 0; top: 0; background-color: #fff; border: 1px solid #f1f1f1; border-right-color: #ccc; border-bottom-color: #ccc;}.w-e-toolbar .w-e-droplist .w-e-dp-title { text-align: center; color: #999; line-height: 2; border-bottom: 1px solid #f1f1f1; font-size: 13px;}.w-e-toolbar .w-e-droplist ul.w-e-list { list-style: none; line-height: 1;}.w-e-toolbar .w-e-droplist ul.w-e-list li.w-e-item { color: #333; padding: 5px 0;}.w-e-toolbar .w-e-droplist ul.w-e-list li.w-e-item:hover { background-color: #f1f1f1;}.w-e-toolbar .w-e-droplist ul.w-e-block { list-style: none; text-align: left; padding: 5px;}.w-e-toolbar .w-e-droplist ul.w-e-block li.w-e-item { display: inline-block; *display: inline; *zoom: 1; padding: 3px 5px;}.w-e-toolbar .w-e-droplist ul.w-e-block li.w-e-item:hover { background-color: #f1f1f1;}@font-face { font-family: \'w-e-icon\'; src: url(data:application/x-font-woff;charset=utf-8;base64,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) format(\'truetype\'); font-weight: normal; font-style: normal;}[class^="w-e-icon-"],[class*=" w-e-icon-"] { /* use !important to prevent issues with browser extensions that change fonts */ font-family: \'w-e-icon\' !important; speak: none; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; /* Better Font Rendering =========== */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;}.w-e-icon-close:before { content: "\\f00d";}.w-e-icon-upload2:before { content: "\\e9c6";}.w-e-icon-trash-o:before { content: "\\f014";}.w-e-icon-header:before { content: "\\f1dc";}.w-e-icon-pencil2:before { content: "\\e906";}.w-e-icon-paint-brush:before { content: "\\f1fc";}.w-e-icon-image:before { content: "\\e90d";}.w-e-icon-play:before { content: "\\e912";}.w-e-icon-location:before { content: "\\e947";}.w-e-icon-undo:before { content: "\\e965";}.w-e-icon-redo:before { content: "\\e966";}.w-e-icon-quotes-left:before { content: "\\e977";}.w-e-icon-list-numbered:before { content: "\\e9b9";}.w-e-icon-list2:before { content: "\\e9bb";}.w-e-icon-link:before { content: "\\e9cb";}.w-e-icon-happy:before { content: "\\e9df";}.w-e-icon-bold:before { content: "\\ea62";}.w-e-icon-underline:before { content: "\\ea63";}.w-e-icon-italic:before { content: "\\ea64";}.w-e-icon-strikethrough:before { content: "\\ea65";}.w-e-icon-table2:before { content: "\\ea71";}.w-e-icon-paragraph-left:before { content: "\\ea77";}.w-e-icon-paragraph-center:before { content: "\\ea78";}.w-e-icon-paragraph-right:before { content: "\\ea79";}.w-e-icon-terminal:before { content: "\\f120";}.w-e-icon-page-break:before { content: "\\ea68";}.w-e-icon-cancel-circle:before { content: "\\ea0d";}.w-e-icon-font:before { content: "\\ea5c";}.w-e-icon-text-heigh:before { content: "\\ea5f";}.w-e-toolbar { display: -webkit-box; display: -ms-flexbox; display: flex; padding: 0 5px; /* flex-wrap: wrap; */ /* 单个菜单 */}.w-e-toolbar .w-e-menu { position: relative; text-align: center; padding: 5px 10px; cursor: pointer;}.w-e-toolbar .w-e-menu i { color: #999;}.w-e-toolbar .w-e-menu:hover i { color: #333;}.w-e-toolbar .w-e-active i { color: #1e88e5;}.w-e-toolbar .w-e-active:hover i { color: #1e88e5;}.w-e-text-container .w-e-panel-container { position: absolute; top: 0; left: 50%; border: 1px solid #ccc; border-top: 0; box-shadow: 1px 1px 2px #ccc; color: #333; background-color: #fff; /* 为 emotion panel 定制的样式 */ /* 上传图片的 panel 定制样式 */}.w-e-text-container .w-e-panel-container .w-e-panel-close { position: absolute; right: 0; top: 0; padding: 5px; margin: 2px 5px 0 0; cursor: pointer; color: #999;}.w-e-text-container .w-e-panel-container .w-e-panel-close:hover { color: #333;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-title { list-style: none; display: -webkit-box; display: -ms-flexbox; display: flex; font-size: 14px; margin: 2px 10px 0 10px; border-bottom: 1px solid #f1f1f1;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-title .w-e-item { padding: 3px 5px; color: #999; cursor: pointer; margin: 0 3px; position: relative; top: 1px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-title .w-e-active { color: #333; border-bottom: 1px solid #333; cursor: default; font-weight: 700;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content { padding: 10px 15px 10px 15px; font-size: 16px; /* 输入框的样式 */ /* 按钮的样式 */}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input:focus,.w-e-text-container .w-e-panel-container .w-e-panel-tab-content textarea:focus,.w-e-text-container .w-e-panel-container .w-e-panel-tab-content button:focus { outline: none;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content textarea { width: 100%; border: 1px solid #ccc; padding: 5px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content textarea:focus { border-color: #1e88e5;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text] { border: none; border-bottom: 1px solid #ccc; font-size: 14px; height: 20px; color: #333; text-align: left;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text].small { width: 30px; text-align: center;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text].block { display: block; width: 100%; margin: 10px 0;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text]:focus { border-bottom: 2px solid #1e88e5;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button { font-size: 14px; color: #1e88e5; border: none; padding: 5px 10px; background-color: #fff; cursor: pointer; border-radius: 3px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.left { float: left; margin-right: 10px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.right { float: right; margin-left: 10px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.gray { color: #999;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.red { color: #c24f4a;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button:hover { background-color: #f1f1f1;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container:after { content: ""; display: table; clear: both;}.w-e-text-container .w-e-panel-container .w-e-emoticon-container .w-e-item { cursor: pointer; font-size: 18px; padding: 0 3px; display: inline-block; *display: inline; *zoom: 1;}.w-e-text-container .w-e-panel-container .w-e-up-img-container { text-align: center;}.w-e-text-container .w-e-panel-container .w-e-up-img-container .w-e-up-btn { display: inline-block; *display: inline; *zoom: 1; color: #999; cursor: pointer; font-size: 60px; line-height: 1;}.w-e-text-container .w-e-panel-container .w-e-up-img-container .w-e-up-btn:hover { color: #333;}.w-e-text-container { position: relative;}.w-e-text-container .w-e-progress { position: absolute; background-color: #1e88e5; bottom: 0; left: 0; height: 1px;}.w-e-text { padding: 0 10px; overflow-y: scroll;}.w-e-text p,.w-e-text h1,.w-e-text h2,.w-e-text h3,.w-e-text h4,.w-e-text h5,.w-e-text table,.w-e-text pre { margin: 10px 0; line-height: 1.5;}.w-e-text ul,.w-e-text ol { margin: 10px 0 10px 20px;}.w-e-text blockquote { display: block; border-left: 8px solid #d0e5f2; padding: 5px 10px; margin: 10px 0; line-height: 1.4; font-size: 100%; background-color: #f1f1f1;}.w-e-text code { display: inline-block; *display: inline; *zoom: 1; background-color: #f1f1f1; border-radius: 3px; padding: 3px 5px; margin: 0 3px;}.w-e-text pre code { display: block;}.w-e-text table { border-top: 1px solid #ccc; border-left: 1px solid #ccc;}.w-e-text table td,.w-e-text table th { border-bottom: 1px solid #ccc; border-right: 1px solid #ccc; padding: 3px 5px;}.w-e-text table th { border-bottom: 2px solid #ccc; text-align: center;}.w-e-text:focus { outline: none;}.w-e-text img { cursor: pointer;}.w-e-text img:hover { box-shadow: 0 0 5px #333;}';
// 将 css 代码添加到 <style> 中
var style = document.createElement('style'); // 创建一个新的<style>元素
style.type = 'text/css'; // 设置<style>元素的类型为CSS
style.innerHTML = inlinecss; // 将内联CSS内容插入到<style>元素中
document.getElementsByTagName('HEAD').item(0).appendChild(style); // 将<style>元素添加到文档的<head>部分
// 返回
var index = window.wangEditor || Editor; // 获取全局对象window中的wangEditor属性如果不存在则使用Editor
return index; // 返回index变量
})));
layui.define(function(exports) {
exports('wangEditor', window.wangEditor); // 定义模块并导出wangEditor对象
});