UNPKG

mdui.editor

Version:

Material Design 样式的富文本编辑器

1,675 lines (1,576 loc) 149 kB
/*! * mdui.editor 1.0.2 (https://github.com/zdhxiong/mdui.editor#readme) * Copyright 2019-2020 zdhxiong * Licensed under MIT */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.Editor = factory()); }(this, (function () { 'use strict'; !function(){try{return new MouseEvent("test")}catch(e$1){}var e=function(e,t){t=t||{bubbles:!1,cancelable:!1};var n=document.createEvent("MouseEvent");return n.initMouseEvent(e,t.bubbles,t.cancelable,window,0,t.screenX||0,t.screenY||0,t.clientX||0,t.clientY||0,t.ctrlKey||!1,t.altKey||!1,t.shiftKey||!1,t.metaKey||!1,t.button||0,t.relatedTarget||null),n};e.prototype=Event.prototype,window.MouseEvent=e;}(); !function(){function t(t,e){e=e||{bubbles:!1,cancelable:!1,detail:void 0};var n=document.createEvent("CustomEvent");return n.initCustomEvent(t,e.bubbles,e.cancelable,e.detail),n}"function"!=typeof window.CustomEvent&&(t.prototype=window.Event.prototype,window.CustomEvent=t);}(); /** * @this {Promise} */ function finallyConstructor(callback) { var constructor = this.constructor; return this.then( function(value) { // @ts-ignore return constructor.resolve(callback()).then(function() { return value; }); }, function(reason) { // @ts-ignore return constructor.resolve(callback()).then(function() { // @ts-ignore return constructor.reject(reason); }); } ); } // Store setTimeout reference so promise-polyfill will be unaffected by // other code modifying setTimeout (like sinon.useFakeTimers()) var setTimeoutFunc = setTimeout; function isArray(x) { return Boolean(x && typeof x.length !== 'undefined'); } function noop() {} // Polyfill for Function.prototype.bind function bind(fn, thisArg) { return function() { fn.apply(thisArg, arguments); }; } /** * @constructor * @param {Function} fn */ function Promise$1(fn) { if (!(this instanceof Promise$1)) { throw new TypeError('Promises must be constructed via new'); } if (typeof fn !== 'function') { throw new TypeError('not a function'); } /** @type {!number} */ this._state = 0; /** @type {!boolean} */ this._handled = false; /** @type {Promise|undefined} */ this._value = undefined; /** @type {!Array<!Function>} */ this._deferreds = []; doResolve(fn, this); } function handle(self, deferred) { while (self._state === 3) { self = self._value; } if (self._state === 0) { self._deferreds.push(deferred); return; } self._handled = true; Promise$1._immediateFn(function() { var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected; if (cb === null) { (self._state === 1 ? resolve : reject)(deferred.promise, self._value); return; } var ret; try { ret = cb(self._value); } catch (e) { reject(deferred.promise, e); return; } resolve(deferred.promise, ret); }); } function resolve(self, newValue) { try { // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure if (newValue === self) { throw new TypeError('A promise cannot be resolved with itself.'); } if ( newValue && (typeof newValue === 'object' || typeof newValue === 'function') ) { var then = newValue.then; if (newValue instanceof Promise$1) { self._state = 3; self._value = newValue; finale(self); return; } else if (typeof then === 'function') { doResolve(bind(then, newValue), self); return; } } self._state = 1; self._value = newValue; finale(self); } catch (e) { reject(self, e); } } function reject(self, newValue) { self._state = 2; self._value = newValue; finale(self); } function finale(self) { if (self._state === 2 && self._deferreds.length === 0) { Promise$1._immediateFn(function() { if (!self._handled) { Promise$1._unhandledRejectionFn(self._value); } }); } for (var i = 0, len = self._deferreds.length; i < len; i++) { handle(self, self._deferreds[i]); } self._deferreds = null; } /** * @constructor */ function Handler(onFulfilled, onRejected, promise) { this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; this.onRejected = typeof onRejected === 'function' ? onRejected : null; this.promise = promise; } /** * Take a potentially misbehaving resolver function and make sure * onFulfilled and onRejected are only called once. * * Makes no guarantees about asynchrony. */ function doResolve(fn, self) { var done = false; try { fn( function(value) { if (done) { return; } done = true; resolve(self, value); }, function(reason) { if (done) { return; } done = true; reject(self, reason); } ); } catch (ex) { if (done) { return; } done = true; reject(self, ex); } } Promise$1.prototype['catch'] = function(onRejected) { return this.then(null, onRejected); }; Promise$1.prototype.then = function(onFulfilled, onRejected) { // @ts-ignore var prom = new this.constructor(noop); handle(this, new Handler(onFulfilled, onRejected, prom)); return prom; }; Promise$1.prototype['finally'] = finallyConstructor; Promise$1.all = function(arr) { return new Promise$1(function(resolve, reject) { if (!isArray(arr)) { return reject(new TypeError('Promise.all accepts an array')); } var args = Array.prototype.slice.call(arr); if (args.length === 0) { return resolve([]); } var remaining = args.length; function res(i, val) { try { if (val && (typeof val === 'object' || typeof val === 'function')) { var then = val.then; if (typeof then === 'function') { then.call( val, function(val) { res(i, val); }, reject ); return; } } args[i] = val; if (--remaining === 0) { resolve(args); } } catch (ex) { reject(ex); } } for (var i = 0; i < args.length; i++) { res(i, args[i]); } }); }; Promise$1.resolve = function(value) { if (value && typeof value === 'object' && value.constructor === Promise$1) { return value; } return new Promise$1(function(resolve) { resolve(value); }); }; Promise$1.reject = function(value) { return new Promise$1(function(resolve, reject) { reject(value); }); }; Promise$1.race = function(arr) { return new Promise$1(function(resolve, reject) { if (!isArray(arr)) { return reject(new TypeError('Promise.race accepts an array')); } for (var i = 0, len = arr.length; i < len; i++) { Promise$1.resolve(arr[i]).then(resolve, reject); } }); }; // Use polyfill for setImmediate for performance gains Promise$1._immediateFn = // @ts-ignore (typeof setImmediate === 'function' && function(fn) { // @ts-ignore setImmediate(fn); }) || function(fn) { setTimeoutFunc(fn, 0); }; Promise$1._unhandledRejectionFn = function _unhandledRejectionFn(err) { if (typeof console !== 'undefined' && console) { console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console } }; /** @suppress {undefinedVars} */ var globalNS = (function() { // the only reliable means to get the global object is // `Function('return this')()` // However, this causes CSP violations in Chrome apps. if (typeof self !== 'undefined') { return self; } if (typeof window !== 'undefined') { return window; } if (typeof global !== 'undefined') { return global; } throw new Error('unable to locate global object'); })(); if (!('Promise' in globalNS)) { globalNS['Promise'] = Promise$1; } else if (!globalNS.Promise.prototype['finally']) { globalNS.Promise.prototype['finally'] = finallyConstructor; } function isFunction(target) { return typeof target === 'function'; } function isString(target) { return typeof target === 'string'; } function isNumber(target) { return typeof target === 'number'; } function isBoolean(target) { return typeof target === 'boolean'; } function isUndefined(target) { return typeof target === 'undefined'; } function isNull(target) { return target === null; } function isWindow(target) { return target instanceof Window; } function isDocument(target) { return target instanceof Document; } function isElement(target) { return target instanceof Element; } function isNode(target) { return target instanceof Node; } /** * 是否是 IE 浏览器 */ function isIE() { // @ts-ignore return !!window.document.documentMode; } function isArrayLike(target) { if (isFunction(target) || isWindow(target)) { return false; } return isNumber(target.length); } function isObjectLike(target) { return typeof target === 'object' && target !== null; } function toElement(target) { return isDocument(target) ? target.documentElement : target; } /** * 把用 - 分隔的字符串转为驼峰(如 box-sizing 转换为 boxSizing) * @param string */ function toCamelCase(string) { return string .replace(/^-ms-/, 'ms-') .replace(/-([a-z])/g, function (_, letter) { return letter.toUpperCase(); }); } /** * 把驼峰法转为用 - 分隔的字符串(如 boxSizing 转换为 box-sizing) * @param string */ function toKebabCase(string) { return string.replace(/[A-Z]/g, function (replacer) { return '-' + replacer.toLowerCase(); }); } /** * 获取元素的样式值 * @param element * @param name */ function getComputedStyleValue(element, name) { return window.getComputedStyle(element).getPropertyValue(toKebabCase(name)); } /** * 检查元素的 box-sizing 是否是 border-box * @param element */ function isBorderBox(element) { return getComputedStyleValue(element, 'box-sizing') === 'border-box'; } /** * 获取元素的 padding, border, margin 宽度(两侧宽度的和,单位为px) * @param element * @param direction * @param extra */ function getExtraWidth(element, direction, extra) { var position = direction === 'width' ? ['Left', 'Right'] : ['Top', 'Bottom']; return [0, 1].reduce(function (prev, _, index) { var prop = extra + position[index]; if (extra === 'border') { prop += 'Width'; } return prev + parseFloat(getComputedStyleValue(element, prop) || '0'); }, 0); } /** * 获取元素的样式值,对 width 和 height 进行过处理 * @param element * @param name */ function getStyle(element, name) { // width、height 属性使用 getComputedStyle 得到的值不准确,需要使用 getBoundingClientRect 获取 if (name === 'width' || name === 'height') { var valueNumber = element.getBoundingClientRect()[name]; if (isBorderBox(element)) { return (valueNumber + "px"); } return ((valueNumber - getExtraWidth(element, name, 'border') - getExtraWidth(element, name, 'padding')) + "px"); } return getComputedStyleValue(element, name); } /** * 获取子节点组成的数组 * @param target * @param parent */ function getChildNodesArray(target, parent) { var tempParent = document.createElement(parent); tempParent.innerHTML = target; return [].slice.call(tempParent.childNodes); } /** * 始终返回 false 的函数 */ function returnFalse() { return false; } /** * 数值单位的 CSS 属性 */ var cssNumber = [ 'animationIterationCount', 'columnCount', 'fillOpacity', 'flexGrow', 'flexShrink', 'fontWeight', 'gridArea', 'gridColumn', 'gridColumnEnd', 'gridColumnStart', 'gridRow', 'gridRowEnd', 'gridRowStart', 'lineHeight', 'opacity', 'order', 'orphans', 'widows', 'zIndex', 'zoom' ]; function each(target, callback) { if (isArrayLike(target)) { for (var i = 0; i < target.length; i += 1) { if (callback.call(target[i], i, target[i]) === false) { return target; } } } else { var keys = Object.keys(target); for (var i$1 = 0; i$1 < keys.length; i$1 += 1) { if (callback.call(target[keys[i$1]], keys[i$1], target[keys[i$1]]) === false) { return target; } } } return target; } /** * 为了使用模块扩充,这里不能使用默认导出 */ var JQ = function JQ(arr) { var this$1 = this; this.length = 0; if (!arr) { return this; } each(arr, function (i, item) { // @ts-ignore this$1[i] = item; }); this.length = arr.length; return this; }; function get$() { var $ = function (selector) { if (!selector) { return new JQ(); } // JQ if (selector instanceof JQ) { return selector; } // function if (isFunction(selector)) { if (/complete|loaded|interactive/.test(document.readyState) && document.body) { selector.call(document, $); } else { document.addEventListener('DOMContentLoaded', function () { return selector.call(document, $); }, false); } return new JQ([document]); } // String if (isString(selector)) { var html = selector.trim(); // 根据 HTML 字符串创建 JQ 对象 if (html[0] === '<' && html[html.length - 1] === '>') { var toCreate = 'div'; var tags = { li: 'ul', tr: 'tbody', td: 'tr', th: 'tr', tbody: 'table', option: 'select', }; each(tags, function (childTag, parentTag) { if (html.indexOf(("<" + childTag)) === 0) { toCreate = parentTag; return false; } return; }); return new JQ(getChildNodesArray(html, toCreate)); } // 根据 CSS 选择器创建 JQ 对象 var isIdSelector = selector[0] === '#' && !selector.match(/[ .<>:~]/); if (!isIdSelector) { return new JQ(document.querySelectorAll(selector)); } var element = document.getElementById(selector.slice(1)); if (element) { return new JQ([element]); } return new JQ(); } if (isArrayLike(selector) && !isNode(selector)) { return new JQ(selector); } return new JQ([selector]); }; $.fn = JQ.prototype; return $; } var $ = get$(); function extend(target, object1) { var objectN = [], len = arguments.length - 2; while ( len-- > 0 ) objectN[ len ] = arguments[ len + 2 ]; objectN.unshift(object1); each(objectN, function (_, object) { each(object, function (prop, value) { if (!isUndefined(value)) { target[prop] = value; } }); }); return target; } $.fn.each = function (callback) { return each(this, callback); }; each(['add', 'remove', 'toggle'], function (_, name) { $.fn[(name + "Class")] = function (className) { if (name === 'remove' && !arguments.length) { return this.each(function (_, element) { element.setAttribute('class', ''); }); } return this.each(function (i, element) { if (!isElement(element)) { return; } var classes = (isFunction(className) ? className.call(element, i, element.getAttribute('class') || '') : className) .split(' ') .filter(function (name) { return name; }); each(classes, function (_, cls) { element.classList[name](cls); }); }); }; }); each(['insertBefore', 'insertAfter'], function (nameIndex, name) { $.fn[name] = function (target) { var $element = nameIndex ? $(this.get().reverse()) : this; // 顺序和 jQuery 保持一致 var $target = $(target); var result = []; $target.each(function (index, target) { if (!target.parentNode) { return; } $element.each(function (_, element) { var newItem = index ? element.cloneNode(true) : element; var existingItem = nameIndex ? target.nextSibling : target; result.push(newItem); target.parentNode.insertBefore(newItem, existingItem); }); }); return $(nameIndex ? result.reverse() : result); }; }); /** * 是否不是 HTML 字符串(包裹在 <> 中) * @param target */ function isPlainText(target) { return (isString(target) && (target[0] !== '<' || target[target.length - 1] !== '>')); } each(['before', 'after'], function (nameIndex, name) { $.fn[name] = function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; // after 方法,多个参数需要按参数顺序添加到元素后面,所以需要将参数顺序反向处理 if (nameIndex === 1) { args = args.reverse(); } return this.each(function (index, element) { var targets = isFunction(args[0]) ? [args[0].call(element, index, element.innerHTML)] : args; each(targets, function (_, target) { var $target; if (isPlainText(target)) { $target = $(getChildNodesArray(target, 'div')); } else if (index && isElement(target)) { $target = $(target.cloneNode(true)); } else { $target = $(target); } $target[nameIndex ? 'insertAfter' : 'insertBefore'](element); }); }); }; }); function map(elements, callback) { var ref; var value; var ret = []; each(elements, function (i, element) { value = callback.call(window, element, i); if (value != null) { ret.push(value); } }); return (ref = []).concat.apply(ref, ret); } $.fn.map = function (callback) { return new JQ(map(this, function (element, i) { return callback.call(element, i, element); })); }; $.fn.clone = function () { return this.map(function () { return this.cloneNode(true); }); }; $.fn.is = function (selector) { var isMatched = false; if (isFunction(selector)) { this.each(function (index, element) { if (selector.call(element, index, element)) { isMatched = true; } }); return isMatched; } if (isString(selector)) { this.each(function (_, element) { if (isDocument(element) || isWindow(element)) { return; } // @ts-ignore var matches = element.matches || element.msMatchesSelector; if (matches.call(element, selector)) { isMatched = true; } }); return isMatched; } var $compareWith = $(selector); this.each(function (_, element) { $compareWith.each(function (_, compare) { if (element === compare) { isMatched = true; } }); }); return isMatched; }; $.fn.remove = function (selector) { return this.each(function (_, element) { if (element.parentNode && (!selector || $(element).is(selector))) { element.parentNode.removeChild(element); } }); }; each(['prepend', 'append'], function (nameIndex, name) { $.fn[name] = function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return this.each(function (index, element) { var ref; var childNodes = element.childNodes; var childLength = childNodes.length; var child = childLength ? childNodes[nameIndex ? childLength - 1 : 0] : document.createElement('div'); if (!childLength) { element.appendChild(child); } var contents = isFunction(args[0]) ? [args[0].call(element, index, element.innerHTML)] : args; // 如果不是字符串,则仅第一个元素使用原始元素,其他的都克隆自第一个元素 if (index) { contents = contents.map(function (content) { return isString(content) ? content : $(content).clone(); }); } (ref = $(child))[nameIndex ? 'after' : 'before'].apply(ref, contents); if (!childLength) { element.removeChild(child); } }); }; }); each(['attr', 'prop', 'css'], function (nameIndex, name) { function set(element, key, value) { // 值为 undefined 时,不修改 if (isUndefined(value)) { return; } switch (nameIndex) { // attr case 0: if (isNull(value)) { element.removeAttribute(key); } else { element.setAttribute(key, value); } break; // prop case 1: // @ts-ignore element[key] = value; break; // css default: key = toCamelCase(key); // @ts-ignore element.style[key] = isNumber(value) ? ("" + value + (cssNumber.indexOf(key) > -1 ? '' : 'px')) : value; break; } } function get(element, key) { switch (nameIndex) { // attr case 0: // 属性不存在时,原生 getAttribute 方法返回 null,而 jquery 返回 undefined。这里和 jquery 保持一致 var value = element.getAttribute(key); return isNull(value) ? undefined : value; // prop case 1: // @ts-ignore return element[key]; // css default: return getStyle(element, key); } } $.fn[name] = function (key, value) { var this$1 = this; if (isObjectLike(key)) { each(key, function (k, v) { // @ts-ignore this$1[name](k, v); }); return this; } if (arguments.length === 1) { var element = this[0]; return isElement(element) ? get(element, key) : undefined; } return this.each(function (i, element) { set(element, key, isFunction(value) ? value.call(element, i, get(element, key)) : value); }); }; }); /** * 过滤掉数组中的重复元素 * @param arr 数组 * @example ```js unique([1, 2, 12, 3, 2, 1, 2, 1, 1]); // [1, 2, 12, 3] ``` */ function unique(arr) { var result = []; each(arr, function (_, val) { if (result.indexOf(val) === -1) { result.push(val); } }); return result; } $.fn.children = function (selector) { var children = []; this.each(function (_, element) { each(element.childNodes, function (__, childNode) { if (!isElement(childNode)) { return; } if (!selector || $(childNode).is(selector)) { children.push(childNode); } }); }); return new JQ(unique(children)); }; $.fn.slice = function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return new JQ([].slice.apply(this, args)); }; $.fn.eq = function (index) { var ret = index === -1 ? this.slice(index) : this.slice(index, +index + 1); return new JQ(ret); }; $.fn.first = function () { return this.eq(0); }; each(['val', 'html', 'text'], function (nameIndex, name) { var props = { 0: 'value', 1: 'innerHTML', 2: 'textContent', }; var propName = props[nameIndex]; function get($elements) { // text() 获取所有元素的文本 if (nameIndex === 2) { // @ts-ignore return map($elements, function (element) { return toElement(element)[propName]; }).join(''); } // 空集合时,val() 和 html() 返回 undefined if (!$elements.length) { return undefined; } // val() 和 html() 仅获取第一个元素的内容 var firstElement = $elements[0]; // select multiple 返回数组 if (nameIndex === 0 && $(firstElement).is('select[multiple]')) { return map($(firstElement).find('option:checked'), function (element) { return element.value; }); } // @ts-ignore return firstElement[propName]; } function set(element, value) { // text() 和 html() 赋值为 undefined,则保持原内容不变 // val() 赋值为 undefined 则赋值为空 if (isUndefined(value)) { if (nameIndex !== 0) { return; } value = ''; } if (nameIndex === 1 && isElement(value)) { value = value.outerHTML; } // @ts-ignore element[propName] = value; } $.fn[name] = function (value) { // 获取值 if (!arguments.length) { return get(this); } // 设置值 return this.each(function (i, element) { var computedValue = isFunction(value) ? value.call(element, i, get($(element))) : value; // value 是数组,则选中数组中的元素,反选不在数组中的元素 if (nameIndex === 0 && Array.isArray(computedValue)) { // select[multiple] if ($(element).is('select[multiple]')) { map($(element).find('option'), function (option) { return (option.selected = computedValue.indexOf(option.value) > -1); }); } // 其他 checkbox, radio 等元素 else { element.checked = computedValue.indexOf(element.value) > -1; } } else { set(element, computedValue); } }); }; }); $.fn.last = function () { return this.eq(-1); }; /** * 检查 container 元素内是否包含 contains 元素 * @param container 父元素 * @param contains 子元素 * @example ```js contains( document, document.body ); // true contains( document.getElementById('test'), document ); // false contains( $('.container').get(0), $('.contains').get(0) ); // false ``` */ function contains(container, contains) { return container !== contains && toElement(container).contains(contains); } /** * 把第二个数组的元素追加到第一个数组中,并返回合并后的数组 * @param first 第一个数组 * @param second 该数组的元素将被追加到第一个数组中 * @example ```js merge( [ 0, 1, 2 ], [ 2, 3, 4 ] ) // [ 0, 1, 2, 2, 3, 4 ] ``` */ function merge(first, second) { each(second, function (_, value) { first.push(value); }); return first; } $.fn.get = function (index) { return index === undefined ? [].slice.call(this) : this[index >= 0 ? index : index + this.length]; }; $.fn.find = function (selector) { var foundElements = []; this.each(function (_, element) { merge(foundElements, $(element.querySelectorAll(selector)).get()); }); return new JQ(foundElements); }; // 存储事件 var handlers = {}; // 元素ID var mduiElementId = 1; /** * 为元素赋予一个唯一的ID */ function getElementId(element) { var key = '_mduiEventId'; // @ts-ignore if (!element[key]) { // @ts-ignore element[key] = ++mduiElementId; } // @ts-ignore return element[key]; } /** * 解析事件名中的命名空间 */ function parse(type) { var parts = type.split('.'); return { type: parts[0], ns: parts.slice(1).sort().join(' '), }; } /** * 命名空间匹配规则 */ function matcherFor(ns) { return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)'); } /** * 获取匹配的事件 * @param element * @param type * @param func * @param selector */ function getHandlers(element, type, func, selector) { var event = parse(type); return (handlers[getElementId(element)] || []).filter(function (handler) { return handler && (!event.type || handler.type === event.type) && (!event.ns || matcherFor(event.ns).test(handler.ns)) && (!func || getElementId(handler.func) === getElementId(func)) && (!selector || handler.selector === selector); }); } /** * 添加事件监听 * @param element * @param types * @param func * @param data * @param selector */ function add(element, types, func, data, selector) { var elementId = getElementId(element); if (!handlers[elementId]) { handlers[elementId] = []; } // 传入 data.useCapture 来设置 useCapture: true var useCapture = false; if (isObjectLike(data) && data.useCapture) { useCapture = true; } types.split(' ').forEach(function (type) { if (!type) { return; } var event = parse(type); function callFn(e, elem) { // 因为鼠标事件模拟事件的 detail 属性是只读的,因此在 e._detail 中存储参数 var result = func.apply(elem, // @ts-ignore e._detail === undefined ? [e] : [e].concat(e._detail)); if (result === false) { e.preventDefault(); e.stopPropagation(); } } function proxyFn(e) { // @ts-ignore if (e._ns && !matcherFor(e._ns).test(event.ns)) { return; } // @ts-ignore e._data = data; if (selector) { // 事件代理 $(element) .find(selector) .get() .reverse() .forEach(function (elem) { if (elem === e.target || contains(elem, e.target)) { callFn(e, elem); } }); } else { // 不使用事件代理 callFn(e, element); } } var handler = { type: event.type, ns: event.ns, func: func, selector: selector, id: handlers[elementId].length, proxy: proxyFn, }; handlers[elementId].push(handler); element.addEventListener(handler.type, proxyFn, useCapture); }); } /** * 移除事件监听 * @param element * @param types * @param func * @param selector */ function remove(element, types, func, selector) { var handlersInElement = handlers[getElementId(element)] || []; var removeEvent = function (handler) { delete handlersInElement[handler.id]; element.removeEventListener(handler.type, handler.proxy, false); }; if (!types) { handlersInElement.forEach(function (handler) { return removeEvent(handler); }); } else { types.split(' ').forEach(function (type) { if (type) { getHandlers(element, type, func, selector).forEach(function (handler) { return removeEvent(handler); }); } }); } } $.fn.off = function (types, selector, callback) { var this$1 = this; // types 是对象 if (isObjectLike(types)) { each(types, function (type, fn) { // this.off('click', undefined, function () {}) // this.off('click', '.box', function () {}) this$1.off(type, selector, fn); }); return this; } // selector 不存在 if (selector === false || isFunction(selector)) { callback = selector; selector = undefined; // this.off('click', undefined, function () {}) } // callback 传入 `false`,相当于 `return false` if (callback === false) { callback = returnFalse; } return this.each(function () { remove(this, types, callback, selector); }); }; $.fn.on = function (types, selector, data, callback, one) { var this$1 = this; // types 可以是 type/func 对象 if (isObjectLike(types)) { // (types-Object, selector, data) if (!isString(selector)) { // (types-Object, data) data = data || selector; selector = undefined; } each(types, function (type, fn) { // selector 和 data 都可能是 undefined // @ts-ignore this$1.on(type, selector, data, fn, one); }); return this; } if (data == null && callback == null) { // (types, fn) callback = selector; data = selector = undefined; } else if (callback == null) { if (isString(selector)) { // (types, selector, fn) callback = data; data = undefined; } else { // (types, data, fn) callback = data; data = selector; selector = undefined; } } if (callback === false) { callback = returnFalse; } else if (!callback) { return this; } // $().one() if (one) { // eslint-disable-next-line @typescript-eslint/no-this-alias var _this = this; var origCallback = callback; callback = function (event) { _this.off(event.type, selector, callback); // eslint-disable-next-line prefer-rest-params return origCallback.apply(this, arguments); }; } return this.each(function () { add(this, types, callback, data, selector); }); }; each(['appendTo', 'prependTo'], function (nameIndex, name) { $.fn[name] = function (target) { var extraChilds = []; var $target = $(target).map(function (_, element) { var childNodes = element.childNodes; var childLength = childNodes.length; if (childLength) { return childNodes[nameIndex ? 0 : childLength - 1]; } var child = document.createElement('div'); element.appendChild(child); extraChilds.push(child); return child; }); var $result = this[nameIndex ? 'insertBefore' : 'insertAfter']($target); $(extraChilds).remove(); return $result; }; }); var CommonAbstract = function CommonAbstract(editor) { this.editor = editor; }; var prototypeAccessors = { $toolbar: { configurable: true },$container: { configurable: true },selection: { configurable: true },command: { configurable: true } }; /** * 工具栏 JQ 对象 */ prototypeAccessors.$toolbar.get = function () { return this.editor.$toolbar; }; /** * 内容区域 JQ 对象 */ prototypeAccessors.$container.get = function () { return this.editor.$container; }; /** * 选区实例 */ prototypeAccessors.selection.get = function () { return this.editor.selection; }; /** * 命令实例 */ prototypeAccessors.command.get = function () { return this.editor.command; }; Object.defineProperties( CommonAbstract.prototype, prototypeAccessors ); /** * 封装 document.execCommand 命令 */ var Command = /*@__PURE__*/(function (CommonAbstract) { function Command () { CommonAbstract.apply(this, arguments); } if ( CommonAbstract ) Command.__proto__ = CommonAbstract; Command.prototype = Object.create( CommonAbstract && CommonAbstract.prototype ); Command.prototype.constructor = Command; Command.prototype.do = function do$1 (name, value) { // 如果无选区,忽略 if (!this.selection.getRange()) { return; } // 恢复选区 this.selection.restore(); var customName = name; // 执行命令 // @ts-ignore if (this[customName]) { // @ts-ignore this[customName](value); } else { document.execCommand(name, false, value); } // 修改菜单状态 this.editor.menus.changeStatus(); // 最后,恢复选区保证光标在原来的位置闪烁 this.selection.saveRange(); this.selection.restore(); // 触发 onchange if (this.editor.change) { this.editor.change(); } }; /** * 自定义 insertHTML 事件,在当前选区中插入指定 HTML * @param html */ // @ts-ignore Command.prototype.insertHTML = function insertHTML (html) { // W3C if (document.queryCommandSupported('insertHTML')) { document.execCommand('insertHTML', false, html); return; } var range = this.selection.getRange(); if (range.insertNode) { // IE range.deleteContents(); range.insertNode($(html)[0]); // @ts-ignore } else if (range.pasteHTML) { // IE <= 10 // @ts-ignore range.pasteHTML(html); } }; /** * 用指定 HTML 替换当前选区的 root 元素 * @param html */ // @ts-ignore Command.prototype.replaceRoot = function replaceRoot (html) { var $oldElem = this.selection.getRootElem(); var $newElem = $(html).insertAfter($oldElem); $oldElem.remove(); this.selection.createRangeByElem($newElem, false, true); this.selection.restore(); }; /** * 在当前选区的 root 元素后面插入指定 html * @param html */ // @ts-ignore Command.prototype.insertAfterRoot = function insertAfterRoot (html) { var $oldElem = this.selection.getRootElem(); var $newElem = $(html).insertAfter($oldElem); this.selection.createRangeByElem($newElem, false, true); this.selection.restore(); }; /** * 在当前 $content 的最后追加 html * @param html */ // @ts-ignore Command.prototype.appendHTML = function appendHTML (html) { var $newElem = $(html).appendTo(this.$container); this.selection.createRangeByElem($newElem, false, true); this.selection.restore(); }; /** * 插入 elem * @param $elem */ // @ts-ignore Command.prototype.insertElem = function insertElem ($elem) { var range = this.selection.getRange(); if (range.insertNode) { range.deleteContents(); range.insertNode($elem[0]); } }; return Command; }(CommonAbstract)); var MenuAbstract = /*@__PURE__*/(function (CommonAbstract) { function MenuAbstract(editor, $button) { CommonAbstract.call(this, editor); this.$button = $button; } if ( CommonAbstract ) MenuAbstract.__proto__ = CommonAbstract; MenuAbstract.prototype = Object.create( CommonAbstract && CommonAbstract.prototype ); MenuAbstract.prototype.constructor = MenuAbstract; /** * 按钮是否激活 */ MenuAbstract.prototype.isActive = function isActive () { return false; }; return MenuAbstract; }(CommonAbstract)); /** * 按钮图标 */ MenuAbstract.icon = ''; /** * 按钮名称 */ MenuAbstract.title = ''; /** * 激活按钮时,需要禁用的其他按钮 */ MenuAbstract.disable = []; /** * 原生命令抽象类 */ var MenuNativeAbstract = /*@__PURE__*/(function (MenuAbstract) { function MenuNativeAbstract () { MenuAbstract.apply(this, arguments); } if ( MenuAbstract ) MenuNativeAbstract.__proto__ = MenuAbstract; MenuNativeAbstract.prototype = Object.create( MenuAbstract && MenuAbstract.prototype ); MenuNativeAbstract.prototype.constructor = MenuNativeAbstract; MenuNativeAbstract.prototype.onclick = function onclick () { var isSelectionEmpty = this.selection.isEmpty(); if (isSelectionEmpty) { // 选区是空的,插入并选中一个“空白” this.selection.createEmptyRange(this.getElementName()); } // 执行 bold 命令 this.command.do(this.getCommandName()); if (isSelectionEmpty) { // 需要将选区折叠起来 this.selection.collapseRange(); this.selection.restore(); } }; MenuNativeAbstract.prototype.isActive = function isActive () { return document.queryCommandState(this.getCommandName()); }; return MenuNativeAbstract; }(MenuAbstract)); /** * 加粗 */ var Bold = /*@__PURE__*/(function (MenuNativeAbstract) { function Bold () { MenuNativeAbstract.apply(this, arguments); } if ( MenuNativeAbstract ) Bold.__proto__ = MenuNativeAbstract; Bold.prototype = Object.create( MenuNativeAbstract && MenuNativeAbstract.prototype ); Bold.prototype.constructor = Bold; Bold.prototype.getCommandName = function getCommandName () { return 'bold'; }; Bold.prototype.getElementName = function getElementName () { return 'strong'; }; return Bold; }(MenuNativeAbstract)); Bold.icon = 'format_bold'; Bold.title = '粗体'; Bold.disable = ['image']; var $document = $(document); var $window = $(window); var $body = $('body'); // 避免页面加载完后直接执行css动画 // https://css-tricks.com/transitions-only-after-page-load/ setTimeout(function () { return $body.addClass('mdui-loaded'); }); var mdui = { $: $, }; $.fn.hasClass = function (className) { return this[0].classList.contains(className); }; /** * 值上面的 padding、border、margin 处理 * @param element * @param name * @param value * @param funcIndex * @param includeMargin * @param multiply */ function handleExtraWidth(element, name, value, funcIndex, includeMargin, multiply) { // 获取元素的 padding, border, margin 宽度(两侧宽度的和) var getExtraWidthValue = function (extra) { return (getExtraWidth(element, name.toLowerCase(), extra) * multiply); }; if (funcIndex === 2 && includeMargin) { value += getExtraWidthValue('margin'); } if (isBorderBox(element)) { // IE 为 box-sizing: border-box 时,得到的值不含 border 和 padding,这里先修复 // 仅获取时需要处理,multiply === 1 为 get if (isIE() && multiply === 1) { value += getExtraWidthValue('border'); value += getExtraWidthValue('padding'); } if (funcIndex === 0) { value -= getExtraWidthValue('border'); } if (funcIndex === 1) { value -= getExtraWidthValue('border'); value -= getExtraWidthValue('padding'); } } else { if (funcIndex === 0) { value += getExtraWidthValue('padding'); } if (funcIndex === 2) { value += getExtraWidthValue('border'); value += getExtraWidthValue('padding'); } } return value; } /** * 获取元素的样式值 * @param element * @param name * @param funcIndex 0: innerWidth, innerHeight; 1: width, height; 2: outerWidth, outerHeight * @param includeMargin */ function get(element, name, funcIndex, includeMargin) { var clientProp = "client" + name; var scrollProp = "scroll" + name; var offsetProp = "offset" + name; var innerProp = "inner" + name; // $(window).width() if (isWindow(element)) { // outerWidth, outerHeight 需要包含滚动条的宽度 return funcIndex === 2 ? element[innerProp] : toElement(document)[clientProp]; } // $(document).width() if (isDocument(element)) { var doc = toElement(element); return Math.max( // @ts-ignore element.body[scrollProp], doc[scrollProp], // @ts-ignore element.body[offsetProp], doc[offsetProp], doc[clientProp]); } var value = parseFloat(getComputedStyleValue(element, name.toLowerCase()) || '0'); return handleExtraWidth(element, name, value, funcIndex, includeMargin, 1); } /** * 设置元素的样式值 * @param element * @param elementIndex * @param name * @param funcIndex 0: innerWidth, innerHeight; 1: width, height; 2: outerWidth, outerHeight * @param includeMargin * @param value */ function set(element, elementIndex, name, funcIndex, includeMargin, value) { var computedValue = isFunction(value) ? value.call(element, elementIndex, get(element, name, funcIndex, includeMargin)) : value; if (computedValue == null) { return; } var $element = $(element); var dimension = name.toLowerCase(); // 特殊的值,不需要计算 padding、border、margin if (['auto', 'inherit', ''].indexOf(computedValue) > -1) { $element.css(dimension, computedValue); return; } // 其他值保留原始单位。注意:如果不使用 px 作为单位,则算出的值一般是不准确的 var suffix = computedValue.toString().replace(/\b[0-9.]*/, ''); var numerical = parseFloat(computedValue); computedValue = handleExtraWidth(element, name, numerical, funcIndex, includeMargin, -1) + (suffix || 'px'); $element.css(dimension, computedValue); } each(['Width', 'Height'], function (_, name) { each([("inner" + name), name.toLowerCase(), ("outer" + name)], function (funcIndex, funcName) { $.fn[funcName] = function (margin, value) { // 是否是赋值操作 var isSet = arguments.length && (funcIndex < 2 || !isBoolean(margin)); var includeMargin = margin === true || value === true; // 获取第一个元素的值 if (!isSet) { return this.length ? get(this[0], name, funcIndex, includeMargin) : undefined; } // 设置每个元素的值 return this.each(function (index, element) { return set(element, index, name, funcIndex, includeMargin, margin); }); }; }); }); $.fn.hide = function () { return this.each(function () { this.style.display = 'none'; }); }; var elementDisplay = {}; /** * 获取元素的初始 display 值,用于 .show() 方法 * @param nodeName */ function defaultDisplay(nodeName) { var element; var display; if (!elementDisplay[nodeName]) { element = document.createElement(nodeName); document.body.appendChild(element); display = getStyle(element, 'display'); element.parentNode.removeChild(element); if (display === 'none') { display = 'block'; } elementDisplay[nodeName] = display; } return elementDisplay[nodeName]; } /** * 显示指定元素 * @returns {JQ} */ $.fn.show = function () { return this.each(function () { if (this.style.display === 'none') { this.style.display = ''; } if (getStyle(this, 'display') === 'none') { this.style.display = defaultDisplay(this.nodeName); } }); }; $.fn.transitionEnd = function (callback) { // eslint-disable-next-line @typescript-eslint/no-this-alias var that = this; var events = ['webkitTransitionEnd', 'transitionend']; function fireCallback(e) { if (e.target !== this) { return; } // @ts-ignore callback.cal