/*! * UEditor * version: ueditor * build: Wed Dec 26 2018 17:24:52 GMT+0800 (CST) */ // const { default: axios } = require('axios') ;(function () { // editor.js UEDITOR_CONFIG = window.UEDITOR_CONFIG || {} var baidu = window.baidu || {} window.baidu = baidu window.UE = baidu.editor = window.UE || {} UE.plugins = {} UE.commands = {} UE.instants = {} UE.I18N = {} UE._customizeUI = {} UE.version = '1.4.3' var dom = (UE.dom = {}) // core/browser.js /** * 浏览器判断模块 * @file * @module UE.browser * @since 1.2.6.1 */ /** * 提供浏览器检测的模块 * @unfile * @module UE.browser */ var browser = (UE.browser = (function () { var agent = navigator.userAgent.toLowerCase(), opera = window.opera, browser = { /** * @property {boolean} ie 检测当前浏览器是否为IE * @example * ```javascript * if ( UE.browser.ie ) { * console.log( '当前浏览器是IE' ); * } * ``` */ ie: /(msie\s|trident.*rv:)([\w.]+)/.test(agent), /** * @property {boolean} opera 检测当前浏览器是否为Opera * @example * ```javascript * if ( UE.browser.opera ) { * console.log( '当前浏览器是Opera' ); * } * ``` */ opera: !!opera && opera.version, /** * @property {boolean} webkit 检测当前浏览器是否是webkit内核的浏览器 * @example * ```javascript * if ( UE.browser.webkit ) { * console.log( '当前浏览器是webkit内核浏览器' ); * } * ``` */ webkit: agent.indexOf(' applewebkit/') > -1, /** * @property {boolean} mac 检测当前浏览器是否是运行在mac平台下 * @example * ```javascript * if ( UE.browser.mac ) { * console.log( '当前浏览器运行在mac平台下' ); * } * ``` */ mac: agent.indexOf('macintosh') > -1, /** * @property {boolean} quirks 检测当前浏览器是否处于“怪异模式”下 * @example * ```javascript * if ( UE.browser.quirks ) { * console.log( '当前浏览器运行处于“怪异模式”' ); * } * ``` */ quirks: document.compatMode == 'BackCompat' } /** * @property {boolean} gecko 检测当前浏览器内核是否是gecko内核 * @example * ```javascript * if ( UE.browser.gecko ) { * console.log( '当前浏览器内核是gecko内核' ); * } * ``` */ browser.gecko = navigator.product == 'Gecko' && !browser.webkit && !browser.opera && !browser.ie var version = 0 // Internet Explorer 6.0+ if (browser.ie) { var v1 = agent.match(/(?:msie\s([\w.]+))/) var v2 = agent.match(/(?:trident.*rv:([\w.]+))/) if (v1 && v2 && v1[1] && v2[1]) { version = Math.max(v1[1] * 1, v2[1] * 1) } else if (v1 && v1[1]) { version = v1[1] * 1 } else if (v2 && v2[1]) { version = v2[1] * 1 } else { version = 0 } browser.ie11Compat = document.documentMode == 11 /** * @property { boolean } ie9Compat 检测浏览器模式是否为 IE9 兼容模式 * @warning 如果浏览器不是IE, 则该值为undefined * @example * ```javascript * if ( UE.browser.ie9Compat ) { * console.log( '当前浏览器运行在IE9兼容模式下' ); * } * ``` */ browser.ie9Compat = document.documentMode == 9 /** * @property { boolean } ie8 检测浏览器是否是IE8浏览器 * @warning 如果浏览器不是IE, 则该值为undefined * @example * ```javascript * if ( UE.browser.ie8 ) { * console.log( '当前浏览器是IE8浏览器' ); * } * ``` */ browser.ie8 = !!document.documentMode /** * @property { boolean } ie8Compat 检测浏览器模式是否为 IE8 兼容模式 * @warning 如果浏览器不是IE, 则该值为undefined * @example * ```javascript * if ( UE.browser.ie8Compat ) { * console.log( '当前浏览器运行在IE8兼容模式下' ); * } * ``` */ browser.ie8Compat = document.documentMode == 8 /** * @property { boolean } ie7Compat 检测浏览器模式是否为 IE7 兼容模式 * @warning 如果浏览器不是IE, 则该值为undefined * @example * ```javascript * if ( UE.browser.ie7Compat ) { * console.log( '当前浏览器运行在IE7兼容模式下' ); * } * ``` */ browser.ie7Compat = (version == 7 && !document.documentMode) || document.documentMode == 7 /** * @property { boolean } ie6Compat 检测浏览器模式是否为 IE6 模式 或者怪异模式 * @warning 如果浏览器不是IE, 则该值为undefined * @example * ```javascript * if ( UE.browser.ie6Compat ) { * console.log( '当前浏览器运行在IE6模式或者怪异模式下' ); * } * ``` */ browser.ie6Compat = version < 7 || browser.quirks browser.ie9above = version > 8 browser.ie9below = version < 9 browser.ie11above = version > 10 browser.ie11below = version < 11 } // Gecko. if (browser.gecko) { var geckoRelease = agent.match(/rv:([\d\.]+)/) if (geckoRelease) { geckoRelease = geckoRelease[1].split('.') version = geckoRelease[0] * 10000 + (geckoRelease[1] || 0) * 100 + (geckoRelease[2] || 0) * 1 } } /** * @property { Number } chrome 检测当前浏览器是否为Chrome, 如果是,则返回Chrome的大版本号 * @warning 如果浏览器不是chrome, 则该值为undefined * @example * ```javascript * if ( UE.browser.chrome ) { * console.log( '当前浏览器是Chrome' ); * } * ``` */ if (/chrome\/(\d+\.\d)/i.test(agent)) { browser.chrome = +RegExp['\x241'] } /** * @property { Number } safari 检测当前浏览器是否为Safari, 如果是,则返回Safari的大版本号 * @warning 如果浏览器不是safari, 则该值为undefined * @example * ```javascript * if ( UE.browser.safari ) { * console.log( '当前浏览器是Safari' ); * } * ``` */ if ( /(\d+\.\d)?(?:\.\d)?\s+safari\/?(\d+\.\d+)?/i.test(agent) && !/chrome/i.test(agent) ) { browser.safari = +(RegExp['\x241'] || RegExp['\x242']) } // Opera 9.50+ if (browser.opera) version = parseFloat(opera.version()) // WebKit 522+ (Safari 3+) if (browser.webkit) version = parseFloat(agent.match(/ applewebkit\/(\d+)/)[1]) /** * @property { Number } version 检测当前浏览器版本号 * @remind * * @example * ```javascript * console.log( '当前浏览器版本号是: ' + UE.browser.version ); * ``` */ browser.version = version /** * @property { boolean } isCompatible 检测当前浏览器是否能够与UEditor良好兼容 * @example * ```javascript * if ( UE.browser.isCompatible ) { * console.log( '浏览器与UEditor能够良好兼容' ); * } * ``` */ browser.isCompatible = !browser.mobile && ((browser.ie && version >= 6) || (browser.gecko && version >= 10801) || (browser.opera && version >= 9.5) || (browser.air && version >= 1) || (browser.webkit && version >= 522) || false) return browser })()) //快捷方式 var ie = browser.ie, webkit = browser.webkit, gecko = browser.gecko, opera = browser.opera // core/utils.js /** * 工具函数包 * @file * @module UE.utils * @since 1.2.6.1 */ /** * UEditor封装使用的静态工具函数 * @module UE.utils * @unfile */ var utils = (UE.utils = { /** * 用给定的迭代器遍历对象 * @method each * @param { Object } obj 需要遍历的对象 * @param { Function } iterator 迭代器, 该方法接受两个参数, 第一个参数是当前所处理的value, 第二个参数是当前遍历对象的key * @example * ```javascript * var demoObj = { * key1: 1, * key2: 2 * }; * * //output: key1: 1, key2: 2 * UE.utils.each( demoObj, funciton ( value, key ) { * * console.log( key + ":" + value ); * * } ); * ``` */ /** * 用给定的迭代器遍历数组或类数组对象 * @method each * @param { Array } array 需要遍历的数组或者类数组 * @param { Function } iterator 迭代器, 该方法接受两个参数, 第一个参数是当前所处理的value, 第二个参数是当前遍历对象的key * @example * ```javascript * var divs = document.getElmentByTagNames( "div" ); * * //output: 0: DIV, 1: DIV ... * UE.utils.each( divs, funciton ( value, key ) { * * console.log( key + ":" + value.tagName ); * * } ); * ``` */ each: function (obj, iterator, context) { if (obj == null) return if (obj.length === +obj.length) { for (var i = 0, l = obj.length; i < l; i++) { if (iterator.call(context, obj[i], i, obj) === false) return false } } else { for (var key in obj) { if (obj.hasOwnProperty(key)) { if (iterator.call(context, obj[key], key, obj) === false) return false } } } }, /** * 以给定对象作为原型创建一个新对象 * @method makeInstance * @param { Object } protoObject 该对象将作为新创建对象的原型 * @return { Object } 新的对象, 该对象的原型是给定的protoObject对象 * @example * ```javascript * * var protoObject = { sayHello: function () { console.log('Hello UEditor!'); } }; * * var newObject = UE.utils.makeInstance( protoObject ); * //output: Hello UEditor! * newObject.sayHello(); * ``` */ makeInstance: function (obj) { var noop = new Function() noop.prototype = obj obj = new noop() noop.prototype = null return obj }, /** * 将source对象中的属性扩展到target对象上 * @method extend * @remind 该方法将强制把source对象上的属性复制到target对象上 * @see UE.utils.extend(Object,Object,Boolean) * @param { Object } target 目标对象, 新的属性将附加到该对象上 * @param { Object } source 源对象, 该对象的属性会被附加到target对象上 * @return { Object } 返回target对象 * @example * ```javascript * * var target = { name: 'target', sex: 1 }, * source = { name: 'source', age: 17 }; * * UE.utils.extend( target, source ); * * //output: { name: 'source', sex: 1, age: 17 } * console.log( target ); * * ``` */ /** * 将source对象中的属性扩展到target对象上, 根据指定的isKeepTarget值决定是否保留目标对象中与 * 源对象属性名相同的属性值。 * @method extend * @param { Object } target 目标对象, 新的属性将附加到该对象上 * @param { Object } source 源对象, 该对象的属性会被附加到target对象上 * @param { Boolean } isKeepTarget 是否保留目标对象中与源对象中属性名相同的属性 * @return { Object } 返回target对象 * @example * ```javascript * * var target = { name: 'target', sex: 1 }, * source = { name: 'source', age: 17 }; * * UE.utils.extend( target, source, true ); * * //output: { name: 'target', sex: 1, age: 17 } * console.log( target ); * * ``` */ extend: function (t, s, b) { if (s) { for (var k in s) { if (!b || !t.hasOwnProperty(k)) { t[k] = s[k] } } } return t }, /** * 将给定的多个对象的属性复制到目标对象target上 * @method extend2 * @remind 该方法将强制把源对象上的属性复制到target对象上 * @remind 该方法支持两个及以上的参数, 从第二个参数开始, 其属性都会被复制到第一个参数上。 如果遇到同名的属性, * 将会覆盖掉之前的值。 * @param { Object } target 目标对象, 新的属性将附加到该对象上 * @param { Object... } source 源对象, 支持多个对象, 该对象的属性会被附加到target对象上 * @return { Object } 返回target对象 * @example * ```javascript * * var target = {}, * source1 = { name: 'source', age: 17 }, * source2 = { title: 'dev' }; * * UE.utils.extend2( target, source1, source2 ); * * //output: { name: 'source', age: 17, title: 'dev' } * console.log( target ); * * ``` */ extend2: function (t) { var a = arguments for (var i = 1; i < a.length; i++) { var x = a[i] for (var k in x) { if (!t.hasOwnProperty(k)) { t[k] = x[k] } } } return t }, /** * 模拟继承机制, 使得subClass继承自superClass * @method inherits * @param { Object } subClass 子类对象 * @param { Object } superClass 超类对象 * @warning 该方法只能让subClass继承超类的原型, subClass对象自身的属性和方法不会被继承 * @return { Object } 继承superClass后的子类对象 * @example * ```javascript * function SuperClass(){ * this.name = "小李"; * } * * SuperClass.prototype = { * hello:function(str){ * console.log(this.name + str); * } * } * * function SubClass(){ * this.name = "小张"; * } * * UE.utils.inherits(SubClass,SuperClass); * * var sub = new SubClass(); * //output: '小张早上好! * sub.hello("早上好!"); * ``` */ inherits: function (subClass, superClass) { var oldP = subClass.prototype, newP = utils.makeInstance(superClass.prototype) utils.extend(newP, oldP, true) subClass.prototype = newP return (newP.constructor = subClass) }, /** * 用指定的context对象作为函数fn的上下文 * @method bind * @param { Function } fn 需要绑定上下文的函数对象 * @param { Object } content 函数fn新的上下文对象 * @return { Function } 一个新的函数, 该函数作为原始函数fn的代理, 将完成fn的上下文调换工作。 * @example * ```javascript * * var name = 'window', * newTest = null; * * function test () { * console.log( this.name ); * } * * newTest = UE.utils.bind( test, { name: 'object' } ); * * //output: object * newTest(); * * //output: window * test(); * * ``` */ bind: function (fn, context) { return function () { return fn.apply(context, arguments) } }, /** * 创建延迟指定时间后执行的函数fn * @method defer * @param { Function } fn 需要延迟执行的函数对象 * @param { int } delay 延迟的时间, 单位是毫秒 * @warning 该方法的时间控制是不精确的,仅仅只能保证函数的执行是在给定的时间之后, * 而不能保证刚好到达延迟时间时执行。 * @return { Function } 目标函数fn的代理函数, 只有执行该函数才能起到延时效果 * @example * ```javascript * var start = 0; * * function test(){ * console.log( new Date() - start ); * } * * var testDefer = UE.utils.defer( test, 1000 ); * // * start = new Date(); * //output: (大约在1000毫秒之后输出) 1000 * testDefer(); * ``` */ /** * 创建延迟指定时间后执行的函数fn, 如果在延迟时间内再次执行该方法, 将会根据指定的exclusion的值, * 决定是否取消前一次函数的执行, 如果exclusion的值为true, 则取消执行,反之,将继续执行前一个方法。 * @method defer * @param { Function } fn 需要延迟执行的函数对象 * @param { int } delay 延迟的时间, 单位是毫秒 * @param { Boolean } exclusion 如果在延迟时间内再次执行该函数,该值将决定是否取消执行前一次函数的执行, * 值为true表示取消执行, 反之则将在执行前一次函数之后才执行本次函数调用。 * @warning 该方法的时间控制是不精确的,仅仅只能保证函数的执行是在给定的时间之后, * 而不能保证刚好到达延迟时间时执行。 * @return { Function } 目标函数fn的代理函数, 只有执行该函数才能起到延时效果 * @example * ```javascript * * function test(){ * console.log(1); * } * * var testDefer = UE.utils.defer( test, 1000, true ); * * //output: (两次调用仅有一次输出) 1 * testDefer(); * testDefer(); * ``` */ defer: function (fn, delay, exclusion) { var timerID return function () { if (exclusion) { clearTimeout(timerID) } timerID = setTimeout(fn, delay) } }, /** * 获取元素item在数组array中首次出现的位置, 如果未找到item, 则返回-1 * @method indexOf * @remind 该方法的匹配过程使用的是恒等“===” * @param { Array } array 需要查找的数组对象 * @param { * } item 需要在目标数组中查找的值 * @return { int } 返回item在目标数组array中首次出现的位置, 如果在数组中未找到item, 则返回-1 * @example * ```javascript * var item = 1, * arr = [ 3, 4, 6, 8, 1, 1, 2 ]; * * //output: 4 * console.log( UE.utils.indexOf( arr, item ) ); * ``` */ /** * 获取元素item数组array中首次出现的位置, 如果未找到item, 则返回-1。通过start的值可以指定搜索的起始位置。 * @method indexOf * @remind 该方法的匹配过程使用的是恒等“===” * @param { Array } array 需要查找的数组对象 * @param { * } item 需要在目标数组中查找的值 * @param { int } start 搜索的起始位置 * @return { int } 返回item在目标数组array中的start位置之后首次出现的位置, 如果在数组中未找到item, 则返回-1 * @example * ```javascript * var item = 1, * arr = [ 3, 4, 6, 8, 1, 2, 8, 3, 2, 1, 1, 4 ]; * * //output: 9 * console.log( UE.utils.indexOf( arr, item, 5 ) ); * ``` */ indexOf: function (array, item, start) { var index = -1 start = this.isNumber(start) ? start : 0 this.each(array, function (v, i) { if (i >= start && v === item) { index = i return false } }) return index }, /** * 移除数组array中所有的元素item * @method removeItem * @param { Array } array 要移除元素的目标数组 * @param { * } item 将要被移除的元素 * @remind 该方法的匹配过程使用的是恒等“===” * @example * ```javascript * var arr = [ 4, 5, 7, 1, 3, 4, 6 ]; * * UE.utils.removeItem( arr, 4 ); * //output: [ 5, 7, 1, 3, 6 ] * console.log( arr ); * * ``` */ removeItem: function (array, item) { for (var i = 0, l = array.length; i < l; i++) { if (array[i] === item) { array.splice(i, 1) i-- } } }, /** * 删除字符串str的首尾空格 * @method trim * @param { String } str 需要删除首尾空格的字符串 * @return { String } 删除了首尾的空格后的字符串 * @example * ```javascript * * var str = " UEdtior "; * * //output: 9 * console.log( str.length ); * * //output: 7 * console.log( UE.utils.trim( " UEdtior " ).length ); * * //output: 9 * console.log( str.length ); * * ``` */ trim: function (str) { return str.replace(/(^[ \t\n\r]+)|([ \t\n\r]+$)/g, '') }, /** * 将字符串str以','分隔成数组后,将该数组转换成哈希对象, 其生成的hash对象的key为数组中的元素, value为1 * @method listToMap * @warning 该方法在生成的hash对象中,会为每一个key同时生成一个另一个全大写的key。 * @param { String } str 该字符串将被以','分割为数组, 然后进行转化 * @return { Object } 转化之后的hash对象 * @example * ```javascript * * //output: Object {UEdtior: 1, UEDTIOR: 1, Hello: 1, HELLO: 1} * console.log( UE.utils.listToMap( 'UEdtior,Hello' ) ); * * ``` */ /** * 将字符串数组转换成哈希对象, 其生成的hash对象的key为数组中的元素, value为1 * @method listToMap * @warning 该方法在生成的hash对象中,会为每一个key同时生成一个另一个全大写的key。 * @param { Array } arr 字符串数组 * @return { Object } 转化之后的hash对象 * @example * ```javascript * * //output: Object {UEdtior: 1, UEDTIOR: 1, Hello: 1, HELLO: 1} * console.log( UE.utils.listToMap( [ 'UEdtior', 'Hello' ] ) ); * * ``` */ listToMap: function (list) { if (!list) return {} list = utils.isArray(list) ? list : list.split(',') for (var i = 0, ci, obj = {}; (ci = list[i++]); ) { obj[ci.toUpperCase()] = obj[ci] = 1 } return obj }, /** * 将str中的html符号转义,将转义“',&,<,",>”五个字符 * @method unhtml * @param { String } str 需要转义的字符串 * @return { String } 转义后的字符串 * @example * ```javascript * var html = '&'; * * //output: <body>&</body> * console.log( UE.utils.unhtml( html ) ); * * ``` */ unhtml: function (str, reg) { return str ? str.replace( reg || /[&<">'](?:(amp|lt|quot|gt|#39|nbsp|#\d+);)?/g, function (a, b) { if (b) { return a } else { return { '<': '<', '&': '&', '"': '"', '>': '>', "'": ''' }[a] } } ) : '' }, /** * 将url中的html字符转义, 仅转义 ', ", <, > 四个字符 * @param { String } str 需要转义的字符串 * @param { RegExp } reg 自定义的正则 * @return { String } 转义后的字符串 */ unhtmlForUrl: function (str, reg) { return str ? str.replace(reg || /[<">']/g, function (a) { return { '<': '<', '&': '&', '"': '"', '>': '>', "'": ''' }[a] }) : '' }, /** * 将str中的转义字符还原成html字符 * @see UE.utils.unhtml(String); * @method html * @param { String } str 需要逆转义的字符串 * @return { String } 逆转义后的字符串 * @example * ```javascript * * var str = '<body>&</body>'; * * //output: & * console.log( UE.utils.html( str ) ); * * ``` */ html: function (str) { return str ? str.replace(/&((g|l|quo)t|amp|#39|nbsp);/g, function (m) { return { '<': '<', '&': '&', '"': '"', '>': '>', ''': "'", ' ': ' ' }[m] }) : '' }, /** * 将css样式转换为驼峰的形式 * @method cssStyleToDomStyle * @param { String } cssName 需要转换的css样式名 * @return { String } 转换成驼峰形式后的css样式名 * @example * ```javascript * * var str = 'border-top'; * * //output: borderTop * console.log( UE.utils.cssStyleToDomStyle( str ) ); * * ``` */ cssStyleToDomStyle: (function () { var test = document.createElement('div').style, cache = { float: test.cssFloat != undefined ? 'cssFloat' : test.styleFloat != undefined ? 'styleFloat' : 'float' } return function (cssName) { return ( cache[cssName] || (cache[cssName] = cssName .toLowerCase() .replace(/-./g, function (match) { return match.charAt(1).toUpperCase() })) ) } })(), /** * 动态加载文件到doc中 * @method loadFile * @param { DomDocument } document 需要加载资源文件的文档对象 * @param { Object } options 加载资源文件的属性集合, 取值请参考代码示例 * @example * ```javascript * * UE.utils.loadFile( document, { * src:"test.js", * tag:"script", * type:"text/javascript", * defer:"defer" * } ); * * ``` */ /** * 动态加载文件到doc中,加载成功后执行的回调函数fn * @method loadFile * @param { DomDocument } document 需要加载资源文件的文档对象 * @param { Object } options 加载资源文件的属性集合, 该集合支持的值是script标签和style标签支持的所有属性。 * @param { Function } fn 资源文件加载成功之后执行的回调 * @warning 对于在同一个文档中多次加载同一URL的文件, 该方法会在第一次加载之后缓存该请求, * 在此之后的所有同一URL的请求, 将会直接触发回调。 * @example * ```javascript * * UE.utils.loadFile( document, { * src:"test.js", * tag:"script", * type:"text/javascript", * defer:"defer" * }, function () { * console.log('加载成功'); * } ); * * ``` */ loadFile: (function () { var tmpList = [] function getItem(doc, obj) { try { for (var i = 0, ci; (ci = tmpList[i++]); ) { if (ci.doc === doc && ci.url == (obj.src || obj.href)) { return ci } } } catch (e) { return null } } return function (doc, obj, fn) { var item = getItem(doc, obj) if (item) { if (item.ready) { fn && fn() } else { item.funs.push(fn) } return } tmpList.push({ doc: doc, url: obj.src || obj.href, funs: [fn] }) if (!doc.body) { var html = [] for (var p in obj) { if (p == 'tag') continue html.push(p + '="' + obj[p] + '"') } doc.write( '<' + obj.tag + ' ' + html.join(' ') + ' >' ) return } if (obj.id && doc.getElementById(obj.id)) { return } var element = doc.createElement(obj.tag) delete obj.tag for (var p in obj) { element.setAttribute(p, obj[p]) } element.onload = element.onreadystatechange = function () { if (!this.readyState || /loaded|complete/.test(this.readyState)) { item = getItem(doc, obj) if (item.funs.length > 0) { item.ready = 1 for (var fi; (fi = item.funs.pop()); ) { fi() } } element.onload = element.onreadystatechange = null } } element.onerror = function () { throw Error( 'The load ' + (obj.href || obj.src) + ' fails,check the url settings of file ueditor.config.js ' ) } doc.getElementsByTagName('head')[0].appendChild(element) } })(), /** * 判断obj对象是否为空 * @method isEmptyObject * @param { * } obj 需要判断的对象 * @remind 如果判断的对象是NULL, 将直接返回true, 如果是数组且为空, 返回true, 如果是字符串, 且字符串为空, * 返回true, 如果是普通对象, 且该对象没有任何实例属性, 返回true * @return { Boolean } 对象是否为空 * @example * ```javascript * * //output: true * console.log( UE.utils.isEmptyObject( {} ) ); * * //output: true * console.log( UE.utils.isEmptyObject( [] ) ); * * //output: true * console.log( UE.utils.isEmptyObject( "" ) ); * * //output: false * console.log( UE.utils.isEmptyObject( { key: 1 } ) ); * * //output: false * console.log( UE.utils.isEmptyObject( [1] ) ); * * //output: false * console.log( UE.utils.isEmptyObject( "1" ) ); * * ``` */ isEmptyObject: function (obj) { if (obj == null) return true if (this.isArray(obj) || this.isString(obj)) return obj.length === 0 for (var key in obj) if (obj.hasOwnProperty(key)) return false return true }, /** * 把rgb格式的颜色值转换成16进制格式 * @method fixColor * @param { String } rgb格式的颜色值 * @param { String } * @example * rgb(255,255,255) => "#ffffff" */ fixColor: function (name, value) { if (/color/i.test(name) && /rgba?/.test(value)) { var array = value.split(',') if (array.length > 3) return '' value = '#' for (var i = 0, color; (color = array[i++]); ) { color = parseInt(color.replace(/[^\d]/gi, ''), 10).toString(16) value += color.length == 1 ? '0' + color : color } value = value.toUpperCase() } return value }, /** * 只针对border,padding,margin做了处理,因为性能问题 * @public * @function * @param {String} val style字符串 */ optCss: function (val) { var padding, margin, border val = val.replace( /(padding|margin|border)\-([^:]+):([^;]+);?/gi, function (str, key, name, val) { if (val.split(' ').length == 1) { switch (key) { case 'padding': !padding && (padding = {}) padding[name] = val return '' case 'margin': !margin && (margin = {}) margin[name] = val return '' case 'border': return val == 'initial' ? '' : str } } return str } ) function opt(obj, name) { if (!obj) { return '' } var t = obj.top, b = obj.bottom, l = obj.left, r = obj.right, val = '' if (!t || !l || !b || !r) { for (var p in obj) { val += ';' + name + '-' + p + ':' + obj[p] + ';' } } else { val += ';' + name + ':' + (t == b && b == l && l == r ? t : t == b && l == r ? t + ' ' + l : l == r ? t + ' ' + l + ' ' + b : t + ' ' + r + ' ' + b + ' ' + l) + ';' } return val } val += opt(padding, 'padding') + opt(margin, 'margin') return val .replace(/^[ \n\r\t;]*|[ \n\r\t]*$/, '') .replace(/;([ \n\r\t]+)|\1;/g, ';') .replace(/(&((l|g)t|quot|#39))?;{2,}/g, function (a, b) { return b ? b + ';;' : ';' }) }, /** * 克隆对象 * @method clone * @param { Object } source 源对象 * @return { Object } source的一个副本 */ /** * 深度克隆对象,将source的属性克隆到target对象, 会覆盖target重名的属性。 * @method clone * @param { Object } source 源对象 * @param { Object } target 目标对象 * @return { Object } 附加了source对象所有属性的target对象 */ clone: function (source, target) { var tmp target = target || {} for (var i in source) { if (source.hasOwnProperty(i)) { tmp = source[i] if (typeof tmp == 'object') { target[i] = utils.isArray(tmp) ? [] : {} utils.clone(source[i], target[i]) } else { target[i] = tmp } } } return target }, /** * 把cm/pt为单位的值转换为px为单位的值 * @method transUnitToPx * @param { String } 待转换的带单位的字符串 * @return { String } 转换为px为计量单位的值的字符串 * @example * ```javascript * * //output: 500px * console.log( UE.utils.transUnitToPx( '20cm' ) ); * * //output: 27px * console.log( UE.utils.transUnitToPx( '20pt' ) ); * * ``` */ transUnitToPx: function (val) { if (!/(pt|cm)/.test(val)) { return val } var unit val.replace(/([\d.]+)(\w+)/, function (str, v, u) { val = v unit = u }) switch (unit) { case 'cm': val = parseFloat(val) * 25 break case 'pt': val = Math.round((parseFloat(val) * 96) / 72) } return val + (val ? 'px' : '') }, /** * 在dom树ready之后执行给定的回调函数 * @method domReady * @remind 如果在执行该方法的时候, dom树已经ready, 那么回调函数将立刻执行 * @param { Function } fn dom树ready之后的回调函数 * @example * ```javascript * * UE.utils.domReady( function () { * * console.log('123'); * * } ); * * ``` */ domReady: (function () { var fnArr = [] function doReady(doc) { //确保onready只执行一次 doc.isReady = true for (var ci; (ci = fnArr.pop()); ci()) {} } return function (onready, win) { win = win || window var doc = win.document onready && fnArr.push(onready) if (doc.readyState === 'complete') { doReady(doc) } else { doc.isReady && doReady(doc) if (browser.ie && browser.version != 11) { ;(function () { if (doc.isReady) return try { doc.documentElement.doScroll('left') } catch (error) { setTimeout(arguments.callee, 0) return } doReady(doc) })() win.attachEvent('onload', function () { doReady(doc) }) } else { doc.addEventListener( 'DOMContentLoaded', function () { doc.removeEventListener( 'DOMContentLoaded', arguments.callee, false ) doReady(doc) }, false ) win.addEventListener( 'load', function () { doReady(doc) }, false ) } } } })(), /** * 动态添加css样式 * @method cssRule * @param { String } 节点名称 * @grammar UE.utils.cssRule('添加的样式的节点名称',['样式','放到哪个document上']) * @grammar UE.utils.cssRule('body','body{background:#ccc}') => null //给body添加背景颜色 * @grammar UE.utils.cssRule('body') =>样式的字符串 //取得key值为body的样式的内容,如果没有找到key值先关的样式将返回空,例如刚才那个背景颜色,将返回 body{background:#ccc} * @grammar UE.utils.cssRule('body',document) => 返回指定key的样式,并且指定是哪个document * @grammar UE.utils.cssRule('body','') =>null //清空给定的key值的背景颜色 */ cssRule: browser.ie && browser.version != 11 ? function (key, style, doc) { var indexList, index if ( style === undefined || (style && style.nodeType && style.nodeType == 9) ) { //获取样式 doc = style && style.nodeType && style.nodeType == 9 ? style : doc || document indexList = doc.indexList || (doc.indexList = {}) index = indexList[key] if (index !== undefined) { return doc.styleSheets[index].cssText } return undefined } doc = doc || document indexList = doc.indexList || (doc.indexList = {}) index = indexList[key] //清除样式 if (style === '') { if (index !== undefined) { doc.styleSheets[index].cssText = '' delete indexList[key] return true } return false } //添加样式 if (index !== undefined) { sheetStyle = doc.styleSheets[index] } else { sheetStyle = doc.createStyleSheet( '', (index = doc.styleSheets.length) ) indexList[key] = index } sheetStyle.cssText = style } : function (key, style, doc) { var head, node if ( style === undefined || (style && style.nodeType && style.nodeType == 9) ) { //获取样式 doc = style && style.nodeType && style.nodeType == 9 ? style : doc || document node = doc.getElementById(key) return node ? node.innerHTML : undefined } doc = doc || document node = doc.getElementById(key) //清除样式 if (style === '') { if (node) { node.parentNode.removeChild(node) return true } return false } //添加样式 if (node) { node.innerHTML = style } else { node = doc.createElement('style') node.id = key node.innerHTML = style doc.getElementsByTagName('head')[0].appendChild(node) } }, sort: function (array, compareFn) { compareFn = compareFn || function (item1, item2) { return item1.localeCompare(item2) } for (var i = 0, len = array.length; i < len; i++) { for (var j = i, length = array.length; j < length; j++) { if (compareFn(array[i], array[j]) > 0) { var t = array[i] array[i] = array[j] array[j] = t } } } return array }, serializeParam: function (json) { var strArr = [] for (var i in json) { //忽略默认的几个参数 if (i == 'method' || i == 'timeout' || i == 'async') continue //传递过来的对象和函数不在提交之列 if ( !( (typeof json[i]).toLowerCase() == 'function' || (typeof json[i]).toLowerCase() == 'object' ) ) { strArr.push(encodeURIComponent(i) + '=' + encodeURIComponent(json[i])) } else if (utils.isArray(json[i])) { //支持传数组内容 for (var j = 0; j < json[i].length; j++) { strArr.push( encodeURIComponent(i) + '[]=' + encodeURIComponent(json[i][j]) ) } } } return strArr.join('&') }, formatUrl: function (url) { var u = url.replace(/&&/g, '&') u = u.replace(/\?&/g, '?') u = u.replace(/&$/g, '') u = u.replace(/&#/g, '#') u = u.replace(/&+/g, '&') return u }, isCrossDomainUrl: function (url) { var a = document.createElement('a') a.href = url if (browser.ie) { a.href = a.href } return !( a.protocol == location.protocol && a.hostname == location.hostname && (a.port == location.port || (a.port == '80' && location.port == '') || (a.port == '' && location.port == '80')) ) }, clearEmptyAttrs: function (obj) { for (var p in obj) { if (obj[p] === '') { delete obj[p] } } return obj }, str2json: function (s) { if (!utils.isString(s)) return null if (window.JSON) { return JSON.parse(s) } else { return new Function('return ' + utils.trim(s || ''))() } }, json2str: (function () { if (window.JSON) { return JSON.stringify } else { var escapeMap = { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"': '\\"', '\\': '\\\\' } function encodeString(source) { if (/["\\\x00-\x1f]/.test(source)) { source = source.replace(/["\\\x00-\x1f]/g, function (match) { var c = escapeMap[match] if (c) { return c } c = match.charCodeAt() return ( '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16) ) }) } return '"' + source + '"' } function encodeArray(source) { var result = ['['], l = source.length, preComma, i, item for (i = 0; i < l; i++) { item = source[i] switch (typeof item) { case 'undefined': case 'function': case 'unknown': break default: if (preComma) { result.push(',') } result.push(utils.json2str(item)) preComma = 1 } } result.push(']') return result.join('') } function pad(source) { return source < 10 ? '0' + source : source } function encodeDate(source) { return ( '"' + source.getFullYear() + '-' + pad(source.getMonth() + 1) + '-' + pad(source.getDate()) + 'T' + pad(source.getHours()) + ':' + pad(source.getMinutes()) + ':' + pad(source.getSeconds()) + '"' ) } return function (value) { switch (typeof value) { case 'undefined': return 'undefined' case 'number': return isFinite(value) ? String(value) : 'null' case 'string': return encodeString(value) case 'boolean': return String(value) default: if (value === null) { return 'null' } else if (utils.isArray(value)) { return encodeArray(value) } else if (utils.isDate(value)) { return encodeDate(value) } else { var result = ['{'], encode = utils.json2str, preComma, item for (var key in value) { if (Object.prototype.hasOwnProperty.call(value, key)) { item = value[key] switch (typeof item) { case 'undefined': case 'unknown': case 'function': break default: if (preComma) { result.push(',') } preComma = 1 result.push(encode(key) + ':' + encode(item)) } } } result.push('}') return result.join('') } } } } })() }) /** * 判断给定的对象是否是字符串 * @method isString * @param { * } object 需要判断的对象 * @return { Boolean } 给定的对象是否是字符串 */ /** * 判断给定的对象是否是数组 * @method isArray * @param { * } object 需要判断的对象 * @return { Boolean } 给定的对象是否是数组 */ /** * 判断给定的对象是否是一个Function * @method isFunction * @param { * } object 需要判断的对象 * @return { Boolean } 给定的对象是否是Function */ /** * 判断给定的对象是否是Number * @method isNumber * @param { * } object 需要判断的对象 * @return { Boolean } 给定的对象是否是Number */ /** * 判断给定的对象是否是一个正则表达式 * @method isRegExp * @param { * } object 需要判断的对象 * @return { Boolean } 给定的对象是否是正则表达式 */ /** * 判断给定的对象是否是一个普通对象 * @method isObject * @param { * } object 需要判断的对象 * @return { Boolean } 给定的对象是否是普通对象 */ utils.each( ['String', 'Function', 'Array', 'Number', 'RegExp', 'Object', 'Date'], function (v) { UE.utils['is' + v] = function (obj) { return Object.prototype.toString.apply(obj) == '[object ' + v + ']' } } ) // core/EventBase.js /** * UE采用的事件基类 * @file * @module UE * @class EventBase * @since 1.2.6.1 */ /** * UEditor公用空间,UEditor所有的功能都挂载在该空间下 * @unfile * @module UE */ /** * UE采用的事件基类,继承此类的对应类将获取addListener,removeListener,fireEvent方法。 * 在UE中,Editor以及所有ui实例都继承了该类,故可以在对应的ui对象以及editor对象上使用上述方法。 * @unfile * @module UE * @class EventBase */ /** * 通过此构造器,子类可以继承EventBase获取事件监听的方法 * @constructor * @example * ```javascript * UE.EventBase.call(editor); * ``` */ var EventBase = (UE.EventBase = function () {}) EventBase.prototype = { /** * 注册事件监听器 * @method addListener * @param { String } types 监听的事件名称,同时监听多个事件使用空格分隔 * @param { Function } fn 监听的事件被触发时,会执行该回调函数 * @waining 事件被触发时,监听的函数假如返回的值恒等于true,回调函数的队列中后面的函数将不执行 * @example * ```javascript * editor.addListener('selectionchange',function(){ * console.log("选区已经变化!"); * }) * editor.addListener('beforegetcontent aftergetcontent',function(type){ * if(type == 'beforegetcontent'){ * //do something * }else{ * //do something * } * console.log(this.getContent) // this是注册的事件的编辑器实例 * }) * ``` * @see UE.EventBase:fireEvent(String) */ addListener: function (types, listener) { types = utils.trim(types).split(/\s+/) for (var i = 0, ti; (ti = types[i++]); ) { getListener(this, ti, true).push(listener) } }, on: function (types, listener) { return this.addListener(types, listener) }, off: function (types, listener) { return this.removeListener(types, listener) }, trigger: function () { return this.fireEvent.apply(this, arguments) }, /** * 移除事件监听器 * @method removeListener * @param { String } types 移除的事件名称,同时移除多个事件使用空格分隔 * @param { Function } fn 移除监听事件的函数引用 * @example * ```javascript * //changeCallback为方法体 * editor.removeListener("selectionchange",changeCallback); * ``` */ removeListener: function (types, listener) { types = utils.trim(types).split(/\s+/) for (var i = 0, ti; (ti = types[i++]); ) { utils.removeItem(getListener(this, ti) || [], listener) } }, /** * 触发事件 * @method fireEvent * @param { String } types 触发的事件名称,同时触发多个事件使用空格分隔 * @remind 该方法会触发addListener * @return { * } 返回触发事件的队列中,最后执行的回调函数的返回值 * @example * ```javascript * editor.fireEvent("selectionchange"); * ``` */ /** * 触发事件 * @method fireEvent * @param { String } types 触发的事件名称,同时触发多个事件使用空格分隔 * @param { *... } options 可选参数,可以传入一个或多个参数,会传给事件触发的回调函数 * @return { * } 返回触发事件的队列中,最后执行的回调函数的返回值 * @example * ```javascript * * editor.addListener( "selectionchange", function ( type, arg1, arg2 ) { * * console.log( arg1 + " " + arg2 ); * * } ); * * //触发selectionchange事件, 会执行上面的事件监听器 * //output: Hello World * editor.fireEvent("selectionchange", "Hello", "World"); * ``` */ fireEvent: function () { var types = arguments[0] types = utils.trim(types).split(' ') for (var i = 0, ti; (ti = types[i++]); ) { var listeners = getListener(this, ti), r, t, k if (listeners) { k = listeners.length while (k--) { if (!listeners[k]) continue t = listeners[k].apply(this, arguments) if (t === true) { return t } if (t !== undefined) { r = t } } } if ((t = this['on' + ti.toLowerCase()])) { r = t.apply(this, arguments) } } return r } } /** * 获得对象所拥有监听类型的所有监听器 * @unfile * @module UE * @since 1.2.6.1 * @method getListener * @public * @param { Object } obj 查询监听器的对象 * @param { String } type 事件类型 * @param { Boolean } force 为true且当前所有type类型的侦听器不存在时,创建一个空监听器数组 * @return { Array } 监听器数组 */ function getListener(obj, type, force) { var allListeners type = type.toLowerCase() return ( (allListeners = obj.__allListeners || (force && (obj.__allListeners = {}))) && (allListeners[type] || (force && (allListeners[type] = []))) ) } // core/dtd.js ///import editor.js ///import core/dom/dom.js ///import core/utils.js /** * dtd html语义化的体现类 * @constructor * @namespace dtd */ var dtd = (dom.dtd = (function () { function _(s) { for (var k in s) { s[k.toUpperCase()] = s[k] } return s } var X = utils.extend2 var A = _({ isindex: 1, fieldset: 1 }), B = _({ input: 1, button: 1, select: 1, textarea: 1, label: 1 }), C = X(_({ a: 1 }), B), D = X({ iframe: 1 }, C), E = _({ hr: 1, ul: 1, menu: 1, div: 1, blockquote: 1, noscript: 1, table: 1, center: 1, address: 1, dir: 1, pre: 1, h5: 1, dl: 1, h4: 1, noframes: 1, h6: 1, ol: 1, h1: 1, h3: 1, h2: 1 }), F = _({ ins: 1, del: 1, script: 1, style: 1 }), G = X( _({ b: 1, acronym: 1, bdo: 1, var: 1, '#': 1, abbr: 1, code: 1, br: 1, i: 1, cite: 1, kbd: 1, u: 1, strike: 1, s: 1, tt: 1, strong: 1, q: 1, samp: 1, em: 1, dfn: 1, span: 1 }), F ), H = X( _({ sub: 1, img: 1, embed: 1, object: 1, sup: 1, basefont: 1, map: 1, applet: 1, font: 1, big: 1, small: 1 }), G ), I = X(_({ p: 1 }), H), J = X(_({ iframe: 1 }), H, B), K = _({ img: 1, embed: 1, noscript: 1, br: 1, kbd: 1, center: 1, button: 1, basefont: 1, h5: 1, h4: 1, samp: 1, h6: 1, ol: 1, h1: 1, h3: 1, h2: 1, form: 1, font: 1, '#': 1, select: 1, menu: 1, ins: 1, abbr: 1, label: 1, code: 1, table: 1, script: 1, cite: 1, input: 1, iframe: 1, strong: 1, textarea: 1, noframes: 1, big: 1, small: 1, span: 1, hr: 1, sub: 1, bdo: 1, var: 1, div: 1, object: 1, sup: 1, strike: 1, dir: 1, map: 1, dl: 1, applet: 1, del: 1, isindex: 1, fieldset: 1, ul: 1, b: 1, acronym: 1, a: 1, blockquote: 1, i: 1, u: 1, s: 1, tt: 1, address: 1, q: 1, pre: 1, p: 1, em: 1, dfn: 1 }), L = X(_({ a: 0 }), J), //a不能被切开,所以把他 M = _({ tr: 1 }), N = _({ '#': 1 }), O = X(_({ param: 1 }), K), P = X(_({ form: 1 }), A, D, E, I), Q = _({ li: 1, ol: 1, ul: 1 }), R = _({ style: 1, script: 1 }), S = _({ base: 1, link: 1, meta: 1, title: 1 }), T = X(S, R), U = _({ head: 1, body: 1 }), V = _({ html: 1 }) var block = _({ address: 1, blockquote: 1, center: 1, dir: 1, div: 1, dl: 1, fieldset: 1, form: 1, h1: 1, h2: 1, h3: 1, h4: 1, h5: 1, h6: 1, hr: 1, isindex: 1, menu: 1, noframes: 1, ol: 1, p: 1, pre: 1, table: 1, ul: 1 }), empty = _({ area: 1, base: 1, basefont: 1, br: 1, col: 1, command: 1, dialog: 1, embed: 1, hr: 1, img: 1, input: 1, isindex: 1, keygen: 1, link: 1, meta: 1, param: 1, source: 1, track: 1, wbr: 1 }) return _({ // $ 表示自定的属性 // body外的元素列表. $nonBodyContent: X(V, U, S), //块结构元素列表 $block: block, //内联元素列表 $inline: L, $inlineWithA: X(_({ a: 1 }), L), $body: X(_({ script: 1, style: 1 }), block), $cdata: _({ script: 1, style: 1 }), //自闭和元素 $empty: empty, //不是自闭合,但不能让range选中里边 $nonChild: _({ iframe: 1, textarea: 1 }), //列表元素列表 $listItem: _({ dd: 1, dt: 1, li: 1 }), //列表根元素列表 $list: _({ ul: 1, ol: 1, dl: 1 }), //不能认为是空的元素 $isNotEmpty: _({ table: 1, ul: 1, ol: 1, dl: 1, iframe: 1, area: 1, base: 1, col: 1, hr: 1, img: 1, embed: 1, input: 1, link: 1, meta: 1, param: 1, h1: 1, h2: 1, h3: 1, h4: 1, h5: 1, h6: 1 }), //如果没有子节点就可以删除的元素列表,像span,a $removeEmpty: _({ a: 1, abbr: 1, acronym: 1, address: 1, b: 1, bdo: 1, big: 1, cite: 1, code: 1, del: 1, dfn: 1, em: 1, font: 1, i: 1, ins: 1, label: 1, kbd: 1, q: 1, s: 1, samp: 1, small: 1, span: 1, strike: 1, strong: 1, sub: 1, sup: 1, tt: 1, u: 1, var: 1 }), $removeEmptyBlock: _({ p: 1, div: 1 }), //在table元素里的元素列表 $tableContent: _({ caption: 1, col: 1, colgroup: 1, tbody: 1, td: 1, tfoot: 1, th: 1, thead: 1, tr: 1, table: 1 }), //不转换的标签 $notTransContent: _({ pre: 1, script: 1, style: 1, textarea: 1 }), html: U, head: T, style: N, script: N, body: P, base: {}, link: {}, meta: {}, title: N, col: {}, tr: _({ td: 1, th: 1 }), img: {}, embed: {}, colgroup: _({ thead: 1, col: 1, tbody: 1, tr: 1, tfoot: 1 }), noscript: P, td: P, br: {}, th: P, center: P, kbd: L, button: X(I, E), basefont: {}, h5: L, h4: L, samp: L, h6: L, ol: Q, h1: L, h3: L, option: N, h2: L, form: X(A, D, E, I), select: _({ optgroup: 1, option: 1 }), font: L, ins: L, menu: Q, abbr: L, label: L, table: _({ thead: 1, col: 1, tbody: 1, tr: 1, colgroup: 1, caption: 1, tfoot: 1 }), code: L, tfoot: M, cite: L, li: P, input: {}, iframe: P, strong: L, textarea: N, noframes: P, big: L, small: L, //trace: span: _({ '#': 1, br: 1, b: 1, strong: 1, u: 1, i: 1, em: 1, sub: 1, sup: 1, strike: 1, span: 1 }), hr: L, dt: L, sub: L, optgroup: _({ option: 1 }), param: {}, bdo: L, var: L, div: P, object: O, sup: L, dd: P, strike: L, area: {}, dir: Q, map: X(_({ area: 1, form: 1, p: 1 }), A, F, E), applet: O, dl: _({ dt: 1, dd: 1 }), del: L, isindex: {}, fieldset: X(_({ legend: 1 }), K), thead: M, ul: Q, acronym: L, b: L, a: X(_({ a: 1 }), J), blockquote: X(_({ td: 1, tr: 1, tbody: 1, li: 1 }), P), caption: L, i: L, u: L, tbody: M, s: L, address: X(D, I), tt: L, legend: L, q: L, pre: X(G, C), p: X(_({ a: 1 }), L), em: L, dfn: L }) })()) // core/domUtils.js /** * Dom操作工具包 * @file * @module UE.dom.domUtils * @since 1.2.6.1 */ /** * Dom操作工具包 * @unfile * @module UE.dom.domUtils */ function getDomNode(node, start, ltr, startFromChild, fn, guard) { var tmpNode = startFromChild && node[start], parent !tmpNode && (tmpNode = node[ltr]) while (!tmpNode && (parent = (parent || node).parentNode)) { if (parent.tagName == 'BODY' || (guard && !guard(parent))) { return null } tmpNode = parent[ltr] } if (tmpNode && fn && !fn(tmpNode)) { return getDomNode(tmpNode, start, ltr, false, fn) } return tmpNode } var attrFix = ie && browser.version < 9 ? { tabindex: 'tabIndex', readonly: 'readOnly', for: 'htmlFor', class: 'className', maxlength: 'maxLength', cellspacing: 'cellSpacing', cellpadding: 'cellPadding', rowspan: 'rowSpan', colspan: 'colSpan', usemap: 'useMap', frameborder: 'frameBorder' } : { tabindex: 'tabIndex', readonly: 'readOnly' }, styleBlock = utils.listToMap([ '-webkit-box', '-moz-box', 'block', 'list-item', 'table', 'table-row-group', 'table-header-group', 'table-footer-group', 'table-row', 'table-column-group', 'table-column', 'table-cell', 'table-caption' ]) var domUtils = (dom.domUtils = { //节点常量 NODE_ELEMENT: 1, NODE_DOCUMENT: 9, NODE_TEXT: 3, NODE_COMMENT: 8, NODE_DOCUMENT_FRAGMENT: 11, //位置关系 POSITION_IDENTICAL: 0, POSITION_DISCONNECTED: 1, POSITION_FOLLOWING: 2, POSITION_PRECEDING: 4, POSITION_IS_CONTAINED: 8, POSITION_CONTAINS: 16, //ie6使用其他的会有一段空白出现 fillChar: ie && browser.version == '6' ? '\ufeff' : '\u200B', //-------------------------Node部分-------------------------------- keys: { /*Backspace*/ 8: 1, /*Delete*/ 46: 1, /*Shift*/ 16: 1, /*Ctrl*/ 17: 1, /*Alt*/ 18: 1, 37: 1, 38: 1, 39: 1, 40: 1, 13: 1 /*enter*/ }, /** * 获取节点A相对于节点B的位置关系 * @method getPosition * @param { Node } nodeA 需要查询位置关系的节点A * @param { Node } nodeB 需要查询位置关系的节点B * @return { Number } 节点A与节点B的关系 * @example * ```javascript * //output: 20 * var position = UE.dom.domUtils.getPosition( document.documentElement, document.body ); * * switch ( position ) { * * //0 * case UE.dom.domUtils.POSITION_IDENTICAL: * console.log('元素相同'); * break; * //1 * case UE.dom.domUtils.POSITION_DISCONNECTED: * console.log('两个节点在不同的文档中'); * break; * //2 * case UE.dom.domUtils.POSITION_FOLLOWING: * console.log('节点A在节点B之后'); * break; * //4 * case UE.dom.domUtils.POSITION_PRECEDING; * console.log('节点A在节点B之前'); * break; * //8 * case UE.dom.domUtils.POSITION_IS_CONTAINED: * console.log('节点A被节点B包含'); * break; * case 10: * console.log('节点A被节点B包含且节点A在节点B之后'); * break; * //16 * case UE.dom.domUtils.POSITION_CONTAINS: * console.log('节点A包含节点B'); * break; * case 20: * console.log('节点A包含节点B且节点A在节点B之前'); * break; * * } * ``` */ getPosition: function (nodeA, nodeB) { // 如果两个节点是同一个节点 if (nodeA === nodeB) { // domUtils.POSITION_IDENTICAL return 0 } var node, parentsA = [nodeA], parentsB = [nodeB] node = nodeA while ((node = node.parentNode)) { // 如果nodeB是nodeA的祖先节点 if (node === nodeB) { // domUtils.POSITION_IS_CONTAINED + domUtils.POSITION_FOLLOWING return 10 } parentsA.push(node) } node = nodeB while ((node = node.parentNode)) { // 如果nodeA是nodeB的祖先节点 if (node === nodeA) { // domUtils.POSITION_CONTAINS + domUtils.POSITION_PRECEDING return 20 } parentsB.push(node) } parentsA.reverse() parentsB.reverse() if (parentsA[0] !== parentsB[0]) { // domUtils.POSITION_DISCONNECTED return 1 } var i = -1 while ((i++, parentsA[i] === parentsB[i])) {} nodeA = parentsA[i] nodeB = parentsB[i] while ((nodeA = nodeA.nextSibling)) { if (nodeA === nodeB) { // domUtils.POSITION_PRECEDING return 4 } } // domUtils.POSITION_FOLLOWING return 2 }, /** * 检测节点node在父节点中的索引位置 * @method getNodeIndex * @param { Node } node 需要检测的节点对象 * @return { Number } 该节点在父节点中的位置 * @see UE.dom.domUtils.getNodeIndex(Node,Boolean) */ /** * 检测节点node在父节点中的索引位置, 根据给定的mergeTextNode参数决定是否要合并多个连续的文本节点为一个节点 * @method getNodeIndex * @param { Node } node 需要检测的节点对象 * @param { Boolean } mergeTextNode 是否合并多个连续的文本节点为一个节点 * @return { Number } 该节点在父节点中的位置 * @example * ```javascript * * var node = document.createElement("div"); * * node.appendChild( document.createTextNode( "hello" ) ); * node.appendChild( document.createTextNode( "world" ) ); * node.appendChild( node = document.createElement( "div" ) ); * * //output: 2 * console.log( UE.dom.domUtils.getNodeIndex( node ) ); * * //output: 1 * console.log( UE.dom.domUtils.getNodeIndex( node, true ) ); * * ``` */ getNodeIndex: function (node, ignoreTextNode) { var preNode = node, i = 0 while ((preNode = preNode.previousSibling)) { if (ignoreTextNode && preNode.nodeType == 3) { if (preNode.nodeType != preNode.nextSibling.nodeType) { i++ } continue } i++ } return i }, /** * 检测节点node是否在给定的document对象上 * @method inDoc * @param { Node } node 需要检测的节点对象 * @param { DomDocument } doc 需要检测的document对象 * @return { Boolean } 该节点node是否在给定的document的dom树上 * @example * ```javascript * * var node = document.createElement("div"); * * //output: false * console.log( UE.do.domUtils.inDoc( node, document ) ); * * document.body.appendChild( node ); * * //output: true * console.log( UE.do.domUtils.inDoc( node, document ) ); * * ``` */ inDoc: function (node, doc) { return domUtils.getPosition(node, doc) == 10 }, /** * 根据给定的过滤规则filterFn, 查找符合该过滤规则的node节点的第一个祖先节点, * 查找的起点是给定node节点的父节点。 * @method findParent * @param { Node } node 需要查找的节点 * @param { Function } filterFn 自定义的过滤方法。 * @warning 查找的终点是到body节点为止 * @remind 自定义的过滤方法filterFn接受一个Node对象作为参数, 该对象代表当前执行检测的祖先节点。 如果该 * 节点满足过滤条件, 则要求返回true, 这时将直接返回该节点作为findParent()的结果, 否则, 请返回false。 * @return { Node | Null } 如果找到符合过滤条件的节点, 就返回该节点, 否则返回NULL * @example * ```javascript * var filterNode = UE.dom.domUtils.findParent( document.body.firstChild, function ( node ) { * * //由于查找的终点是body节点, 所以永远也不会匹配当前过滤器的条件, 即这里永远会返回false * return node.tagName === "HTML"; * * } ); * * //output: true * console.log( filterNode === null ); * ``` */ /** * 根据给定的过滤规则filterFn, 查找符合该过滤规则的node节点的第一个祖先节点, * 如果includeSelf的值为true,则查找的起点是给定的节点node, 否则, 起点是node的父节点 * @method findParent * @param { Node } node 需要查找的节点 * @param { Function } filterFn 自定义的过滤方法。 * @param { Boolean } includeSelf 查找过程是否包含自身 * @warning 查找的终点是到body节点为止 * @remind 自定义的过滤方法filterFn接受一个Node对象作为参数, 该对象代表当前执行检测的祖先节点。 如果该 * 节点满足过滤条件, 则要求返回true, 这时将直接返回该节点作为findParent()的结果, 否则, 请返回false。 * @remind 如果includeSelf为true, 则过滤器第一次执行时的参数会是节点本身。 * 反之, 过滤器第一次执行时的参数将是该节点的父节点。 * @return { Node | Null } 如果找到符合过滤条件的节点, 就返回该节点, 否则返回NULL * @example * ```html * * *
*
* * * * ``` */ findParent: function (node, filterFn, includeSelf) { if (node && !domUtils.isBody(node)) { node = includeSelf ? node : node.parentNode while (node) { if (!filterFn || filterFn(node) || domUtils.isBody(node)) { return filterFn && !filterFn(node) && domUtils.isBody(node) ? null : node } node = node.parentNode } } return null }, /** * 查找node的节点名为tagName的第一个祖先节点, 查找的起点是node节点的父节点。 * @method findParentByTagName * @param { Node } node 需要查找的节点对象 * @param { Array } tagNames 需要查找的父节点的名称数组 * @warning 查找的终点是到body节点为止 * @return { Node | NULL } 如果找到符合条件的节点, 则返回该节点, 否则返回NULL * @example * ```javascript * var node = UE.dom.domUtils.findParentByTagName( document.getElementsByTagName("div")[0], [ "BODY" ] ); * //output: BODY * console.log( node.tagName ); * ``` */ /** * 查找node的节点名为tagName的祖先节点, 如果includeSelf的值为true,则查找的起点是给定的节点node, * 否则, 起点是node的父节点。 * @method findParentByTagName * @param { Node } node 需要查找的节点对象 * @param { Array } tagNames 需要查找的父节点的名称数组 * @param { Boolean } includeSelf 查找过程是否包含node节点自身 * @warning 查找的终点是到body节点为止 * @return { Node | NULL } 如果找到符合条件的节点, 则返回该节点, 否则返回NULL * @example * ```javascript * var queryTarget = document.getElementsByTagName("div")[0]; * var node = UE.dom.domUtils.findParentByTagName( queryTarget, [ "DIV" ], true ); * //output: true * console.log( queryTarget === node ); * ``` */ findParentByTagName: function (node, tagNames, includeSelf, excludeFn) { tagNames = utils.listToMap( utils.isArray(tagNames) ? tagNames : [tagNames] ) return domUtils.findParent( node, function (node) { return tagNames[node.tagName] && !(excludeFn && excludeFn(node)) }, includeSelf ) }, /** * 查找节点node的祖先节点集合, 查找的起点是给定节点的父节点,结果集中不包含给定的节点。 * @method findParents * @param { Node } node 需要查找的节点对象 * @return { Array } 给定节点的祖先节点数组 * @grammar UE.dom.domUtils.findParents(node) => Array //返回一个祖先节点数组集合,不包含自身 * @grammar UE.dom.domUtils.findParents(node,includeSelf) => Array //返回一个祖先节点数组集合,includeSelf指定是否包含自身 * @grammar UE.dom.domUtils.findParents(node,includeSelf,filterFn) => Array //返回一个祖先节点数组集合,filterFn指定过滤条件,返回true的node将被选取 * @grammar UE.dom.domUtils.findParents(node,includeSelf,filterFn,closerFirst) => Array //返回一个祖先节点数组集合,closerFirst为true的话,node的直接父亲节点是数组的第0个 */ /** * 查找节点node的祖先节点集合, 如果includeSelf的值为true, * 则返回的结果集中允许出现当前给定的节点, 否则, 该节点不会出现在其结果集中。 * @method findParents * @param { Node } node 需要查找的节点对象 * @param { Boolean } includeSelf 查找的结果中是否允许包含当前查找的节点对象 * @return { Array } 给定节点的祖先节点数组 */ findParents: function (node, includeSelf, filterFn, closerFirst) { var parents = includeSelf && ((filterFn && filterFn(node)) || !filterFn) ? [node] : [] while ((node = domUtils.findParent(node, filterFn))) { parents.push(node) } return closerFirst ? parents : parents.reverse() }, /** * 在节点node后面插入新节点newNode * @method insertAfter * @param { Node } node 目标节点 * @param { Node } newNode 新插入的节点, 该节点将置于目标节点之后 * @return { Node } 新插入的节点 */ insertAfter: function (node, newNode) { return node.nextSibling ? node.parentNode.insertBefore(newNode, node.nextSibling) : node.parentNode.appendChild(newNode) }, /** * 删除节点node及其下属的所有节点 * @method remove * @param { Node } node 需要删除的节点对象 * @return { Node } 返回刚删除的节点对象 * @example * ```html *
*
你好
*
* * ``` */ /** * 删除节点node,并根据keepChildren的值决定是否保留子节点 * @method remove * @param { Node } node 需要删除的节点对象 * @param { Boolean } keepChildren 是否需要保留子节点 * @return { Node } 返回刚删除的节点对象 * @example * ```html *
*
你好
*
* * ``` */ remove: function (node, keepChildren) { var parent = node.parentNode, child if (parent) { if (keepChildren && node.hasChildNodes()) { while ((child = node.firstChild)) { parent.insertBefore(child, node) } } parent.removeChild(node) } return node }, /** * 取得node节点的下一个兄弟节点, 如果该节点其后没有兄弟节点, 则递归查找其父节点之后的第一个兄弟节点, * 直到找到满足条件的节点或者递归到BODY节点之后才会结束。 * @method getNextDomNode * @param { Node } node 需要获取其后的兄弟节点的节点对象 * @return { Node | NULL } 如果找满足条件的节点, 则返回该节点, 否则返回NULL * @example * ```html * *
* *
* xxx * * * ``` * @example * ```html * *
* * xxx *
* xxx * * * ``` */ /** * 取得node节点的下一个兄弟节点, 如果startFromChild的值为ture,则先获取其子节点, * 如果有子节点则直接返回第一个子节点;如果没有子节点或者startFromChild的值为false, * 则执行getNextDomNode(Node node)的查找过程。 * @method getNextDomNode * @param { Node } node 需要获取其后的兄弟节点的节点对象 * @param { Boolean } startFromChild 查找过程是否从其子节点开始 * @return { Node | NULL } 如果找满足条件的节点, 则返回该节点, 否则返回NULL * @see UE.dom.domUtils.getNextDomNode(Node) */ getNextDomNode: function (node, startFromChild, filterFn, guard) { return getDomNode( node, 'firstChild', 'nextSibling', startFromChild, filterFn, guard ) }, getPreDomNode: function (node, startFromChild, filterFn, guard) { return getDomNode( node, 'lastChild', 'previousSibling', startFromChild, filterFn, guard ) }, /** * 检测节点node是否属是UEditor定义的bookmark节点 * @method isBookmarkNode * @private * @param { Node } node 需要检测的节点对象 * @return { Boolean } 是否是bookmark节点 * @example * ```html * * * ``` */ isBookmarkNode: function (node) { return node.nodeType == 1 && node.id && /^_baidu_bookmark_/i.test(node.id) }, /** * 获取节点node所属的window对象 * @method getWindow * @param { Node } node 节点对象 * @return { Window } 当前节点所属的window对象 * @example * ```javascript * //output: true * console.log( UE.dom.domUtils.getWindow( document.body ) === window ); * ``` */ getWindow: function (node) { var doc = node.ownerDocument || node return doc.defaultView || doc.parentWindow }, /** * 获取离nodeA与nodeB最近的公共的祖先节点 * @method getCommonAncestor * @param { Node } nodeA 第一个节点 * @param { Node } nodeB 第二个节点 * @remind 如果给定的两个节点是同一个节点, 将直接返回该节点。 * @return { Node | NULL } 如果未找到公共节点, 返回NULL, 否则返回最近的公共祖先节点。 * @example * ```javascript * var commonAncestor = UE.dom.domUtils.getCommonAncestor( document.body, document.body.firstChild ); * //output: true * console.log( commonAncestor.tagName.toLowerCase() === 'body' ); * ``` */ getCommonAncestor: function (nodeA, nodeB) { if (nodeA === nodeB) return nodeA var parentsA = [nodeA], parentsB = [nodeB], parent = nodeA, i = -1 while ((parent = parent.parentNode)) { if (parent === nodeB) { return parent } parentsA.push(parent) } parent = nodeB while ((parent = parent.parentNode)) { if (parent === nodeA) return parent parentsB.push(parent) } parentsA.reverse() parentsB.reverse() while ((i++, parentsA[i] === parentsB[i])) {} return i == 0 ? null : parentsA[i - 1] }, /** * 清除node节点左右连续为空的兄弟inline节点 * @method clearEmptySibling * @param { Node } node 执行的节点对象, 如果该节点的左右连续的兄弟节点是空的inline节点, * 则这些兄弟节点将被删除 * @grammar UE.dom.domUtils.clearEmptySibling(node,ignoreNext) //ignoreNext指定是否忽略右边空节点 * @grammar UE.dom.domUtils.clearEmptySibling(node,ignoreNext,ignorePre) //ignorePre指定是否忽略左边空节点 * @example * ```html * *
* * * * xxx * * * * ``` */ /** * 清除node节点左右连续为空的兄弟inline节点, 如果ignoreNext的值为true, * 则忽略对右边兄弟节点的操作。 * @method clearEmptySibling * @param { Node } node 执行的节点对象, 如果该节点的左右连续的兄弟节点是空的inline节点, * @param { Boolean } ignoreNext 是否忽略忽略对右边的兄弟节点的操作 * 则这些兄弟节点将被删除 * @see UE.dom.domUtils.clearEmptySibling(Node) */ /** * 清除node节点左右连续为空的兄弟inline节点, 如果ignoreNext的值为true, * 则忽略对右边兄弟节点的操作, 如果ignorePre的值为true,则忽略对左边兄弟节点的操作。 * @method clearEmptySibling * @param { Node } node 执行的节点对象, 如果该节点的左右连续的兄弟节点是空的inline节点, * @param { Boolean } ignoreNext 是否忽略忽略对右边的兄弟节点的操作 * @param { Boolean } ignorePre 是否忽略忽略对左边的兄弟节点的操作 * 则这些兄弟节点将被删除 * @see UE.dom.domUtils.clearEmptySibling(Node) */ clearEmptySibling: function (node, ignoreNext, ignorePre) { function clear(next, dir) { var tmpNode while ( next && !domUtils.isBookmarkNode(next) && (domUtils.isEmptyInlineElement(next) || //这里不能把空格算进来会吧空格干掉,出现文字间的空格丢掉了 !new RegExp('[^\t\n\r' + domUtils.fillChar + ']').test( next.nodeValue )) ) { tmpNode = next[dir] domUtils.remove(next) next = tmpNode } } !ignoreNext && clear(node.nextSibling, 'nextSibling') !ignorePre && clear(node.previousSibling, 'previousSibling') }, /** * 将一个文本节点textNode拆分成两个文本节点,offset指定拆分位置 * @method split * @param { Node } textNode 需要拆分的文本节点对象 * @param { int } offset 需要拆分的位置, 位置计算从0开始 * @return { Node } 拆分后形成的新节点 * @example * ```html *
abcdef
* * ``` */ split: function (node, offset) { var doc = node.ownerDocument if (browser.ie && offset == node.nodeValue.length) { var next = doc.createTextNode('') return domUtils.insertAfter(node, next) } var retval = node.splitText(offset) //ie8下splitText不会跟新childNodes,我们手动触发他的更新 if (browser.ie8) { var tmpNode = doc.createTextNode('') domUtils.insertAfter(retval, tmpNode) domUtils.remove(tmpNode) } return retval }, /** * 检测文本节点textNode是否为空节点(包括空格、换行、占位符等字符) * @method isWhitespace * @param { Node } node 需要检测的节点对象 * @return { Boolean } 检测的节点是否为空 * @example * ```html *
* *
* * ``` */ isWhitespace: function (node) { return !new RegExp('[^ \t\n\r' + domUtils.fillChar + ']').test( node.nodeValue ) }, /** * 获取元素element相对于viewport的位置坐标 * @method getXY * @param { Node } element 需要计算位置的节点对象 * @return { Object } 返回形如{x:left,y:top}的一个key-value映射对象, 其中键x代表水平偏移距离, * y代表垂直偏移距离。 * * @example * ```javascript * var location = UE.dom.domUtils.getXY( document.getElementById("test") ); * //output: test的坐标为: 12, 24 * console.log( 'test的坐标为: ', location.x, ',', location.y ); * ``` */ getXY: function (element) { var x = 0, y = 0 while (element.offsetParent) { y += element.offsetTop x += element.offsetLeft element = element.offsetParent } return { x: x, y: y } }, /** * 为元素element绑定原生DOM事件,type为事件类型,handler为处理函数 * @method on * @param { Node } element 需要绑定事件的节点对象 * @param { String } type 绑定的事件类型 * @param { Function } handler 事件处理器 * @example * ```javascript * UE.dom.domUtils.on(document.body,"click",function(e){ * //e为事件对象,this为被点击元素对戏那个 * }); * ``` */ /** * 为元素element绑定原生DOM事件,type为事件类型,handler为处理函数 * @method on * @param { Node } element 需要绑定事件的节点对象 * @param { Array } type 绑定的事件类型数组 * @param { Function } handler 事件处理器 * @example * ```javascript * UE.dom.domUtils.on(document.body,["click","mousedown"],function(evt){ * //evt为事件对象,this为被点击元素对象 * }); * ``` */ on: function (element, type, handler) { var types = utils.isArray(type) ? type : utils.trim(type).split(/\s+/), k = types.length if (k) while (k--) { type = types[k] if (element.addEventListener) { element.addEventListener(type, handler, false) } else { if (!handler._d) { handler._d = { els: [] } } var key = type + handler.toString(), index = utils.indexOf(handler._d.els, element) if (!handler._d[key] || index == -1) { if (index == -1) { handler._d.els.push(element) } if (!handler._d[key]) { handler._d[key] = function (evt) { return handler.call(evt.srcElement, evt || window.event) } } element.attachEvent('on' + type, handler._d[key]) } } } element = null }, /** * 解除DOM事件绑定 * @method un * @param { Node } element 需要解除事件绑定的节点对象 * @param { String } type 需要接触绑定的事件类型 * @param { Function } handler 对应的事件处理器 * @example * ```javascript * UE.dom.domUtils.un(document.body,"click",function(evt){ * //evt为事件对象,this为被点击元素对象 * }); * ``` */ /** * 解除DOM事件绑定 * @method un * @param { Node } element 需要解除事件绑定的节点对象 * @param { Array } type 需要接触绑定的事件类型数组 * @param { Function } handler 对应的事件处理器 * @example * ```javascript * UE.dom.domUtils.un(document.body, ["click","mousedown"],function(evt){ * //evt为事件对象,this为被点击元素对象 * }); * ``` */ un: function (element, type, handler) { var types = utils.isArray(type) ? type : utils.trim(type).split(/\s+/), k = types.length if (k) while (k--) { type = types[k] if (element.removeEventListener) { element.removeEventListener(type, handler, false) } else { var key = type + handler.toString() try { element.detachEvent( 'on' + type, handler._d ? handler._d[key] : handler ) } catch (e) {} if (handler._d && handler._d[key]) { var index = utils.indexOf(handler._d.els, element) if (index != -1) { handler._d.els.splice(index, 1) } handler._d.els.length == 0 && delete handler._d[key] } } } }, /** * 比较节点nodeA与节点nodeB是否具有相同的标签名、属性名以及属性值 * @method isSameElement * @param { Node } nodeA 需要比较的节点 * @param { Node } nodeB 需要比较的节点 * @return { Boolean } 两个节点是否具有相同的标签名、属性名以及属性值 * @example * ```html * ssss * bbbbb * ssss * bbbbb * * * ``` */ isSameElement: function (nodeA, nodeB) { if (nodeA.tagName != nodeB.tagName) { return false } var thisAttrs = nodeA.attributes, otherAttrs = nodeB.attributes if (!ie && thisAttrs.length != otherAttrs.length) { return false } var attrA, attrB, al = 0, bl = 0 for (var i = 0; (attrA = thisAttrs[i++]); ) { if (attrA.nodeName == 'style') { if (attrA.specified) { al++ } if (domUtils.isSameStyle(nodeA, nodeB)) { continue } else { return false } } if (ie) { if (attrA.specified) { al++ attrB = otherAttrs.getNamedItem(attrA.nodeName) } else { continue } } else { attrB = nodeB.attributes[attrA.nodeName] } if (!attrB.specified || attrA.nodeValue != attrB.nodeValue) { return false } } // 有可能attrB的属性包含了attrA的属性之外还有自己的属性 if (ie) { for (i = 0; (attrB = otherAttrs[i++]); ) { if (attrB.specified) { bl++ } } if (al != bl) { return false } } return true }, /** * 判断节点nodeA与节点nodeB的元素的style属性是否一致 * @method isSameStyle * @param { Node } nodeA 需要比较的节点 * @param { Node } nodeB 需要比较的节点 * @return { Boolean } 两个节点是否具有相同的style属性值 * @example * ```html * ssss * bbbbb * ssss * bbbbb * * * ``` */ isSameStyle: function (nodeA, nodeB) { var styleA = nodeA.style.cssText .replace(/( ?; ?)/g, ';') .replace(/( ?: ?)/g, ':'), styleB = nodeB.style.cssText .replace(/( ?; ?)/g, ';') .replace(/( ?: ?)/g, ':') if (browser.opera) { styleA = nodeA.style styleB = nodeB.style if (styleA.length != styleB.length) return false for (var p in styleA) { if (/^(\d+|csstext)$/i.test(p)) { continue } if (styleA[p] != styleB[p]) { return false } } return true } if (!styleA || !styleB) { return styleA == styleB } styleA = styleA.split(';') styleB = styleB.split(';') if (styleA.length != styleB.length) { return false } for (var i = 0, ci; (ci = styleA[i++]); ) { if (utils.indexOf(styleB, ci) == -1) { return false } } return true }, /** * 检查节点node是否为block元素 * @method isBlockElm * @param { Node } node 需要检测的节点对象 * @return { Boolean } 是否是block元素节点 * @warning 该方法的判断规则如下: 如果该元素原本是block元素, 则不论该元素当前的css样式是什么都会返回true; * 否则,检测该元素的css样式, 如果该元素当前是block元素, 则返回true。 其余情况下都返回false。 * @example * ```html * * *
* * * ``` */ isBlockElm: function (node) { return ( node.nodeType == 1 && (dtd.$block[node.tagName] || styleBlock[domUtils.getComputedStyle(node, 'display')]) && !dtd.$nonChild[node.tagName] ) }, /** * 检测node节点是否为body节点 * @method isBody * @param { Element } node 需要检测的dom元素 * @return { Boolean } 给定的元素是否是body元素 * @example * ```javascript * //output: true * console.log( UE.dom.domUtils.isBody( document.body ) ); * ``` */ isBody: function (node) { return node && node.nodeType == 1 && node.tagName.toLowerCase() == 'body' }, /** * 以node节点为分界,将该节点的指定祖先节点parent拆分成两个独立的节点, * 拆分形成的两个节点之间是node节点 * @method breakParent * @param { Node } node 作为分界的节点对象 * @param { Node } parent 该节点必须是node节点的祖先节点, 且是block节点。 * @return { Node } 给定的node分界节点 * @example * ```javascript * * var node = document.createElement("span"), * wrapNode = document.createElement( "div" ), * parent = document.createElement("p"); * * parent.appendChild( node ); * wrapNode.appendChild( parent ); * * //拆分前 * //output:

* console.log( wrapNode.innerHTML ); * * * UE.dom.domUtils.breakParent( node, parent ); * //拆分后 * //output:

* console.log( wrapNode.innerHTML ); * * ``` */ breakParent: function (node, parent) { var tmpNode, parentClone = node, clone = node, leftNodes, rightNodes do { parentClone = parentClone.parentNode if (leftNodes) { tmpNode = parentClone.cloneNode(false) tmpNode.appendChild(leftNodes) leftNodes = tmpNode tmpNode = parentClone.cloneNode(false) tmpNode.appendChild(rightNodes) rightNodes = tmpNode } else { leftNodes = parentClone.cloneNode(false) rightNodes = leftNodes.cloneNode(false) } while ((tmpNode = clone.previousSibling)) { leftNodes.insertBefore(tmpNode, leftNodes.firstChild) } while ((tmpNode = clone.nextSibling)) { rightNodes.appendChild(tmpNode) } clone = parentClone } while (parent !== parentClone) tmpNode = parent.parentNode tmpNode.insertBefore(leftNodes, parent) tmpNode.insertBefore(rightNodes, parent) tmpNode.insertBefore(node, rightNodes) domUtils.remove(parent) return node }, /** * 检查节点node是否是空inline节点 * @method isEmptyInlineElement * @param { Node } node 需要检测的节点对象 * @return { Number } 如果给定的节点是空的inline节点, 则返回1, 否则返回0。 * @example * ```html * => 1 * => 1 * => 1 * xx => 0 * ``` */ isEmptyInlineElement: function (node) { if (node.nodeType != 1 || !dtd.$removeEmpty[node.tagName]) { return 0 } node = node.firstChild while (node) { //如果是创建的bookmark就跳过 if (domUtils.isBookmarkNode(node)) { return 0 } if ( (node.nodeType == 1 && !domUtils.isEmptyInlineElement(node)) || (node.nodeType == 3 && !domUtils.isWhitespace(node)) ) { return 0 } node = node.nextSibling } return 1 }, /** * 删除node节点下首尾两端的空白文本子节点 * @method trimWhiteTextNode * @param { Element } node 需要执行删除操作的元素对象 * @example * ```javascript * var node = document.createElement("div"); * * node.appendChild( document.createTextNode( "" ) ); * * node.appendChild( document.createElement("div") ); * * node.appendChild( document.createTextNode( "" ) ); * * //3 * console.log( node.childNodes.length ); * * UE.dom.domUtils.trimWhiteTextNode( node ); * * //1 * console.log( node.childNodes.length ); * ``` */ trimWhiteTextNode: function (node) { function remove(dir) { var child while ( (child = node[dir]) && child.nodeType == 3 && domUtils.isWhitespace(child) ) { node.removeChild(child) } } remove('firstChild') remove('lastChild') }, /** * 合并node节点下相同的子节点 * @name mergeChild * @desc * UE.dom.domUtils.mergeChild(node,tagName) //tagName要合并的子节点的标签 * @example *

xxaaxx

* ==> UE.dom.domUtils.mergeChild(node,'span') *

xxaaxx

*/ mergeChild: function (node, tagName, attrs) { var list = domUtils.getElementsByTagName(node, node.tagName.toLowerCase()) for (var i = 0, ci; (ci = list[i++]); ) { if (!ci.parentNode || domUtils.isBookmarkNode(ci)) { continue } //span单独处理 if (ci.tagName.toLowerCase() == 'span') { if (node === ci.parentNode) { domUtils.trimWhiteTextNode(node) if (node.childNodes.length == 1) { node.style.cssText = ci.style.cssText + ';' + node.style.cssText domUtils.remove(ci, true) continue } } ci.style.cssText = node.style.cssText + ';' + ci.style.cssText if (attrs) { var style = attrs.style if (style) { style = style.split(';') for (var j = 0, s; (s = style[j++]); ) { ci.style[utils.cssStyleToDomStyle(s.split(':')[0])] = s.split(':')[1] } } } if (domUtils.isSameStyle(ci, node)) { domUtils.remove(ci, true) } continue } if (domUtils.isSameElement(node, ci)) { domUtils.remove(ci, true) } } }, /** * 原生方法getElementsByTagName的封装 * @method getElementsByTagName * @param { Node } node 目标节点对象 * @param { String } tagName 需要查找的节点的tagName, 多个tagName以空格分割 * @return { Array } 符合条件的节点集合 */ getElementsByTagName: function (node, name, filter) { if (filter && utils.isString(filter)) { var className = filter filter = function (node) { return domUtils.hasClass(node, className) } } name = utils .trim(name) .replace(/[ ]{2,}/g, ' ') .split(' ') var arr = [] for (var n = 0, ni; (ni = name[n++]); ) { var list = node.getElementsByTagName(ni) for (var i = 0, ci; (ci = list[i++]); ) { if (!filter || filter(ci)) arr.push(ci) } } return arr }, /** * 将节点node提取到父节点上 * @method mergeToParent * @param { Element } node 需要提取的元素对象 * @example * ```html *
*
* *
*
* * * ``` */ mergeToParent: function (node) { var parent = node.parentNode while (parent && dtd.$removeEmpty[parent.tagName]) { if (parent.tagName == node.tagName || parent.tagName == 'A') { //针对a标签单独处理 domUtils.trimWhiteTextNode(parent) //span需要特殊处理 不处理这样的情况 xxxxxxxxx if ( (parent.tagName == 'SPAN' && !domUtils.isSameStyle(parent, node)) || (parent.tagName == 'A' && node.tagName == 'SPAN') ) { if (parent.childNodes.length > 1 || parent !== node.parentNode) { node.style.cssText = parent.style.cssText + ';' + node.style.cssText parent = parent.parentNode continue } else { parent.style.cssText += ';' + node.style.cssText //trace:952 a标签要保持下划线 if (parent.tagName == 'A') { parent.style.textDecoration = 'underline' } } } if (parent.tagName != 'A') { parent === node.parentNode && domUtils.remove(node, true) break } } parent = parent.parentNode } }, /** * 合并节点node的左右兄弟节点 * @method mergeSibling * @param { Element } node 需要合并的目标节点 * @example * ```html * xxxxoooxxxx * * * ``` */ /** * 合并节点node的左右兄弟节点, 可以根据给定的条件选择是否忽略合并左节点。 * @method mergeSibling * @param { Element } node 需要合并的目标节点 * @param { Boolean } ignorePre 是否忽略合并左节点 * @example * ```html * xxxxoooxxxx * * * ``` */ /** * 合并节点node的左右兄弟节点,可以根据给定的条件选择是否忽略合并左右节点。 * @method mergeSibling * @param { Element } node 需要合并的目标节点 * @param { Boolean } ignorePre 是否忽略合并左节点 * @param { Boolean } ignoreNext 是否忽略合并右节点 * @remind 如果同时忽略左右节点, 则该操作什么也不会做 * @example * ```html * xxxxoooxxxx * * * ``` */ mergeSibling: function (node, ignorePre, ignoreNext) { function merge(rtl, start, node) { var next if ( (next = node[rtl]) && !domUtils.isBookmarkNode(next) && next.nodeType == 1 && domUtils.isSameElement(node, next) ) { while (next.firstChild) { if (start == 'firstChild') { node.insertBefore(next.lastChild, node.firstChild) } else { node.appendChild(next.firstChild) } } domUtils.remove(next) } } !ignorePre && merge('previousSibling', 'firstChild', node) !ignoreNext && merge('nextSibling', 'lastChild', node) }, /** * 设置节点node及其子节点不会被选中 * @method unSelectable * @param { Element } node 需要执行操作的dom元素 * @remind 执行该操作后的节点, 将不能被鼠标选中 * @example * ```javascript * UE.dom.domUtils.unSelectable( document.body ); * ``` */ unSelectable: (ie && browser.ie9below) || browser.opera ? function (node) { //for ie9 node.onselectstart = function () { return false } node.onclick = node.onkeyup = node.onkeydown = function () { return false } node.unselectable = 'on' node.setAttribute('unselectable', 'on') for (var i = 0, ci; (ci = node.all[i++]); ) { switch (ci.tagName.toLowerCase()) { case 'iframe': case 'textarea': case 'input': case 'select': break default: ci.unselectable = 'on' node.setAttribute('unselectable', 'on') } } } : function (node) { node.style.MozUserSelect = node.style.webkitUserSelect = node.style.msUserSelect = node.style.KhtmlUserSelect = 'none' }, /** * 删除节点node上的指定属性名称的属性 * @method removeAttributes * @param { Node } node 需要删除属性的节点对象 * @param { String } attrNames 可以是空格隔开的多个属性名称,该操作将会依次删除相应的属性 * @example * ```html *
* xxxxx *
* * * ``` */ /** * 删除节点node上的指定属性名称的属性 * @method removeAttributes * @param { Node } node 需要删除属性的节点对象 * @param { Array } attrNames 需要删除的属性名数组 * @example * ```html *
* xxxxx *
* * * ``` */ removeAttributes: function (node, attrNames) { attrNames = utils.isArray(attrNames) ? attrNames : utils .trim(attrNames) .replace(/[ ]{2,}/g, ' ') .split(' ') for (var i = 0, ci; (ci = attrNames[i++]); ) { ci = attrFix[ci] || ci switch (ci) { case 'className': node[ci] = '' break case 'style': node.style.cssText = '' var val = node.getAttributeNode('style') !browser.ie && val && node.removeAttributeNode(val) } node.removeAttribute(ci) } }, /** * 在doc下创建一个标签名为tag,属性为attrs的元素 * @method createElement * @param { DomDocument } doc 新创建的元素属于该document节点创建 * @param { String } tagName 需要创建的元素的标签名 * @param { Object } attrs 新创建的元素的属性key-value集合 * @return { Element } 新创建的元素对象 * @example * ```javascript * var ele = UE.dom.domUtils.createElement( document, 'div', { * id: 'test' * } ); * * //output: DIV * console.log( ele.tagName ); * * //output: test * console.log( ele.id ); * * ``` */ createElement: function (doc, tag, attrs) { return domUtils.setAttributes(doc.createElement(tag), attrs) }, /** * 为节点node添加属性attrs,attrs为属性键值对 * @method setAttributes * @param { Element } node 需要设置属性的元素对象 * @param { Object } attrs 需要设置的属性名-值对 * @return { Element } 设置属性的元素对象 * @example * ```html * * * * */ setAttributes: function (node, attrs) { for (var attr in attrs) { if (attrs.hasOwnProperty(attr)) { var value = attrs[attr] switch (attr) { case 'class': //ie下要这样赋值,setAttribute不起作用 node.className = value break case 'style': node.style.cssText = node.style.cssText + ';' + value break case 'innerHTML': node[attr] = value break case 'value': node.value = value break default: node.setAttribute(attrFix[attr] || attr, value) } } } return node }, /** * 获取元素element经过计算后的样式值 * @method getComputedStyle * @param { Element } element 需要获取样式的元素对象 * @param { String } styleName 需要获取的样式名 * @return { String } 获取到的样式值 * @example * ```html * * * * * * ``` */ getComputedStyle: function (element, styleName) { //一下的属性单独处理 var pros = 'width height top left' if (pros.indexOf(styleName) > -1) { return ( element[ 'offset' + styleName.replace(/^\w/, function (s) { return s.toUpperCase() }) ] + 'px' ) } //忽略文本节点 if (element.nodeType == 3) { element = element.parentNode } //ie下font-size若body下定义了font-size,则从currentStyle里会取到这个font-size. 取不到实际值,故此修改. if ( browser.ie && browser.version < 9 && styleName == 'font-size' && !element.style.fontSize && !dtd.$empty[element.tagName] && !dtd.$nonChild[element.tagName] ) { var span = element.ownerDocument.createElement('span') span.style.cssText = 'padding:0;border:0;font-family:simsun;' span.innerHTML = '.' element.appendChild(span) var result = span.offsetHeight element.removeChild(span) span = null return result + 'px' } try { var value = domUtils.getStyle(element, styleName) || (window.getComputedStyle ? domUtils .getWindow(element) .getComputedStyle(element, '') .getPropertyValue(styleName) : (element.currentStyle || element.style)[ utils.cssStyleToDomStyle(styleName) ]) } catch (e) { return '' } return utils.transUnitToPx(utils.fixColor(styleName, value)) }, /** * 删除元素element指定的className * @method removeClasses * @param { Element } ele 需要删除class的元素节点 * @param { String } classNames 需要删除的className, 多个className之间以空格分开 * @example * ```html * xxx * * * ``` */ /** * 删除元素element指定的className * @method removeClasses * @param { Element } ele 需要删除class的元素节点 * @param { Array } classNames 需要删除的className数组 * @example * ```html * xxx * * * ``` */ removeClasses: function (elm, classNames) { classNames = utils.isArray(classNames) ? classNames : utils .trim(classNames) .replace(/[ ]{2,}/g, ' ') .split(' ') for (var i = 0, ci, cls = elm.className; (ci = classNames[i++]); ) { cls = cls.replace(new RegExp('\\b' + ci + '\\b'), '') } cls = utils.trim(cls).replace(/[ ]{2,}/g, ' ') if (cls) { elm.className = cls } else { domUtils.removeAttributes(elm, ['class']) } }, /** * 给元素element添加className * @method addClass * @param { Node } ele 需要增加className的元素 * @param { String } classNames 需要添加的className, 多个className之间以空格分割 * @remind 相同的类名不会被重复添加 * @example * ```html * * * * ``` */ /** * 判断元素element是否包含给定的样式类名className * @method hasClass * @param { Node } ele 需要检测的元素 * @param { Array } classNames 需要检测的className数组 * @return { Boolean } 元素是否包含所有给定的className * @example * ```html * * * * ``` */ hasClass: function (element, className) { if (utils.isRegExp(className)) { return className.test(element.className) } className = utils .trim(className) .replace(/[ ]{2,}/g, ' ') .split(' ') for (var i = 0, ci, cls = element.className; (ci = className[i++]); ) { if (!new RegExp('\\b' + ci + '\\b', 'i').test(cls)) { return false } } return i - 1 == className.length }, /** * 阻止事件默认行为 * @method preventDefault * @param { Event } evt 需要阻止默认行为的事件对象 * @example * ```javascript * UE.dom.domUtils.preventDefault( evt ); * ``` */ preventDefault: function (evt) { evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false) }, /** * 删除元素element指定的样式 * @method removeStyle * @param { Element } element 需要删除样式的元素 * @param { String } styleName 需要删除的样式名 * @example * ```html * * * * ``` */ removeStyle: function (element, name) { if (browser.ie) { //针对color先单独处理一下 if (name == 'color') { name = '(^|;)' + name } element.style.cssText = element.style.cssText.replace( new RegExp(name + '[^:]*:[^;]+;?', 'ig'), '' ) } else { if (element.style.removeProperty) { element.style.removeProperty(name) } else { element.style.removeAttribute(utils.cssStyleToDomStyle(name)) } } if (!element.style.cssText) { domUtils.removeAttributes(element, ['style']) } }, /** * 获取元素element的style属性的指定值 * @method getStyle * @param { Element } element 需要获取属性值的元素 * @param { String } styleName 需要获取的style的名称 * @warning 该方法仅获取元素style属性中所标明的值 * @return { String } 该元素包含指定的style属性值 * @example * ```html *
* * * ``` */ getStyle: function (element, name) { var value = element.style[utils.cssStyleToDomStyle(name)] return utils.fixColor(name, value) }, /** * 为元素element设置样式属性值 * @method setStyle * @param { Element } element 需要设置样式的元素 * @param { String } styleName 样式名 * @param { String } styleValue 样式值 * @example * ```html *
* * * ``` */ setStyle: function (element, name, value) { element.style[utils.cssStyleToDomStyle(name)] = value if (!utils.trim(element.style.cssText)) { this.removeAttributes(element, 'style') } }, /** * 为元素element设置多个样式属性值 * @method setStyles * @param { Element } element 需要设置样式的元素 * @param { Object } styles 样式名值对 * @example * ```html *
* * * ``` */ setStyles: function (element, styles) { for (var name in styles) { if (styles.hasOwnProperty(name)) { domUtils.setStyle(element, name, styles[name]) } } }, /** * 删除_moz_dirty属性 * @private * @method removeDirtyAttr */ removeDirtyAttr: function (node) { for ( var i = 0, ci, nodes = node.getElementsByTagName('*'); (ci = nodes[i++]); ) { ci.removeAttribute('_moz_dirty') } node.removeAttribute('_moz_dirty') }, /** * 获取子节点的数量 * @method getChildCount * @param { Element } node 需要检测的元素 * @return { Number } 给定的node元素的子节点数量 * @example * ```html *
* *
* * * ``` */ /** * 根据给定的过滤规则, 获取符合条件的子节点的数量 * @method getChildCount * @param { Element } node 需要检测的元素 * @param { Function } fn 过滤器, 要求对符合条件的子节点返回true, 反之则要求返回false * @return { Number } 符合过滤条件的node元素的子节点数量 * @example * ```html *
* *
* * * ``` */ getChildCount: function (node, fn) { var count = 0, first = node.firstChild fn = fn || function () { return 1 } while (first) { if (fn(first)) { count++ } first = first.nextSibling } return count }, /** * 判断给定节点是否为空节点 * @method isEmptyNode * @param { Node } node 需要检测的节点对象 * @return { Boolean } 节点是否为空 * @example * ```javascript * UE.dom.domUtils.isEmptyNode( document.body ); * ``` */ isEmptyNode: function (node) { return ( !node.firstChild || domUtils.getChildCount(node, function (node) { return ( !domUtils.isBr(node) && !domUtils.isBookmarkNode(node) && !domUtils.isWhitespace(node) ) }) == 0 ) }, clearSelectedArr: function (nodes) { var node while ((node = nodes.pop())) { domUtils.removeAttributes(node, ['class']) } }, /** * 将显示区域滚动到指定节点的位置 * @method scrollToView * @param {Node} node 节点 * @param {window} win window对象 * @param {Number} offsetTop 距离上方的偏移量 */ scrollToView: function (node, win, offsetTop) { var getViewPaneSize = function () { var doc = win.document, mode = doc.compatMode == 'CSS1Compat' return { width: (mode ? doc.documentElement.clientWidth : doc.body.clientWidth) || 0, height: (mode ? doc.documentElement.clientHeight : doc.body.clientHeight) || 0 } }, getScrollPosition = function (win) { if ('pageXOffset' in win) { return { x: win.pageXOffset || 0, y: win.pageYOffset || 0 } } else { var doc = win.document return { x: doc.documentElement.scrollLeft || doc.body.scrollLeft || 0, y: doc.documentElement.scrollTop || doc.body.scrollTop || 0 } } } var winHeight = getViewPaneSize().height, offset = winHeight * -1 + offsetTop offset += node.offsetHeight || 0 var elementPosition = domUtils.getXY(node) offset += elementPosition.y var currentScroll = getScrollPosition(win).y // offset += 50; if (offset > currentScroll || offset < currentScroll - winHeight) { win.scrollTo(0, offset + (offset < 0 ? -20 : 20)) } }, /** * 判断给定节点是否为br * @method isBr * @param { Node } node 需要判断的节点对象 * @return { Boolean } 给定的节点是否是br节点 */ isBr: function (node) { return node.nodeType == 1 && node.tagName == 'BR' }, /** * 判断给定的节点是否是一个“填充”节点 * @private * @method isFillChar * @param { Node } node 需要判断的节点 * @param { Boolean } isInStart 是否从节点内容的开始位置匹配 * @returns { Boolean } 节点是否是填充节点 */ isFillChar: function (node, isInStart) { if (node.nodeType != 3) return false var text = node.nodeValue if (isInStart) { return new RegExp('^' + domUtils.fillChar).test(text) } return !text.replace(new RegExp(domUtils.fillChar, 'g'), '').length }, isStartInblock: function (range) { var tmpRange = range.cloneRange(), flag = 0, start = tmpRange.startContainer, tmp if (start.nodeType == 1 && start.childNodes[tmpRange.startOffset]) { start = start.childNodes[tmpRange.startOffset] var pre = start.previousSibling while (pre && domUtils.isFillChar(pre)) { start = pre pre = pre.previousSibling } } if (this.isFillChar(start, true) && tmpRange.startOffset == 1) { tmpRange.setStartBefore(start) start = tmpRange.startContainer } while (start && domUtils.isFillChar(start)) { tmp = start start = start.previousSibling } if (tmp) { tmpRange.setStartBefore(tmp) start = tmpRange.startContainer } if ( start.nodeType == 1 && domUtils.isEmptyNode(start) && tmpRange.startOffset == 1 ) { tmpRange.setStart(start, 0).collapse(true) } while (!tmpRange.startOffset) { start = tmpRange.startContainer if (domUtils.isBlockElm(start) || domUtils.isBody(start)) { flag = 1 break } var pre = tmpRange.startContainer.previousSibling, tmpNode if (!pre) { tmpRange.setStartBefore(tmpRange.startContainer) } else { while (pre && domUtils.isFillChar(pre)) { tmpNode = pre pre = pre.previousSibling } if (tmpNode) { tmpRange.setStartBefore(tmpNode) } else { tmpRange.setStartBefore(tmpRange.startContainer) } } } return flag && !domUtils.isBody(tmpRange.startContainer) ? 1 : 0 }, /** * 判断给定的元素是否是一个空元素 * @method isEmptyBlock * @param { Element } node 需要判断的元素 * @return { Boolean } 是否是空元素 * @example * ```html *
* * * ``` */ /** * 根据指定的判断规则判断给定的元素是否是一个空元素 * @method isEmptyBlock * @param { Element } node 需要判断的元素 * @param { RegExp } reg 对内容执行判断的正则表达式对象 * @return { Boolean } 是否是空元素 */ isEmptyBlock: function (node, reg) { // HaoChuan9421 if (!node) { return } if (node.nodeType != 1) return 0 reg = reg || new RegExp('[ \xa0\t\r\n' + domUtils.fillChar + ']', 'g') if ( node[browser.ie ? 'innerText' : 'textContent'].replace(reg, '').length > 0 ) { return 0 } for (var n in dtd.$isNotEmpty) { if (node.getElementsByTagName(n).length) { return 0 } } return 1 }, /** * 移动元素使得该元素的位置移动指定的偏移量的距离 * @method setViewportOffset * @param { Element } element 需要设置偏移量的元素 * @param { Object } offset 偏移量, 形如{ left: 100, top: 50 }的一个键值对, 表示该元素将在 * 现有的位置上向水平方向偏移offset.left的距离, 在竖直方向上偏移 * offset.top的距离 * @example * ```html *
* * * ``` */ setViewportOffset: function (element, offset) { var left = parseInt(element.style.left) | 0 var top = parseInt(element.style.top) | 0 var rect = element.getBoundingClientRect() var offsetLeft = offset.left - rect.left var offsetTop = offset.top - rect.top if (offsetLeft) { element.style.left = left + offsetLeft + 'px' } if (offsetTop) { element.style.top = top + offsetTop + 'px' } }, /** * 用“填充字符”填充节点 * @method fillNode * @private * @param { DomDocument } doc 填充的节点所在的docment对象 * @param { Node } node 需要填充的节点对象 * @example * ```html *
* * * ``` */ fillNode: function (doc, node) { var tmpNode = browser.ie ? doc.createTextNode(domUtils.fillChar) : doc.createElement('br') node.innerHTML = '' node.appendChild(tmpNode) }, /** * 把节点src的所有子节点追加到另一个节点tag上去 * @method moveChild * @param { Node } src 源节点, 该节点下的所有子节点将被移除 * @param { Node } tag 目标节点, 从源节点移除的子节点将被追加到该节点下 * @example * ```html *
* *
*
*
*
* * * ``` */ /** * 把节点src的所有子节点移动到另一个节点tag上去, 可以通过dir参数控制附加的行为是“追加”还是“插入顶部” * @method moveChild * @param { Node } src 源节点, 该节点下的所有子节点将被移除 * @param { Node } tag 目标节点, 从源节点移除的子节点将被附加到该节点下 * @param { Boolean } dir 附加方式, 如果为true, 则附加进去的节点将被放到目标节点的顶部, 反之,则放到末尾 * @example * ```html *
* *
*
*
*
* * * ``` */ moveChild: function (src, tag, dir) { while (src.firstChild) { if (dir && tag.firstChild) { tag.insertBefore(src.lastChild, tag.firstChild) } else { tag.appendChild(src.firstChild) } } }, /** * 判断节点的标签上是否不存在任何属性 * @method hasNoAttributes * @private * @param { Node } node 需要检测的节点对象 * @return { Boolean } 节点是否不包含任何属性 * @example * ```html *
xxxx
* * * ``` */ hasNoAttributes: function (node) { return browser.ie ? /^<\w+\s*?>/.test(node.outerHTML) : node.attributes.length == 0 }, /** * 检测节点是否是UEditor所使用的辅助节点 * @method isCustomeNode * @private * @param { Node } node 需要检测的节点 * @remind 辅助节点是指编辑器要完成工作临时添加的节点, 在输出的时候将会从编辑器内移除, 不会影响最终的结果。 * @return { Boolean } 给定的节点是否是一个辅助节点 */ isCustomeNode: function (node) { return node.nodeType == 1 && node.getAttribute('_ue_custom_node_') }, /** * 检测节点的标签是否是给定的标签 * @method isTagNode * @param { Node } node 需要检测的节点对象 * @param { String } tagName 标签 * @return { Boolean } 节点的标签是否是给定的标签 * @example * ```html *
* * * ``` */ isTagNode: function (node, tagNames) { return ( node.nodeType == 1 && new RegExp('\\b' + node.tagName + '\\b', 'i').test(tagNames) ) }, /** * 给定一个节点数组,在通过指定的过滤器过滤后, 获取其中满足过滤条件的第一个节点 * @method filterNodeList * @param { Array } nodeList 需要过滤的节点数组 * @param { Function } fn 过滤器, 对符合条件的节点, 执行结果返回true, 反之则返回false * @return { Node | NULL } 如果找到符合过滤条件的节点, 则返回该节点, 否则返回NULL * @example * ```javascript * var divNodes = document.getElementsByTagName("div"); * divNodes = [].slice.call( divNodes, 0 ); * * //output: null * console.log( UE.dom.domUtils.filterNodeList( divNodes, function ( node ) { * return node.tagName.toLowerCase() !== 'div'; * } ) ); * ``` */ /** * 给定一个节点数组nodeList和一组标签名tagNames, 获取其中能够匹配标签名的节点集合中的第一个节点 * @method filterNodeList * @param { Array } nodeList 需要过滤的节点数组 * @param { String } tagNames 需要匹配的标签名, 多个标签名之间用空格分割 * @return { Node | NULL } 如果找到标签名匹配的节点, 则返回该节点, 否则返回NULL * @example * ```javascript * var divNodes = document.getElementsByTagName("div"); * divNodes = [].slice.call( divNodes, 0 ); * * //output: null * console.log( UE.dom.domUtils.filterNodeList( divNodes, 'a span' ) ); * ``` */ /** * 给定一个节点数组,在通过指定的过滤器过滤后, 如果参数forAll为true, 则会返回所有满足过滤 * 条件的节点集合, 否则, 返回满足条件的节点集合中的第一个节点 * @method filterNodeList * @param { Array } nodeList 需要过滤的节点数组 * @param { Function } fn 过滤器, 对符合条件的节点, 执行结果返回true, 反之则返回false * @param { Boolean } forAll 是否返回整个节点数组, 如果该参数为false, 则返回节点集合中的第一个节点 * @return { Array | Node | NULL } 如果找到符合过滤条件的节点, 则根据参数forAll的值决定返回满足 * 过滤条件的节点数组或第一个节点, 否则返回NULL * @example * ```javascript * var divNodes = document.getElementsByTagName("div"); * divNodes = [].slice.call( divNodes, 0 ); * * //output: 3(假定有3个div) * console.log( divNodes.length ); * * var nodes = UE.dom.domUtils.filterNodeList( divNodes, function ( node ) { * return node.tagName.toLowerCase() === 'div'; * }, true ); * * //output: 3 * console.log( nodes.length ); * * var node = UE.dom.domUtils.filterNodeList( divNodes, function ( node ) { * return node.tagName.toLowerCase() === 'div'; * }, false ); * * //output: div * console.log( node.nodeName ); * ``` */ filterNodeList: function (nodelist, filter, forAll) { var results = [] if (!utils.isFunction(filter)) { var str = filter filter = function (n) { return ( utils.indexOf( utils.isArray(str) ? str : str.split(' '), n.tagName.toLowerCase() ) != -1 ) } } utils.each(nodelist, function (n) { filter(n) && results.push(n) }) return results.length == 0 ? null : results.length == 1 || !forAll ? results[0] : results }, /** * 查询给定的range选区是否在给定的node节点内,且在该节点的最末尾 * @method isInNodeEndBoundary * @param { UE.dom.Range } rng 需要判断的range对象, 该对象的startContainer不能为NULL * @param node 需要检测的节点对象 * @return { Number } 如果给定的选取range对象是在node内部的最末端, 则返回1, 否则返回0 */ isInNodeEndBoundary: function (rng, node) { var start = rng.startContainer if (start.nodeType == 3 && rng.startOffset != start.nodeValue.length) { return 0 } if (start.nodeType == 1 && rng.startOffset != start.childNodes.length) { return 0 } while (start !== node) { if (start.nextSibling) { return 0 } start = start.parentNode } return 1 }, isBoundaryNode: function (node, dir) { var tmp while (!domUtils.isBody(node)) { tmp = node node = node.parentNode if (tmp !== node[dir]) { return false } } return true }, fillHtml: browser.ie11below ? ' ' : '
' }) var fillCharReg = new RegExp(domUtils.fillChar, 'g') // core/Range.js /** * Range封装 * @file * @module UE.dom * @class Range * @since 1.2.6.1 */ /** * dom操作封装 * @unfile * @module UE.dom */ /** * Range实现类,本类是UEditor底层核心类,封装不同浏览器之间的Range操作。 * @unfile * @module UE.dom * @class Range */ ;(function () { var guid = 0, fillChar = domUtils.fillChar, fillData /** * 更新range的collapse状态 * @param {Range} range range对象 */ function updateCollapse(range) { range.collapsed = range.startContainer && range.endContainer && range.startContainer === range.endContainer && range.startOffset == range.endOffset } function selectOneNode(rng) { return ( !rng.collapsed && rng.startContainer.nodeType == 1 && rng.startContainer === rng.endContainer && rng.endOffset - rng.startOffset == 1 ) } function setEndPoint(toStart, node, offset, range) { //如果node是自闭合标签要处理 if ( node.nodeType == 1 && (dtd.$empty[node.tagName] || dtd.$nonChild[node.tagName]) ) { offset = domUtils.getNodeIndex(node) + (toStart ? 0 : 1) node = node.parentNode } if (toStart) { range.startContainer = node range.startOffset = offset if (!range.endContainer) { range.collapse(true) } } else { range.endContainer = node range.endOffset = offset if (!range.startContainer) { range.collapse(false) } } updateCollapse(range) return range } function execContentsAction(range, action) { //调整边界 //range.includeBookmark(); var start = range.startContainer, end = range.endContainer, startOffset = range.startOffset, endOffset = range.endOffset, doc = range.document, frag = doc.createDocumentFragment(), tmpStart, tmpEnd if (start.nodeType == 1) { start = start.childNodes[startOffset] || (tmpStart = start.appendChild(doc.createTextNode(''))) } if (end.nodeType == 1) { end = end.childNodes[endOffset] || (tmpEnd = end.appendChild(doc.createTextNode(''))) } if (start === end && start.nodeType == 3) { frag.appendChild( doc.createTextNode( start.substringData(startOffset, endOffset - startOffset) ) ) //is not clone if (action) { start.deleteData(startOffset, endOffset - startOffset) range.collapse(true) } return frag } var current, currentLevel, clone = frag, startParents = domUtils.findParents(start, true), endParents = domUtils.findParents(end, true) for (var i = 0; startParents[i] == endParents[i]; ) { i++ } for (var j = i, si; (si = startParents[j]); j++) { current = si.nextSibling if (si == start) { if (!tmpStart) { if (range.startContainer.nodeType == 3) { clone.appendChild( doc.createTextNode(start.nodeValue.slice(startOffset)) ) //is not clone if (action) { start.deleteData( startOffset, start.nodeValue.length - startOffset ) } } else { clone.appendChild(!action ? start.cloneNode(true) : start) } } } else { currentLevel = si.cloneNode(false) clone.appendChild(currentLevel) } while (current) { if (current === end || current === endParents[j]) { break } si = current.nextSibling clone.appendChild(!action ? current.cloneNode(true) : current) current = si } clone = currentLevel } clone = frag if (!startParents[i]) { clone.appendChild(startParents[i - 1].cloneNode(false)) clone = clone.firstChild } for (var j = i, ei; (ei = endParents[j]); j++) { current = ei.previousSibling if (ei == end) { if (!tmpEnd && range.endContainer.nodeType == 3) { clone.appendChild( doc.createTextNode(end.substringData(0, endOffset)) ) //is not clone if (action) { end.deleteData(0, endOffset) } } } else { currentLevel = ei.cloneNode(false) clone.appendChild(currentLevel) } //如果两端同级,右边第一次已经被开始做了 if (j != i || !startParents[i]) { while (current) { if (current === start) { break } ei = current.previousSibling clone.insertBefore( !action ? current.cloneNode(true) : current, clone.firstChild ) current = ei } } clone = currentLevel } if (action) { range .setStartBefore( !endParents[i] ? endParents[i - 1] : !startParents[i] ? startParents[i - 1] : endParents[i] ) .collapse(true) } tmpStart && domUtils.remove(tmpStart) tmpEnd && domUtils.remove(tmpEnd) return frag } /** * 创建一个跟document绑定的空的Range实例 * @constructor * @param { Document } document 新建的选区所属的文档对象 */ /** * @property { Node } startContainer 当前Range的开始边界的容器节点, 可以是一个元素节点或者是文本节点 */ /** * @property { Node } startOffset 当前Range的开始边界容器节点的偏移量, 如果是元素节点, * 该值就是childNodes中的第几个节点, 如果是文本节点就是文本内容的第几个字符 */ /** * @property { Node } endContainer 当前Range的结束边界的容器节点, 可以是一个元素节点或者是文本节点 */ /** * @property { Node } endOffset 当前Range的结束边界容器节点的偏移量, 如果是元素节点, * 该值就是childNodes中的第几个节点, 如果是文本节点就是文本内容的第几个字符 */ /** * @property { Boolean } collapsed 当前Range是否闭合 * @default true * @remind Range是闭合的时候, startContainer === endContainer && startOffset === endOffset */ /** * @property { Document } document 当前Range所属的Document对象 * @remind 不同range的的document属性可以是不同的 */ var Range = (dom.Range = function (document) { var me = this me.startContainer = me.startOffset = me.endContainer = me.endOffset = null me.document = document me.collapsed = true }) /** * 删除fillData * @param doc * @param excludeNode */ function removeFillData(doc, excludeNode) { try { if (fillData && domUtils.inDoc(fillData, doc)) { if (!fillData.nodeValue.replace(fillCharReg, '').length) { var tmpNode = fillData.parentNode domUtils.remove(fillData) while ( tmpNode && domUtils.isEmptyInlineElement(tmpNode) && //safari的contains有bug (browser.safari ? !( domUtils.getPosition(tmpNode, excludeNode) & domUtils.POSITION_CONTAINS ) : !tmpNode.contains(excludeNode)) ) { fillData = tmpNode.parentNode domUtils.remove(tmpNode) tmpNode = fillData } } else { fillData.nodeValue = fillData.nodeValue.replace(fillCharReg, '') } } } catch (e) {} } /** * @param node * @param dir */ function mergeSibling(node, dir) { var tmpNode node = node[dir] while (node && domUtils.isFillChar(node)) { tmpNode = node[dir] domUtils.remove(node) node = tmpNode } } Range.prototype = { /** * 克隆选区的内容到一个DocumentFragment里 * @method cloneContents * @return { DocumentFragment | NULL } 如果选区是闭合的将返回null, 否则, 返回包含所clone内容的DocumentFragment元素 * @example * ```html * * * xx[xxx]x * * * * ``` */ cloneContents: function () { return this.collapsed ? null : execContentsAction(this, 0) }, /** * 删除当前选区范围中的所有内容 * @method deleteContents * @remind 执行完该操作后, 当前Range对象变成了闭合状态 * @return { UE.dom.Range } 当前操作的Range对象 * @example * ```html * * * xx[xxx]x * * * * ``` */ deleteContents: function () { var txt if (!this.collapsed) { execContentsAction(this, 1) } if (browser.webkit) { txt = this.startContainer if (txt.nodeType == 3 && !txt.nodeValue.length) { this.setStartBefore(txt).collapse(true) domUtils.remove(txt) } } return this }, /** * 将当前选区的内容提取到一个DocumentFragment里 * @method extractContents * @remind 执行该操作后, 选区将变成闭合状态 * @warning 执行该操作后, 原来选区所选中的内容将从dom树上剥离出来 * @return { DocumentFragment } 返回包含所提取内容的DocumentFragment对象 * @example * ```html * * * xx[xxx]x * * * */ extractContents: function () { return this.collapsed ? null : execContentsAction(this, 2) }, /** * 设置Range的开始容器节点和偏移量 * @method setStart * @remind 如果给定的节点是元素节点,那么offset指的是其子元素中索引为offset的元素, * 如果是文本节点,那么offset指的是其文本内容的第offset个字符 * @remind 如果提供的容器节点是一个不能包含子元素的节点, 则该选区的开始容器将被设置 * 为该节点的父节点, 此时, 其距离开始容器的偏移量也变成了该节点在其父节点 * 中的索引 * @param { Node } node 将被设为当前选区开始边界容器的节点对象 * @param { int } offset 选区的开始位置偏移量 * @return { UE.dom.Range } 当前range对象 * @example * ```html * * xxxxxxxxxxxxx[xxx] * * * ``` * @example * ```html * * xxx[xx]x * * * ``` */ setStart: function (node, offset) { return setEndPoint(true, node, offset, this) }, /** * 设置Range的结束容器和偏移量 * @method setEnd * @param { Node } node 作为当前选区结束边界容器的节点对象 * @param { int } offset 结束边界的偏移量 * @see UE.dom.Range:setStart(Node,int) * @return { UE.dom.Range } 当前range对象 */ setEnd: function (node, offset) { return setEndPoint(false, node, offset, this) }, /** * 将Range开始位置设置到node节点之后 * @method setStartAfter * @remind 该操作将会把给定节点的父节点作为range的开始容器, 且偏移量是该节点在其父节点中的位置索引+1 * @param { Node } node 选区的开始边界将紧接着该节点之后 * @return { UE.dom.Range } 当前range对象 * @example * ```html * * xxxxxxx[xxxx] * * * ``` */ setStartAfter: function (node) { return this.setStart(node.parentNode, domUtils.getNodeIndex(node) + 1) }, /** * 将Range开始位置设置到node节点之前 * @method setStartBefore * @remind 该操作将会把给定节点的父节点作为range的开始容器, 且偏移量是该节点在其父节点中的位置索引 * @param { Node } node 新的选区开始位置在该节点之前 * @see UE.dom.Range:setStartAfter(Node) * @return { UE.dom.Range } 当前range对象 */ setStartBefore: function (node) { return this.setStart(node.parentNode, domUtils.getNodeIndex(node)) }, /** * 将Range结束位置设置到node节点之后 * @method setEndAfter * @remind 该操作将会把给定节点的父节点作为range的结束容器, 且偏移量是该节点在其父节点中的位置索引+1 * @param { Node } node 目标节点 * @see UE.dom.Range:setStartAfter(Node) * @return { UE.dom.Range } 当前range对象 * @example * ```html * * [xxxxxxx]xxxx * * * ``` */ setEndAfter: function (node) { return this.setEnd(node.parentNode, domUtils.getNodeIndex(node) + 1) }, /** * 将Range结束位置设置到node节点之前 * @method setEndBefore * @remind 该操作将会把给定节点的父节点作为range的结束容器, 且偏移量是该节点在其父节点中的位置索引 * @param { Node } node 目标节点 * @see UE.dom.Range:setEndAfter(Node) * @return { UE.dom.Range } 当前range对象 */ setEndBefore: function (node) { return this.setEnd(node.parentNode, domUtils.getNodeIndex(node)) }, /** * 设置Range的开始位置到node节点内的第一个子节点之前 * @method setStartAtFirst * @remind 选区的开始容器将变成给定的节点, 且偏移量为0 * @remind 如果给定的节点是元素节点, 则该节点必须是允许包含子节点的元素。 * @param { Node } node 目标节点 * @see UE.dom.Range:setStartBefore(Node) * @return { UE.dom.Range } 当前range对象 * @example * ```html * * xxxxx[xx]xxxx * * * ``` */ setStartAtFirst: function (node) { return this.setStart(node, 0) }, /** * 设置Range的开始位置到node节点内的最后一个节点之后 * @method setStartAtLast * @remind 选区的开始容器将变成给定的节点, 且偏移量为该节点的子节点数 * @remind 如果给定的节点是元素节点, 则该节点必须是允许包含子节点的元素。 * @param { Node } node 目标节点 * @see UE.dom.Range:setStartAtFirst(Node) * @return { UE.dom.Range } 当前range对象 */ setStartAtLast: function (node) { return this.setStart( node, node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length ) }, /** * 设置Range的结束位置到node节点内的第一个节点之前 * @method setEndAtFirst * @param { Node } node 目标节点 * @remind 选区的结束容器将变成给定的节点, 且偏移量为0 * @remind node必须是一个元素节点, 且必须是允许包含子节点的元素。 * @see UE.dom.Range:setStartAtFirst(Node) * @return { UE.dom.Range } 当前range对象 */ setEndAtFirst: function (node) { return this.setEnd(node, 0) }, /** * 设置Range的结束位置到node节点内的最后一个节点之后 * @method setEndAtLast * @param { Node } node 目标节点 * @remind 选区的结束容器将变成给定的节点, 且偏移量为该节点的子节点数量 * @remind node必须是一个元素节点, 且必须是允许包含子节点的元素。 * @see UE.dom.Range:setStartAtFirst(Node) * @return { UE.dom.Range } 当前range对象 */ setEndAtLast: function (node) { return this.setEnd( node, node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length ) }, /** * 选中给定节点 * @method selectNode * @remind 此时, 选区的开始容器和结束容器都是该节点的父节点, 其startOffset是该节点在父节点中的位置索引, * 而endOffset为startOffset+1 * @param { Node } node 需要选中的节点 * @return { UE.dom.Range } 当前range对象,此时的range仅包含当前给定的节点对象 * @example * ```html * * xxxxx[xx]xxxx * * * ``` */ selectNode: function (node) { return this.setStartBefore(node).setEndAfter(node) }, /** * 选中给定节点内部的所有节点 * @method selectNodeContents * @remind 此时, 选区的开始容器和结束容器都是该节点, 其startOffset为0, * 而endOffset是该节点的子节点数。 * @param { Node } node 目标节点, 当前range将包含该节点内的所有节点 * @return { UE.dom.Range } 当前range对象, 此时range仅包含给定节点的所有子节点 * @example * ```html * * xxxxx[xx]xxxx * * * ``` */ selectNodeContents: function (node) { return this.setStart(node, 0).setEndAtLast(node) }, /** * clone当前Range对象 * @method cloneRange * @remind 返回的range是一个全新的range对象, 其内部所有属性与当前被clone的range相同。 * @return { UE.dom.Range } 当前range对象的一个副本 */ cloneRange: function () { var me = this return new Range(me.document) .setStart(me.startContainer, me.startOffset) .setEnd(me.endContainer, me.endOffset) }, /** * 向当前选区的结束处闭合选区 * @method collapse * @return { UE.dom.Range } 当前range对象 * @example * ```html * * xxxxx[xx]xxxx * * * ``` */ /** * 闭合当前选区,根据给定的toStart参数项决定是向当前选区开始处闭合还是向结束处闭合, * 如果toStart的值为true,则向开始位置闭合, 反之,向结束位置闭合。 * @method collapse * @param { Boolean } toStart 是否向选区开始处闭合 * @return { UE.dom.Range } 当前range对象,此时range对象处于闭合状态 * @see UE.dom.Range:collapse() * @example * ```html * * xxxxx[xx]xxxx * * * ``` */ collapse: function (toStart) { var me = this if (toStart) { me.endContainer = me.startContainer me.endOffset = me.startOffset } else { me.startContainer = me.endContainer me.startOffset = me.endOffset } me.collapsed = true return me }, /** * 调整range的开始位置和结束位置,使其"收缩"到最小的位置 * @method shrinkBoundary * @return { UE.dom.Range } 当前range对象 * @example * ```html * xxxx[xxxxx] => xxxx[xxxxx] * ``` * * @example * ```html * * x[xx]xxx * * * ``` * * @example * ```html * [xxxxxxxxxxx] => [xxxxxxxxxxx] * ``` */ /** * 调整range的开始位置和结束位置,使其"收缩"到最小的位置, * 如果ignoreEnd的值为true,则忽略对结束位置的调整 * @method shrinkBoundary * @param { Boolean } ignoreEnd 是否忽略对结束位置的调整 * @return { UE.dom.Range } 当前range对象 * @see UE.dom.domUtils.Range:shrinkBoundary() */ shrinkBoundary: function (ignoreEnd) { var me = this, child, collapsed = me.collapsed function check(node) { return ( node.nodeType == 1 && !domUtils.isBookmarkNode(node) && !dtd.$empty[node.tagName] && !dtd.$nonChild[node.tagName] ) } while ( me.startContainer.nodeType == 1 && //是element (child = me.startContainer.childNodes[me.startOffset]) && //子节点也是element check(child) ) { me.setStart(child, 0) } if (collapsed) { return me.collapse(true) } if (!ignoreEnd) { while ( me.endContainer.nodeType == 1 && //是element me.endOffset > 0 && //如果是空元素就退出 endOffset=0那么endOffst-1为负值,childNodes[endOffset]报错 (child = me.endContainer.childNodes[me.endOffset - 1]) && //子节点也是element check(child) ) { me.setEnd(child, child.childNodes.length) } } return me }, /** * 获取离当前选区内包含的所有节点最近的公共祖先节点, * @method getCommonAncestor * @remind 返回的公共祖先节点一定不是range自身的容器节点, 但有可能是一个文本节点 * @return { Node } 当前range对象内所有节点的公共祖先节点 * @example * ```html * //选区示例 * xxxx[xxx]xxxxxx * * ``` */ /** * 获取当前选区所包含的所有节点的公共祖先节点, 可以根据给定的参数 includeSelf 决定获取到 * 的公共祖先节点是否可以是当前选区的startContainer或endContainer节点, 如果 includeSelf * 的取值为true, 则返回的节点可以是自身的容器节点, 否则, 则不能是容器节点 * @method getCommonAncestor * @param { Boolean } includeSelf 是否允许获取到的公共祖先节点是当前range对象的容器节点 * @return { Node } 当前range对象内所有节点的公共祖先节点 * @see UE.dom.Range:getCommonAncestor() * @example * ```html * * * * xxxxxxxxx[xxx]xxxxxxxx * * * * * ``` */ /** * 获取当前选区所包含的所有节点的公共祖先节点, 可以根据给定的参数 includeSelf 决定获取到 * 的公共祖先节点是否可以是当前选区的startContainer或endContainer节点, 如果 includeSelf * 的取值为true, 则返回的节点可以是自身的容器节点, 否则, 则不能是容器节点; 同时可以根据 * ignoreTextNode 参数的取值决定是否忽略类型为文本节点的祖先节点。 * @method getCommonAncestor * @param { Boolean } includeSelf 是否允许获取到的公共祖先节点是当前range对象的容器节点 * @param { Boolean } ignoreTextNode 获取祖先节点的过程中是否忽略类型为文本节点的祖先节点 * @return { Node } 当前range对象内所有节点的公共祖先节点 * @see UE.dom.Range:getCommonAncestor() * @see UE.dom.Range:getCommonAncestor(Boolean) * @example * ```html * * * * xxxxxxxx[x]xxxxxxxxxxx * * * * * ``` */ getCommonAncestor: function (includeSelf, ignoreTextNode) { var me = this, start = me.startContainer, end = me.endContainer if (start === end) { if (includeSelf && selectOneNode(this)) { start = start.childNodes[me.startOffset] if (start.nodeType == 1) return start } //只有在上来就相等的情况下才会出现是文本的情况 return ignoreTextNode && start.nodeType == 3 ? start.parentNode : start } return domUtils.getCommonAncestor(start, end) }, /** * 调整当前Range的开始和结束边界容器,如果是容器节点是文本节点,就调整到包含该文本节点的父节点上 * @method trimBoundary * @remind 该操作有可能会引起文本节点被切开 * @return { UE.dom.Range } 当前range对象 * @example * ```html * * //选区示例 * xxx[xxxxx]xxx * * * ``` */ /** * 调整当前Range的开始和结束边界容器,如果是容器节点是文本节点,就调整到包含该文本节点的父节点上, * 可以根据 ignoreEnd 参数的值决定是否调整对结束边界的调整 * @method trimBoundary * @param { Boolean } ignoreEnd 是否忽略对结束边界的调整 * @return { UE.dom.Range } 当前range对象 * @example * ```html * * //选区示例 * xxx[xxxxx]xxx * * * ``` */ trimBoundary: function (ignoreEnd) { this.txtToElmBoundary() var start = this.startContainer, offset = this.startOffset, collapsed = this.collapsed, end = this.endContainer if (start.nodeType == 3) { if (offset == 0) { this.setStartBefore(start) } else { if (offset >= start.nodeValue.length) { this.setStartAfter(start) } else { var textNode = domUtils.split(start, offset) //跟新结束边界 if (start === end) { this.setEnd(textNode, this.endOffset - offset) } else if (start.parentNode === end) { this.endOffset += 1 } this.setStartBefore(textNode) } } if (collapsed) { return this.collapse(true) } } if (!ignoreEnd) { offset = this.endOffset end = this.endContainer if (end.nodeType == 3) { if (offset == 0) { this.setEndBefore(end) } else { offset < end.nodeValue.length && domUtils.split(end, offset) this.setEndAfter(end) } } } return this }, /** * 如果选区在文本的边界上,就扩展选区到文本的父节点上, 如果当前选区是闭合的, 则什么也不做 * @method txtToElmBoundary * @remind 该操作不会修改dom节点 * @return { UE.dom.Range } 当前range对象 */ /** * 如果选区在文本的边界上,就扩展选区到文本的父节点上, 如果当前选区是闭合的, 则根据参数项 * ignoreCollapsed 的值决定是否执行该调整 * @method txtToElmBoundary * @param { Boolean } ignoreCollapsed 是否忽略选区的闭合状态, 如果该参数取值为true, 则 * 不论选区是否闭合, 都会执行该操作, 反之, 则不会对闭合的选区执行该操作 * @return { UE.dom.Range } 当前range对象 */ txtToElmBoundary: function (ignoreCollapsed) { function adjust(r, c) { var container = r[c + 'Container'], offset = r[c + 'Offset'] if (container.nodeType == 3) { if (!offset) { r[ 'set' + c.replace(/(\w)/, function (a) { return a.toUpperCase() }) + 'Before' ](container) } else if (offset >= container.nodeValue.length) { r[ 'set' + c.replace(/(\w)/, function (a) { return a.toUpperCase() }) + 'After' ](container) } } } if (ignoreCollapsed || !this.collapsed) { adjust(this, 'start') adjust(this, 'end') } return this }, /** * 在当前选区的开始位置前插入节点,新插入的节点会被该range包含 * @method insertNode * @param { Node } node 需要插入的节点 * @remind 插入的节点可以是一个DocumentFragment依次插入多个节点 * @return { UE.dom.Range } 当前range对象 */ insertNode: function (node) { var first = node, length = 1 if (node.nodeType == 11) { first = node.firstChild length = node.childNodes.length } this.trimBoundary(true) var start = this.startContainer, offset = this.startOffset var nextNode = start.childNodes[offset] if (nextNode) { start.insertBefore(node, nextNode) } else { start.appendChild(node) } if (first.parentNode === this.endContainer) { this.endOffset = this.endOffset + length } return this.setStartBefore(first) }, /** * 闭合选区到当前选区的开始位置, 并且定位光标到闭合后的位置 * @method setCursor * @return { UE.dom.Range } 当前range对象 * @see UE.dom.Range:collapse() */ /** * 闭合选区,可以根据参数toEnd的值控制选区是向前闭合还是向后闭合, 并且定位光标到闭合后的位置。 * @method setCursor * @param { Boolean } toEnd 是否向后闭合, 如果为true, 则闭合选区时, 将向结束容器方向闭合, * 反之,则向开始容器方向闭合 * @return { UE.dom.Range } 当前range对象 * @see UE.dom.Range:collapse(Boolean) */ setCursor: function (toEnd, noFillData) { return this.collapse(!toEnd).select(noFillData) }, /** * 创建当前range的一个书签,记录下当前range的位置,方便当dom树改变时,还能找回原来的选区位置 * @method createBookmark * @param { Boolean } serialize 控制返回的标记位置是对当前位置的引用还是ID,如果该值为true,则 * 返回标记位置的ID, 反之则返回标记位置节点的引用 * @return { Object } 返回一个书签记录键值对, 其包含的key有: start => 开始标记的ID或者引用, * end => 结束标记的ID或引用, id => 当前标记的类型, 如果为true,则表示 * 返回的记录的类型为ID, 反之则为引用 */ createBookmark: function (serialize, same) { var endNode, startNode = this.document.createElement('span') startNode.style.cssText = 'display:none;line-height:0px;' startNode.appendChild(this.document.createTextNode('\u200D')) startNode.id = '_baidu_bookmark_start_' + (same ? '' : guid++) if (!this.collapsed) { endNode = startNode.cloneNode(true) endNode.id = '_baidu_bookmark_end_' + (same ? '' : guid++) } this.insertNode(startNode) if (endNode) { this.collapse().insertNode(endNode).setEndBefore(endNode) } this.setStartAfter(startNode) return { start: serialize ? startNode.id : startNode, end: endNode ? (serialize ? endNode.id : endNode) : null, id: serialize } }, /** * 调整当前range的边界到书签位置,并删除该书签对象所标记的位置内的节点 * @method moveToBookmark * @param { BookMark } bookmark createBookmark所创建的标签对象 * @return { UE.dom.Range } 当前range对象 * @see UE.dom.Range:createBookmark(Boolean) */ moveToBookmark: function (bookmark) { var start = bookmark.id ? this.document.getElementById(bookmark.start) : bookmark.start, end = bookmark.end && bookmark.id ? this.document.getElementById(bookmark.end) : bookmark.end this.setStartBefore(start) domUtils.remove(start) if (end) { this.setEndBefore(end) domUtils.remove(end) } else { this.collapse(true) } return this }, /** * 调整range的边界,使其"放大"到最近的父节点 * @method enlarge * @remind 会引起选区的变化 * @return { UE.dom.Range } 当前range对象 */ /** * 调整range的边界,使其"放大"到最近的父节点,根据参数 toBlock 的取值, 可以 * 要求扩大之后的父节点是block节点 * @method enlarge * @param { Boolean } toBlock 是否要求扩大之后的父节点必须是block节点 * @return { UE.dom.Range } 当前range对象 */ enlarge: function (toBlock, stopFn) { var isBody = domUtils.isBody, pre, node, tmp = this.document.createTextNode('') if (toBlock) { node = this.startContainer if (node.nodeType == 1) { if (node.childNodes[this.startOffset]) { pre = node = node.childNodes[this.startOffset] } else { node.appendChild(tmp) pre = node = tmp } } else { pre = node } while (1) { if (domUtils.isBlockElm(node)) { node = pre while ( (pre = node.previousSibling) && !domUtils.isBlockElm(pre) ) { node = pre } this.setStartBefore(node) break } pre = node node = node.parentNode } node = this.endContainer if (node.nodeType == 1) { if ((pre = node.childNodes[this.endOffset])) { node.insertBefore(tmp, pre) } else { node.appendChild(tmp) } pre = node = tmp } else { pre = node } while (1) { if (domUtils.isBlockElm(node)) { node = pre while ((pre = node.nextSibling) && !domUtils.isBlockElm(pre)) { node = pre } this.setEndAfter(node) break } pre = node node = node.parentNode } if (tmp.parentNode === this.endContainer) { this.endOffset-- } domUtils.remove(tmp) } // 扩展边界到最大 if (!this.collapsed) { while (this.startOffset == 0) { if (stopFn && stopFn(this.startContainer)) { break } if (isBody(this.startContainer)) { break } this.setStartBefore(this.startContainer) } while ( this.endOffset == (this.endContainer.nodeType == 1 ? this.endContainer.childNodes.length : this.endContainer.nodeValue.length) ) { if (stopFn && stopFn(this.endContainer)) { break } if (isBody(this.endContainer)) { break } this.setEndAfter(this.endContainer) } } return this }, enlargeToBlockElm: function (ignoreEnd) { while (!domUtils.isBlockElm(this.startContainer)) { this.setStartBefore(this.startContainer) } if (!ignoreEnd) { while (!domUtils.isBlockElm(this.endContainer)) { this.setEndAfter(this.endContainer) } } return this }, /** * 调整Range的边界,使其"缩小"到最合适的位置 * @method adjustmentBoundary * @return { UE.dom.Range } 当前range对象 * @see UE.dom.Range:shrinkBoundary() */ adjustmentBoundary: function () { if (!this.collapsed) { while ( !domUtils.isBody(this.startContainer) && this.startOffset == this.startContainer[ this.startContainer.nodeType == 3 ? 'nodeValue' : 'childNodes' ].length && this.startContainer[ this.startContainer.nodeType == 3 ? 'nodeValue' : 'childNodes' ].length ) { this.setStartAfter(this.startContainer) } while ( !domUtils.isBody(this.endContainer) && !this.endOffset && this.endContainer[ this.endContainer.nodeType == 3 ? 'nodeValue' : 'childNodes' ].length ) { this.setEndBefore(this.endContainer) } } return this }, /** * 给range选区中的内容添加给定的inline标签 * @method applyInlineStyle * @param { String } tagName 需要添加的标签名 * @example * ```html *

xxxx[xxxx]x

==> range.applyInlineStyle("strong") ==>

xxxx[xxxx]x

* ``` */ /** * 给range选区中的内容添加给定的inline标签, 并且为标签附加上一些初始化属性。 * @method applyInlineStyle * @param { String } tagName 需要添加的标签名 * @param { Object } attrs 跟随新添加的标签的属性 * @return { UE.dom.Range } 当前选区 * @example * ```html *

xxxx[xxxx]x

* * ==> * * * range.applyInlineStyle("strong",{"style":"font-size:12px"}) * * ==> * *

xxxx[xxxx]x

* ``` */ applyInlineStyle: function (tagName, attrs, list) { if (this.collapsed) return this this.trimBoundary() .enlarge(false, function (node) { return node.nodeType == 1 && domUtils.isBlockElm(node) }) .adjustmentBoundary() var bookmark = this.createBookmark(), end = bookmark.end, filterFn = function (node) { return node.nodeType == 1 ? node.tagName.toLowerCase() != 'br' : !domUtils.isWhitespace(node) }, current = domUtils.getNextDomNode(bookmark.start, false, filterFn), node, pre, range = this.cloneRange() while ( current && domUtils.getPosition(current, end) & domUtils.POSITION_PRECEDING ) { if (current.nodeType == 3 || dtd[tagName][current.tagName]) { range.setStartBefore(current) node = current while ( node && (node.nodeType == 3 || dtd[tagName][node.tagName]) && node !== end ) { pre = node node = domUtils.getNextDomNode( node, node.nodeType == 1, null, function (parent) { return dtd[tagName][parent.tagName] } ) } var frag = range.setEndAfter(pre).extractContents(), elm if (list && list.length > 0) { var level, top top = level = list[0].cloneNode(false) for (var i = 1, ci; (ci = list[i++]); ) { level.appendChild(ci.cloneNode(false)) level = level.firstChild } elm = level } else { elm = range.document.createElement(tagName) } if (attrs) { domUtils.setAttributes(elm, attrs) } elm.appendChild(frag) range.insertNode(list ? top : elm) //处理下滑线在a上的情况 var aNode if ( tagName == 'span' && attrs.style && /text\-decoration/.test(attrs.style) && (aNode = domUtils.findParentByTagName(elm, 'a', true)) ) { domUtils.setAttributes(aNode, attrs) domUtils.remove(elm, true) elm = aNode } else { domUtils.mergeSibling(elm) domUtils.clearEmptySibling(elm) } //去除子节点相同的 domUtils.mergeChild(elm, attrs) current = domUtils.getNextDomNode(elm, false, filterFn) domUtils.mergeToParent(elm) if (node === end) { break } } else { current = domUtils.getNextDomNode(current, true, filterFn) } } return this.moveToBookmark(bookmark) }, /** * 移除当前选区内指定的inline标签,但保留其中的内容 * @method removeInlineStyle * @param { String } tagName 需要移除的标签名 * @return { UE.dom.Range } 当前的range对象 * @example * ```html * xx[xxxxyyyzz]z => range.removeInlineStyle(["em"]) => xx[xxxxyyyzz]z * ``` */ /** * 移除当前选区内指定的一组inline标签,但保留其中的内容 * @method removeInlineStyle * @param { Array } tagNameArr 需要移除的标签名的数组 * @return { UE.dom.Range } 当前的range对象 * @see UE.dom.Range:removeInlineStyle(String) */ removeInlineStyle: function (tagNames) { if (this.collapsed) return this tagNames = utils.isArray(tagNames) ? tagNames : [tagNames] this.shrinkBoundary().adjustmentBoundary() var start = this.startContainer, end = this.endContainer while (1) { if (start.nodeType == 1) { if (utils.indexOf(tagNames, start.tagName.toLowerCase()) > -1) { break } if (start.tagName.toLowerCase() == 'body') { start = null break } } start = start.parentNode } while (1) { if (end.nodeType == 1) { if (utils.indexOf(tagNames, end.tagName.toLowerCase()) > -1) { break } if (end.tagName.toLowerCase() == 'body') { end = null break } } end = end.parentNode } var bookmark = this.createBookmark(), frag, tmpRange if (start) { tmpRange = this.cloneRange() .setEndBefore(bookmark.start) .setStartBefore(start) frag = tmpRange.extractContents() tmpRange.insertNode(frag) domUtils.clearEmptySibling(start, true) start.parentNode.insertBefore(bookmark.start, start) } if (end) { tmpRange = this.cloneRange() .setStartAfter(bookmark.end) .setEndAfter(end) frag = tmpRange.extractContents() tmpRange.insertNode(frag) domUtils.clearEmptySibling(end, false, true) end.parentNode.insertBefore(bookmark.end, end.nextSibling) } var current = domUtils.getNextDomNode( bookmark.start, false, function (node) { return node.nodeType == 1 } ), next while (current && current !== bookmark.end) { next = domUtils.getNextDomNode(current, true, function (node) { return node.nodeType == 1 }) if (utils.indexOf(tagNames, current.tagName.toLowerCase()) > -1) { domUtils.remove(current, true) } current = next } return this.moveToBookmark(bookmark) }, /** * 获取当前选中的自闭合的节点 * @method getClosedNode * @return { Node | NULL } 如果当前选中的是自闭合节点, 则返回该节点, 否则返回NULL */ getClosedNode: function () { var node if (!this.collapsed) { var range = this.cloneRange().adjustmentBoundary().shrinkBoundary() if (selectOneNode(range)) { var child = range.startContainer.childNodes[range.startOffset] if ( child && child.nodeType == 1 && (dtd.$empty[child.tagName] || dtd.$nonChild[child.tagName]) ) { node = child } } } return node }, /** * 在页面上高亮range所表示的选区 * @method select * @return { UE.dom.Range } 返回当前Range对象 */ //这里不区分ie9以上,trace:3824 select: browser.ie ? function (noFillData, textRange) { var nativeRange if (!this.collapsed) this.shrinkBoundary() var node = this.getClosedNode() if (node && !textRange) { try { nativeRange = this.document.body.createControlRange() nativeRange.addElement(node) nativeRange.select() } catch (e) {} return this } var bookmark = this.createBookmark(), start = bookmark.start, end nativeRange = this.document.body.createTextRange() nativeRange.moveToElementText(start) nativeRange.moveStart('character', 1) if (!this.collapsed) { var nativeRangeEnd = this.document.body.createTextRange() end = bookmark.end nativeRangeEnd.moveToElementText(end) nativeRange.setEndPoint('EndToEnd', nativeRangeEnd) } else { if (!noFillData && this.startContainer.nodeType != 3) { //使用|x固定住光标 var tmpText = this.document.createTextNode(fillChar), tmp = this.document.createElement('span') tmp.appendChild(this.document.createTextNode(fillChar)) start.parentNode.insertBefore(tmp, start) start.parentNode.insertBefore(tmpText, start) //当点b,i,u时,不能清除i上边的b removeFillData(this.document, tmpText) fillData = tmpText mergeSibling(tmp, 'previousSibling') mergeSibling(start, 'nextSibling') nativeRange.moveStart('character', -1) nativeRange.collapse(true) } } this.moveToBookmark(bookmark) tmp && domUtils.remove(tmp) //IE在隐藏状态下不支持range操作,catch一下 try { nativeRange.select() } catch (e) {} return this } : function (notInsertFillData) { function checkOffset(rng) { function check(node, offset, dir) { if (node.nodeType == 3 && node.nodeValue.length < offset) { rng[dir + 'Offset'] = node.nodeValue.length } } check(rng.startContainer, rng.startOffset, 'start') check(rng.endContainer, rng.endOffset, 'end') } var win = domUtils.getWindow(this.document), sel = win.getSelection(), txtNode //FF下关闭自动长高时滚动条在关闭dialog时会跳 //ff下如果不body.focus将不能定位闭合光标到编辑器内 browser.gecko ? this.document.body.focus() : win.focus() if (sel) { sel.removeAllRanges() // trace:870 chrome/safari后边是br对于闭合得range不能定位 所以去掉了判断 // this.startContainer.nodeType != 3 &&! ((child = this.startContainer.childNodes[this.startOffset]) && child.nodeType == 1 && child.tagName == 'BR' if (this.collapsed && !notInsertFillData) { // //opear如果没有节点接着,原生的不能够定位,不能在body的第一级插入空白节点 // if (notInsertFillData && browser.opera && !domUtils.isBody(this.startContainer) && this.startContainer.nodeType == 1) { // var tmp = this.document.createTextNode(''); // this.insertNode(tmp).setStart(tmp, 0).collapse(true); // } // //处理光标落在文本节点的情况 //处理以下的情况 //|xxxx //xxxx|xxxx //xxxx| var start = this.startContainer, child = start if (start.nodeType == 1) { child = start.childNodes[this.startOffset] } if ( !(start.nodeType == 3 && this.startOffset) && (child ? !child.previousSibling || child.previousSibling.nodeType != 3 : !start.lastChild || start.lastChild.nodeType != 3) ) { txtNode = this.document.createTextNode(fillChar) //跟着前边走 this.insertNode(txtNode) removeFillData(this.document, txtNode) mergeSibling(txtNode, 'previousSibling') mergeSibling(txtNode, 'nextSibling') fillData = txtNode this.setStart(txtNode, browser.webkit ? 1 : 0).collapse(true) } } var nativeRange = this.document.createRange() if ( this.collapsed && browser.opera && this.startContainer.nodeType == 1 ) { var child = this.startContainer.childNodes[this.startOffset] if (!child) { //往前靠拢 child = this.startContainer.lastChild if (child && domUtils.isBr(child)) { this.setStartBefore(child).collapse(true) } } else { //向后靠拢 while (child && domUtils.isBlockElm(child)) { if (child.nodeType == 1 && child.childNodes[0]) { child = child.childNodes[0] } else { break } } child && this.setStartBefore(child).collapse(true) } } //是createAddress最后一位算的不准,现在这里进行微调 checkOffset(this) nativeRange.setStart(this.startContainer, this.startOffset) nativeRange.setEnd(this.endContainer, this.endOffset) sel.addRange(nativeRange) } return this }, /** * 滚动到当前range开始的位置 * @method scrollToView * @param { Window } win 当前range对象所属的window对象 * @return { UE.dom.Range } 当前Range对象 */ /** * 滚动到距离当前range开始位置 offset 的位置处 * @method scrollToView * @param { Window } win 当前range对象所属的window对象 * @param { Number } offset 距离range开始位置处的偏移量, 如果为正数, 则向下偏移, 反之, 则向上偏移 * @return { UE.dom.Range } 当前Range对象 */ scrollToView: function (win, offset) { win = win ? window : domUtils.getWindow(this.document) var me = this, span = me.document.createElement('span') //trace:717 span.innerHTML = ' ' me.cloneRange().insertNode(span) domUtils.scrollToView(span, win, offset) domUtils.remove(span) return me }, /** * 判断当前选区内容是否占位符 * @private * @method inFillChar * @return { Boolean } 如果是占位符返回true,否则返回false */ inFillChar: function () { var start = this.startContainer if ( this.collapsed && start.nodeType == 3 && start.nodeValue.replace(new RegExp('^' + domUtils.fillChar), '') .length + 1 == start.nodeValue.length ) { return true } return false }, /** * 保存 * @method createAddress * @private * @return { Boolean } 返回开始和结束的位置 * @example * ```html * *

* aaaa * * * bbbb * * *

* * * * ``` */ createAddress: function (ignoreEnd, ignoreTxt) { var addr = {}, me = this function getAddress(isStart) { var node = isStart ? me.startContainer : me.endContainer var parents = domUtils.findParents(node, true, function (node) { return !domUtils.isBody(node) }), addrs = [] for (var i = 0, ci; (ci = parents[i++]); ) { addrs.push(domUtils.getNodeIndex(ci, ignoreTxt)) } var firstIndex = 0 if (ignoreTxt) { if (node.nodeType == 3) { var tmpNode = node.previousSibling while (tmpNode && tmpNode.nodeType == 3) { firstIndex += tmpNode.nodeValue.replace(fillCharReg, '').length tmpNode = tmpNode.previousSibling } firstIndex += isStart ? me.startOffset : me.endOffset // - (fillCharReg.test(node.nodeValue) ? 1 : 0 ) } else { node = node.childNodes[isStart ? me.startOffset : me.endOffset] if (node) { firstIndex = domUtils.getNodeIndex(node, ignoreTxt) } else { node = isStart ? me.startContainer : me.endContainer var first = node.firstChild while (first) { if (domUtils.isFillChar(first)) { first = first.nextSibling continue } firstIndex++ if (first.nodeType == 3) { while (first && first.nodeType == 3) { first = first.nextSibling } } else { first = first.nextSibling } } } } } else { firstIndex = isStart ? domUtils.isFillChar(node) ? 0 : me.startOffset : me.endOffset } if (firstIndex < 0) { firstIndex = 0 } addrs.push(firstIndex) return addrs } addr.startAddress = getAddress(true) if (!ignoreEnd) { addr.endAddress = me.collapsed ? [].concat(addr.startAddress) : getAddress() } return addr }, /** * 保存 * @method createAddress * @private * @return { Boolean } 返回开始和结束的位置 * @example * ```html * *

* aaaa * * * bbbb * * *

* * * * ``` */ moveToAddress: function (addr, ignoreEnd) { var me = this function getNode(address, isStart) { var tmpNode = me.document.body, parentNode, offset for (var i = 0, ci, l = address.length; i < l; i++) { ci = address[i] parentNode = tmpNode tmpNode = tmpNode.childNodes[ci] if (!tmpNode) { offset = ci break } } if (isStart) { if (tmpNode) { me.setStartBefore(tmpNode) } else { me.setStart(parentNode, offset) } } else { if (tmpNode) { me.setEndBefore(tmpNode) } else { me.setEnd(parentNode, offset) } } } getNode(addr.startAddress, true) !ignoreEnd && addr.endAddress && getNode(addr.endAddress) return me }, /** * 判断给定的Range对象是否和当前Range对象表示的是同一个选区 * @method equals * @param { UE.dom.Range } 需要判断的Range对象 * @return { Boolean } 如果给定的Range对象与当前Range对象表示的是同一个选区, 则返回true, 否则返回false */ equals: function (rng) { for (var p in this) { if (this.hasOwnProperty(p)) { if (this[p] !== rng[p]) return false } } return true }, /** * 遍历range内的节点。每当遍历一个节点时, 都会执行参数项 doFn 指定的函数, 该函数的接受当前遍历的节点 * 作为其参数。 * @method traversal * @param { Function } doFn 对每个遍历的节点要执行的方法, 该方法接受当前遍历的节点作为其参数 * @return { UE.dom.Range } 当前range对象 * @example * ```html * * * * * * * * * * * ``` */ /** * 遍历range内的节点。 * 每当遍历一个节点时, 都会执行参数项 doFn 指定的函数, 该函数的接受当前遍历的节点 * 作为其参数。 * 可以通过参数项 filterFn 来指定一个过滤器, 只有符合该过滤器过滤规则的节点才会触 * 发doFn函数的执行 * @method traversal * @param { Function } doFn 对每个遍历的节点要执行的方法, 该方法接受当前遍历的节点作为其参数 * @param { Function } filterFn 过滤器, 该函数接受当前遍历的节点作为参数, 如果该节点满足过滤 * 规则, 请返回true, 该节点会触发doFn, 否则, 请返回false, 则该节点不 * 会触发doFn。 * @return { UE.dom.Range } 当前range对象 * @see UE.dom.Range:traversal(Function) * @example * ```html * * * * * * * * * * * ``` */ traversal: function (doFn, filterFn) { if (this.collapsed) return this var bookmark = this.createBookmark(), end = bookmark.end, current = domUtils.getNextDomNode(bookmark.start, false, filterFn) while ( current && current !== end && domUtils.getPosition(current, end) & domUtils.POSITION_PRECEDING ) { var tmpNode = domUtils.getNextDomNode(current, false, filterFn) doFn(current) current = tmpNode } return this.moveToBookmark(bookmark) } } })() // core/Selection.js /** * 选集 * @file * @module UE.dom * @class Selection * @since 1.2.6.1 */ /** * 选区集合 * @unfile * @module UE.dom * @class Selection */ ;(function () { function getBoundaryInformation(range, start) { var getIndex = domUtils.getNodeIndex range = range.duplicate() range.collapse(start) var parent = range.parentElement() //如果节点里没有子节点,直接退出 if (!parent.hasChildNodes()) { return { container: parent, offset: 0 } } var siblings = parent.children, child, testRange = range.duplicate(), startIndex = 0, endIndex = siblings.length - 1, index = -1, distance while (startIndex <= endIndex) { index = Math.floor((startIndex + endIndex) / 2) child = siblings[index] testRange.moveToElementText(child) var position = testRange.compareEndPoints('StartToStart', range) if (position > 0) { endIndex = index - 1 } else if (position < 0) { startIndex = index + 1 } else { //trace:1043 return { container: parent, offset: getIndex(child) } } } if (index == -1) { testRange.moveToElementText(parent) testRange.setEndPoint('StartToStart', range) distance = testRange.text.replace(/(\r\n|\r)/g, '\n').length siblings = parent.childNodes if (!distance) { child = siblings[siblings.length - 1] return { container: child, offset: child.nodeValue.length } } var i = siblings.length while (distance > 0) { distance -= siblings[--i].nodeValue.length } return { container: siblings[i], offset: -distance } } testRange.collapse(position > 0) testRange.setEndPoint(position > 0 ? 'StartToStart' : 'EndToStart', range) distance = testRange.text.replace(/(\r\n|\r)/g, '\n').length if (!distance) { return dtd.$empty[child.tagName] || dtd.$nonChild[child.tagName] ? { container: parent, offset: getIndex(child) + (position > 0 ? 0 : 1) } : { container: child, offset: position > 0 ? 0 : child.childNodes.length } } while (distance > 0) { try { var pre = child child = child[position > 0 ? 'previousSibling' : 'nextSibling'] distance -= child.nodeValue.length } catch (e) { return { container: parent, offset: getIndex(pre) } } } return { container: child, offset: position > 0 ? -distance : child.nodeValue.length + distance } } /** * 将ieRange转换为Range对象 * @param {Range} ieRange ieRange对象 * @param {Range} range Range对象 * @return {Range} range 返回转换后的Range对象 */ function transformIERangeToRange(ieRange, range) { if (ieRange.item) { range.selectNode(ieRange.item(0)) } else { var bi = getBoundaryInformation(ieRange, true) range.setStart(bi.container, bi.offset) if (ieRange.compareEndPoints('StartToEnd', ieRange) != 0) { bi = getBoundaryInformation(ieRange, false) range.setEnd(bi.container, bi.offset) } } return range } /** * 获得ieRange * @param {Selection} sel Selection对象 * @return {ieRange} 得到ieRange */ function _getIERange(sel) { var ieRange //ie下有可能报错 try { ieRange = sel.getNative().createRange() } catch (e) { return null } var el = ieRange.item ? ieRange.item(0) : ieRange.parentElement() if ((el.ownerDocument || el) === sel.document) { return ieRange } return null } var Selection = (dom.Selection = function (doc) { var me = this, iframe me.document = doc if (browser.ie9below) { iframe = domUtils.getWindow(doc).frameElement domUtils.on(iframe, 'beforedeactivate', function () { me._bakIERange = me.getIERange() }) domUtils.on(iframe, 'activate', function () { try { if (!_getIERange(me) && me._bakIERange) { me._bakIERange.select() } } catch (ex) {} me._bakIERange = null }) } iframe = doc = null }) Selection.prototype = { rangeInBody: function (rng, txtRange) { var node = browser.ie9below || txtRange ? rng.item ? rng.item() : rng.parentElement() : rng.startContainer return ( node === this.document.body || domUtils.inDoc(node, this.document) ) }, /** * 获取原生seleciton对象 * @method getNative * @return { Object } 获得selection对象 * @example * ```javascript * editor.selection.getNative(); * ``` */ getNative: function () { var doc = this.document try { return !doc ? null : browser.ie9below ? doc.selection : domUtils.getWindow(doc).getSelection() } catch (e) { return null } }, /** * 获得ieRange * @method getIERange * @return { Object } 返回ie原生的Range * @example * ```javascript * editor.selection.getIERange(); * ``` */ getIERange: function () { var ieRange = _getIERange(this) if (!ieRange) { if (this._bakIERange) { return this._bakIERange } } return ieRange }, /** * 缓存当前选区的range和选区的开始节点 * @method cache */ cache: function () { this.clear() this._cachedRange = this.getRange() this._cachedStartElement = this.getStart() this._cachedStartElementPath = this.getStartElementPath() }, /** * 获取选区开始位置的父节点到body * @method getStartElementPath * @return { Array } 返回父节点集合 * @example * ```javascript * editor.selection.getStartElementPath(); * ``` */ getStartElementPath: function () { if (this._cachedStartElementPath) { return this._cachedStartElementPath } var start = this.getStart() if (start) { return domUtils.findParents(start, true, null, true) } return [] }, /** * 清空缓存 * @method clear */ clear: function () { this._cachedStartElementPath = this._cachedRange = this._cachedStartElement = null }, /** * 编辑器是否得到了选区 * @method isFocus */ isFocus: function () { try { if (browser.ie9below) { var nativeRange = _getIERange(this) return !!(nativeRange && this.rangeInBody(nativeRange)) } else { return !!this.getNative().rangeCount } } catch (e) { return false } }, /** * 获取选区对应的Range * @method getRange * @return { Object } 得到Range对象 * @example * ```javascript * editor.selection.getRange(); * ``` */ getRange: function () { var me = this function optimze(range) { var child = me.document.body.firstChild, collapsed = range.collapsed while (child && child.firstChild) { range.setStart(child, 0) child = child.firstChild } if (!range.startContainer) { range.setStart(me.document.body, 0) } if (collapsed) { range.collapse(true) } } if (me._cachedRange != null) { return this._cachedRange } var range = new baidu.editor.dom.Range(me.document) if (browser.ie9below) { var nativeRange = me.getIERange() if (nativeRange) { //备份的_bakIERange可能已经实效了,dom树发生了变化比如从源码模式切回来,所以try一下,实效就放到body开始位置 try { transformIERangeToRange(nativeRange, range) } catch (e) { optimze(range) } } else { optimze(range) } } else { var sel = me.getNative() if (sel && sel.rangeCount) { var firstRange = sel.getRangeAt(0) var lastRange = sel.getRangeAt(sel.rangeCount - 1) range .setStart(firstRange.startContainer, firstRange.startOffset) .setEnd(lastRange.endContainer, lastRange.endOffset) if ( range.collapsed && domUtils.isBody(range.startContainer) && !range.startOffset ) { optimze(range) } } else { //trace:1734 有可能已经不在dom树上了,标识的节点 if ( this._bakRange && domUtils.inDoc(this._bakRange.startContainer, this.document) ) { return this._bakRange } optimze(range) } } return (this._bakRange = range) }, /** * 获取开始元素,用于状态反射 * @method getStart * @return { Element } 获得开始元素 * @example * ```javascript * editor.selection.getStart(); * ``` */ getStart: function () { if (this._cachedStartElement) { return this._cachedStartElement } var range = browser.ie9below ? this.getIERange() : this.getRange(), tmpRange, start, tmp, parent if (browser.ie9below) { if (!range) { //todo 给第一个值可能会有问题 return this.document.body.firstChild } //control元素 if (range.item) { return range.item(0) } tmpRange = range.duplicate() //修正ie下x[xx] 闭合后 x|xx tmpRange.text.length > 0 && tmpRange.moveStart('character', 1) tmpRange.collapse(1) start = tmpRange.parentElement() parent = tmp = range.parentElement() while ((tmp = tmp.parentNode)) { if (tmp == start) { start = parent break } } } else { range.shrinkBoundary() start = range.startContainer if (start.nodeType == 1 && start.hasChildNodes()) { start = start.childNodes[ Math.min(start.childNodes.length - 1, range.startOffset) ] } if (start.nodeType == 3) { return start.parentNode } } return start }, /** * 得到选区中的文本 * @method getText * @return { String } 选区中包含的文本 * @example * ```javascript * editor.selection.getText(); * ``` */ getText: function () { var nativeSel, nativeRange if (this.isFocus() && (nativeSel = this.getNative())) { nativeRange = browser.ie9below ? nativeSel.createRange() : nativeSel.getRangeAt(0) return browser.ie9below ? nativeRange.text : nativeRange.toString() } return '' }, /** * 清除选区 * @method clearRange * @example * ```javascript * editor.selection.clearRange(); * ``` */ clearRange: function () { this.getNative()[browser.ie9below ? 'empty' : 'removeAllRanges']() } } })() // core/Editor.js /** * 编辑器主类,包含编辑器提供的大部分公用接口 * @file * @module UE * @class Editor * @since 1.2.6.1 */ /** * UEditor公用空间,UEditor所有的功能都挂载在该空间下 * @unfile * @module UE */ /** * UEditor的核心类,为用户提供与编辑器交互的接口。 * @unfile * @module UE * @class Editor */ ;(function () { var uid = 0, _selectionChangeTimer /** * 获取编辑器的html内容,赋值到编辑器所在表单的textarea文本域里面 * @private * @method setValue * @param { UE.Editor } editor 编辑器事例 */ function setValue(form, editor) { var textarea if (editor.textarea) { if (utils.isString(editor.textarea)) { for ( var i = 0, ti, tis = domUtils.getElementsByTagName(form, 'textarea'); (ti = tis[i++]); ) { if (ti.id == 'ueditor_textarea_' + editor.options.textarea) { textarea = ti break } } } else { textarea = editor.textarea } } if (!textarea) { form.appendChild( (textarea = domUtils.createElement(document, 'textarea', { name: editor.options.textarea, id: 'ueditor_textarea_' + editor.options.textarea, style: 'display:none' })) ) //不要产生多个textarea editor.textarea = textarea } !textarea.getAttribute('name') && textarea.setAttribute('name', editor.options.textarea) textarea.value = editor.hasContents() ? editor.options.allHtmlEnabled ? editor.getAllHtml() : editor.getContent(null, null, true) : '' } function loadPlugins(me) { //初始化插件 for (var pi in UE.plugins) { UE.plugins[pi].call(me) } } function checkCurLang(I18N) { for (var lang in I18N) { return lang } } function langReadied(me) { me.langIsReady = true me.fireEvent('langReady') } /** * 编辑器准备就绪后会触发该事件 * @module UE * @class Editor * @event ready * @remind render方法执行完成之后,会触发该事件 * @remind * @example * ```javascript * editor.addListener( 'ready', function( editor ) { * editor.execCommand( 'focus' ); //编辑器家在完成后,让编辑器拿到焦点 * } ); * ``` */ /** * 执行destroy方法,会触发该事件 * @module UE * @class Editor * @event destroy * @see UE.Editor:destroy() */ /** * 执行reset方法,会触发该事件 * @module UE * @class Editor * @event reset * @see UE.Editor:reset() */ /** * 执行focus方法,会触发该事件 * @module UE * @class Editor * @event focus * @see UE.Editor:focus(Boolean) */ /** * 语言加载完成会触发该事件 * @module UE * @class Editor * @event langReady */ /** * 运行命令之后会触发该命令 * @module UE * @class Editor * @event beforeExecCommand */ /** * 运行命令之后会触发该命令 * @module UE * @class Editor * @event afterExecCommand */ /** * 运行命令之前会触发该命令 * @module UE * @class Editor * @event firstBeforeExecCommand */ /** * 在getContent方法执行之前会触发该事件 * @module UE * @class Editor * @event beforeGetContent * @see UE.Editor:getContent() */ /** * 在getContent方法执行之后会触发该事件 * @module UE * @class Editor * @event afterGetContent * @see UE.Editor:getContent() */ /** * 在getAllHtml方法执行时会触发该事件 * @module UE * @class Editor * @event getAllHtml * @see UE.Editor:getAllHtml() */ /** * 在setContent方法执行之前会触发该事件 * @module UE * @class Editor * @event beforeSetContent * @see UE.Editor:setContent(String) */ /** * 在setContent方法执行之后会触发该事件 * @module UE * @class Editor * @event afterSetContent * @see UE.Editor:setContent(String) */ /** * 每当编辑器内部选区发生改变时,将触发该事件 * @event selectionchange * @warning 该事件的触发非常频繁,不建议在该事件的处理过程中做重量级的处理 * @example * ```javascript * editor.addListener( 'selectionchange', function( editor ) { * console.log('选区发生改变'); * } */ /** * 在所有selectionchange的监听函数执行之前,会触发该事件 * @module UE * @class Editor * @event beforeSelectionChange * @see UE.Editor:selectionchange */ /** * 在所有selectionchange的监听函数执行完之后,会触发该事件 * @module UE * @class Editor * @event afterSelectionChange * @see UE.Editor:selectionchange */ /** * 编辑器内容发生改变时会触发该事件 * @module UE * @class Editor * @event contentChange */ /** * 以默认参数构建一个编辑器实例 * @constructor * @remind 通过 改构造方法实例化的编辑器,不带ui层.需要render到一个容器,编辑器实例才能正常渲染到页面 * @example * ```javascript * var editor = new UE.Editor(); * editor.execCommand('blod'); * ``` * @see UE.Config */ /** * 以给定的参数集合创建一个编辑器实例,对于未指定的参数,将应用默认参数。 * @constructor * @remind 通过 改构造方法实例化的编辑器,不带ui层.需要render到一个容器,编辑器实例才能正常渲染到页面 * @param { Object } setting 创建编辑器的参数 * @example * ```javascript * var editor = new UE.Editor(); * editor.execCommand('blod'); * ``` * @see UE.Config */ var Editor = (UE.Editor = function (options) { var me = this me.uid = uid++ EventBase.call(me) me.commands = {} me.options = utils.extend( utils.clone(options || {}), UEDITOR_CONFIG, true ) me.shortcutkeys = {} me.inputRules = [] me.outputRules = [] //设置默认的常用属性 me.setOpt(Editor.defaultOptions(me)) /* 尝试异步加载后台配置 */ me.loadServerConfig() if (!utils.isEmptyObject(UE.I18N)) { //修改默认的语言类型 me.options.lang = checkCurLang(UE.I18N) UE.plugin.load(me) langReadied(me) } else { utils.loadFile( document, { src: me.options.langPath + me.options.lang + '/' + me.options.lang + '.js', tag: 'script', type: 'text/javascript', defer: 'defer' }, function () { UE.plugin.load(me) langReadied(me) } ) } UE.instants['ueditorInstant' + me.uid] = me }) Editor.prototype = { registerCommand: function (name, obj) { this.commands[name] = obj }, /** * 编辑器对外提供的监听ready事件的接口, 通过调用该方法,达到的效果与监听ready事件是一致的 * @method ready * @param { Function } fn 编辑器ready之后所执行的回调, 如果在注册事件之前编辑器已经ready,将会 * 立即触发该回调。 * @remind 需要等待编辑器加载完成后才能执行的代码,可以使用该方法传入 * @example * ```javascript * editor.ready( function( editor ) { * editor.setContent('初始化完毕'); * } ); * ``` * @see UE.Editor.event:ready */ ready: function (fn) { var me = this if (fn) { me.isReady ? fn.apply(me) : me.addListener('ready', fn) } }, /** * 该方法是提供给插件里面使用,设置配置项默认值 * @method setOpt * @warning 三处设置配置项的优先级: 实例化时传入参数 > setOpt()设置 > config文件里设置 * @warning 该方法仅供编辑器插件内部和编辑器初始化时调用,其他地方不能调用。 * @param { String } key 编辑器的可接受的选项名称 * @param { * } val 该选项可接受的值 * @example * ```javascript * editor.setOpt( 'initContent', '欢迎使用编辑器' ); * ``` */ /** * 该方法是提供给插件里面使用,以{key:value}集合的方式设置插件内用到的配置项默认值 * @method setOpt * @warning 三处设置配置项的优先级: 实例化时传入参数 > setOpt()设置 > config文件里设置 * @warning 该方法仅供编辑器插件内部和编辑器初始化时调用,其他地方不能调用。 * @param { Object } options 将要设置的选项的键值对对象 * @example * ```javascript * editor.setOpt( { * 'initContent': '欢迎使用编辑器' * } ); * ``` */ setOpt: function (key, val) { var obj = {} if (utils.isString(key)) { obj[key] = val } else { obj = key } utils.extend(this.options, obj, true) }, getOpt: function (key) { return this.options[key] }, /** * 销毁编辑器实例,使用textarea代替 * @method destroy * @example * ```javascript * editor.destroy(); * ``` */ destroy: function () { var me = this me.fireEvent('destroy') var container = me.container.parentNode var textarea = me.textarea if (!textarea) { textarea = document.createElement('textarea') container.parentNode.insertBefore(textarea, container) } else { textarea.style.display = '' } textarea.style.width = me.iframe.offsetWidth + 'px' textarea.style.height = me.iframe.offsetHeight + 'px' textarea.value = me.getContent() textarea.id = me.key container.innerHTML = '' domUtils.remove(container) var key = me.key //trace:2004 for (var p in me) { if (me.hasOwnProperty(p)) { delete this[p] } } UE.delEditor(key) }, /** * 渲染编辑器的DOM到指定容器 * @method render * @param { String } containerId 指定一个容器ID * @remind 执行该方法,会触发ready事件 * @warning 必须且只能调用一次 */ /** * 渲染编辑器的DOM到指定容器 * @method render * @param { Element } containerDom 直接指定容器对象 * @remind 执行该方法,会触发ready事件 * @warning 必须且只能调用一次 */ render: function (container) { var me = this, options = me.options, getStyleValue = function (attr) { return parseInt(domUtils.getComputedStyle(container, attr)) } if (utils.isString(container)) { container = document.getElementById(container) } if (container) { if (options.initialFrameWidth) { options.minFrameWidth = options.initialFrameWidth } else { options.minFrameWidth = options.initialFrameWidth = container.offsetWidth } if (options.initialFrameHeight) { options.minFrameHeight = options.initialFrameHeight } else { options.initialFrameHeight = options.minFrameHeight = container.offsetHeight } container.style.width = /%$/.test(options.initialFrameWidth) ? '100%' : options.initialFrameWidth - getStyleValue('padding-left') - getStyleValue('padding-right') + 'px' container.style.height = /%$/.test(options.initialFrameHeight) ? '100%' : options.initialFrameHeight - getStyleValue('padding-top') - getStyleValue('padding-bottom') + 'px' container.style.zIndex = options.zIndex var html = (ie && browser.version < 9 ? '' : '') + "" + "' + (options.iframeCssUrl ? "" : '') + (options.initialStyle ? '' : '') + "" + "" container.appendChild( domUtils.createElement(document, 'iframe', { id: 'ueditor_' + me.uid, width: '100%', height: '100%', frameborder: '0', //先注释掉了,加的原因忘记了,但开启会直接导致全屏模式下内容多时不会出现滚动条 // scrolling :'no', src: 'javascript:void(function(){document.open();' + (options.customDomain && document.domain != location.hostname ? 'document.domain="' + document.domain + '";' : '') + 'document.write("' + html + '");document.close();}())' }) ) container.style.overflow = 'hidden' //解决如果是给定的百分比,会导致高度算不对的问题 setTimeout(function () { if (/%$/.test(options.initialFrameWidth)) { options.minFrameWidth = options.initialFrameWidth = container.offsetWidth //如果这里给定宽度,会导致ie在拖动窗口大小时,编辑区域不随着变化 // container.style.width = options.initialFrameWidth + 'px'; } if (/%$/.test(options.initialFrameHeight)) { options.minFrameHeight = options.initialFrameHeight = container.offsetHeight container.style.height = options.initialFrameHeight + 'px' } }) } }, /** * 编辑器初始化 * @method _setup * @private * @param { Element } doc 编辑器Iframe中的文档对象 */ _setup: function (doc) { var me = this, options = me.options if (ie) { doc.body.disabled = true doc.body.contentEditable = true doc.body.disabled = false } else { doc.body.contentEditable = true } doc.body.spellcheck = false me.document = doc me.window = doc.defaultView || doc.parentWindow me.iframe = me.window.frameElement me.body = doc.body me.selection = new dom.Selection(doc) //gecko初始化就能得到range,无法判断isFocus了 var geckoSel if (browser.gecko && (geckoSel = this.selection.getNative())) { geckoSel.removeAllRanges() } this._initEvents() //为form提交提供一个隐藏的textarea for ( var form = this.iframe.parentNode; !domUtils.isBody(form); form = form.parentNode ) { if (form.tagName == 'FORM') { me.form = form if (me.options.autoSyncData) { domUtils.on(me.window, 'blur', function () { setValue(form, me) }) } else { domUtils.on(form, 'submit', function () { setValue(this, me) }) } break } } if (options.initialContent) { if (options.autoClearinitialContent) { var oldExecCommand = me.execCommand me.execCommand = function () { me.fireEvent('firstBeforeExecCommand') return oldExecCommand.apply(me, arguments) } this._setDefaultContent(options.initialContent) } else this.setContent(options.initialContent, false, true) } //编辑器不能为空内容 if (domUtils.isEmptyNode(me.body)) { me.body.innerHTML = '

' + (browser.ie ? '' : '
') + '

' } //如果要求focus, 就把光标定位到内容开始 if (options.focus) { setTimeout(function () { me.focus(me.options.focusInEnd) //如果自动清除开着,就不需要做selectionchange; !me.options.autoClearinitialContent && me._selectionChange() }, 0) } if (!me.container) { me.container = this.iframe.parentNode } if (options.fullscreen && me.ui) { me.ui.setFullScreen(true) } try { me.document.execCommand('2D-position', false, false) } catch (e) {} try { me.document.execCommand('enableInlineTableEditing', false, false) } catch (e) {} try { me.document.execCommand('enableObjectResizing', false, false) } catch (e) {} //挂接快捷键 me._bindshortcutKeys() me.isReady = 1 me.fireEvent('ready') options.onready && options.onready.call(me) if (!browser.ie9below) { domUtils.on(me.window, ['blur', 'focus'], function (e) { //chrome下会出现alt+tab切换时,导致选区位置不对 if (e.type == 'blur') { me._bakRange = me.selection.getRange() try { me._bakNativeRange = me.selection.getNative().getRangeAt(0) me.selection.getNative().removeAllRanges() } catch (e) { me._bakNativeRange = null } } else { try { me._bakRange && me._bakRange.select() } catch (e) {} } }) } //trace:1518 ff3.6body不够寛,会导致点击空白处无法获得焦点 if (browser.gecko && browser.version <= 10902) { //修复ff3.6初始化进来,不能点击获得焦点 me.body.contentEditable = false setTimeout(function () { me.body.contentEditable = true }, 100) setInterval(function () { me.body.style.height = me.iframe.offsetHeight - 20 + 'px' }, 100) } !options.isShow && me.setHide() options.readonly && me.setDisabled() }, /** * 同步数据到编辑器所在的form * 从编辑器的容器节点向上查找form元素,若找到,就同步编辑内容到找到的form里,为提交数据做准备,主要用于是手动提交的情况 * 后台取得数据的键值,使用你容器上的name属性,如果没有就使用参数里的textarea项 * @method sync * @example * ```javascript * editor.sync(); * form.sumbit(); //form变量已经指向了form元素 * ``` */ /** * 根据传入的formId,在页面上查找要同步数据的表单,若找到,就同步编辑内容到找到的form里,为提交数据做准备 * 后台取得数据的键值,该键值默认使用给定的编辑器容器的name属性,如果没有name属性则使用参数项里给定的“textarea”项 * @method sync * @param { String } formID 指定一个要同步数据的form的id,编辑器的数据会同步到你指定form下 */ sync: function (formId) { var me = this, form = formId ? document.getElementById(formId) : domUtils.findParent( me.iframe.parentNode, function (node) { return node.tagName == 'FORM' }, true ) form && setValue(form, me) }, /** * 设置编辑器高度 * @method setHeight * @remind 当配置项autoHeightEnabled为真时,该方法无效 * @param { Number } number 设置的高度值,纯数值,不带单位 * @example * ```javascript * editor.setHeight(number); * ``` */ setHeight: function (height, notSetHeight) { if (height !== parseInt(this.iframe.parentNode.style.height)) { this.iframe.parentNode.style.height = height + 'px' } !notSetHeight && (this.options.minFrameHeight = this.options.initialFrameHeight = height) this.body.style.height = height + 'px' !notSetHeight && this.trigger('setHeight') }, /** * 为编辑器的编辑命令提供快捷键 * 这个接口是为插件扩展提供的接口,主要是为新添加的插件,如果需要添加快捷键,所提供的接口 * @method addshortcutkey * @param { Object } keyset 命令名和快捷键键值对对象,多个按钮的快捷键用“+”分隔 * @example * ```javascript * editor.addshortcutkey({ * "Bold" : "ctrl+66",//^B * "Italic" : "ctrl+73", //^I * }); * ``` */ /** * 这个接口是为插件扩展提供的接口,主要是为新添加的插件,如果需要添加快捷键,所提供的接口 * @method addshortcutkey * @param { String } cmd 触发快捷键时,响应的命令 * @param { String } keys 快捷键的字符串,多个按钮用“+”分隔 * @example * ```javascript * editor.addshortcutkey("Underline", "ctrl+85"); //^U * ``` */ addshortcutkey: function (cmd, keys) { var obj = {} if (keys) { obj[cmd] = keys } else { obj = cmd } utils.extend(this.shortcutkeys, obj) }, /** * 对编辑器设置keydown事件监听,绑定快捷键和命令,当快捷键组合触发成功,会响应对应的命令 * @method _bindshortcutKeys * @private */ _bindshortcutKeys: function () { var me = this, shortcutkeys = this.shortcutkeys me.addListener('keydown', function (type, e) { var keyCode = e.keyCode || e.which for (var i in shortcutkeys) { var tmp = shortcutkeys[i].split(',') for (var t = 0, ti; (ti = tmp[t++]); ) { ti = ti.split(':') var key = ti[0], param = ti[1] if ( /^(ctrl)(\+shift)?\+(\d+)$/.test(key.toLowerCase()) || /^(\d+)$/.test(key) ) { if ( ((RegExp.$1 == 'ctrl' ? e.ctrlKey || e.metaKey : 0) && (RegExp.$2 != '' ? e[RegExp.$2.slice(1) + 'Key'] : 1) && keyCode == RegExp.$3) || keyCode == RegExp.$1 ) { if (me.queryCommandState(i, param) != -1) me.execCommand(i, param) domUtils.preventDefault(e) } } } } }) }, /** * 获取编辑器的内容 * @method getContent * @warning 该方法获取到的是经过编辑器内置的过滤规则进行过滤后得到的内容 * @return { String } 编辑器的内容字符串, 如果编辑器的内容为空,或者是空的标签内容(如:”<p><br/></p>“), 则返回空字符串 * @example * ```javascript * //编辑器html内容:

123456

* var content = editor.getContent(); //返回值:

123456

* ``` */ /** * 获取编辑器的内容。 可以通过参数定义编辑器内置的判空规则 * @method getContent * @param { Function } fn 自定的判空规则, 要求该方法返回一个boolean类型的值, * 代表当前编辑器的内容是否空, * 如果返回true, 则该方法将直接返回空字符串;如果返回false,则编辑器将返回 * 经过内置过滤规则处理后的内容。 * @remind 该方法在处理包含有初始化内容的时候能起到很好的作用。 * @warning 该方法获取到的是经过编辑器内置的过滤规则进行过滤后得到的内容 * @return { String } 编辑器的内容字符串 * @example * ```javascript * // editor 是一个编辑器的实例 * var content = editor.getContent( function ( editor ) { * return editor.body.innerHTML === '欢迎使用UEditor'; //返回空字符串 * } ); * ``` */ getContent: function (cmd, fn, notSetCursor, ignoreBlank, formatter) { var me = this if (cmd && utils.isFunction(cmd)) { fn = cmd cmd = '' } if (fn ? !fn() : !this.hasContents()) { return '' } me.fireEvent('beforegetcontent') var root = UE.htmlparser(me.body.innerHTML, ignoreBlank) me.filterOutputRule(root) me.fireEvent('aftergetcontent', cmd, root) return root.toHtml(formatter) }, /** * 取得完整的html代码,可以直接显示成完整的html文档 * @method getAllHtml * @return { String } 编辑器的内容html文档字符串 * @eaxmple * ```javascript * editor.getAllHtml(); //返回格式大致是: ...... * ``` */ getAllHtml: function () { var me = this, headHtml = [], html = '' me.fireEvent('getAllHtml', headHtml) if (browser.ie && browser.version > 8) { var headHtmlForIE9 = '' utils.each(me.document.styleSheets, function (si) { headHtmlForIE9 += si.href ? '' : '' }) utils.each(me.document.getElementsByTagName('script'), function (si) { headHtmlForIE9 += si.outerHTML }) } return ( '' + (me.options.charset ? '' : '') + (headHtmlForIE9 || me.document.getElementsByTagName('head')[0].innerHTML) + headHtml.join('\n') + '' + '' + me.getContent(null, null, true) + '' ) }, /** * 得到编辑器的纯文本内容,但会保留段落格式 * @method getPlainTxt * @return { String } 编辑器带段落格式的纯文本内容字符串 * @example * ```javascript * //编辑器html内容:

1

2

* console.log(editor.getPlainTxt()); //输出:"1\n2\n * ``` */ getPlainTxt: function () { var reg = new RegExp(domUtils.fillChar, 'g'), html = this.body.innerHTML.replace(/[\n\r]/g, '') //ie要先去了\n在处理 html = html .replace(/<(p|div)[^>]*>(| )<\/\1>/gi, '\n') .replace(//gi, '\n') .replace(/<[^>/]+>/g, '') .replace(/(\n)?<\/([^>]+)>/g, function (a, b, c) { return dtd.$block[c] ? '\n' : b ? b : '' }) //取出来的空格会有c2a0会变成乱码,处理这种情况\u00a0 return html .replace(reg, '') .replace(/\u00a0/g, ' ') .replace(/ /g, ' ') }, /** * 获取编辑器中的纯文本内容,没有段落格式 * @method getContentTxt * @return { String } 编辑器不带段落格式的纯文本内容字符串 * @example * ```javascript * //编辑器html内容:

1

2

* console.log(editor.getPlainTxt()); //输出:"12 * ``` */ getContentTxt: function () { var reg = new RegExp(domUtils.fillChar, 'g') //取出来的空格会有c2a0会变成乱码,处理这种情况\u00a0 return this.body[browser.ie ? 'innerText' : 'textContent'] .replace(reg, '') .replace(/\u00a0/g, ' ') }, /** * 设置编辑器的内容,可修改编辑器当前的html内容 * @method setContent * @warning 通过该方法插入的内容,是经过编辑器内置的过滤规则进行过滤后得到的内容 * @warning 该方法会触发selectionchange事件 * @param { String } html 要插入的html内容 * @example * ```javascript * editor.getContent('

test

'); * ``` */ /** * 设置编辑器的内容,可修改编辑器当前的html内容 * @method setContent * @warning 通过该方法插入的内容,是经过编辑器内置的过滤规则进行过滤后得到的内容 * @warning 该方法会触发selectionchange事件 * @param { String } html 要插入的html内容 * @param { Boolean } isAppendTo 若传入true,不清空原来的内容,在最后插入内容,否则,清空内容再插入 * @example * ```javascript * //假设设置前的编辑器内容是

old text

* editor.setContent('

new text

', true); //插入的结果是

old text

new text

* ``` */ setContent: function (html, isAppendTo, notFireSelectionchange) { var me = this me.fireEvent('beforesetcontent', html) var root = UE.htmlparser(html) me.filterInputRule(root) html = root.toHtml() me.body.innerHTML = (isAppendTo ? me.body.innerHTML : '') + html function isCdataDiv(node) { return node.tagName == 'DIV' && node.getAttribute('cdata_tag') } //给文本或者inline节点套p标签 if (me.options.enterTag == 'p') { var child = this.body.firstChild, tmpNode if ( !child || (child.nodeType == 1 && (dtd.$cdata[child.tagName] || isCdataDiv(child) || domUtils.isCustomeNode(child)) && child === this.body.lastChild) ) { this.body.innerHTML = '

' + (browser.ie ? ' ' : '
') + '

' + this.body.innerHTML } else { var p = me.document.createElement('p') while (child) { while ( child && (child.nodeType == 3 || (child.nodeType == 1 && dtd.p[child.tagName] && !dtd.$cdata[child.tagName])) ) { tmpNode = child.nextSibling p.appendChild(child) child = tmpNode } if (p.firstChild) { if (!child) { me.body.appendChild(p) break } else { child.parentNode.insertBefore(p, child) p = me.document.createElement('p') } } child = child.nextSibling } } } me.fireEvent('aftersetcontent') me.fireEvent('contentchange') !notFireSelectionchange && me._selectionChange() //清除保存的选区 me._bakRange = me._bakIERange = me._bakNativeRange = null //trace:1742 setContent后gecko能得到焦点问题 var geckoSel if (browser.gecko && (geckoSel = this.selection.getNative())) { geckoSel.removeAllRanges() } if (me.options.autoSyncData) { me.form && setValue(me.form, me) } }, /** * 让编辑器获得焦点,默认focus到编辑器头部 * @method focus * @example * ```javascript * editor.focus() * ``` */ /** * 让编辑器获得焦点,toEnd确定focus位置 * @method focus * @param { Boolean } toEnd 默认focus到编辑器头部,toEnd为true时focus到内容尾部 * @example * ```javascript * editor.focus(true) * ``` */ focus: function (toEnd) { try { var me = this, rng = me.selection.getRange() if (toEnd) { var node = me.body.lastChild if (node && node.nodeType == 1 && !dtd.$empty[node.tagName]) { if (domUtils.isEmptyBlock(node)) { rng.setStartAtFirst(node) } else { rng.setStartAtLast(node) } rng.collapse(true) } rng.setCursor(true) } else { if ( !rng.collapsed && domUtils.isBody(rng.startContainer) && rng.startOffset == 0 ) { var node = me.body.firstChild if (node && node.nodeType == 1 && !dtd.$empty[node.tagName]) { rng.setStartAtFirst(node).collapse(true) } } rng.select(true) } this.fireEvent('focus selectionchange') } catch (e) {} }, isFocus: function () { return this.selection.isFocus() }, blur: function () { var sel = this.selection.getNative() if (sel.empty && browser.ie) { var nativeRng = document.body.createTextRange() nativeRng.moveToElementText(document.body) nativeRng.collapse(true) nativeRng.select() sel.empty() } else { sel.removeAllRanges() } //this.fireEvent('blur selectionchange'); }, /** * 初始化UE事件及部分事件代理 * @method _initEvents * @private */ _initEvents: function () { var me = this, doc = me.document, win = me.window me._proxyDomEvent = utils.bind(me._proxyDomEvent, me) domUtils.on( doc, [ 'click', 'contextmenu', 'mousedown', 'keydown', 'keyup', 'keypress', 'mouseup', 'mouseover', 'mouseout', 'selectstart' ], me._proxyDomEvent ) domUtils.on(win, ['focus', 'blur'], me._proxyDomEvent) domUtils.on(me.body, 'drop', function (e) { //阻止ff下默认的弹出新页面打开图片 if (browser.gecko && e.stopPropagation) { e.stopPropagation() } me.fireEvent('contentchange') }) domUtils.on(doc, ['mouseup', 'keydown'], function (evt) { //特殊键不触发selectionchange if ( evt.type == 'keydown' && (evt.ctrlKey || evt.metaKey || evt.shiftKey || evt.altKey) ) { return } if (evt.button == 2) return me._selectionChange(250, evt) }) }, /** * 触发事件代理 * @method _proxyDomEvent * @private * @return { * } fireEvent的返回值 * @see UE.EventBase:fireEvent(String) */ _proxyDomEvent: function (evt) { if ( this.fireEvent( 'before' + evt.type.replace(/^on/, '').toLowerCase() ) === false ) { return false } if (this.fireEvent(evt.type.replace(/^on/, ''), evt) === false) { return false } return this.fireEvent( 'after' + evt.type.replace(/^on/, '').toLowerCase() ) }, /** * 变化选区 * @method _selectionChange * @private */ _selectionChange: function (delay, evt) { var me = this //有光标才做selectionchange 为了解决未focus时点击source不能触发更改工具栏状态的问题(source命令notNeedUndo=1) // if ( !me.selection.isFocus() ){ // return; // } var hackForMouseUp = false var mouseX, mouseY if (browser.ie && browser.version < 9 && evt && evt.type == 'mouseup') { var range = this.selection.getRange() if (!range.collapsed) { hackForMouseUp = true mouseX = evt.clientX mouseY = evt.clientY } } clearTimeout(_selectionChangeTimer) _selectionChangeTimer = setTimeout(function () { if (!me.selection || !me.selection.getNative()) { return } //修复一个IE下的bug: 鼠标点击一段已选择的文本中间时,可能在mouseup后的一段时间内取到的range是在selection的type为None下的错误值. //IE下如果用户是拖拽一段已选择文本,则不会触发mouseup事件,所以这里的特殊处理不会对其有影响 var ieRange if (hackForMouseUp && me.selection.getNative().type == 'None') { ieRange = me.document.body.createTextRange() try { ieRange.moveToPoint(mouseX, mouseY) } catch (ex) { ieRange = null } } var bakGetIERange if (ieRange) { bakGetIERange = me.selection.getIERange me.selection.getIERange = function () { return ieRange } } me.selection.cache() if (bakGetIERange) { me.selection.getIERange = bakGetIERange } if (me.selection._cachedRange && me.selection._cachedStartElement) { me.fireEvent('beforeselectionchange') // 第二个参数causeByUi为true代表由用户交互造成的selectionchange. me.fireEvent('selectionchange', !!evt) me.fireEvent('afterselectionchange') me.selection.clear() } }, delay || 50) }, /** * 执行编辑命令 * @method _callCmdFn * @private * @param { String } fnName 函数名称 * @param { * } args 传给命令函数的参数 * @return { * } 返回命令函数运行的返回值 */ _callCmdFn: function (fnName, args) { var cmdName = args[0].toLowerCase(), cmd, cmdFn cmd = this.commands[cmdName] || UE.commands[cmdName] cmdFn = cmd && cmd[fnName] //没有querycommandstate或者没有command的都默认返回0 if ((!cmd || !cmdFn) && fnName == 'queryCommandState') { return 0 } else if (cmdFn) { return cmdFn.apply(this, args) } }, /** * 执行编辑命令cmdName,完成富文本编辑效果 * @method execCommand * @param { String } cmdName 需要执行的命令 * @remind 具体命令的使用请参考命令列表 * @return { * } 返回命令函数运行的返回值 * @example * ```javascript * editor.execCommand(cmdName); * ``` */ execCommand: function (cmdName) { cmdName = cmdName.toLowerCase() var me = this, result, cmd = me.commands[cmdName] || UE.commands[cmdName] if (!cmd || !cmd.execCommand) { return null } if (!cmd.notNeedUndo && !me.__hasEnterExecCommand) { me.__hasEnterExecCommand = true if (me.queryCommandState.apply(me, arguments) != -1) { me.fireEvent('saveScene') me.fireEvent.apply( me, ['beforeexeccommand', cmdName].concat(arguments) ) result = this._callCmdFn('execCommand', arguments) //保存场景时,做了内容对比,再看是否进行contentchange触发,这里多触发了一次,去掉 // (!cmd.ignoreContentChange && !me._ignoreContentChange) && me.fireEvent('contentchange'); me.fireEvent.apply( me, ['afterexeccommand', cmdName].concat(arguments) ) me.fireEvent('saveScene') } me.__hasEnterExecCommand = false } else { result = this._callCmdFn('execCommand', arguments) !me.__hasEnterExecCommand && !cmd.ignoreContentChange && !me._ignoreContentChange && me.fireEvent('contentchange') } !me.__hasEnterExecCommand && !cmd.ignoreContentChange && !me._ignoreContentChange && me._selectionChange() return result }, /** * 根据传入的command命令,查选编辑器当前的选区,返回命令的状态 * @method queryCommandState * @param { String } cmdName 需要查询的命令名称 * @remind 具体命令的使用请参考命令列表 * @return { Number } number 返回放前命令的状态,返回值三种情况:(-1|0|1) * @example * ```javascript * editor.queryCommandState(cmdName) => (-1|0|1) * ``` * @see COMMAND.LIST */ queryCommandState: function (cmdName) { return this._callCmdFn('queryCommandState', arguments) }, /** * 根据传入的command命令,查选编辑器当前的选区,根据命令返回相关的值 * @method queryCommandValue * @param { String } cmdName 需要查询的命令名称 * @remind 具体命令的使用请参考命令列表 * @remind 只有部分插件有此方法 * @return { * } 返回每个命令特定的当前状态值 * @grammar editor.queryCommandValue(cmdName) => {*} * @see COMMAND.LIST */ queryCommandValue: function (cmdName) { return this._callCmdFn('queryCommandValue', arguments) }, /** * 检查编辑区域中是否有内容 * @method hasContents * @remind 默认有文本内容,或者有以下节点都不认为是空 * table,ul,ol,dl,iframe,area,base,col,hr,img,embed,input,link,meta,param * @return { Boolean } 检查有内容返回true,否则返回false * @example * ```javascript * editor.hasContents() * ``` */ /** * 检查编辑区域中是否有内容,若包含参数tags中的节点类型,直接返回true * @method hasContents * @param { Array } tags 传入数组判断时用到的节点类型 * @return { Boolean } 若文档中包含tags数组里对应的tag,返回true,否则返回false * @example * ```javascript * editor.hasContents(['span']); * ``` */ hasContents: function (tags) { if (tags) { for (var i = 0, ci; (ci = tags[i++]); ) { if (this.document.getElementsByTagName(ci).length > 0) { return true } } } if (!domUtils.isEmptyBlock(this.body)) { return true } //随时添加,定义的特殊标签如果存在,不能认为是空 tags = ['div'] for (i = 0; (ci = tags[i++]); ) { var nodes = domUtils.getElementsByTagName(this.document, ci) for (var n = 0, cn; (cn = nodes[n++]); ) { if (domUtils.isCustomeNode(cn)) { return true } } } return false }, /** * 重置编辑器,可用来做多个tab使用同一个编辑器实例 * @method reset * @remind 此方法会清空编辑器内容,清空回退列表,会触发reset事件 * @example * ```javascript * editor.reset() * ``` */ reset: function () { this.fireEvent('reset') }, /** * 设置当前编辑区域可以编辑 * @method setEnabled * @example * ```javascript * editor.setEnabled() * ``` */ setEnabled: function () { var me = this, range if (me.body.contentEditable == 'false') { me.body.contentEditable = true range = me.selection.getRange() //有可能内容丢失了 try { range.moveToBookmark(me.lastBk) delete me.lastBk } catch (e) { range.setStartAtFirst(me.body).collapse(true) } range.select(true) if (me.bkqueryCommandState) { me.queryCommandState = me.bkqueryCommandState delete me.bkqueryCommandState } if (me.bkqueryCommandValue) { me.queryCommandValue = me.bkqueryCommandValue delete me.bkqueryCommandValue } me.fireEvent('selectionchange') } }, enable: function () { return this.setEnabled() }, /** 设置当前编辑区域不可编辑 * @method setDisabled */ /** 设置当前编辑区域不可编辑,except中的命令除外 * @method setDisabled * @param { String } except 例外命令的字符串 * @remind 即使设置了disable,此处配置的例外命令仍然可以执行 * @example * ```javascript * editor.setDisabled('bold'); //禁用工具栏中除加粗之外的所有功能 * ``` */ /** 设置当前编辑区域不可编辑,except中的命令除外 * @method setDisabled * @param { Array } except 例外命令的字符串数组,数组中的命令仍然可以执行 * @remind 即使设置了disable,此处配置的例外命令仍然可以执行 * @example * ```javascript * editor.setDisabled(['bold','insertimage']); //禁用工具栏中除加粗和插入图片之外的所有功能 * ``` */ setDisabled: function (except) { var me = this except = except ? (utils.isArray(except) ? except : [except]) : [] if (me.body.contentEditable == 'true') { if (!me.lastBk) { me.lastBk = me.selection.getRange().createBookmark(true) } me.body.contentEditable = false me.bkqueryCommandState = me.queryCommandState me.bkqueryCommandValue = me.queryCommandValue me.queryCommandState = function (type) { if (utils.indexOf(except, type) != -1) { return me.bkqueryCommandState.apply(me, arguments) } return -1 } me.queryCommandValue = function (type) { if (utils.indexOf(except, type) != -1) { return me.bkqueryCommandValue.apply(me, arguments) } return null } me.fireEvent('selectionchange') } }, disable: function (except) { return this.setDisabled(except) }, /** * 设置默认内容 * @method _setDefaultContent * @private * @param { String } cont 要存入的内容 */ _setDefaultContent: (function () { function clear() { var me = this if (me.document.getElementById('initContent')) { me.body.innerHTML = '

' + (ie ? '' : '
') + '

' me.removeListener('firstBeforeExecCommand focus', clear) setTimeout(function () { me.focus() me._selectionChange() }, 0) } } return function (cont) { var me = this me.body.innerHTML = '

' + cont + '

' me.addListener('firstBeforeExecCommand focus', clear) } })(), /** * 显示编辑器 * @method setShow * @example * ```javascript * editor.setShow() * ``` */ setShow: function () { var me = this, range = me.selection.getRange() if (me.container.style.display == 'none') { //有可能内容丢失了 try { range.moveToBookmark(me.lastBk) delete me.lastBk } catch (e) { range.setStartAtFirst(me.body).collapse(true) } //ie下focus实效,所以做了个延迟 setTimeout(function () { range.select(true) }, 100) me.container.style.display = '' } }, show: function () { return this.setShow() }, /** * 隐藏编辑器 * @method setHide * @example * ```javascript * editor.setHide() * ``` */ setHide: function () { var me = this if (!me.lastBk) { me.lastBk = me.selection.getRange().createBookmark(true) } me.container.style.display = 'none' }, hide: function () { return this.setHide() }, /** * 根据指定的路径,获取对应的语言资源 * @method getLang * @param { String } path 路径根据的是lang目录下的语言文件的路径结构 * @return { Object | String } 根据路径返回语言资源的Json格式对象或者语言字符串 * @example * ```javascript * editor.getLang('contextMenu.delete'); //如果当前是中文,那返回是的是'删除' * ``` */ getLang: function (path) { // HaoChuan9421 if (!this.options) { return '' } var lang = UE.I18N[this.options.lang] if (!lang) { throw Error('not import language file') } path = (path || '').split('.') for (var i = 0, ci; (ci = path[i++]); ) { lang = lang[ci] if (!lang) break } return lang }, /** * 计算编辑器html内容字符串的长度 * @method getContentLength * @return { Number } 返回计算的长度 * @example * ```javascript * //编辑器html内容

132

* editor.getContentLength() //返回27 * ``` */ /** * 计算编辑器当前纯文本内容的长度 * @method getContentLength * @param { Boolean } ingoneHtml 传入true时,只按照纯文本来计算 * @return { Number } 返回计算的长度,内容中有hr/img/iframe标签,长度加1 * @example * ```javascript * //编辑器html内容

132

* editor.getContentLength() //返回3 * ``` */ getContentLength: function (ingoneHtml, tagNames) { var count = this.getContent(false, false, true).length if (ingoneHtml) { tagNames = (tagNames || []).concat(['hr', 'img', 'iframe']) count = this.getContentTxt().replace(/[\t\r\n]+/g, '').length for (var i = 0, ci; (ci = tagNames[i++]); ) { count += this.document.getElementsByTagName(ci).length } } return count }, /** * 注册输入过滤规则 * @method addInputRule * @param { Function } rule 要添加的过滤规则 * @example * ```javascript * editor.addInputRule(function(root){ * $.each(root.getNodesByTagName('div'),function(i,node){ * node.tagName="p"; * }); * }); * ``` */ addInputRule: function (rule) { this.inputRules.push(rule) }, /** * 执行注册的过滤规则 * @method filterInputRule * @param { UE.uNode } root 要过滤的uNode节点 * @remind 执行editor.setContent方法和执行'inserthtml'命令后,会运行该过滤函数 * @example * ```javascript * editor.filterInputRule(editor.body); * ``` * @see UE.Editor:addInputRule */ filterInputRule: function (root) { for (var i = 0, ci; (ci = this.inputRules[i++]); ) { ci.call(this, root) } }, /** * 注册输出过滤规则 * @method addOutputRule * @param { Function } rule 要添加的过滤规则 * @example * ```javascript * editor.addOutputRule(function(root){ * $.each(root.getNodesByTagName('p'),function(i,node){ * node.tagName="div"; * }); * }); * ``` */ addOutputRule: function (rule) { this.outputRules.push(rule) }, /** * 根据输出过滤规则,过滤编辑器内容 * @method filterOutputRule * @remind 执行editor.getContent方法的时候,会先运行该过滤函数 * @param { UE.uNode } root 要过滤的uNode节点 * @example * ```javascript * editor.filterOutputRule(editor.body); * ``` * @see UE.Editor:addOutputRule */ filterOutputRule: function (root) { for (var i = 0, ci; (ci = this.outputRules[i++]); ) { ci.call(this, root) } }, /** * 根据action名称获取请求的路径 * @method getActionUrl * @remind 假如没有设置serverUrl,会根据imageUrl设置默认的controller路径 * @param { String } action action名称 * @example * ```javascript * editor.getActionUrl('config'); //返回 "/ueditor/php/controller.php?action=config" * editor.getActionUrl('image'); //返回 "/ueditor/php/controller.php?action=uplaodimage" * editor.getActionUrl('scrawl'); //返回 "/ueditor/php/controller.php?action=uplaodscrawl" * editor.getActionUrl('imageManager'); //返回 "/ueditor/php/controller.php?action=listimage" * ``` */ getActionUrl: function (action) { var actionName = this.getOpt(action) || action, imageUrl = this.getOpt('imageUrl'), serverUrl = this.getOpt('serverUrl') if (!serverUrl && imageUrl) { serverUrl = imageUrl.replace(/^(.*[\/]).+([\.].+)$/, '$1controller$2') } if (serverUrl) { serverUrl = serverUrl + (serverUrl.indexOf('?') == -1 ? '?' : '&') + 'action=' + (actionName || '') return utils.formatUrl(serverUrl) } else { return '' } } } utils.inherits(Editor, EventBase) })() // core/Editor.defaultoptions.js //维护编辑器一下默认的不在插件中的配置项 UE.Editor.defaultOptions = function (editor) { var _url = editor.options.UEDITOR_HOME_URL return { isShow: true, initialContent: '', initialStyle: '', autoClearinitialContent: false, iframeCssUrl: _url + 'themes/iframe.css', textarea: 'editorValue', focus: false, focusInEnd: true, autoClearEmptyNode: true, fullscreen: false, readonly: false, zIndex: 999, imagePopup: true, enterTag: 'p', customDomain: false, lang: 'zh-cn', langPath: _url + 'lang/', theme: 'default', themePath: _url + 'themes/', allHtmlEnabled: false, scaleEnabled: false, tableNativeEditInFF: false, autoSyncData: true, fileNameFormat: '{time}{rand:6}' } } // core/loadconfig.js ;(function () { UE.Editor.prototype.loadServerConfig = function () { var me = this setTimeout(function () { try { me.options.imageUrl && me.setOpt( 'serverUrl', me.options.imageUrl.replace( /^(.*[\/]).+([\.].+)$/, '$1controller$2' ) ) var configUrl = me.getActionUrl('config'), isJsonp = utils.isCrossDomainUrl(configUrl) /* 发出ajax请求 */ me._serverConfigLoaded = false configUrl && UE.ajax.request(configUrl, { method: 'GET', dataType: isJsonp ? 'jsonp' : '', onsuccess: function (r) { try { var config = isJsonp ? r : eval('(' + r.responseText + ')') utils.extend(me.options, config) me.fireEvent('serverConfigLoaded') me._serverConfigLoaded = true } catch (e) { showErrorMsg(me.getLang('loadconfigFormatError')) } }, onerror: function () { showErrorMsg(me.getLang('loadconfigHttpError')) } }) } catch (e) { showErrorMsg(me.getLang('loadconfigError')) } }) function showErrorMsg(msg) { console && console.error(msg) //me.fireEvent('showMessage', { // 'title': msg, // 'type': 'error' //}); } } UE.Editor.prototype.isServerConfigLoaded = function () { var me = this return me._serverConfigLoaded || false } UE.Editor.prototype.afterConfigReady = function (handler) { if (!handler || !utils.isFunction(handler)) return var me = this var readyHandler = function () { handler.apply(me, arguments) me.removeListener('serverConfigLoaded', readyHandler) } if (me.isServerConfigLoaded()) { handler.call(me, 'serverConfigLoaded') } else { me.addListener('serverConfigLoaded', readyHandler) } } })() // core/ajax.js /** * @file * @module UE.ajax * @since 1.2.6.1 */ /** * 提供对ajax请求的支持 * @module UE.ajax */ UE.ajax = (function () { //创建一个ajaxRequest对象 var fnStr = 'XMLHttpRequest()' try { new ActiveXObject('Msxml2.XMLHTTP') fnStr = "ActiveXObject('Msxml2.XMLHTTP')" } catch (e) { try { new ActiveXObject('Microsoft.XMLHTTP') fnStr = "ActiveXObject('Microsoft.XMLHTTP')" } catch (e) {} } var creatAjaxRequest = new Function('return new ' + fnStr) /** * 将json参数转化成适合ajax提交的参数列表 * @param json */ function json2str(json) { var strArr = [] for (var i in json) { //忽略默认的几个参数 if ( i == 'method' || i == 'timeout' || i == 'async' || i == 'dataType' || i == 'callback' ) continue //忽略控制 if (json[i] == undefined || json[i] == null) continue //传递过来的对象和函数不在提交之列 if ( !( (typeof json[i]).toLowerCase() == 'function' || (typeof json[i]).toLowerCase() == 'object' ) ) { strArr.push(encodeURIComponent(i) + '=' + encodeURIComponent(json[i])) } else if (utils.isArray(json[i])) { //支持传数组内容 for (var j = 0; j < json[i].length; j++) { strArr.push( encodeURIComponent(i) + '[]=' + encodeURIComponent(json[i][j]) ) } } } return strArr.join('&') } function doAjax(url, ajaxOptions) { var xhr = creatAjaxRequest(), //是否超时 timeIsOut = false, //默认参数 defaultAjaxOptions = { method: 'POST', timeout: 5000, async: true, data: {}, //需要传递对象的话只能覆盖 onsuccess: function () {}, onerror: function () {} } if (typeof url === 'object') { ajaxOptions = url url = ajaxOptions.url } if (!xhr || !url) return var ajaxOpts = ajaxOptions ? utils.extend(defaultAjaxOptions, ajaxOptions) : defaultAjaxOptions var submitStr = json2str(ajaxOpts) // { name:"Jim",city:"Beijing" } --> "name=Jim&city=Beijing" //如果用户直接通过data参数传递json对象过来,则也要将此json对象转化为字符串 if (!utils.isEmptyObject(ajaxOpts.data)) { submitStr += (submitStr ? '&' : '') + json2str(ajaxOpts.data) } //超时检测 var timerID = setTimeout(function () { if (xhr.readyState != 4) { timeIsOut = true xhr.abort() clearTimeout(timerID) } }, ajaxOpts.timeout) var method = ajaxOpts.method.toUpperCase() var str = url + (url.indexOf('?') == -1 ? '?' : '&') + (method == 'POST' ? '' : submitStr + '&noCache=' + +new Date()) xhr.open(method, str, ajaxOpts.async) xhr.onreadystatechange = function () { if (xhr.readyState == 4) { if (!timeIsOut && xhr.status == 200) { ajaxOpts.onsuccess(xhr) } else { ajaxOpts.onerror(xhr) } } } if (method == 'POST') { xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' ) xhr.send(submitStr) } else { xhr.send(null) } } function doJsonp(url, opts) { var successhandler = opts.onsuccess || function () {}, scr = document.createElement('SCRIPT'), options = opts || {}, charset = options['charset'], callbackField = options['jsonp'] || 'callback', callbackFnName, timeOut = options['timeOut'] || 0, timer, reg = new RegExp('(\\?|&)' + callbackField + '=([^&]*)'), matches if (utils.isFunction(successhandler)) { callbackFnName = 'bd__editor__' + Math.floor(Math.random() * 2147483648).toString(36) window[callbackFnName] = getCallBack(0) } else if (utils.isString(successhandler)) { callbackFnName = successhandler } else { if ((matches = reg.exec(url))) { callbackFnName = matches[2] } } url = url.replace(reg, '\x241' + callbackField + '=' + callbackFnName) if (url.search(reg) < 0) { url += (url.indexOf('?') < 0 ? '?' : '&') + callbackField + '=' + callbackFnName } var queryStr = json2str(opts) // { name:"Jim",city:"Beijing" } --> "name=Jim&city=Beijing" //如果用户直接通过data参数传递json对象过来,则也要将此json对象转化为字符串 if (!utils.isEmptyObject(opts.data)) { queryStr += (queryStr ? '&' : '') + json2str(opts.data) } if (queryStr) { url = url.replace(/\?/, '?' + queryStr + '&') } scr.onerror = getCallBack(1) if (timeOut) { timer = setTimeout(getCallBack(1), timeOut) } createScriptTag(scr, url, charset) function createScriptTag(scr, url, charset) { scr.setAttribute('type', 'text/javascript') scr.setAttribute('defer', 'defer') charset && scr.setAttribute('charset', charset) scr.setAttribute('src', url) document.getElementsByTagName('head')[0].appendChild(scr) } function getCallBack(onTimeOut) { return function () { try { if (onTimeOut) { options.onerror && options.onerror() } else { try { clearTimeout(timer) successhandler.apply(window, arguments) } catch (e) {} } } catch (exception) { options.onerror && options.onerror.call(window, exception) } finally { options.oncomplete && options.oncomplete.apply(window, arguments) scr.parentNode && scr.parentNode.removeChild(scr) window[callbackFnName] = null try { delete window[callbackFnName] } catch (e) {} } } } } return { /** * 根据给定的参数项,向指定的url发起一个ajax请求。 ajax请求完成后,会根据请求结果调用相应回调: 如果请求 * 成功, 则调用onsuccess回调, 失败则调用 onerror 回调 * @method request * @param { URLString } url ajax请求的url地址 * @param { Object } ajaxOptions ajax请求选项的键值对,支持的选项如下: * @example * ```javascript * //向sayhello.php发起一个异步的Ajax GET请求, 请求超时时间为10s, 请求完成后执行相应的回调。 * UE.ajax.requeset( 'sayhello.php', { * * //请求方法。可选值: 'GET', 'POST',默认值是'POST' * method: 'GET', * * //超时时间。 默认为5000, 单位是ms * timeout: 10000, * * //是否是异步请求。 true为异步请求, false为同步请求 * async: true, * * //请求携带的数据。如果请求为GET请求, data会经过stringify后附加到请求url之后。 * data: { * name: 'ueditor' * }, * * //请求成功后的回调, 该回调接受当前的XMLHttpRequest对象作为参数。 * onsuccess: function ( xhr ) { * console.log( xhr.responseText ); * }, * * //请求失败或者超时后的回调。 * onerror: function ( xhr ) { * alert( 'Ajax请求失败' ); * } * * } ); * ``` */ /** * 根据给定的参数项发起一个ajax请求, 参数项里必须包含一个url地址。 ajax请求完成后,会根据请求结果调用相应回调: 如果请求 * 成功, 则调用onsuccess回调, 失败则调用 onerror 回调。 * @method request * @warning 如果在参数项里未提供一个key为“url”的地址值,则该请求将直接退出。 * @param { Object } ajaxOptions ajax请求选项的键值对,支持的选项如下: * @example * ```javascript * * //向sayhello.php发起一个异步的Ajax POST请求, 请求超时时间为5s, 请求完成后不执行任何回调。 * UE.ajax.requeset( 'sayhello.php', { * * //请求的地址, 该项是必须的。 * url: 'sayhello.php' * * } ); * ``` */ request: function (url, opts) { if (opts && opts.dataType == 'jsonp') { doJsonp(url, opts) } else { doAjax(url, opts) } }, getJSONP: function (url, data, fn) { var opts = { data: data, oncomplete: fn } doJsonp(url, opts) } } })() // core/filterword.js /** * UE过滤word的静态方法 * @file */ /** * UEditor公用空间,UEditor所有的功能都挂载在该空间下 * @module UE */ /** * 根据传入html字符串过滤word * @module UE * @since 1.2.6.1 * @method filterWord * @param { String } html html字符串 * @return { String } 已过滤后的结果字符串 * @example * ```javascript * UE.filterWord(html); * ``` */ var filterWord = (UE.filterWord = (function () { //是否是word过来的内容 function isWordDocument(str) { return /(class="?Mso|style="[^"]*\bmso\-|w:WordDocument|<(v|o):|lang=)/gi.test( str ) } //去掉小数 function transUnit(v) { v = v.replace(/[\d.]+\w+/g, function (m) { return utils.transUnitToPx(m) }) return v } function filterPasteWord(str) { return ( str .replace(/[\t\r\n]+/g, ' ') .replace(//gi, '') //转换图片 .replace(/]*>[\s\S]*?.<\/v:shape>/gi, function (str) { //opera能自己解析出image所这里直接返回空 if (browser.opera) { return '' } try { //有可能是bitmap占为图,无用,直接过滤掉,主要体现在粘贴excel表格中 if (/Bitmap/i.test(str)) { return '' } var width = str.match(/width:([ \d.]*p[tx])/i)[1], height = str.match(/height:([ \d.]*p[tx])/i)[1], src = str.match(/src=\s*"([^"]*)"/i)[1] return ( '' ) } catch (e) { return '' } }) //针对wps添加的多余标签处理 .replace(/<\/?div[^>]*>/g, '') //去掉多余的属性 .replace(/v:\w+=(["']?)[^'"]+\1/g, '') .replace( /<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|xml|meta|link|style|\w+:\w+)(?=[\s\/>]))[^>]*>/gi, '' ) .replace( /

]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi, '

$1

' ) //去掉多余的属性 .replace( /\s+(class|lang|align)\s*=\s*(['"]?)([\w-]+)\2/gi, function (str, name, marks, val) { //保留list的标示 return name == 'class' && val == 'MsoListParagraph' ? str : '' } ) //清除多余的font/span不能匹配 有可能是空格 .replace(/<(font|span)[^>]*>(\s*)<\/\1>/gi, function (a, b, c) { return c.replace(/[\t\r\n ]+/g, ' ') }) //处理style的问题 .replace( /(<[a-z][^>]*)\sstyle=(["'])([^\2]*?)\2/gi, function (str, tag, tmp, style) { var n = [], s = style .replace(/^\s+|\s+$/, '') .replace(/'/g, "'") .replace(/"/gi, "'") .replace(/[\d.]+(cm|pt)/g, function (str) { return utils.transUnitToPx(str) }) .split(/;\s*/g) for (var i = 0, v; (v = s[i]); i++) { var name, value, parts = v.split(':') if (parts.length == 2) { name = parts[0].toLowerCase() value = parts[1].toLowerCase() if ( (/^(background)\w*/.test(name) && value.replace(/(initial|\s)/g, '').length == 0) || (/^(margin)\w*/.test(name) && /^0\w+$/.test(value)) ) { continue } switch (name) { case 'mso-padding-alt': case 'mso-padding-top-alt': case 'mso-padding-right-alt': case 'mso-padding-bottom-alt': case 'mso-padding-left-alt': case 'mso-margin-alt': case 'mso-margin-top-alt': case 'mso-margin-right-alt': case 'mso-margin-bottom-alt': case 'mso-margin-left-alt': //ie下会出现挤到一起的情况 //case "mso-table-layout-alt": case 'mso-height': case 'mso-width': case 'mso-vertical-align-alt': //trace:1819 ff下会解析出padding在table上 if (!/]/.test(html)) { return UE.htmlparser(html).children[0] } else { return new uNode({ type: 'element', children: [], tagName: html }) } } uNode.createText = function (data, noTrans) { return new UE.uNode({ type: 'text', data: noTrans ? data : utils.unhtml(data || '') }) } function nodeToHtml(node, arr, formatter, current) { switch (node.type) { case 'root': for (var i = 0, ci; (ci = node.children[i++]); ) { //插入新行 if ( formatter && ci.type == 'element' && !dtd.$inlineWithA[ci.tagName] && i > 1 ) { insertLine(arr, current, true) insertIndent(arr, current) } nodeToHtml(ci, arr, formatter, current) } break case 'text': isText(node, arr) break case 'element': isElement(node, arr, formatter, current) break case 'comment': isComment(node, arr, formatter) } return arr } function isText(node, arr) { if (node.parentNode.tagName == 'pre') { //源码模式下输入html标签,不能做转换处理,直接输出 arr.push(node.data) } else { arr.push( notTransTagName[node.parentNode.tagName] ? utils.html(node.data) : node.data.replace(/[ ]{2}/g, '  ') ) } } function isElement(node, arr, formatter, current) { var attrhtml = '' if (node.attrs) { attrhtml = [] var attrs = node.attrs for (var a in attrs) { //这里就针对 //

'

//这里边的\"做转换,要不用innerHTML直接被截断了,属性src //有可能做的不够 attrhtml.push( a + (attrs[a] !== undefined ? '="' + (notTransAttrs[a] ? utils.html(attrs[a]).replace(/["]/g, function (a) { return '"' }) : utils.unhtml(attrs[a])) + '"' : '') ) } attrhtml = attrhtml.join(' ') } arr.push( '<' + node.tagName + (attrhtml ? ' ' + attrhtml : '') + (dtd.$empty[node.tagName] ? '/' : '') + '>' ) //插入新行 if ( formatter && !dtd.$inlineWithA[node.tagName] && node.tagName != 'pre' ) { if (node.children && node.children.length) { current = insertLine(arr, current, true) insertIndent(arr, current) } } if (node.children && node.children.length) { for (var i = 0, ci; (ci = node.children[i++]); ) { if ( formatter && ci.type == 'element' && !dtd.$inlineWithA[ci.tagName] && i > 1 ) { insertLine(arr, current) insertIndent(arr, current) } nodeToHtml(ci, arr, formatter, current) } } if (!dtd.$empty[node.tagName]) { if ( formatter && !dtd.$inlineWithA[node.tagName] && node.tagName != 'pre' ) { if (node.children && node.children.length) { current = insertLine(arr, current) insertIndent(arr, current) } } arr.push('') } } function isComment(node, arr) { arr.push('') } function getNodeById(root, id) { var node if (root.type == 'element' && root.getAttr('id') == id) { return root } if (root.children && root.children.length) { for (var i = 0, ci; (ci = root.children[i++]); ) { if ((node = getNodeById(ci, id))) { return node } } } } function getNodesByTagName(node, tagName, arr) { if (node.type == 'element' && node.tagName == tagName) { arr.push(node) } if (node.children && node.children.length) { for (var i = 0, ci; (ci = node.children[i++]); ) { getNodesByTagName(ci, tagName, arr) } } } function nodeTraversal(root, fn) { if (root.children && root.children.length) { for (var i = 0, ci; (ci = root.children[i]); ) { nodeTraversal(ci, fn) //ci被替换的情况,这里就不再走 fn了 if (ci.parentNode) { if (ci.children && ci.children.length) { fn(ci) } if (ci.parentNode) i++ } } } else { fn(root) } } uNode.prototype = { /** * 当前节点对象,转换成html文本 * @method toHtml * @return { String } 返回转换后的html字符串 * @example * ```javascript * node.toHtml(); * ``` */ /** * 当前节点对象,转换成html文本 * @method toHtml * @param { Boolean } formatter 是否格式化返回值 * @return { String } 返回转换后的html字符串 * @example * ```javascript * node.toHtml( true ); * ``` */ toHtml: function (formatter) { var arr = [] nodeToHtml(this, arr, formatter, 0) return arr.join('') }, /** * 获取节点的html内容 * @method innerHTML * @warning 假如节点的type不是'element',或节点的标签名称不在dtd列表里,直接返回当前节点 * @return { String } 返回节点的html内容 * @example * ```javascript * var htmlstr = node.innerHTML(); * ``` */ /** * 设置节点的html内容 * @method innerHTML * @warning 假如节点的type不是'element',或节点的标签名称不在dtd列表里,直接返回当前节点 * @param { String } htmlstr 传入要设置的html内容 * @return { UE.uNode } 返回节点本身 * @example * ```javascript * node.innerHTML('text'); * ``` */ innerHTML: function (htmlstr) { if (this.type != 'element' || dtd.$empty[this.tagName]) { return this } if (utils.isString(htmlstr)) { if (this.children) { for (var i = 0, ci; (ci = this.children[i++]); ) { ci.parentNode = null } } this.children = [] var tmpRoot = UE.htmlparser(htmlstr) for (var i = 0, ci; (ci = tmpRoot.children[i++]); ) { this.children.push(ci) ci.parentNode = this } return this } else { var tmpRoot = new UE.uNode({ type: 'root', children: this.children }) return tmpRoot.toHtml() } }, /** * 获取节点的纯文本内容 * @method innerText * @warning 假如节点的type不是'element',或节点的标签名称不在dtd列表里,直接返回当前节点 * @return { String } 返回节点的存文本内容 * @example * ```javascript * var textStr = node.innerText(); * ``` */ /** * 设置节点的纯文本内容 * @method innerText * @warning 假如节点的type不是'element',或节点的标签名称不在dtd列表里,直接返回当前节点 * @param { String } textStr 传入要设置的文本内容 * @return { UE.uNode } 返回节点本身 * @example * ```javascript * node.innerText('text'); * ``` */ innerText: function (textStr, noTrans) { if (this.type != 'element' || dtd.$empty[this.tagName]) { return this } if (textStr) { if (this.children) { for (var i = 0, ci; (ci = this.children[i++]); ) { ci.parentNode = null } } this.children = [] this.appendChild(uNode.createText(textStr, noTrans)) return this } else { return this.toHtml().replace(/<[^>]+>/g, '') } }, /** * 获取当前对象的data属性 * @method getData * @return { Object } 若节点的type值是elemenet,返回空字符串,否则返回节点的data属性 * @example * ```javascript * node.getData(); * ``` */ getData: function () { if (this.type == 'element') return '' return this.data }, /** * 获取当前节点下的第一个子节点 * @method firstChild * @return { UE.uNode } 返回第一个子节点 * @example * ```javascript * node.firstChild(); //返回第一个子节点 * ``` */ firstChild: function () { // if (this.type != 'element' || dtd.$empty[this.tagName]) { // return this; // } return this.children ? this.children[0] : null }, /** * 获取当前节点下的最后一个子节点 * @method lastChild * @return { UE.uNode } 返回最后一个子节点 * @example * ```javascript * node.lastChild(); //返回最后一个子节点 * ``` */ lastChild: function () { // if (this.type != 'element' || dtd.$empty[this.tagName] ) { // return this; // } return this.children ? this.children[this.children.length - 1] : null }, /** * 获取和当前节点有相同父亲节点的前一个节点 * @method previousSibling * @return { UE.uNode } 返回前一个节点 * @example * ```javascript * node.children[2].previousSibling(); //返回子节点node.children[1] * ``` */ previousSibling: function () { var parent = this.parentNode for (var i = 0, ci; (ci = parent.children[i]); i++) { if (ci === this) { return i == 0 ? null : parent.children[i - 1] } } }, /** * 获取和当前节点有相同父亲节点的后一个节点 * @method nextSibling * @return { UE.uNode } 返回后一个节点,找不到返回null * @example * ```javascript * node.children[2].nextSibling(); //如果有,返回子节点node.children[3] * ``` */ nextSibling: function () { var parent = this.parentNode for (var i = 0, ci; (ci = parent.children[i++]); ) { if (ci === this) { return parent.children[i] } } }, /** * 用新的节点替换当前节点 * @method replaceChild * @param { UE.uNode } target 要替换成该节点参数 * @param { UE.uNode } source 要被替换掉的节点 * @return { UE.uNode } 返回替换之后的节点对象 * @example * ```javascript * node.replaceChild(newNode, childNode); //用newNode替换childNode,childNode是node的子节点 * ``` */ replaceChild: function (target, source) { if (this.children) { if (target.parentNode) { target.parentNode.removeChild(target) } for (var i = 0, ci; (ci = this.children[i]); i++) { if (ci === source) { this.children.splice(i, 1, target) source.parentNode = null target.parentNode = this return target } } } }, /** * 在节点的子节点列表最后位置插入一个节点 * @method appendChild * @param { UE.uNode } node 要插入的节点 * @return { UE.uNode } 返回刚插入的子节点 * @example * ```javascript * node.appendChild( newNode ); //在node内插入子节点newNode * ``` */ appendChild: function (node) { if ( this.type == 'root' || (this.type == 'element' && !dtd.$empty[this.tagName]) ) { if (!this.children) { this.children = [] } if (node.parentNode) { node.parentNode.removeChild(node) } for (var i = 0, ci; (ci = this.children[i]); i++) { if (ci === node) { this.children.splice(i, 1) break } } this.children.push(node) node.parentNode = this return node } }, /** * 在传入节点的前面插入一个节点 * @method insertBefore * @param { UE.uNode } target 要插入的节点 * @param { UE.uNode } source 在该参数节点前面插入 * @return { UE.uNode } 返回刚插入的子节点 * @example * ```javascript * node.parentNode.insertBefore(newNode, node); //在node节点后面插入newNode * ``` */ insertBefore: function (target, source) { if (this.children) { if (target.parentNode) { target.parentNode.removeChild(target) } for (var i = 0, ci; (ci = this.children[i]); i++) { if (ci === source) { this.children.splice(i, 0, target) target.parentNode = this return target } } } }, /** * 在传入节点的后面插入一个节点 * @method insertAfter * @param { UE.uNode } target 要插入的节点 * @param { UE.uNode } source 在该参数节点后面插入 * @return { UE.uNode } 返回刚插入的子节点 * @example * ```javascript * node.parentNode.insertAfter(newNode, node); //在node节点后面插入newNode * ``` */ insertAfter: function (target, source) { if (this.children) { if (target.parentNode) { target.parentNode.removeChild(target) } for (var i = 0, ci; (ci = this.children[i]); i++) { if (ci === source) { this.children.splice(i + 1, 0, target) target.parentNode = this return target } } } }, /** * 从当前节点的子节点列表中,移除节点 * @method removeChild * @param { UE.uNode } node 要移除的节点引用 * @param { Boolean } keepChildren 是否保留移除节点的子节点,若传入true,自动把移除节点的子节点插入到移除的位置 * @return { * } 返回刚移除的子节点 * @example * ```javascript * node.removeChild(childNode,true); //在node的子节点列表中移除child节点,并且吧child的子节点插入到移除的位置 * ``` */ removeChild: function (node, keepChildren) { if (this.children) { for (var i = 0, ci; (ci = this.children[i]); i++) { if (ci === node) { this.children.splice(i, 1) ci.parentNode = null if (keepChildren && ci.children && ci.children.length) { for (var j = 0, cj; (cj = ci.children[j]); j++) { this.children.splice(i + j, 0, cj) cj.parentNode = this } } return ci } } } }, /** * 获取当前节点所代表的元素属性,即获取attrs对象下的属性值 * @method getAttr * @param { String } attrName 要获取的属性名称 * @return { * } 返回attrs对象下的属性值 * @example * ```javascript * node.getAttr('title'); * ``` */ getAttr: function (attrName) { return this.attrs && this.attrs[attrName.toLowerCase()] }, /** * 设置当前节点所代表的元素属性,即设置attrs对象下的属性值 * @method setAttr * @param { String } attrName 要设置的属性名称 * @param { * } attrVal 要设置的属性值,类型视设置的属性而定 * @return { * } 返回attrs对象下的属性值 * @example * ```javascript * node.setAttr('title','标题'); * ``` */ setAttr: function (attrName, attrVal) { if (!attrName) { delete this.attrs return } if (!this.attrs) { this.attrs = {} } if (utils.isObject(attrName)) { for (var a in attrName) { if (!attrName[a]) { delete this.attrs[a] } else { this.attrs[a.toLowerCase()] = attrName[a] } } } else { if (!attrVal) { delete this.attrs[attrName] } else { this.attrs[attrName.toLowerCase()] = attrVal } } }, /** * 获取当前节点在父节点下的位置索引 * @method getIndex * @return { Number } 返回索引数值,如果没有父节点,返回-1 * @example * ```javascript * node.getIndex(); * ``` */ getIndex: function () { var parent = this.parentNode for (var i = 0, ci; (ci = parent.children[i]); i++) { if (ci === this) { return i } } return -1 }, /** * 在当前节点下,根据id查找节点 * @method getNodeById * @param { String } id 要查找的id * @return { UE.uNode } 返回找到的节点 * @example * ```javascript * node.getNodeById('textId'); * ``` */ getNodeById: function (id) { var node if (this.children && this.children.length) { for (var i = 0, ci; (ci = this.children[i++]); ) { if ((node = getNodeById(ci, id))) { return node } } } }, /** * 在当前节点下,根据元素名称查找节点列表 * @method getNodesByTagName * @param { String } tagNames 要查找的元素名称 * @return { Array } 返回找到的节点列表 * @example * ```javascript * node.getNodesByTagName('span'); * ``` */ getNodesByTagName: function (tagNames) { tagNames = utils .trim(tagNames) .replace(/[ ]{2,}/g, ' ') .split(' ') var arr = [], me = this utils.each(tagNames, function (tagName) { if (me.children && me.children.length) { for (var i = 0, ci; (ci = me.children[i++]); ) { getNodesByTagName(ci, tagName, arr) } } }) return arr }, /** * 根据样式名称,获取节点的样式值 * @method getStyle * @param { String } name 要获取的样式名称 * @return { String } 返回样式值 * @example * ```javascript * node.getStyle('font-size'); * ``` */ getStyle: function (name) { var cssStyle = this.getAttr('style') if (!cssStyle) { return '' } var reg = new RegExp('(^|;)\\s*' + name + ':([^;]+)', 'i') var match = cssStyle.match(reg) if (match && match[0]) { return match[2] } return '' }, /** * 给节点设置样式 * @method setStyle * @param { String } name 要设置的的样式名称 * @param { String } val 要设置的的样值 * @example * ```javascript * node.setStyle('font-size', '12px'); * ``` */ setStyle: function (name, val) { function exec(name, val) { var reg = new RegExp('(^|;)\\s*' + name + ':([^;]+;?)', 'gi') cssStyle = cssStyle.replace(reg, '$1') if (val) { cssStyle = name + ':' + utils.unhtml(val) + ';' + cssStyle } } var cssStyle = this.getAttr('style') if (!cssStyle) { cssStyle = '' } if (utils.isObject(name)) { for (var a in name) { exec(a, name[a]) } } else { exec(name, val) } this.setAttr('style', utils.trim(cssStyle)) }, /** * 传入一个函数,递归遍历当前节点下的所有节点 * @method traversal * @param { Function } fn 遍历到节点的时,传入节点作为参数,运行此函数 * @example * ```javascript * traversal(node, function(){ * console.log(node.type); * }); * ``` */ traversal: function (fn) { if (this.children && this.children.length) { nodeTraversal(this, fn) } return this } } })() // core/htmlparser.js /** * html字符串转换成uNode节点 * @file * @module UE * @since 1.2.6.1 */ /** * UEditor公用空间,UEditor所有的功能都挂载在该空间下 * @unfile * @module UE */ /** * html字符串转换成uNode节点的静态方法 * @method htmlparser * @param { String } htmlstr 要转换的html代码 * @param { Boolean } ignoreBlank 若设置为true,转换的时候忽略\n\r\t等空白字符 * @return { uNode } 给定的html片段转换形成的uNode对象 * @example * ```javascript * var root = UE.htmlparser('

htmlparser

', true); * ``` */ var htmlparser = (UE.htmlparser = function (htmlstr, ignoreBlank) { //todo 原来的方式 [^"'<>\/] 有\/就不能配对上 ' ) } html.push('') } //禁止指定table-width return '
这样的标签了 //先去掉了,加上的原因忘了,这里先记录 var re_tag = /<(?:(?:\/([^>]+)>)|(?:!--([\S|\s]*?)-->)|(?:([^\s\/<>]+)\s*((?:(?:"[^"]*")|(?:'[^']*')|[^"'<>])*)\/?>))/g, re_attr = /([\w\-:.]+)(?:(?:\s*=\s*(?:(?:"([^"]*)")|(?:'([^']*)')|([^\s>]+)))|(?=\s|$))/g //ie下取得的html可能会有\n存在,要去掉,在处理replace(/[\t\r\n]*/g,'');代码高量的\n不能去除 var allowEmptyTags = { b: 1, code: 1, i: 1, u: 1, strike: 1, s: 1, tt: 1, strong: 1, q: 1, samp: 1, em: 1, span: 1, sub: 1, img: 1, sup: 1, font: 1, big: 1, small: 1, iframe: 1, a: 1, br: 1, pre: 1 } htmlstr = htmlstr.replace(new RegExp(domUtils.fillChar, 'g'), '') if (!ignoreBlank) { htmlstr = htmlstr.replace( new RegExp( '[\\r\\t\\n' + (ignoreBlank ? '' : ' ') + ']*]*)>[\\r\\t\\n' + (ignoreBlank ? '' : ' ') + ']*', 'g' ), function (a, b) { //br暂时单独处理 if (b && allowEmptyTags[b.toLowerCase()]) { return a.replace(/(^[\n\r]+)|([\n\r]+$)/g, '') } return a .replace( new RegExp('^[\\r\\n' + (ignoreBlank ? '' : ' ') + ']+'), '' ) .replace( new RegExp('[\\r\\n' + (ignoreBlank ? '' : ' ') + ']+$'), '' ) } ) } var notTransAttrs = { href: 1, src: 1 } var uNode = UE.uNode, needParentNode = { td: 'tr', tr: ['tbody', 'thead', 'tfoot'], tbody: 'table', th: 'tr', thead: 'table', tfoot: 'table', caption: 'table', li: ['ul', 'ol'], dt: 'dl', dd: 'dl', option: 'select' }, needChild = { ol: 'li', ul: 'li' } function text(parent, data) { if (needChild[parent.tagName]) { var tmpNode = uNode.createElement(needChild[parent.tagName]) parent.appendChild(tmpNode) tmpNode.appendChild(uNode.createText(data)) parent = tmpNode } else { parent.appendChild(uNode.createText(data)) } } function element(parent, tagName, htmlattr) { var needParentTag if ((needParentTag = needParentNode[tagName])) { var tmpParent = parent, hasParent while (tmpParent.type != 'root') { if ( utils.isArray(needParentTag) ? utils.indexOf(needParentTag, tmpParent.tagName) != -1 : needParentTag == tmpParent.tagName ) { parent = tmpParent hasParent = true break } tmpParent = tmpParent.parentNode } if (!hasParent) { parent = element( parent, utils.isArray(needParentTag) ? needParentTag[0] : needParentTag ) } } //按dtd处理嵌套 // if(parent.type != 'root' && !dtd[parent.tagName][tagName]) // parent = parent.parentNode; var elm = new uNode({ parentNode: parent, type: 'element', tagName: tagName.toLowerCase(), //是自闭合的处理一下 children: dtd.$empty[tagName] ? null : [] }) //如果属性存在,处理属性 if (htmlattr) { var attrs = {}, match while ((match = re_attr.exec(htmlattr))) { attrs[match[1].toLowerCase()] = notTransAttrs[match[1].toLowerCase()] ? match[2] || match[3] || match[4] : utils.unhtml(match[2] || match[3] || match[4]) } elm.attrs = attrs } //trace:3970 // //如果parent下不能放elm // if(dtd.$inline[parent.tagName] && dtd.$block[elm.tagName] && !dtd[parent.tagName][elm.tagName]){ // parent = parent.parentNode; // elm.parentNode = parent; // } parent.children.push(elm) //如果是自闭合节点返回父亲节点 return dtd.$empty[tagName] ? parent : elm } function comment(parent, data) { parent.children.push( new uNode({ type: 'comment', data: data, parentNode: parent }) ) } var match, currentIndex = 0, nextIndex = 0 //设置根节点 var root = new uNode({ type: 'root', children: [] }) var currentParent = root while ((match = re_tag.exec(htmlstr))) { currentIndex = match.index try { if (currentIndex > nextIndex) { //text node text(currentParent, htmlstr.slice(nextIndex, currentIndex)) } if (match[3]) { if (dtd.$cdata[currentParent.tagName]) { text(currentParent, match[0]) } else { //start tag currentParent = element( currentParent, match[3].toLowerCase(), match[4] ) } } else if (match[1]) { if (currentParent.type != 'root') { if (dtd.$cdata[currentParent.tagName] && !dtd.$cdata[match[1]]) { text(currentParent, match[0]) } else { var tmpParent = currentParent while ( currentParent.type == 'element' && currentParent.tagName != match[1].toLowerCase() ) { currentParent = currentParent.parentNode if (currentParent.type == 'root') { currentParent = tmpParent throw 'break' } } //end tag currentParent = currentParent.parentNode } } } else if (match[2]) { //comment comment(currentParent, match[2]) } } catch (e) {} nextIndex = re_tag.lastIndex } //如果结束是文本,就有可能丢掉,所以这里手动判断一下 //例如
  • sdfsdfsdf
  • sdfsdfsdfsdf if (nextIndex < htmlstr.length) { text(currentParent, htmlstr.slice(nextIndex)) } return root }) // core/filternode.js /** * UE过滤节点的静态方法 * @file */ /** * UEditor公用空间,UEditor所有的功能都挂载在该空间下 * @module UE */ /** * 根据传入节点和过滤规则过滤相应节点 * @module UE * @since 1.2.6.1 * @method filterNode * @param { Object } root 指定root节点 * @param { Object } rules 过滤规则json对象 * @example * ```javascript * UE.filterNode(root,editor.options.filterRules); * ``` */ var filterNode = (UE.filterNode = (function () { function filterNode(node, rules) { switch (node.type) { case 'text': break case 'element': var val if ((val = rules[node.tagName])) { if (val === '-') { node.parentNode.removeChild(node) } else if (utils.isFunction(val)) { var parentNode = node.parentNode, index = node.getIndex() val(node) if (node.parentNode) { if (node.children) { for (var i = 0, ci; (ci = node.children[i]); ) { filterNode(ci, rules) if (ci.parentNode) { i++ } } } } else { for (var i = index, ci; (ci = parentNode.children[i]); ) { filterNode(ci, rules) if (ci.parentNode) { i++ } } } } else { var attrs = val['$'] if (attrs && node.attrs) { var tmpAttrs = {}, tmpVal for (var a in attrs) { tmpVal = node.getAttr(a) //todo 只先对style单独处理 if (a == 'style' && utils.isArray(attrs[a])) { var tmpCssStyle = [] utils.each(attrs[a], function (v) { var tmp if ((tmp = node.getStyle(v))) { tmpCssStyle.push(v + ':' + tmp) } }) tmpVal = tmpCssStyle.join(';') } if (tmpVal) { tmpAttrs[a] = tmpVal } } node.attrs = tmpAttrs } if (node.children) { for (var i = 0, ci; (ci = node.children[i]); ) { filterNode(ci, rules) if (ci.parentNode) { i++ } } } } } else { //如果不在名单里扣出子节点并删除该节点,cdata除外 if (dtd.$cdata[node.tagName]) { node.parentNode.removeChild(node) } else { var parentNode = node.parentNode, index = node.getIndex() node.parentNode.removeChild(node, true) for (var i = index, ci; (ci = parentNode.children[i]); ) { filterNode(ci, rules) if (ci.parentNode) { i++ } } } } break case 'comment': node.parentNode.removeChild(node) } } return function (root, rules) { if (utils.isEmptyObject(rules)) { return root } var val if ((val = rules['-'])) { utils.each(val.split(' '), function (k) { rules[k] = '-' }) } for (var i = 0, ci; (ci = root.children[i]); ) { filterNode(ci, rules) if (ci.parentNode) { i++ } } return root } })()) // core/plugin.js /** * Created with JetBrains PhpStorm. * User: campaign * Date: 10/8/13 * Time: 6:15 PM * To change this template use File | Settings | File Templates. */ UE.plugin = (function () { var _plugins = {} return { register: function (pluginName, fn, oldOptionName, afterDisabled) { if (oldOptionName && utils.isFunction(oldOptionName)) { afterDisabled = oldOptionName oldOptionName = null } _plugins[pluginName] = { optionName: oldOptionName || pluginName, execFn: fn, //当插件被禁用时执行 afterDisabled: afterDisabled } }, load: function (editor) { utils.each(_plugins, function (plugin) { var _export = plugin.execFn.call(editor) if (editor.options[plugin.optionName] !== false) { if (_export) { //后边需要再做扩展 utils.each(_export, function (v, k) { switch (k.toLowerCase()) { case 'shortcutkey': editor.addshortcutkey(v) break case 'bindevents': utils.each(v, function (fn, eventName) { editor.addListener(eventName, fn) }) break case 'bindmultievents': utils.each(utils.isArray(v) ? v : [v], function (event) { var types = utils.trim(event.type).split(/\s+/) utils.each(types, function (eventName) { editor.addListener(eventName, event.handler) }) }) break case 'commands': utils.each(v, function (execFn, execName) { editor.commands[execName] = execFn }) break case 'outputrule': editor.addOutputRule(v) break case 'inputrule': editor.addInputRule(v) break case 'defaultoptions': editor.setOpt(v) } }) } } else if (plugin.afterDisabled) { plugin.afterDisabled.call(editor) } }) //向下兼容 utils.each(UE.plugins, function (plugin) { plugin.call(editor) }) }, run: function (pluginName, editor) { var plugin = _plugins[pluginName] if (plugin) { plugin.exeFn.call(editor) } } } })() // core/keymap.js var keymap = (UE.keymap = { Backspace: 8, Tab: 9, Enter: 13, Shift: 16, Control: 17, Alt: 18, CapsLock: 20, Esc: 27, Spacebar: 32, PageUp: 33, PageDown: 34, End: 35, Home: 36, Left: 37, Up: 38, Right: 39, Down: 40, Insert: 45, Del: 46, NumLock: 144, Cmd: 91, '=': 187, '-': 189, b: 66, i: 73, //回退 z: 90, y: 89, //粘贴 v: 86, x: 88, s: 83, n: 78 }) // core/localstorage.js //存储媒介封装 var LocalStorage = (UE.LocalStorage = (function () { var storage = window.localStorage || getUserData() || null, LOCAL_FILE = 'localStorage' return { saveLocalData: function (key, data) { if (storage && data) { storage.setItem(key, data) return true } return false }, getLocalData: function (key) { if (storage) { return storage.getItem(key) } return null }, removeItem: function (key) { storage && storage.removeItem(key) } } function getUserData() { var container = document.createElement('div') container.style.display = 'none' if (!container.addBehavior) { return null } container.addBehavior('#default#userdata') return { getItem: function (key) { var result = null try { document.body.appendChild(container) container.load(LOCAL_FILE) result = container.getAttribute(key) document.body.removeChild(container) } catch (e) {} return result }, setItem: function (key, value) { document.body.appendChild(container) container.setAttribute(key, value) container.save(LOCAL_FILE) document.body.removeChild(container) }, //// 暂时没有用到 //clear: function () { // // var expiresTime = new Date(); // expiresTime.setFullYear(expiresTime.getFullYear() - 1); // document.body.appendChild(container); // container.expires = expiresTime.toUTCString(); // container.save(LOCAL_FILE); // document.body.removeChild(container); // //}, removeItem: function (key) { document.body.appendChild(container) container.removeAttribute(key) container.save(LOCAL_FILE) document.body.removeChild(container) } } } })()) ;(function () { var ROOTKEY = 'ueditor_preference' UE.Editor.prototype.setPreferences = function (key, value) { var obj = {} if (utils.isString(key)) { obj[key] = value } else { obj = key } var data = LocalStorage.getLocalData(ROOTKEY) if (data && (data = utils.str2json(data))) { utils.extend(data, obj) } else { data = obj } data && LocalStorage.saveLocalData(ROOTKEY, utils.json2str(data)) } UE.Editor.prototype.getPreferences = function (key) { var data = LocalStorage.getLocalData(ROOTKEY) if (data && (data = utils.str2json(data))) { return key ? data[key] : data } return null } UE.Editor.prototype.removePreferences = function (key) { var data = LocalStorage.getLocalData(ROOTKEY) if (data && (data = utils.str2json(data))) { data[key] = undefined delete data[key] } data && LocalStorage.saveLocalData(ROOTKEY, utils.json2str(data)) } })() // plugins/defaultfilter.js ///import core ///plugin 编辑器默认的过滤转换机制 UE.plugins['defaultfilter'] = function () { var me = this me.setOpt({ allowDivTransToP: true, disabledTableInTable: true }) //默认的过滤处理 //进入编辑器的内容处理 me.addInputRule(function (root) { var allowDivTransToP = this.options.allowDivTransToP var val function tdParent(node) { while (node && node.type == 'element') { if (node.tagName == 'td') { return true } node = node.parentNode } return false } //进行默认的处理 root.traversal(function (node) { if (node.type == 'element') { if ( !dtd.$cdata[node.tagName] && me.options.autoClearEmptyNode && dtd.$inline[node.tagName] && !dtd.$empty[node.tagName] && (!node.attrs || utils.isEmptyObject(node.attrs)) ) { if (!node.firstChild()) node.parentNode.removeChild(node) else if ( node.tagName == 'span' && (!node.attrs || utils.isEmptyObject(node.attrs)) ) { node.parentNode.removeChild(node, true) } return } switch (node.tagName) { case 'style': case 'script': node.setAttr({ cdata_tag: node.tagName, cdata_data: node.innerHTML() || '', _ue_custom_node_: 'true' }) node.tagName = 'div' node.innerHTML('') break case 'a': if ((val = node.getAttr('href'))) { node.setAttr('_href', val) } break case 'img': //todo base64暂时去掉,后边做远程图片上传后,干掉这个 if ((val = node.getAttr('src'))) { if (/^data:/.test(val)) { node.parentNode.removeChild(node) break } } node.setAttr('_src', node.getAttr('src')) break case 'span': if (browser.webkit && (val = node.getStyle('white-space'))) { if (/nowrap|normal/.test(val)) { node.setStyle('white-space', '') if ( me.options.autoClearEmptyNode && utils.isEmptyObject(node.attrs) ) { node.parentNode.removeChild(node, true) } } } val = node.getAttr('id') if (val && /^_baidu_bookmark_/i.test(val)) { node.parentNode.removeChild(node) } break case 'p': if ((val = node.getAttr('align'))) { node.setAttr('align') node.setStyle('text-align', val) } //trace:3431 // var cssStyle = node.getAttr('style'); // if (cssStyle) { // cssStyle = cssStyle.replace(/(margin|padding)[^;]+/g, ''); // node.setAttr('style', cssStyle) // // } //p标签不允许嵌套 utils.each(node.children, function (n) { if (n.type == 'element' && n.tagName == 'p') { var next = n.nextSibling() node.parentNode.insertAfter(n, node) var last = n while (next) { var tmp = next.nextSibling() node.parentNode.insertAfter(next, last) last = next next = tmp } return false } }) if (!node.firstChild()) { node.innerHTML(browser.ie ? ' ' : '
    ') } break case 'div': if (node.getAttr('cdata_tag')) { break } //针对代码这里不处理插入代码的div val = node.getAttr('class') if (val && /^line number\d+/.test(val)) { break } if (!allowDivTransToP) { break } var tmpNode, p = UE.uNode.createElement('p') while ((tmpNode = node.firstChild())) { if ( tmpNode.type == 'text' || !UE.dom.dtd.$block[tmpNode.tagName] ) { p.appendChild(tmpNode) } else { if (p.firstChild()) { node.parentNode.insertBefore(p, node) p = UE.uNode.createElement('p') } else { node.parentNode.insertBefore(tmpNode, node) } } } if (p.firstChild()) { node.parentNode.insertBefore(p, node) } node.parentNode.removeChild(node) break case 'dl': node.tagName = 'ul' break case 'dt': case 'dd': node.tagName = 'li' break case 'li': var className = node.getAttr('class') if (!className || !/list\-/.test(className)) { node.setAttr() } var tmpNodes = node.getNodesByTagName('ol ul') UE.utils.each(tmpNodes, function (n) { node.parentNode.insertAfter(n, node) }) break case 'td': case 'th': case 'caption': if (!node.children || !node.children.length) { node.appendChild( browser.ie11below ? UE.uNode.createText(' ') : UE.uNode.createElement('br') ) } break case 'table': if (me.options.disabledTableInTable && tdParent(node)) { node.parentNode.insertBefore( UE.uNode.createText(node.innerText()), node ) node.parentNode.removeChild(node) } } } // if(node.type == 'comment'){ // node.parentNode.removeChild(node); // } }) }) //从编辑器出去的内容处理 me.addOutputRule(function (root) { var val root.traversal(function (node) { if (node.type == 'element') { if ( me.options.autoClearEmptyNode && dtd.$inline[node.tagName] && !dtd.$empty[node.tagName] && (!node.attrs || utils.isEmptyObject(node.attrs)) ) { if (!node.firstChild()) node.parentNode.removeChild(node) else if ( node.tagName == 'span' && (!node.attrs || utils.isEmptyObject(node.attrs)) ) { node.parentNode.removeChild(node, true) } return } switch (node.tagName) { case 'div': if ((val = node.getAttr('cdata_tag'))) { node.tagName = val node.appendChild( UE.uNode.createText(node.getAttr('cdata_data')) ) node.setAttr({ cdata_tag: '', cdata_data: '', _ue_custom_node_: '' }) } break case 'a': if ((val = node.getAttr('_href'))) { node.setAttr({ href: utils.html(val), _href: '' }) } break break case 'span': val = node.getAttr('id') if (val && /^_baidu_bookmark_/i.test(val)) { node.parentNode.removeChild(node) } break case 'img': if ((val = node.getAttr('_src'))) { node.setAttr({ src: node.getAttr('_src'), _src: '' }) } } } }) }) } // plugins/inserthtml.js /** * 插入html字符串插件 * @file * @since 1.2.6.1 */ /** * 插入html代码 * @command inserthtml * @method execCommand * @param { String } cmd 命令字符串 * @param { String } html 插入的html字符串 * @remaind 插入的标签内容是在当前的选区位置上插入,如果当前是闭合状态,那直接插入内容, 如果当前是选中状态,将先清除当前选中内容后,再做插入 * @warning 注意:该命令会对当前选区的位置,对插入的内容进行过滤转换处理。 过滤的规则遵循html语意化的原则。 * @example * ```javascript * //xxx[BB]xxx 当前选区为非闭合选区,选中BB这两个文本 * //执行命令,插入CC * //插入后的效果 xxxCCxxx * //

    xx|xxx

    当前选区为闭合状态 * //插入

    CC

    * //结果

    xx

    CC

    xxx

    * //

    xxxx

    |

    xxx

    当前选区在两个p标签之间 * //插入 xxxx * //结果

    xxxx

    xxxx

    xxx

    * ``` */ UE.commands['inserthtml'] = { execCommand: function (command, html, notNeedFilter) { var me = this, range, div if (!html) { return } if (me.fireEvent('beforeinserthtml', html) === true) { return } range = me.selection.getRange() div = range.document.createElement('div') div.style.display = 'inline' if (!notNeedFilter) { var root = UE.htmlparser(html) //如果给了过滤规则就先进行过滤 if (me.options.filterRules) { UE.filterNode(root, me.options.filterRules) } //执行默认的处理 me.filterInputRule(root) html = root.toHtml() } div.innerHTML = utils.trim(html) if (!range.collapsed) { var tmpNode = range.startContainer if (domUtils.isFillChar(tmpNode)) { range.setStartBefore(tmpNode) } tmpNode = range.endContainer if (domUtils.isFillChar(tmpNode)) { range.setEndAfter(tmpNode) } range.txtToElmBoundary() //结束边界可能放到了br的前边,要把br包含进来 // x[xxx]
    if (range.endContainer && range.endContainer.nodeType == 1) { tmpNode = range.endContainer.childNodes[range.endOffset] if (tmpNode && domUtils.isBr(tmpNode)) { range.setEndAfter(tmpNode) } } if (range.startOffset == 0) { tmpNode = range.startContainer if (domUtils.isBoundaryNode(tmpNode, 'firstChild')) { tmpNode = range.endContainer if ( range.endOffset == (tmpNode.nodeType == 3 ? tmpNode.nodeValue.length : tmpNode.childNodes.length) && domUtils.isBoundaryNode(tmpNode, 'lastChild') ) { me.body.innerHTML = '

    ' + (browser.ie ? '' : '
    ') + '

    ' range.setStart(me.body.firstChild, 0).collapse(true) } } } !range.collapsed && range.deleteContents() if (range.startContainer.nodeType == 1) { var child = range.startContainer.childNodes[range.startOffset], pre if ( child && domUtils.isBlockElm(child) && (pre = child.previousSibling) && domUtils.isBlockElm(pre) ) { range.setEnd(pre, pre.childNodes.length).collapse() while (child.firstChild) { pre.appendChild(child.firstChild) } domUtils.remove(child) } } } var child, parent, pre, tmp, hadBreak = 0, nextNode //如果当前位置选中了fillchar要干掉,要不会产生空行 if (range.inFillChar()) { child = range.startContainer if (domUtils.isFillChar(child)) { range.setStartBefore(child).collapse(true) domUtils.remove(child) } else if (domUtils.isFillChar(child, true)) { child.nodeValue = child.nodeValue.replace(fillCharReg, '') range.startOffset-- range.collapsed && range.collapse(true) } } //列表单独处理 var li = domUtils.findParentByTagName(range.startContainer, 'li', true) if (li) { var next, last while ((child = div.firstChild)) { //针对hr单独处理一下先 while ( child && (child.nodeType == 3 || !domUtils.isBlockElm(child) || child.tagName == 'HR') ) { next = child.nextSibling range.insertNode(child).collapse() last = child child = next } if (child) { if (/^(ol|ul)$/i.test(child.tagName)) { while (child.firstChild) { last = child.firstChild domUtils.insertAfter(li, child.firstChild) li = li.nextSibling } domUtils.remove(child) } else { var tmpLi next = child.nextSibling tmpLi = me.document.createElement('li') domUtils.insertAfter(li, tmpLi) tmpLi.appendChild(child) last = child child = next li = tmpLi } } } li = domUtils.findParentByTagName(range.startContainer, 'li', true) if (domUtils.isEmptyBlock(li)) { domUtils.remove(li) } if (last) { range.setStartAfter(last).collapse(true).select(true) } } else { while ((child = div.firstChild)) { if (hadBreak) { var p = me.document.createElement('p') while ( child && (child.nodeType == 3 || !dtd.$block[child.tagName]) ) { nextNode = child.nextSibling p.appendChild(child) child = nextNode } if (p.firstChild) { child = p } } range.insertNode(child) nextNode = child.nextSibling if ( !hadBreak && child.nodeType == domUtils.NODE_ELEMENT && domUtils.isBlockElm(child) ) { parent = domUtils.findParent(child, function (node) { return domUtils.isBlockElm(node) }) if ( parent && parent.tagName.toLowerCase() != 'body' && !( dtd[parent.tagName][child.nodeName] && child.parentNode === parent ) ) { if (!dtd[parent.tagName][child.nodeName]) { pre = parent } else { tmp = child.parentNode while (tmp !== parent) { pre = tmp tmp = tmp.parentNode } } domUtils.breakParent(child, pre || tmp) //去掉break后前一个多余的节点

    |<[p> ==>

    |

    var pre = child.previousSibling domUtils.trimWhiteTextNode(pre) if (!pre.childNodes.length) { domUtils.remove(pre) } //trace:2012,在非ie的情况,切开后剩下的节点有可能不能点入光标添加br占位 if ( !browser.ie && (next = child.nextSibling) && domUtils.isBlockElm(next) && next.lastChild && !domUtils.isBr(next.lastChild) ) { next.appendChild(me.document.createElement('br')) } hadBreak = 1 } } var next = child.nextSibling if (!div.firstChild && next && domUtils.isBlockElm(next)) { range.setStart(next, 0).collapse(true) break } range.setEndAfter(child).collapse() } child = range.startContainer if (nextNode && domUtils.isBr(nextNode)) { domUtils.remove(nextNode) } //用chrome可能有空白展位符 if (domUtils.isBlockElm(child) && domUtils.isEmptyNode(child)) { if ((nextNode = child.nextSibling)) { domUtils.remove(child) if (nextNode.nodeType == 1 && dtd.$block[nextNode.tagName]) { range.setStart(nextNode, 0).collapse(true).shrinkBoundary() } } else { try { child.innerHTML = browser.ie ? domUtils.fillChar : '
    ' } catch (e) { range.setStartBefore(child) domUtils.remove(child) } } } //加上true因为在删除表情等时会删两次,第一次是删的fillData try { range.select(true) } catch (e) {} } setTimeout(function () { range = me.selection.getRange() range.scrollToView( me.autoHeightEnabled, me.autoHeightEnabled ? domUtils.getXY(me.iframe).y : 0 ) me.fireEvent('afterinserthtml', html) }, 200) } } // plugins/autotypeset.js /** * 自动排版 * @file * @since 1.2.6.1 */ /** * 对当前编辑器的内容执行自动排版, 排版的行为根据config配置文件里的“autotypeset”选项进行控制。 * @command autotypeset * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand( 'autotypeset' ); * ``` */ UE.plugins['autotypeset'] = function () { this.setOpt({ autotypeset: { mergeEmptyline: true, //合并空行 removeClass: true, //去掉冗余的class removeEmptyline: false, //去掉空行 textAlign: 'left', //段落的排版方式,可以是 left,right,center,justify 去掉这个属性表示不执行排版 imageBlockLine: 'center', //图片的浮动方式,独占一行剧中,左右浮动,默认: center,left,right,none 去掉这个属性表示不执行排版 pasteFilter: false, //根据规则过滤没事粘贴进来的内容 clearFontSize: false, //去掉所有的内嵌字号,使用编辑器默认的字号 clearFontFamily: false, //去掉所有的内嵌字体,使用编辑器默认的字体 removeEmptyNode: false, // 去掉空节点 //可以去掉的标签 removeTagNames: utils.extend({ div: 1 }, dtd.$removeEmpty), indent: false, // 行首缩进 indentValue: '2em', //行首缩进的大小 bdc2sb: false, tobdc: false } }) var me = this, opt = me.options.autotypeset, remainClass = { selectTdClass: 1, pagebreak: 1, anchorclass: 1 }, remainTag = { li: 1 }, tags = { div: 1, p: 1, //trace:2183 这些也认为是行 blockquote: 1, center: 1, h1: 1, h2: 1, h3: 1, h4: 1, h5: 1, h6: 1, span: 1 }, highlightCont //升级了版本,但配置项目里没有autotypeset if (!opt) { return } readLocalOpts() function isLine(node, notEmpty) { if (!node || node.nodeType == 3) return 0 if (domUtils.isBr(node)) return 1 if (node && node.parentNode && tags[node.tagName.toLowerCase()]) { if ( (highlightCont && highlightCont.contains(node)) || node.getAttribute('pagebreak') ) { return 0 } return notEmpty ? !domUtils.isEmptyBlock(node) : domUtils.isEmptyBlock( node, new RegExp('[\\s' + domUtils.fillChar + ']', 'g') ) } } function removeNotAttributeSpan(node) { if (!node.style.cssText) { domUtils.removeAttributes(node, ['style']) if ( node.tagName.toLowerCase() == 'span' && domUtils.hasNoAttributes(node) ) { domUtils.remove(node, true) } } } function autotype(type, html) { var me = this, cont if (html) { if (!opt.pasteFilter) { return } cont = me.document.createElement('div') cont.innerHTML = html.html } else { cont = me.document.body } var nodes = domUtils.getElementsByTagName(cont, '*') // 行首缩进,段落方向,段间距,段内间距 for (var i = 0, ci; (ci = nodes[i++]); ) { if (me.fireEvent('excludeNodeinautotype', ci) === true) { continue } //font-size if (opt.clearFontSize && ci.style.fontSize) { domUtils.removeStyle(ci, 'font-size') removeNotAttributeSpan(ci) } //font-family if (opt.clearFontFamily && ci.style.fontFamily) { domUtils.removeStyle(ci, 'font-family') removeNotAttributeSpan(ci) } if (isLine(ci)) { //合并空行 if (opt.mergeEmptyline) { var next = ci.nextSibling, tmpNode, isBr = domUtils.isBr(ci) while (isLine(next)) { tmpNode = next next = tmpNode.nextSibling if (isBr && (!next || (next && !domUtils.isBr(next)))) { break } domUtils.remove(tmpNode) } } //去掉空行,保留占位的空行 if ( opt.removeEmptyline && domUtils.inDoc(ci, cont) && !remainTag[ci.parentNode.tagName.toLowerCase()] ) { if (domUtils.isBr(ci)) { next = ci.nextSibling if (next && !domUtils.isBr(next)) { continue } } domUtils.remove(ci) continue } } if (isLine(ci, true) && ci.tagName != 'SPAN') { if (opt.indent) { ci.style.textIndent = opt.indentValue } if (opt.textAlign) { ci.style.textAlign = opt.textAlign } // if(opt.lineHeight) // ci.style.lineHeight = opt.lineHeight + 'cm'; } //去掉class,保留的class不去掉 if ( opt.removeClass && ci.className && !remainClass[ci.className.toLowerCase()] ) { if (highlightCont && highlightCont.contains(ci)) { continue } domUtils.removeAttributes(ci, ['class']) } //表情不处理 if ( opt.imageBlockLine && ci.tagName.toLowerCase() == 'img' && !ci.getAttribute('emotion') ) { if (html) { var img = ci switch (opt.imageBlockLine) { case 'left': case 'right': case 'none': var pN = img.parentNode, tmpNode, pre, next while (dtd.$inline[pN.tagName] || pN.tagName == 'A') { pN = pN.parentNode } tmpNode = pN if ( tmpNode.tagName == 'P' && domUtils.getStyle(tmpNode, 'text-align') == 'center' ) { if ( !domUtils.isBody(tmpNode) && domUtils.getChildCount(tmpNode, function (node) { return ( !domUtils.isBr(node) && !domUtils.isWhitespace(node) ) }) == 1 ) { pre = tmpNode.previousSibling next = tmpNode.nextSibling if ( pre && next && pre.nodeType == 1 && next.nodeType == 1 && pre.tagName == next.tagName && domUtils.isBlockElm(pre) ) { pre.appendChild(tmpNode.firstChild) while (next.firstChild) { pre.appendChild(next.firstChild) } domUtils.remove(tmpNode) domUtils.remove(next) } else { domUtils.setStyle(tmpNode, 'text-align', '') } } } domUtils.setStyle(img, 'float', opt.imageBlockLine) break case 'center': if (me.queryCommandValue('imagefloat') != 'center') { pN = img.parentNode domUtils.setStyle(img, 'float', 'none') tmpNode = img while ( pN && domUtils.getChildCount(pN, function (node) { return ( !domUtils.isBr(node) && !domUtils.isWhitespace(node) ) }) == 1 && (dtd.$inline[pN.tagName] || pN.tagName == 'A') ) { tmpNode = pN pN = pN.parentNode } var pNode = me.document.createElement('p') domUtils.setAttributes(pNode, { style: 'text-align:center' }) tmpNode.parentNode.insertBefore(pNode, tmpNode) pNode.appendChild(tmpNode) domUtils.setStyle(tmpNode, 'float', '') } } } else { var range = me.selection.getRange() range.selectNode(ci).select() me.execCommand('imagefloat', opt.imageBlockLine) } } //去掉冗余的标签 if (opt.removeEmptyNode) { if ( opt.removeTagNames[ci.tagName.toLowerCase()] && domUtils.hasNoAttributes(ci) && domUtils.isEmptyBlock(ci) ) { domUtils.remove(ci) } } } if (opt.tobdc) { var root = UE.htmlparser(cont.innerHTML) root.traversal(function (node) { if (node.type == 'text') { node.data = ToDBC(node.data) } }) cont.innerHTML = root.toHtml() } if (opt.bdc2sb) { var root = UE.htmlparser(cont.innerHTML) root.traversal(function (node) { if (node.type == 'text') { node.data = DBC2SB(node.data) } }) cont.innerHTML = root.toHtml() } if (html) { html.html = cont.innerHTML } } if (opt.pasteFilter) { me.addListener('beforepaste', autotype) } function DBC2SB(str) { var result = '' for (var i = 0; i < str.length; i++) { var code = str.charCodeAt(i) //获取当前字符的unicode编码 if (code >= 65281 && code <= 65373) { //在这个unicode编码范围中的是所有的英文字母已经各种字符 result += String.fromCharCode(str.charCodeAt(i) - 65248) //把全角字符的unicode编码转换为对应半角字符的unicode码 } else if (code == 12288) { //空格 result += String.fromCharCode(str.charCodeAt(i) - 12288 + 32) } else { result += str.charAt(i) } } return result } function ToDBC(txtstring) { txtstring = utils.html(txtstring) var tmp = '' var mark = '' /*用于判断,如果是html尖括里的标记,则不进行全角的转换*/ for (var i = 0; i < txtstring.length; i++) { if (txtstring.charCodeAt(i) == 32) { tmp = tmp + String.fromCharCode(12288) } else if (txtstring.charCodeAt(i) < 127) { tmp = tmp + String.fromCharCode(txtstring.charCodeAt(i) + 65248) } else { tmp += txtstring.charAt(i) } } return tmp } function readLocalOpts() { var cookieOpt = me.getPreferences('autotypeset') utils.extend(me.options.autotypeset, cookieOpt) } me.commands['autotypeset'] = { execCommand: function () { me.removeListener('beforepaste', autotype) if (opt.pasteFilter) { me.addListener('beforepaste', autotype) } autotype.call(me) } } } // plugins/autosubmit.js /** * 快捷键提交 * @file * @since 1.2.6.1 */ /** * 提交表单 * @command autosubmit * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand( 'autosubmit' ); * ``` */ UE.plugin.register('autosubmit', function () { return { shortcutkey: { autosubmit: 'ctrl+13' //手动提交 }, commands: { autosubmit: { execCommand: function () { var me = this, form = domUtils.findParentByTagName(me.iframe, 'form', false) if (form) { if (me.fireEvent('beforesubmit') === false) { return } me.sync() form.submit() } } } } } }) // plugins/background.js /** * 背景插件,为UEditor提供设置背景功能 * @file * @since 1.2.6.1 */ UE.plugin.register('background', function () { var me = this, cssRuleId = 'editor_background', isSetColored, reg = new RegExp('body[\\s]*\\{(.+)\\}', 'i') function stringToObj(str) { var obj = {}, styles = str.split(';') utils.each(styles, function (v) { var index = v.indexOf(':'), key = utils.trim(v.substr(0, index)).toLowerCase() key && (obj[key] = utils.trim(v.substr(index + 1) || '')) }) return obj } function setBackground(obj) { if (obj) { var styles = [] for (var name in obj) { if (obj.hasOwnProperty(name)) { styles.push(name + ':' + obj[name] + '; ') } } utils.cssRule( cssRuleId, styles.length ? 'body{' + styles.join('') + '}' : '', me.document ) } else { utils.cssRule(cssRuleId, '', me.document) } } //重写editor.hasContent方法 var orgFn = me.hasContents me.hasContents = function () { if (me.queryCommandValue('background')) { return true } return orgFn.apply(me, arguments) } return { bindEvents: { getAllHtml: function (type, headHtml) { var body = this.body, su = domUtils.getComputedStyle(body, 'background-image'), url = '' if (su.indexOf(me.options.imagePath) > 0) { url = su .substring(su.indexOf(me.options.imagePath), su.length - 1) .replace(/"|\(|\)/gi, '') } else { url = su != 'none' ? su.replace(/url\("?|"?\)/gi, '') : '' } var html = ' ' headHtml.push(html) }, aftersetcontent: function () { if (isSetColored == false) setBackground() } }, inputRule: function (root) { isSetColored = false utils.each(root.getNodesByTagName('p'), function (p) { var styles = p.getAttr('data-background') if (styles) { isSetColored = true setBackground(stringToObj(styles)) p.parentNode.removeChild(p) } }) }, outputRule: function (root) { var me = this, styles = (utils.cssRule(cssRuleId, me.document) || '') .replace(/[\n\r]+/g, '') .match(reg) if (styles) { root.appendChild( UE.uNode.createElement( '


    ' ) ) } }, commands: { background: { execCommand: function (cmd, obj) { setBackground(obj) }, queryCommandValue: function () { var me = this, styles = (utils.cssRule(cssRuleId, me.document) || '') .replace(/[\n\r]+/g, '') .match(reg) return styles ? stringToObj(styles[1]) : null }, notNeedUndo: true } } } }) // plugins/image.js /** * 图片插入、排版插件 * @file * @since 1.2.6.1 */ /** * 图片对齐方式 * @command imagefloat * @method execCommand * @remind 值center为独占一行居中 * @param { String } cmd 命令字符串 * @param { String } align 对齐方式,可传left、right、none、center * @remaind center表示图片独占一行 * @example * ```javascript * editor.execCommand( 'imagefloat', 'center' ); * ``` */ /** * 如果选区所在位置是图片区域 * @command imagefloat * @method queryCommandValue * @param { String } cmd 命令字符串 * @return { String } 返回图片对齐方式 * @example * ```javascript * editor.queryCommandValue( 'imagefloat' ); * ``` */ UE.commands['imagefloat'] = { execCommand: function (cmd, align) { var me = this, range = me.selection.getRange() if (!range.collapsed) { var img = range.getClosedNode() if (img && img.tagName == 'IMG') { switch (align) { case 'left': case 'right': case 'none': var pN = img.parentNode, tmpNode, pre, next while (dtd.$inline[pN.tagName] || pN.tagName == 'A') { pN = pN.parentNode } tmpNode = pN if ( tmpNode.tagName == 'P' && domUtils.getStyle(tmpNode, 'text-align') == 'center' ) { if ( !domUtils.isBody(tmpNode) && domUtils.getChildCount(tmpNode, function (node) { return !domUtils.isBr(node) && !domUtils.isWhitespace(node) }) == 1 ) { pre = tmpNode.previousSibling next = tmpNode.nextSibling if ( pre && next && pre.nodeType == 1 && next.nodeType == 1 && pre.tagName == next.tagName && domUtils.isBlockElm(pre) ) { pre.appendChild(tmpNode.firstChild) while (next.firstChild) { pre.appendChild(next.firstChild) } domUtils.remove(tmpNode) domUtils.remove(next) } else { domUtils.setStyle(tmpNode, 'text-align', '') } } range.selectNode(img).select() } domUtils.setStyle(img, 'float', align == 'none' ? '' : align) if (align == 'none') { domUtils.removeAttributes(img, 'align') } break case 'center': if (me.queryCommandValue('imagefloat') != 'center') { pN = img.parentNode domUtils.setStyle(img, 'float', '') domUtils.removeAttributes(img, 'align') tmpNode = img while ( pN && domUtils.getChildCount(pN, function (node) { return !domUtils.isBr(node) && !domUtils.isWhitespace(node) }) == 1 && (dtd.$inline[pN.tagName] || pN.tagName == 'A') ) { tmpNode = pN pN = pN.parentNode } range.setStartBefore(tmpNode).setCursor(false) pN = me.document.createElement('div') pN.appendChild(tmpNode) domUtils.setStyle(tmpNode, 'float', '') me.execCommand( 'insertHtml', '

    ' + pN.innerHTML + '

    ' ) tmpNode = me.document.getElementById('_img_parent_tmp') tmpNode.removeAttribute('id') tmpNode = tmpNode.firstChild range.selectNode(tmpNode).select() //去掉后边多余的元素 next = tmpNode.parentNode.nextSibling if (next && domUtils.isEmptyNode(next)) { domUtils.remove(next) } } break } } } }, queryCommandValue: function () { var range = this.selection.getRange(), startNode, floatStyle if (range.collapsed) { return 'none' } startNode = range.getClosedNode() if (startNode && startNode.nodeType == 1 && startNode.tagName == 'IMG') { floatStyle = domUtils.getComputedStyle(startNode, 'float') || startNode.getAttribute('align') if (floatStyle == 'none') { floatStyle = domUtils.getComputedStyle(startNode.parentNode, 'text-align') == 'center' ? 'center' : floatStyle } return { left: 1, right: 1, center: 1 }[floatStyle] ? floatStyle : 'none' } return 'none' }, queryCommandState: function () { var range = this.selection.getRange(), startNode if (range.collapsed) return -1 startNode = range.getClosedNode() if (startNode && startNode.nodeType == 1 && startNode.tagName == 'IMG') { return 0 } return -1 } } /** * 插入图片 * @command insertimage * @method execCommand * @param { String } cmd 命令字符串 * @param { Object } opt 属性键值对,这些属性都将被复制到当前插入图片 * @remind 该命令第二个参数可接受一个图片配置项对象的数组,可以插入多张图片, * 此时数组的每一个元素都是一个Object类型的图片属性集合。 * @example * ```javascript * editor.execCommand( 'insertimage', { * src:'a/b/c.jpg', * width:'100', * height:'100' * } ); * ``` * @example * ```javascript * editor.execCommand( 'insertimage', [{ * src:'a/b/c.jpg', * width:'100', * height:'100' * },{ * src:'a/b/d.jpg', * width:'100', * height:'100' * }] ); * ``` */ UE.commands['insertimage'] = { execCommand: function (cmd, opt) { opt = utils.isArray(opt) ? opt : [opt] if (!opt.length) { return } var me = this, range = me.selection.getRange(), img = range.getClosedNode() if (me.fireEvent('beforeinsertimage', opt) === true) { return } function unhtmlData(imgCi) { utils.each( 'width,height,border,hspace,vspace'.split(','), function (item) { if (imgCi[item]) { imgCi[item] = parseInt(imgCi[item], 10) || 0 } } ) utils.each('src,_src'.split(','), function (item) { if (imgCi[item]) { imgCi[item] = utils.unhtmlForUrl(imgCi[item]) } }) utils.each('title,alt'.split(','), function (item) { if (imgCi[item]) { imgCi[item] = utils.unhtml(imgCi[item]) } }) } if ( img && /img/i.test(img.tagName) && (img.className != 'edui-faked-video' || img.className.indexOf('edui-upload-video') != -1) && !img.getAttribute('word_img') ) { var first = opt.shift() var floatStyle = first['floatStyle'] delete first['floatStyle'] //// img.style.border = (first.border||0) +"px solid #000"; //// img.style.margin = (first.margin||0) +"px"; // img.style.cssText += ';margin:' + (first.margin||0) +"px;" + 'border:' + (first.border||0) +"px solid #000"; domUtils.setAttributes(img, first) me.execCommand('imagefloat', floatStyle) if (opt.length > 0) { range.setStartAfter(img).setCursor(false, true) me.execCommand('insertimage', opt) } } else { var html = [], str = '', ci ci = opt[0] if (opt.length == 1) { unhtmlData(ci) str = '' + ci.alt + '' if (ci['floatStyle'] == 'center') { str = '

    ' + str + '

    ' } html.push(str) } else { for (var i = 0; (ci = opt[i++]); ) { unhtmlData(ci) str = '

    ' html.push(str) } } me.execCommand('insertHtml', html.join('')) } me.fireEvent('afterinsertimage', opt) } } // plugins/justify.js /** * 段落格式 * @file * @since 1.2.6.1 */ /** * 段落对齐方式 * @command justify * @method execCommand * @param { String } cmd 命令字符串 * @param { String } align 对齐方式:left => 居左,right => 居右,center => 居中,justify => 两端对齐 * @example * ```javascript * editor.execCommand( 'justify', 'center' ); * ``` */ /** * 如果选区所在位置是段落区域,返回当前段落对齐方式 * @command justify * @method queryCommandValue * @param { String } cmd 命令字符串 * @return { String } 返回段落对齐方式 * @example * ```javascript * editor.queryCommandValue( 'justify' ); * ``` */ UE.plugins['justify'] = function () { var me = this, block = domUtils.isBlockElm, defaultValue = { left: 1, right: 1, center: 1, justify: 1 }, doJustify = function (range, style) { var bookmark = range.createBookmark(), filterFn = function (node) { return node.nodeType == 1 ? node.tagName.toLowerCase() != 'br' && !domUtils.isBookmarkNode(node) : !domUtils.isWhitespace(node) } range.enlarge(true) var bookmark2 = range.createBookmark(), current = domUtils.getNextDomNode(bookmark2.start, false, filterFn), tmpRange = range.cloneRange(), tmpNode while ( current && !( domUtils.getPosition(current, bookmark2.end) & domUtils.POSITION_FOLLOWING ) ) { if (current.nodeType == 3 || !block(current)) { tmpRange.setStartBefore(current) while (current && current !== bookmark2.end && !block(current)) { tmpNode = current current = domUtils.getNextDomNode( current, false, null, function (node) { return !block(node) } ) } tmpRange.setEndAfter(tmpNode) var common = tmpRange.getCommonAncestor() if (!domUtils.isBody(common) && block(common)) { domUtils.setStyles( common, utils.isString(style) ? { 'text-align': style } : style ) current = common } else { var p = range.document.createElement('p') domUtils.setStyles( p, utils.isString(style) ? { 'text-align': style } : style ) var frag = tmpRange.extractContents() p.appendChild(frag) tmpRange.insertNode(p) current = p } current = domUtils.getNextDomNode(current, false, filterFn) } else { current = domUtils.getNextDomNode(current, true, filterFn) } } return range.moveToBookmark(bookmark2).moveToBookmark(bookmark) } UE.commands['justify'] = { execCommand: function (cmdName, align) { var range = this.selection.getRange(), txt //闭合时单独处理 if (range.collapsed) { txt = this.document.createTextNode('p') range.insertNode(txt) } doJustify(range, align) if (txt) { range.setStartBefore(txt).collapse(true) domUtils.remove(txt) } range.select() return true }, queryCommandValue: function () { var startNode = this.selection.getStart(), value = domUtils.getComputedStyle(startNode, 'text-align') return defaultValue[value] ? value : 'left' }, queryCommandState: function () { var start = this.selection.getStart(), cell = start && domUtils.findParentByTagName(start, ['td', 'th', 'caption'], true) return cell ? -1 : 0 } } } // plugins/font.js /** * 字体颜色,背景色,字号,字体,下划线,删除线 * @file * @since 1.2.6.1 */ /** * 字体颜色 * @command forecolor * @method execCommand * @param { String } cmd 命令字符串 * @param { String } value 色值(必须十六进制) * @example * ```javascript * editor.execCommand( 'forecolor', '#000' ); * ``` */ /** * 返回选区字体颜色 * @command forecolor * @method queryCommandValue * @param { String } cmd 命令字符串 * @return { String } 返回字体颜色 * @example * ```javascript * editor.queryCommandValue( 'forecolor' ); * ``` */ /** * 字体背景颜色 * @command backcolor * @method execCommand * @param { String } cmd 命令字符串 * @param { String } value 色值(必须十六进制) * @example * ```javascript * editor.execCommand( 'backcolor', '#000' ); * ``` */ /** * 返回选区字体颜色 * @command backcolor * @method queryCommandValue * @param { String } cmd 命令字符串 * @return { String } 返回字体背景颜色 * @example * ```javascript * editor.queryCommandValue( 'backcolor' ); * ``` */ /** * 字体大小 * @command fontsize * @method execCommand * @param { String } cmd 命令字符串 * @param { String } value 字体大小 * @example * ```javascript * editor.execCommand( 'fontsize', '14px' ); * ``` */ /** * 返回选区字体大小 * @command fontsize * @method queryCommandValue * @param { String } cmd 命令字符串 * @return { String } 返回字体大小 * @example * ```javascript * editor.queryCommandValue( 'fontsize' ); * ``` */ /** * 字体样式 * @command fontfamily * @method execCommand * @param { String } cmd 命令字符串 * @param { String } value 字体样式 * @example * ```javascript * editor.execCommand( 'fontfamily', '微软雅黑' ); * ``` */ /** * 返回选区字体样式 * @command fontfamily * @method queryCommandValue * @param { String } cmd 命令字符串 * @return { String } 返回字体样式 * @example * ```javascript * editor.queryCommandValue( 'fontfamily' ); * ``` */ /** * 字体下划线,与删除线互斥 * @command underline * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand( 'underline' ); * ``` */ /** * 字体删除线,与下划线互斥 * @command strikethrough * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand( 'strikethrough' ); * ``` */ /** * 字体边框 * @command fontborder * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand( 'fontborder' ); * ``` */ UE.plugins['font'] = function () { var me = this, fonts = { forecolor: 'color', backcolor: 'background-color', fontsize: 'font-size', fontfamily: 'font-family', underline: 'text-decoration', strikethrough: 'text-decoration', fontborder: 'border' }, needCmd = { underline: 1, strikethrough: 1, fontborder: 1 }, needSetChild = { forecolor: 'color', backcolor: 'background-color', fontsize: 'font-size', fontfamily: 'font-family' } me.setOpt({ fontfamily: [ { name: 'songti', val: '宋体,SimSun' }, { name: 'yahei', val: '微软雅黑,Microsoft YaHei' }, { name: 'kaiti', val: '楷体,楷体_GB2312, SimKai' }, { name: 'heiti', val: '黑体, SimHei' }, { name: 'lishu', val: '隶书, SimLi' }, { name: 'andaleMono', val: 'andale mono' }, { name: 'arial', val: 'arial, helvetica,sans-serif' }, { name: 'arialBlack', val: 'arial black,avant garde' }, { name: 'comicSansMs', val: 'comic sans ms' }, { name: 'impact', val: 'impact,chicago' }, { name: 'timesNewRoman', val: 'times new roman' } ], fontsize: [10, 11, 12, 14, 16, 18, 20, 24, 36] }) function mergeWithParent(node) { var parent while ((parent = node.parentNode)) { if ( parent.tagName == 'SPAN' && domUtils.getChildCount(parent, function (child) { return !domUtils.isBookmarkNode(child) && !domUtils.isBr(child) }) == 1 ) { parent.style.cssText += node.style.cssText domUtils.remove(node, true) node = parent } else { break } } } function mergeChild(rng, cmdName, value) { if (needSetChild[cmdName]) { rng.adjustmentBoundary() if (!rng.collapsed && rng.startContainer.nodeType == 1) { var start = rng.startContainer.childNodes[rng.startOffset] if (start && domUtils.isTagNode(start, 'span')) { var bk = rng.createBookmark() utils.each( domUtils.getElementsByTagName(start, 'span'), function (span) { if (!span.parentNode || domUtils.isBookmarkNode(span)) return if ( cmdName == 'backcolor' && domUtils .getComputedStyle(span, 'background-color') .toLowerCase() === value ) { return } domUtils.removeStyle(span, needSetChild[cmdName]) if (span.style.cssText.replace(/^\s+$/, '').length == 0) { domUtils.remove(span, true) } } ) rng.moveToBookmark(bk) } } } } function mergesibling(rng, cmdName, value) { var collapsed = rng.collapsed, bk = rng.createBookmark(), common if (collapsed) { common = bk.start.parentNode while (dtd.$inline[common.tagName]) { common = common.parentNode } } else { common = domUtils.getCommonAncestor(bk.start, bk.end) } utils.each( domUtils.getElementsByTagName(common, 'span'), function (span) { if (!span.parentNode || domUtils.isBookmarkNode(span)) return if (/\s*border\s*:\s*none;?\s*/i.test(span.style.cssText)) { if (/^\s*border\s*:\s*none;?\s*$/.test(span.style.cssText)) { domUtils.remove(span, true) } else { domUtils.removeStyle(span, 'border') } return } if ( /border/i.test(span.style.cssText) && span.parentNode.tagName == 'SPAN' && /border/i.test(span.parentNode.style.cssText) ) { span.style.cssText = span.style.cssText.replace( /border[^:]*:[^;]+;?/gi, '' ) } if (!(cmdName == 'fontborder' && value == 'none')) { var next = span.nextSibling while (next && next.nodeType == 1 && next.tagName == 'SPAN') { if (domUtils.isBookmarkNode(next) && cmdName == 'fontborder') { span.appendChild(next) next = span.nextSibling continue } if (next.style.cssText == span.style.cssText) { domUtils.moveChild(next, span) domUtils.remove(next) } if (span.nextSibling === next) break next = span.nextSibling } } mergeWithParent(span) if (browser.ie && browser.version > 8) { //拷贝父亲们的特别的属性,这里只做背景颜色的处理 var parent = domUtils.findParent(span, function (n) { return ( n.tagName == 'SPAN' && /background-color/.test(n.style.cssText) ) }) if (parent && !/background-color/.test(span.style.cssText)) { span.style.backgroundColor = parent.style.backgroundColor } } } ) rng.moveToBookmark(bk) mergeChild(rng, cmdName, value) } me.addInputRule(function (root) { utils.each( root.getNodesByTagName('u s del font strike'), function (node) { if (node.tagName == 'font') { var cssStyle = [] for (var p in node.attrs) { switch (p) { case 'size': cssStyle.push( 'font-size:' + ({ 1: '10', 2: '12', 3: '16', 4: '18', 5: '24', 6: '32', 7: '48' }[node.attrs[p]] || node.attrs[p]) + 'px' ) break case 'color': cssStyle.push('color:' + node.attrs[p]) break case 'face': cssStyle.push('font-family:' + node.attrs[p]) break case 'style': cssStyle.push(node.attrs[p]) } } node.attrs = { style: cssStyle.join(';') } } else { var val = node.tagName == 'u' ? 'underline' : 'line-through' node.attrs = { style: (node.getAttr('style') || '') + 'text-decoration:' + val + ';' } } node.tagName = 'span' } ) // utils.each(root.getNodesByTagName('span'), function (node) { // var val; // if(val = node.getAttr('class')){ // if(/fontstrikethrough/.test(val)){ // node.setStyle('text-decoration','line-through'); // if(node.attrs['class']){ // node.attrs['class'] = node.attrs['class'].replace(/fontstrikethrough/,''); // }else{ // node.setAttr('class') // } // } // if(/fontborder/.test(val)){ // node.setStyle('border','1px solid #000'); // if(node.attrs['class']){ // node.attrs['class'] = node.attrs['class'].replace(/fontborder/,''); // }else{ // node.setAttr('class') // } // } // } // }); }) // me.addOutputRule(function(root){ // utils.each(root.getNodesByTagName('span'), function (node) { // var val; // if(val = node.getStyle('text-decoration')){ // if(/line-through/.test(val)){ // if(node.attrs['class']){ // node.attrs['class'] += ' fontstrikethrough'; // }else{ // node.setAttr('class','fontstrikethrough') // } // } // // node.setStyle('text-decoration') // } // if(val = node.getStyle('border')){ // if(/1px/.test(val) && /solid/.test(val)){ // if(node.attrs['class']){ // node.attrs['class'] += ' fontborder'; // // }else{ // node.setAttr('class','fontborder') // } // } // node.setStyle('border') // // } // }); // }); for (var p in fonts) { ;(function (cmd, style) { UE.commands[cmd] = { execCommand: function (cmdName, value) { value = value || (this.queryCommandState(cmdName) ? 'none' : cmdName == 'underline' ? 'underline' : cmdName == 'fontborder' ? '1px solid #000' : 'line-through') var me = this, range = this.selection.getRange(), text if (value == 'default') { if (range.collapsed) { text = me.document.createTextNode('font') range.insertNode(text).select() } me.execCommand('removeFormat', 'span,a', style) if (text) { range.setStartBefore(text).collapse(true) domUtils.remove(text) } mergesibling(range, cmdName, value) range.select() } else { if (!range.collapsed) { if (needCmd[cmd] && me.queryCommandValue(cmd)) { me.execCommand('removeFormat', 'span,a', style) } range = me.selection.getRange() range.applyInlineStyle('span', { style: style + ':' + value }) mergesibling(range, cmdName, value) range.select() } else { var span = domUtils.findParentByTagName( range.startContainer, 'span', true ) text = me.document.createTextNode('font') if ( span && !span.children.length && !span[browser.ie ? 'innerText' : 'textContent'].replace( fillCharReg, '' ).length ) { //for ie hack when enter range.insertNode(text) if (needCmd[cmd]) { range.selectNode(text).select() me.execCommand('removeFormat', 'span,a', style, null) span = domUtils.findParentByTagName(text, 'span', true) range.setStartBefore(text) } span && (span.style.cssText += ';' + style + ':' + value) range.collapse(true).select() } else { range.insertNode(text) range.selectNode(text).select() span = range.document.createElement('span') if (needCmd[cmd]) { //a标签内的不处理跳过 if (domUtils.findParentByTagName(text, 'a', true)) { range.setStartBefore(text).setCursor() domUtils.remove(text) return } me.execCommand('removeFormat', 'span,a', style) } span.style.cssText = style + ':' + value text.parentNode.insertBefore(span, text) //修复,span套span 但样式不继承的问题 if (!browser.ie || (browser.ie && browser.version == 9)) { var spanParent = span.parentNode while (!domUtils.isBlockElm(spanParent)) { if (spanParent.tagName == 'SPAN') { //opera合并style不会加入";" span.style.cssText = spanParent.style.cssText + ';' + span.style.cssText } spanParent = spanParent.parentNode } } if (opera) { setTimeout(function () { range.setStart(span, 0).collapse(true) mergesibling(range, cmdName, value) range.select() }) } else { range.setStart(span, 0).collapse(true) mergesibling(range, cmdName, value) range.select() } //trace:981 //domUtils.mergeToParent(span) } domUtils.remove(text) } } return true }, queryCommandValue: function (cmdName) { var startNode = this.selection.getStart() //trace:946 if (cmdName == 'underline' || cmdName == 'strikethrough') { var tmpNode = startNode, value while ( tmpNode && !domUtils.isBlockElm(tmpNode) && !domUtils.isBody(tmpNode) ) { if (tmpNode.nodeType == 1) { value = domUtils.getComputedStyle(tmpNode, style) if (value != 'none') { return value } } tmpNode = tmpNode.parentNode } return 'none' } if (cmdName == 'fontborder') { var tmp = startNode, val while (tmp && dtd.$inline[tmp.tagName]) { if ((val = domUtils.getComputedStyle(tmp, 'border'))) { if (/1px/.test(val) && /solid/.test(val)) { return val } } tmp = tmp.parentNode } return '' } if (cmdName == 'FontSize') { var styleVal = domUtils.getComputedStyle(startNode, style), tmp = /^([\d\.]+)(\w+)$/.exec(styleVal) if (tmp) { return Math.floor(tmp[1]) + tmp[2] } return styleVal } return domUtils.getComputedStyle(startNode, style) }, queryCommandState: function (cmdName) { if (!needCmd[cmdName]) return 0 var val = this.queryCommandValue(cmdName) if (cmdName == 'fontborder') { return /1px/.test(val) && /solid/.test(val) } else { return cmdName == 'underline' ? /underline/.test(val) : /line\-through/.test(val) } } } })(p, fonts[p]) } } // plugins/link.js /** * 超链接 * @file * @since 1.2.6.1 */ /** * 插入超链接 * @command link * @method execCommand * @param { String } cmd 命令字符串 * @param { Object } options 设置自定义属性,例如:url、title、target * @example * ```javascript * editor.execCommand( 'link', '{ * url:'ueditor.baidu.com', * title:'ueditor', * target:'_blank' * }' ); * ``` */ /** * 返回当前选中的第一个超链接节点 * @command link * @method queryCommandValue * @param { String } cmd 命令字符串 * @return { Element } 超链接节点 * @example * ```javascript * editor.queryCommandValue( 'link' ); * ``` */ /** * 取消超链接 * @command unlink * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand( 'unlink'); * ``` */ UE.plugins['link'] = function () { function optimize(range) { var start = range.startContainer, end = range.endContainer if ((start = domUtils.findParentByTagName(start, 'a', true))) { range.setStartBefore(start) } if ((end = domUtils.findParentByTagName(end, 'a', true))) { range.setEndAfter(end) } } UE.commands['unlink'] = { execCommand: function () { var range = this.selection.getRange(), bookmark if ( range.collapsed && !domUtils.findParentByTagName(range.startContainer, 'a', true) ) { return } bookmark = range.createBookmark() optimize(range) range.removeInlineStyle('a').moveToBookmark(bookmark).select() }, queryCommandState: function () { return !this.highlight && this.queryCommandValue('link') ? 0 : -1 } } function doLink(range, opt, me) { var rngClone = range.cloneRange(), link = me.queryCommandValue('link') optimize((range = range.adjustmentBoundary())) var start = range.startContainer if (start.nodeType == 1 && link) { start = start.childNodes[range.startOffset] if ( start && start.nodeType == 1 && start.tagName == 'A' && /^(?:https?|ftp|file)\s*:\s*\/\//.test( start[browser.ie ? 'innerText' : 'textContent'] ) ) { start[browser.ie ? 'innerText' : 'textContent'] = utils.html( opt.textValue || opt.href ) } } if (!rngClone.collapsed || link) { range.removeInlineStyle('a') rngClone = range.cloneRange() } if (rngClone.collapsed) { var a = range.document.createElement('a'), text = '' if (opt.textValue) { text = utils.html(opt.textValue) delete opt.textValue } else { text = utils.html(opt.href) } domUtils.setAttributes(a, opt) start = domUtils.findParentByTagName(rngClone.startContainer, 'a', true) if (start && domUtils.isInNodeEndBoundary(rngClone, start)) { range.setStartAfter(start).collapse(true) } a[browser.ie ? 'innerText' : 'textContent'] = text range.insertNode(a).selectNode(a) } else { range.applyInlineStyle('a', opt) } } UE.commands['link'] = { execCommand: function (cmdName, opt) { var range opt._href && (opt._href = utils.unhtml(opt._href, /[<">]/g)) opt.href && (opt.href = utils.unhtml(opt.href, /[<">]/g)) opt.textValue && (opt.textValue = utils.unhtml(opt.textValue, /[<">]/g)) doLink((range = this.selection.getRange()), opt, this) //闭合都不加占位符,如果加了会在a后边多个占位符节点,导致a是图片背景组成的列表,出现空白问题 range.collapse().select(true) }, queryCommandValue: function () { var range = this.selection.getRange(), node if (range.collapsed) { // node = this.selection.getStart(); //在ie下getstart()取值偏上了 node = range.startContainer node = node.nodeType == 1 ? node : node.parentNode if ( node && (node = domUtils.findParentByTagName(node, 'a', true)) && !domUtils.isInNodeEndBoundary(range, node) ) { return node } } else { //trace:1111 如果是

    xx

    startContainer是p就会找不到a range.shrinkBoundary() var start = range.startContainer.nodeType == 3 || !range.startContainer.childNodes[range.startOffset] ? range.startContainer : range.startContainer.childNodes[range.startOffset], end = range.endContainer.nodeType == 3 || range.endOffset == 0 ? range.endContainer : range.endContainer.childNodes[range.endOffset - 1], common = range.getCommonAncestor() node = domUtils.findParentByTagName(common, 'a', true) if (!node && common.nodeType == 1) { var as = common.getElementsByTagName('a'), ps, pe for (var i = 0, ci; (ci = as[i++]); ) { ;(ps = domUtils.getPosition(ci, start)), (pe = domUtils.getPosition(ci, end)) if ( (ps & domUtils.POSITION_FOLLOWING || ps & domUtils.POSITION_CONTAINS) && (pe & domUtils.POSITION_PRECEDING || pe & domUtils.POSITION_CONTAINS) ) { node = ci break } } } return node } }, queryCommandState: function () { //判断如果是视频的话连接不可用 //fix 853 var img = this.selection.getRange().getClosedNode(), flag = img && (img.className == 'edui-faked-video' || img.className.indexOf('edui-upload-video') != -1) return flag ? -1 : 0 } } } // plugins/iframe.js ///import core ///import plugins\inserthtml.js ///commands 插入框架 ///commandsName InsertFrame ///commandsTitle 插入Iframe ///commandsDialog dialogs\insertframe UE.plugins['insertframe'] = function () { var me = this function deleteIframe() { me._iframe && delete me._iframe } me.addListener('selectionchange', function () { deleteIframe() }) } // plugins/scrawl.js ///import core ///commands 涂鸦 ///commandsName Scrawl ///commandsTitle 涂鸦 ///commandsDialog dialogs\scrawl UE.commands['scrawl'] = { queryCommandState: function () { return browser.ie && browser.version <= 8 ? -1 : 0 } } // plugins/removeformat.js /** * 清除格式 * @file * @since 1.2.6.1 */ /** * 清除文字样式 * @command removeformat * @method execCommand * @param { String } cmd 命令字符串 * @param {String} tags 以逗号隔开的标签。如:strong * @param {String} style 样式如:color * @param {String} attrs 属性如:width * @example * ```javascript * editor.execCommand( 'removeformat', 'strong','color','width' ); * ``` */ UE.plugins['removeformat'] = function () { var me = this me.setOpt({ removeFormatTags: 'b,big,code,del,dfn,em,font,i,ins,kbd,q,samp,small,span,strike,strong,sub,sup,tt,u,var', removeFormatAttributes: 'class,style,lang,width,height,align,hspace,valign' }) me.commands['removeformat'] = { execCommand: function (cmdName, tags, style, attrs, notIncludeA) { var tagReg = new RegExp( '^(?:' + (tags || this.options.removeFormatTags).replace(/,/g, '|') + ')$', 'i' ), removeFormatAttributes = style ? [] : (attrs || this.options.removeFormatAttributes).split(','), range = new dom.Range(this.document), bookmark, node, parent, filter = function (node) { return node.nodeType == 1 } function isRedundantSpan(node) { if (node.nodeType == 3 || node.tagName.toLowerCase() != 'span') { return 0 } if (browser.ie) { //ie 下判断实效,所以只能简单用style来判断 //return node.style.cssText == '' ? 1 : 0; var attrs = node.attributes if (attrs.length) { for (var i = 0, l = attrs.length; i < l; i++) { if (attrs[i].specified) { return 0 } } return 1 } } return !node.attributes.length } function doRemove(range) { var bookmark1 = range.createBookmark() if (range.collapsed) { range.enlarge(true) } //不能把a标签切了 if (!notIncludeA) { var aNode = domUtils.findParentByTagName( range.startContainer, 'a', true ) if (aNode) { range.setStartBefore(aNode) } aNode = domUtils.findParentByTagName(range.endContainer, 'a', true) if (aNode) { range.setEndAfter(aNode) } } bookmark = range.createBookmark() node = bookmark.start //切开始 while ((parent = node.parentNode) && !domUtils.isBlockElm(parent)) { domUtils.breakParent(node, parent) domUtils.clearEmptySibling(node) } if (bookmark.end) { //切结束 node = bookmark.end while ((parent = node.parentNode) && !domUtils.isBlockElm(parent)) { domUtils.breakParent(node, parent) domUtils.clearEmptySibling(node) } //开始去除样式 var current = domUtils.getNextDomNode( bookmark.start, false, filter ), next while (current) { if (current == bookmark.end) { break } next = domUtils.getNextDomNode(current, true, filter) if ( !dtd.$empty[current.tagName.toLowerCase()] && !domUtils.isBookmarkNode(current) ) { if (tagReg.test(current.tagName)) { if (style) { domUtils.removeStyle(current, style) if ( isRedundantSpan(current) && style != 'text-decoration' ) { domUtils.remove(current, true) } } else { domUtils.remove(current, true) } } else { //trace:939 不能把list上的样式去掉 if ( !dtd.$tableContent[current.tagName] && !dtd.$list[current.tagName] ) { domUtils.removeAttributes(current, removeFormatAttributes) if (isRedundantSpan(current)) { domUtils.remove(current, true) } } } } current = next } } //trace:1035 //trace:1096 不能把td上的样式去掉,比如边框 var pN = bookmark.start.parentNode if ( domUtils.isBlockElm(pN) && !dtd.$tableContent[pN.tagName] && !dtd.$list[pN.tagName] ) { domUtils.removeAttributes(pN, removeFormatAttributes) } pN = bookmark.end.parentNode if ( bookmark.end && domUtils.isBlockElm(pN) && !dtd.$tableContent[pN.tagName] && !dtd.$list[pN.tagName] ) { domUtils.removeAttributes(pN, removeFormatAttributes) } range.moveToBookmark(bookmark).moveToBookmark(bookmark1) //清除冗余的代码 var node = range.startContainer, tmp, collapsed = range.collapsed while ( node.nodeType == 1 && domUtils.isEmptyNode(node) && dtd.$removeEmpty[node.tagName] ) { tmp = node.parentNode range.setStartBefore(node) //trace:937 //更新结束边界 if (range.startContainer === range.endContainer) { range.endOffset-- } domUtils.remove(node) node = tmp } if (!collapsed) { node = range.endContainer while ( node.nodeType == 1 && domUtils.isEmptyNode(node) && dtd.$removeEmpty[node.tagName] ) { tmp = node.parentNode range.setEndBefore(node) domUtils.remove(node) node = tmp } } } range = this.selection.getRange() doRemove(range) range.select() } } } // plugins/blockquote.js /** * 添加引用 * @file * @since 1.2.6.1 */ /** * 添加引用 * @command blockquote * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand( 'blockquote' ); * ``` */ /** * 添加引用 * @command blockquote * @method execCommand * @param { String } cmd 命令字符串 * @param { Object } attrs 节点属性 * @example * ```javascript * editor.execCommand( 'blockquote',{ * style: "color: red;" * } ); * ``` */ UE.plugins['blockquote'] = function () { var me = this function getObj(editor) { return domUtils.filterNodeList( editor.selection.getStartElementPath(), 'blockquote' ) } me.commands['blockquote'] = { execCommand: function (cmdName, attrs) { var range = this.selection.getRange(), obj = getObj(this), blockquote = dtd.blockquote, bookmark = range.createBookmark() if (obj) { var start = range.startContainer, startBlock = domUtils.isBlockElm(start) ? start : domUtils.findParent(start, function (node) { return domUtils.isBlockElm(node) }), end = range.endContainer, endBlock = domUtils.isBlockElm(end) ? end : domUtils.findParent(end, function (node) { return domUtils.isBlockElm(node) }) //处理一下li startBlock = domUtils.findParentByTagName(startBlock, 'li', true) || startBlock endBlock = domUtils.findParentByTagName(endBlock, 'li', true) || endBlock if ( startBlock.tagName == 'LI' || startBlock.tagName == 'TD' || startBlock === obj || domUtils.isBody(startBlock) ) { domUtils.remove(obj, true) } else { domUtils.breakParent(startBlock, obj) } if (startBlock !== endBlock) { obj = domUtils.findParentByTagName(endBlock, 'blockquote') if (obj) { if ( endBlock.tagName == 'LI' || endBlock.tagName == 'TD' || domUtils.isBody(endBlock) ) { obj.parentNode && domUtils.remove(obj, true) } else { domUtils.breakParent(endBlock, obj) } } } var blockquotes = domUtils.getElementsByTagName( this.document, 'blockquote' ) for (var i = 0, bi; (bi = blockquotes[i++]); ) { if (!bi.childNodes.length) { domUtils.remove(bi) } else if ( domUtils.getPosition(bi, startBlock) & domUtils.POSITION_FOLLOWING && domUtils.getPosition(bi, endBlock) & domUtils.POSITION_PRECEDING ) { domUtils.remove(bi, true) } } } else { var tmpRange = range.cloneRange(), node = tmpRange.startContainer.nodeType == 1 ? tmpRange.startContainer : tmpRange.startContainer.parentNode, preNode = node, doEnd = 1 //调整开始 while (1) { if (domUtils.isBody(node)) { if (preNode !== node) { if (range.collapsed) { tmpRange.selectNode(preNode) doEnd = 0 } else { tmpRange.setStartBefore(preNode) } } else { tmpRange.setStart(node, 0) } break } if (!blockquote[node.tagName]) { if (range.collapsed) { tmpRange.selectNode(preNode) } else { tmpRange.setStartBefore(preNode) } break } preNode = node node = node.parentNode } //调整结束 if (doEnd) { preNode = node = node = tmpRange.endContainer.nodeType == 1 ? tmpRange.endContainer : tmpRange.endContainer.parentNode while (1) { if (domUtils.isBody(node)) { if (preNode !== node) { tmpRange.setEndAfter(preNode) } else { tmpRange.setEnd(node, node.childNodes.length) } break } if (!blockquote[node.tagName]) { tmpRange.setEndAfter(preNode) break } preNode = node node = node.parentNode } } node = range.document.createElement('blockquote') domUtils.setAttributes(node, attrs) node.appendChild(tmpRange.extractContents()) tmpRange.insertNode(node) //去除重复的 var childs = domUtils.getElementsByTagName(node, 'blockquote') for (var i = 0, ci; (ci = childs[i++]); ) { if (ci.parentNode) { domUtils.remove(ci, true) } } } range.moveToBookmark(bookmark).select() }, queryCommandState: function () { return getObj(this) ? 1 : 0 } } } // plugins/convertcase.js /** * 大小写转换 * @file * @since 1.2.6.1 */ /** * 把选区内文本变大写,与“tolowercase”命令互斥 * @command touppercase * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand( 'touppercase' ); * ``` */ /** * 把选区内文本变小写,与“touppercase”命令互斥 * @command tolowercase * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand( 'tolowercase' ); * ``` */ UE.commands['touppercase'] = UE.commands['tolowercase'] = { execCommand: function (cmd) { var me = this var rng = me.selection.getRange() if (rng.collapsed) { return rng } var bk = rng.createBookmark(), bkEnd = bk.end, filterFn = function (node) { return !domUtils.isBr(node) && !domUtils.isWhitespace(node) }, curNode = domUtils.getNextDomNode(bk.start, false, filterFn) while ( curNode && domUtils.getPosition(curNode, bkEnd) & domUtils.POSITION_PRECEDING ) { if (curNode.nodeType == 3) { curNode.nodeValue = curNode.nodeValue[ cmd == 'touppercase' ? 'toUpperCase' : 'toLowerCase' ]() } curNode = domUtils.getNextDomNode(curNode, true, filterFn) if (curNode === bkEnd) { break } } rng.moveToBookmark(bk).select() } } // plugins/indent.js /** * 首行缩进 * @file * @since 1.2.6.1 */ /** * 缩进 * @command indent * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand( 'indent' ); * ``` */ UE.commands['indent'] = { execCommand: function () { var me = this, value = me.queryCommandState('indent') ? '0em' : me.options.indentValue || '2em' me.execCommand('Paragraph', 'p', { style: 'text-indent:' + value }) }, queryCommandState: function () { var pN = domUtils.filterNodeList( this.selection.getStartElementPath(), 'p h1 h2 h3 h4 h5 h6' ) return pN && pN.style.textIndent && parseInt(pN.style.textIndent) ? 1 : 0 } } // plugins/print.js /** * 打印 * @file * @since 1.2.6.1 */ /** * 打印 * @command print * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand( 'print' ); * ``` */ UE.commands['print'] = { execCommand: function () { this.window.print() }, notNeedUndo: 1 } // plugins/preview.js /** * 预览 * @file * @since 1.2.6.1 */ /** * 预览 * @command preview * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand( 'preview' ); * ``` */ UE.commands['preview'] = { execCommand: function () { var w = window.open('', '_blank', ''), d = w.document d.open() d.write( '
    ' + this.getContent(null, null, true) + '
    ' ) d.close() }, notNeedUndo: 1 } // plugins/selectall.js /** * 全选 * @file * @since 1.2.6.1 */ /** * 选中所有内容 * @command selectall * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand( 'selectall' ); * ``` */ UE.plugins['selectall'] = function () { var me = this me.commands['selectall'] = { execCommand: function () { //去掉了原生的selectAll,因为会出现报错和当内容为空时,不能出现闭合状态的光标 var me = this, body = me.body, range = me.selection.getRange() range.selectNodeContents(body) if (domUtils.isEmptyBlock(body)) { //opera不能自动合并到元素的里边,要手动处理一下 if ( browser.opera && body.firstChild && body.firstChild.nodeType == 1 ) { range.setStartAtFirst(body.firstChild) } range.collapse(true) } range.select(true) }, notNeedUndo: 1 } //快捷键 me.addshortcutkey({ selectAll: 'ctrl+65' }) } // plugins/paragraph.js /** * 段落样式 * @file * @since 1.2.6.1 */ /** * 段落格式 * @command paragraph * @method execCommand * @param { String } cmd 命令字符串 * @param {String} style 标签值为:'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' * @param {Object} attrs 标签的属性 * @example * ```javascript * editor.execCommand( 'Paragraph','h1','{ * class:'test' * }' ); * ``` */ /** * 返回选区内节点标签名 * @command paragraph * @method queryCommandValue * @param { String } cmd 命令字符串 * @return { String } 节点标签名 * @example * ```javascript * editor.queryCommandValue( 'Paragraph' ); * ``` */ UE.plugins['paragraph'] = function () { var me = this, block = domUtils.isBlockElm, notExchange = ['TD', 'LI', 'PRE'], doParagraph = function (range, style, attrs, sourceCmdName) { var bookmark = range.createBookmark(), filterFn = function (node) { return node.nodeType == 1 ? node.tagName.toLowerCase() != 'br' && !domUtils.isBookmarkNode(node) : !domUtils.isWhitespace(node) }, para range.enlarge(true) var bookmark2 = range.createBookmark(), current = domUtils.getNextDomNode(bookmark2.start, false, filterFn), tmpRange = range.cloneRange(), tmpNode while ( current && !( domUtils.getPosition(current, bookmark2.end) & domUtils.POSITION_FOLLOWING ) ) { if (current.nodeType == 3 || !block(current)) { tmpRange.setStartBefore(current) while (current && current !== bookmark2.end && !block(current)) { tmpNode = current current = domUtils.getNextDomNode( current, false, null, function (node) { return !block(node) } ) } tmpRange.setEndAfter(tmpNode) para = range.document.createElement(style) if (attrs) { domUtils.setAttributes(para, attrs) if ( sourceCmdName && sourceCmdName == 'customstyle' && attrs.style ) { para.style.cssText = attrs.style } } para.appendChild(tmpRange.extractContents()) //需要内容占位 if (domUtils.isEmptyNode(para)) { domUtils.fillChar(range.document, para) } tmpRange.insertNode(para) var parent = para.parentNode //如果para上一级是一个block元素且不是body,td就删除它 if ( block(parent) && !domUtils.isBody(para.parentNode) && utils.indexOf(notExchange, parent.tagName) == -1 ) { //存储dir,style if (!(sourceCmdName && sourceCmdName == 'customstyle')) { parent.getAttribute('dir') && para.setAttribute('dir', parent.getAttribute('dir')) //trace:1070 parent.style.cssText && (para.style.cssText = parent.style.cssText + ';' + para.style.cssText) //trace:1030 parent.style.textAlign && !para.style.textAlign && (para.style.textAlign = parent.style.textAlign) parent.style.textIndent && !para.style.textIndent && (para.style.textIndent = parent.style.textIndent) parent.style.padding && !para.style.padding && (para.style.padding = parent.style.padding) } //trace:1706 选择的就是h1-6要删除 if ( attrs && /h\d/i.test(parent.tagName) && !/h\d/i.test(para.tagName) ) { domUtils.setAttributes(parent, attrs) if ( sourceCmdName && sourceCmdName == 'customstyle' && attrs.style ) { parent.style.cssText = attrs.style } domUtils.remove(para, true) para = parent } else { domUtils.remove(para.parentNode, true) } } if (utils.indexOf(notExchange, parent.tagName) != -1) { current = parent } else { current = para } current = domUtils.getNextDomNode(current, false, filterFn) } else { current = domUtils.getNextDomNode(current, true, filterFn) } } return range.moveToBookmark(bookmark2).moveToBookmark(bookmark) } me.setOpt('paragraph', { p: '', h1: '', h2: '', h3: '', h4: '', h5: '', h6: '' }) me.commands['paragraph'] = { execCommand: function (cmdName, style, attrs, sourceCmdName) { var range = this.selection.getRange() //闭合时单独处理 if (range.collapsed) { var txt = this.document.createTextNode('p') range.insertNode(txt) //去掉冗余的fillchar if (browser.ie) { var node = txt.previousSibling if (node && domUtils.isWhitespace(node)) { domUtils.remove(node) } node = txt.nextSibling if (node && domUtils.isWhitespace(node)) { domUtils.remove(node) } } } range = doParagraph(range, style, attrs, sourceCmdName) if (txt) { range.setStartBefore(txt).collapse(true) pN = txt.parentNode domUtils.remove(txt) if (domUtils.isBlockElm(pN) && domUtils.isEmptyNode(pN)) { domUtils.fillNode(this.document, pN) } } if ( browser.gecko && range.collapsed && range.startContainer.nodeType == 1 ) { var child = range.startContainer.childNodes[range.startOffset] if ( child && child.nodeType == 1 && child.tagName.toLowerCase() == style ) { range.setStart(child, 0).collapse(true) } } //trace:1097 原来有true,原因忘了,但去了就不能清除多余的占位符了 range.select() return true }, queryCommandValue: function () { var node = domUtils.filterNodeList( this.selection.getStartElementPath(), 'p h1 h2 h3 h4 h5 h6' ) return node ? node.tagName.toLowerCase() : '' } } } // plugins/directionality.js /** * 设置文字输入的方向的插件 * @file * @since 1.2.6.1 */ ;(function () { var block = domUtils.isBlockElm, getObj = function (editor) { // var startNode = editor.selection.getStart(), // parents; // if ( startNode ) { // //查找所有的是block的父亲节点 // parents = domUtils.findParents( startNode, true, block, true ); // for ( var i = 0,ci; ci = parents[i++]; ) { // if ( ci.getAttribute( 'dir' ) ) { // return ci; // } // } // } return domUtils.filterNodeList( editor.selection.getStartElementPath(), function (n) { return n && n.nodeType == 1 && n.getAttribute('dir') } ) }, doDirectionality = function (range, editor, forward) { var bookmark, filterFn = function (node) { return node.nodeType == 1 ? !domUtils.isBookmarkNode(node) : !domUtils.isWhitespace(node) }, obj = getObj(editor) if (obj && range.collapsed) { obj.setAttribute('dir', forward) return range } bookmark = range.createBookmark() range.enlarge(true) var bookmark2 = range.createBookmark(), current = domUtils.getNextDomNode(bookmark2.start, false, filterFn), tmpRange = range.cloneRange(), tmpNode while ( current && !( domUtils.getPosition(current, bookmark2.end) & domUtils.POSITION_FOLLOWING ) ) { if (current.nodeType == 3 || !block(current)) { tmpRange.setStartBefore(current) while (current && current !== bookmark2.end && !block(current)) { tmpNode = current current = domUtils.getNextDomNode( current, false, null, function (node) { return !block(node) } ) } tmpRange.setEndAfter(tmpNode) var common = tmpRange.getCommonAncestor() if (!domUtils.isBody(common) && block(common)) { //遍历到了block节点 common.setAttribute('dir', forward) current = common } else { //没有遍历到,添加一个block节点 var p = range.document.createElement('p') p.setAttribute('dir', forward) var frag = tmpRange.extractContents() p.appendChild(frag) tmpRange.insertNode(p) current = p } current = domUtils.getNextDomNode(current, false, filterFn) } else { current = domUtils.getNextDomNode(current, true, filterFn) } } return range.moveToBookmark(bookmark2).moveToBookmark(bookmark) } /** * 文字输入方向 * @command directionality * @method execCommand * @param { String } cmdName 命令字符串 * @param { String } forward 传入'ltr'表示从左向右输入,传入'rtl'表示从右向左输入 * @example * ```javascript * editor.execCommand( 'directionality', 'ltr'); * ``` */ /** * 查询当前选区的文字输入方向 * @command directionality * @method queryCommandValue * @param { String } cmdName 命令字符串 * @return { String } 返回'ltr'表示从左向右输入,返回'rtl'表示从右向左输入 * @example * ```javascript * editor.queryCommandValue( 'directionality'); * ``` */ UE.commands['directionality'] = { execCommand: function (cmdName, forward) { var range = this.selection.getRange() //闭合时单独处理 if (range.collapsed) { var txt = this.document.createTextNode('d') range.insertNode(txt) } doDirectionality(range, this, forward) if (txt) { range.setStartBefore(txt).collapse(true) domUtils.remove(txt) } range.select() return true }, queryCommandValue: function () { var node = getObj(this) return node ? node.getAttribute('dir') : 'ltr' } } })() // plugins/horizontal.js /** * 插入分割线插件 * @file * @since 1.2.6.1 */ /** * 插入分割线 * @command horizontal * @method execCommand * @param { String } cmdName 命令字符串 * @example * ```javascript * editor.execCommand( 'horizontal' ); * ``` */ UE.plugins['horizontal'] = function () { var me = this me.commands['horizontal'] = { execCommand: function (cmdName) { var me = this if (me.queryCommandState(cmdName) !== -1) { me.execCommand('insertHtml', '
    ') var range = me.selection.getRange(), start = range.startContainer if (start.nodeType == 1 && !start.childNodes[range.startOffset]) { var tmp if ((tmp = start.childNodes[range.startOffset - 1])) { if (tmp.nodeType == 1 && tmp.tagName == 'HR') { if (me.options.enterTag == 'p') { tmp = me.document.createElement('p') range.insertNode(tmp) range.setStart(tmp, 0).setCursor() } else { tmp = me.document.createElement('br') range.insertNode(tmp) range.setStartBefore(tmp).setCursor() } } } } return true } }, //边界在table里不能加分隔线 queryCommandState: function () { return domUtils.filterNodeList( this.selection.getStartElementPath(), 'table' ) ? -1 : 0 } } // me.addListener('delkeyup',function(){ // var rng = this.selection.getRange(); // if(browser.ie && browser.version > 8){ // rng.txtToElmBoundary(true); // if(domUtils.isStartInblock(rng)){ // var tmpNode = rng.startContainer; // var pre = tmpNode.previousSibling; // if(pre && domUtils.isTagNode(pre,'hr')){ // domUtils.remove(pre); // rng.select(); // return; // } // } // } // if(domUtils.isBody(rng.startContainer)){ // var hr = rng.startContainer.childNodes[rng.startOffset -1]; // if(hr && hr.nodeName == 'HR'){ // var next = hr.nextSibling; // if(next){ // rng.setStart(next,0) // }else if(hr.previousSibling){ // rng.setStartAtLast(hr.previousSibling) // }else{ // var p = this.document.createElement('p'); // hr.parentNode.insertBefore(p,hr); // domUtils.fillNode(this.document,p); // rng.setStart(p,0); // } // domUtils.remove(hr); // rng.setCursor(false,true); // } // } // }) me.addListener('delkeydown', function (name, evt) { var rng = this.selection.getRange() rng.txtToElmBoundary(true) if (domUtils.isStartInblock(rng)) { var tmpNode = rng.startContainer var pre = tmpNode.previousSibling if (pre && domUtils.isTagNode(pre, 'hr')) { domUtils.remove(pre) rng.select() domUtils.preventDefault(evt) return true } } }) } // plugins/time.js /** * 插入时间和日期 * @file * @since 1.2.6.1 */ /** * 插入时间,默认格式:12:59:59 * @command time * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand( 'time'); * ``` */ /** * 插入日期,默认格式:2013-08-30 * @command date * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand( 'date'); * ``` */ UE.commands['time'] = UE.commands['date'] = { execCommand: function (cmd, format) { var date = new Date() function formatTime(date, format) { var hh = ('0' + date.getHours()).slice(-2), ii = ('0' + date.getMinutes()).slice(-2), ss = ('0' + date.getSeconds()).slice(-2) format = format || 'hh:ii:ss' return format .replace(/hh/gi, hh) .replace(/ii/gi, ii) .replace(/ss/gi, ss) } function formatDate(date, format) { var yyyy = ('000' + date.getFullYear()).slice(-4), yy = yyyy.slice(-2), mm = ('0' + (date.getMonth() + 1)).slice(-2), dd = ('0' + date.getDate()).slice(-2) format = format || 'yyyy-mm-dd' return format .replace(/yyyy/gi, yyyy) .replace(/yy/gi, yy) .replace(/mm/gi, mm) .replace(/dd/gi, dd) } this.execCommand( 'insertHtml', cmd == 'time' ? formatTime(date, format) : formatDate(date, format) ) } } // plugins/rowspacing.js /** * 段前段后间距插件 * @file * @since 1.2.6.1 */ /** * 设置段间距 * @command rowspacing * @method execCommand * @param { String } cmd 命令字符串 * @param { String } value 段间距的值,以px为单位 * @param { String } dir 间距位置,top或bottom,分别表示段前和段后 * @example * ```javascript * editor.execCommand( 'rowspacing', '10', 'top' ); * ``` */ UE.plugins['rowspacing'] = function () { var me = this me.setOpt({ rowspacingtop: ['5', '10', '15', '20', '25'], rowspacingbottom: ['5', '10', '15', '20', '25'] }) me.commands['rowspacing'] = { execCommand: function (cmdName, value, dir) { this.execCommand('paragraph', 'p', { style: 'margin-' + dir + ':' + value + 'px' }) return true }, queryCommandValue: function (cmdName, dir) { var pN = domUtils.filterNodeList( this.selection.getStartElementPath(), function (node) { return domUtils.isBlockElm(node) } ), value //trace:1026 if (pN) { value = domUtils .getComputedStyle(pN, 'margin-' + dir) .replace(/[^\d]/g, '') return !value ? 0 : value } return 0 } } } // plugins/lineheight.js /** * 设置行内间距 * @file * @since 1.2.6.1 */ UE.plugins['lineheight'] = function () { var me = this me.setOpt({ lineheight: ['1', '1.5', '1.75', '2', '3', '4', '5'] }) /** * 行距 * @command lineheight * @method execCommand * @param { String } cmdName 命令字符串 * @param { String } value 传入的行高值, 该值是当前字体的倍数, 例如: 1.5, 1.75 * @example * ```javascript * editor.execCommand( 'lineheight', 1.5); * ``` */ /** * 查询当前选区内容的行高大小 * @command lineheight * @method queryCommandValue * @param { String } cmd 命令字符串 * @return { String } 返回当前行高大小 * @example * ```javascript * editor.queryCommandValue( 'lineheight' ); * ``` */ me.commands['lineheight'] = { execCommand: function (cmdName, value) { this.execCommand('paragraph', 'p', { style: 'line-height:' + (value == '1' ? 'normal' : value + 'em') }) return true }, queryCommandValue: function () { var pN = domUtils.filterNodeList( this.selection.getStartElementPath(), function (node) { return domUtils.isBlockElm(node) } ) if (pN) { var value = domUtils.getComputedStyle(pN, 'line-height') return value == 'normal' ? 1 : value.replace(/[^\d.]*/gi, '') } } } } // plugins/insertcode.js /** * 插入代码插件 * @file * @since 1.2.6.1 */ UE.plugins['insertcode'] = function () { var me = this me.ready(function () { utils.cssRule( 'pre', 'pre{margin:.5em 0;padding:.4em .6em;border-radius:8px;background:#f8f8f8;}', me.document ) }) me.setOpt('insertcode', { as3: 'ActionScript3', bash: 'Bash/Shell', cpp: 'C/C++', css: 'Css', cf: 'CodeFunction', 'c#': 'C#', delphi: 'Delphi', diff: 'Diff', erlang: 'Erlang', groovy: 'Groovy', html: 'Html', java: 'Java', jfx: 'JavaFx', js: 'Javascript', pl: 'Perl', php: 'Php', plain: 'Plain Text', ps: 'PowerShell', python: 'Python', ruby: 'Ruby', scala: 'Scala', sql: 'Sql', vb: 'Vb', xml: 'Xml' }) /** * 插入代码 * @command insertcode * @method execCommand * @param { String } cmd 命令字符串 * @param { String } lang 插入代码的语言 * @example * ```javascript * editor.execCommand( 'insertcode', 'javascript' ); * ``` */ /** * 如果选区所在位置是插入插入代码区域,返回代码的语言 * @command insertcode * @method queryCommandValue * @param { String } cmd 命令字符串 * @return { String } 返回代码的语言 * @example * ```javascript * editor.queryCommandValue( 'insertcode' ); * ``` */ me.commands['insertcode'] = { execCommand: function (cmd, lang) { var me = this, rng = me.selection.getRange(), pre = domUtils.findParentByTagName(rng.startContainer, 'pre', true) if (pre) { pre.className = 'brush:' + lang + ';toolbar:false;' } else { var code = '' if (rng.collapsed) { code = browser.ie && browser.ie11below ? browser.version <= 8 ? ' ' : '' : '
    ' } else { var frag = rng.extractContents() var div = me.document.createElement('div') div.appendChild(frag) utils.each( UE.filterNode( UE.htmlparser(div.innerHTML.replace(/[\r\t]/g, '')), me.options.filterTxtRules ).children, function (node) { if (browser.ie && browser.ie11below && browser.version > 8) { if (node.type == 'element') { if (node.tagName == 'br') { code += '\n' } else if (!dtd.$empty[node.tagName]) { utils.each(node.children, function (cn) { if (cn.type == 'element') { if (cn.tagName == 'br') { code += '\n' } else if (!dtd.$empty[node.tagName]) { code += cn.innerText() } } else { code += cn.data } }) if (!/\n$/.test(code)) { code += '\n' } } } else { code += node.data + '\n' } if (!node.nextSibling() && /\n$/.test(code)) { code = code.replace(/\n$/, '') } } else { if (browser.ie && browser.ie11below) { if (node.type == 'element') { if (node.tagName == 'br') { code += '
    ' } else if (!dtd.$empty[node.tagName]) { utils.each(node.children, function (cn) { if (cn.type == 'element') { if (cn.tagName == 'br') { code += '
    ' } else if (!dtd.$empty[node.tagName]) { code += cn.innerText() } } else { code += cn.data } }) if (!/br>$/.test(code)) { code += '
    ' } } } else { code += node.data + '
    ' } if (!node.nextSibling() && /
    $/.test(code)) { code = code.replace(/
    $/, '') } } else { code += node.type == 'element' ? dtd.$empty[node.tagName] ? '' : node.innerText() : node.data if (!/br\/?\s*>$/.test(code)) { if (!node.nextSibling()) return code += '
    ' } } } } ) } me.execCommand( 'inserthtml', '
    ' +
                  code +
                  '
    ', true ) pre = me.document.getElementById('coder') domUtils.removeAttributes(pre, 'id') var tmpNode = pre.previousSibling if ( tmpNode && ((tmpNode.nodeType == 3 && tmpNode.nodeValue.length == 1 && browser.ie && browser.version == 6) || domUtils.isEmptyBlock(tmpNode)) ) { domUtils.remove(tmpNode) } var rng = me.selection.getRange() if (domUtils.isEmptyBlock(pre)) { rng.setStart(pre, 0).setCursor(false, true) } else { rng.selectNodeContents(pre).select() } } }, queryCommandValue: function () { var path = this.selection.getStartElementPath() var lang = '' utils.each(path, function (node) { if (node.nodeName == 'PRE') { var match = node.className.match(/brush:([^;]+)/) lang = match && match[1] ? match[1] : '' return false } }) return lang } } me.addInputRule(function (root) { utils.each(root.getNodesByTagName('pre'), function (pre) { var brs = pre.getNodesByTagName('br') if (brs.length) { browser.ie && browser.ie11below && browser.version > 8 && utils.each(brs, function (br) { var txt = UE.uNode.createText('\n') br.parentNode.insertBefore(txt, br) br.parentNode.removeChild(br) }) return } if (browser.ie && browser.ie11below && browser.version > 8) return var code = pre.innerText().split(/\n/) pre.innerHTML('') utils.each(code, function (c) { if (c.length) { pre.appendChild(UE.uNode.createText(c)) } pre.appendChild(UE.uNode.createElement('br')) }) }) }) me.addOutputRule(function (root) { utils.each(root.getNodesByTagName('pre'), function (pre) { var code = '' utils.each(pre.children, function (n) { if (n.type == 'text') { //在ie下文本内容有可能末尾带有\n要去掉 //trace:3396 code += n.data.replace(/[ ]/g, ' ').replace(/\n$/, '') } else { if (n.tagName == 'br') { code += '\n' } else { code += !dtd.$empty[n.tagName] ? '' : n.innerText() } } }) pre.innerText(code.replace(/( |\n)+$/, '')) }) }) //不需要判断highlight的command列表 me.notNeedCodeQuery = { help: 1, undo: 1, redo: 1, source: 1, print: 1, searchreplace: 1, fullscreen: 1, preview: 1, insertparagraph: 1, elementpath: 1, insertcode: 1, inserthtml: 1, selectall: 1 } //将queyCommamndState重置 var orgQuery = me.queryCommandState me.queryCommandState = function (cmd) { var me = this if ( !me.notNeedCodeQuery[cmd.toLowerCase()] && me.selection && me.queryCommandValue('insertcode') ) { return -1 } return UE.Editor.prototype.queryCommandState.apply(this, arguments) } me.addListener('beforeenterkeydown', function () { var rng = me.selection.getRange() var pre = domUtils.findParentByTagName(rng.startContainer, 'pre', true) if (pre) { me.fireEvent('saveScene') if (!rng.collapsed) { rng.deleteContents() } if (!browser.ie || browser.ie9above) { var tmpNode = me.document.createElement('br'), pre rng.insertNode(tmpNode).setStartAfter(tmpNode).collapse(true) var next = tmpNode.nextSibling if (!next && (!browser.ie || browser.version > 10)) { rng.insertNode(tmpNode.cloneNode(false)) } else { rng.setStartAfter(tmpNode) } pre = tmpNode.previousSibling var tmp while (pre) { tmp = pre pre = pre.previousSibling if (!pre || pre.nodeName == 'BR') { pre = tmp break } } if (pre) { var str = '' while ( pre && pre.nodeName != 'BR' && new RegExp('^[\\s' + domUtils.fillChar + ']*$').test( pre.nodeValue ) ) { str += pre.nodeValue pre = pre.nextSibling } if (pre.nodeName != 'BR') { var match = pre.nodeValue.match( new RegExp('^([\\s' + domUtils.fillChar + ']+)') ) if (match && match[1]) { str += match[1] } } if (str) { str = me.document.createTextNode(str) rng.insertNode(str).setStartAfter(str) } } rng.collapse(true).select(true) } else { if (browser.version > 8) { var txt = me.document.createTextNode('\n') var start = rng.startContainer if (rng.startOffset == 0) { var preNode = start.previousSibling if (preNode) { rng.insertNode(txt) var fillchar = me.document.createTextNode(' ') rng .setStartAfter(txt) .insertNode(fillchar) .setStart(fillchar, 0) .collapse(true) .select(true) } } else { rng.insertNode(txt).setStartAfter(txt) var fillchar = me.document.createTextNode(' ') start = rng.startContainer.childNodes[rng.startOffset] if (start && !/^\n/.test(start.nodeValue)) { rng.setStartBefore(txt) } rng .insertNode(fillchar) .setStart(fillchar, 0) .collapse(true) .select(true) } } else { var tmpNode = me.document.createElement('br') rng.insertNode(tmpNode) rng.insertNode(me.document.createTextNode(domUtils.fillChar)) rng.setStartAfter(tmpNode) pre = tmpNode.previousSibling var tmp while (pre) { tmp = pre pre = pre.previousSibling if (!pre || pre.nodeName == 'BR') { pre = tmp break } } if (pre) { var str = '' while ( pre && pre.nodeName != 'BR' && new RegExp('^[ ' + domUtils.fillChar + ']*$').test( pre.nodeValue ) ) { str += pre.nodeValue pre = pre.nextSibling } if (pre.nodeName != 'BR') { var match = pre.nodeValue.match( new RegExp('^([ ' + domUtils.fillChar + ']+)') ) if (match && match[1]) { str += match[1] } } str = me.document.createTextNode(str) rng.insertNode(str).setStartAfter(str) } rng.collapse(true).select() } } me.fireEvent('saveScene') return true } }) me.addListener('tabkeydown', function (cmd, evt) { var rng = me.selection.getRange() var pre = domUtils.findParentByTagName(rng.startContainer, 'pre', true) if (pre) { me.fireEvent('saveScene') if (evt.shiftKey) { } else { if (!rng.collapsed) { var bk = rng.createBookmark() var start = bk.start.previousSibling while (start) { if (pre.firstChild === start && !domUtils.isBr(start)) { pre.insertBefore(me.document.createTextNode(' '), start) break } if (domUtils.isBr(start)) { pre.insertBefore( me.document.createTextNode(' '), start.nextSibling ) break } start = start.previousSibling } var end = bk.end start = bk.start.nextSibling if (pre.firstChild === bk.start) { pre.insertBefore( me.document.createTextNode(' '), start.nextSibling ) } while (start && start !== end) { if (domUtils.isBr(start) && start.nextSibling) { if (start.nextSibling === end) { break } pre.insertBefore( me.document.createTextNode(' '), start.nextSibling ) } start = start.nextSibling } rng.moveToBookmark(bk).select() } else { var tmpNode = me.document.createTextNode(' ') rng .insertNode(tmpNode) .setStartAfter(tmpNode) .collapse(true) .select(true) } } me.fireEvent('saveScene') return true } }) me.addListener('beforeinserthtml', function (evtName, html) { var me = this, rng = me.selection.getRange(), pre = domUtils.findParentByTagName(rng.startContainer, 'pre', true) if (pre) { if (!rng.collapsed) { rng.deleteContents() } var htmlstr = '' if (browser.ie && browser.version > 8) { utils.each( UE.filterNode(UE.htmlparser(html), me.options.filterTxtRules) .children, function (node) { if (node.type == 'element') { if (node.tagName == 'br') { htmlstr += '\n' } else if (!dtd.$empty[node.tagName]) { utils.each(node.children, function (cn) { if (cn.type == 'element') { if (cn.tagName == 'br') { htmlstr += '\n' } else if (!dtd.$empty[node.tagName]) { htmlstr += cn.innerText() } } else { htmlstr += cn.data } }) if (!/\n$/.test(htmlstr)) { htmlstr += '\n' } } } else { htmlstr += node.data + '\n' } if (!node.nextSibling() && /\n$/.test(htmlstr)) { htmlstr = htmlstr.replace(/\n$/, '') } } ) var tmpNode = me.document.createTextNode( utils.html(htmlstr.replace(/ /g, ' ')) ) rng.insertNode(tmpNode).selectNode(tmpNode).select() } else { var frag = me.document.createDocumentFragment() utils.each( UE.filterNode(UE.htmlparser(html), me.options.filterTxtRules) .children, function (node) { if (node.type == 'element') { if (node.tagName == 'br') { frag.appendChild(me.document.createElement('br')) } else if (!dtd.$empty[node.tagName]) { utils.each(node.children, function (cn) { if (cn.type == 'element') { if (cn.tagName == 'br') { frag.appendChild(me.document.createElement('br')) } else if (!dtd.$empty[node.tagName]) { frag.appendChild( me.document.createTextNode( utils.html(cn.innerText().replace(/ /g, ' ')) ) ) } } else { frag.appendChild( me.document.createTextNode( utils.html(cn.data.replace(/ /g, ' ')) ) ) } }) if (frag.lastChild.nodeName != 'BR') { frag.appendChild(me.document.createElement('br')) } } } else { frag.appendChild( me.document.createTextNode( utils.html(node.data.replace(/ /g, ' ')) ) ) } if (!node.nextSibling() && frag.lastChild.nodeName == 'BR') { frag.removeChild(frag.lastChild) } } ) rng.insertNode(frag).select() } return true } }) //方向键的处理 me.addListener('keydown', function (cmd, evt) { var me = this, keyCode = evt.keyCode || evt.which if (keyCode == 40) { var rng = me.selection.getRange(), pre, start = rng.startContainer if ( rng.collapsed && (pre = domUtils.findParentByTagName( rng.startContainer, 'pre', true )) && !pre.nextSibling ) { var last = pre.lastChild while (last && last.nodeName == 'BR') { last = last.previousSibling } if ( last === start || (rng.startContainer === pre && rng.startOffset == pre.childNodes.length) ) { me.execCommand('insertparagraph') domUtils.preventDefault(evt) } } } }) //trace:3395 me.addListener('delkeydown', function (type, evt) { var rng = this.selection.getRange() rng.txtToElmBoundary(true) var start = rng.startContainer if ( domUtils.isTagNode(start, 'pre') && rng.collapsed && domUtils.isStartInblock(rng) ) { var p = me.document.createElement('p') domUtils.fillNode(me.document, p) start.parentNode.insertBefore(p, start) domUtils.remove(start) rng.setStart(p, 0).setCursor(false, true) domUtils.preventDefault(evt) return true } }) } // plugins/cleardoc.js /** * 清空文档插件 * @file * @since 1.2.6.1 */ /** * 清空文档 * @command cleardoc * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * //editor 是编辑器实例 * editor.execCommand('cleardoc'); * ``` */ UE.commands['cleardoc'] = { execCommand: function (cmdName) { var me = this, enterTag = me.options.enterTag, range = me.selection.getRange() if (enterTag == 'br') { me.body.innerHTML = '
    ' range.setStart(me.body, 0).setCursor() } else { me.body.innerHTML = '

    ' + (ie ? '' : '
    ') + '

    ' range.setStart(me.body.firstChild, 0).setCursor(false, true) } setTimeout(function () { me.fireEvent('clearDoc') }, 0) } } // plugins/anchor.js /** * 锚点插件,为UEditor提供插入锚点支持 * @file * @since 1.2.6.1 */ UE.plugin.register('anchor', function () { return { bindEvents: { ready: function () { utils.cssRule( 'anchor', ".anchorclass{background: url('" + this.options.themePath + this.options.theme + "/images/anchor.gif') no-repeat scroll left center transparent;cursor: auto;display: inline-block;height: 16px;width: 15px;}", this.document ) } }, outputRule: function (root) { utils.each(root.getNodesByTagName('img'), function (a) { var val if ((val = a.getAttr('anchorname'))) { a.tagName = 'a' a.setAttr({ anchorname: '', name: val, class: '' }) } }) }, inputRule: function (root) { utils.each(root.getNodesByTagName('a'), function (a) { var val if ((val = a.getAttr('name')) && !a.getAttr('href')) { a.tagName = 'img' a.setAttr({ anchorname: a.getAttr('name'), class: 'anchorclass' }) a.setAttr('name') } }) }, commands: { /** * 插入锚点 * @command anchor * @method execCommand * @param { String } cmd 命令字符串 * @param { String } name 锚点名称字符串 * @example * ```javascript * //editor 是编辑器实例 * editor.execCommand('anchor', 'anchor1'); * ``` */ anchor: { execCommand: function (cmd, name) { var range = this.selection.getRange(), img = range.getClosedNode() if (img && img.getAttribute('anchorname')) { if (name) { img.setAttribute('anchorname', name) } else { range.setStartBefore(img).setCursor() domUtils.remove(img) } } else { if (name) { //只在选区的开始插入 var anchor = this.document.createElement('img') range.collapse(true) domUtils.setAttributes(anchor, { anchorname: name, class: 'anchorclass' }) range .insertNode(anchor) .setStartAfter(anchor) .setCursor(false, true) } } } } } } }) // plugins/wordcount.js ///import core ///commands 字数统计 ///commandsName WordCount,wordCount ///commandsTitle 字数统计 /* * Created by JetBrains WebStorm. * User: taoqili * Date: 11-9-7 * Time: 下午8:18 * To change this template use File | Settings | File Templates. */ UE.plugins['wordcount'] = function () { var me = this me.setOpt('wordCount', true) me.addListener('contentchange', function () { me.fireEvent('wordcount') }) var timer me.addListener('ready', function () { var me = this domUtils.on(me.body, 'keyup', function (evt) { var code = evt.keyCode || evt.which, //忽略的按键,ctr,alt,shift,方向键 ignores = { 16: 1, 18: 1, 20: 1, 37: 1, 38: 1, 39: 1, 40: 1 } if (code in ignores) return clearTimeout(timer) timer = setTimeout(function () { me.fireEvent('wordcount') }, 200) }) }) } // plugins/pagebreak.js /** * 分页功能插件 * @file * @since 1.2.6.1 */ UE.plugins['pagebreak'] = function () { var me = this, notBreakTags = ['td'] me.setOpt('pageBreakTag', '_ueditor_page_break_tag_') function fillNode(node) { if (domUtils.isEmptyBlock(node)) { var firstChild = node.firstChild, tmpNode while ( firstChild && firstChild.nodeType == 1 && domUtils.isEmptyBlock(firstChild) ) { tmpNode = firstChild firstChild = firstChild.firstChild } !tmpNode && (tmpNode = node) domUtils.fillNode(me.document, tmpNode) } } //分页符样式添加 me.ready(function () { utils.cssRule( 'pagebreak', '.pagebreak{display:block;clear:both !important;cursor:default !important;width: 100% !important;margin:0;}', me.document ) }) function isHr(node) { return ( node && node.nodeType == 1 && node.tagName == 'HR' && node.className == 'pagebreak' ) } me.addInputRule(function (root) { root.traversal(function (node) { if (node.type == 'text' && node.data == me.options.pageBreakTag) { var hr = UE.uNode.createElement( '
    ' ) node.parentNode.insertBefore(hr, node) node.parentNode.removeChild(node) } }) }) me.addOutputRule(function (node) { utils.each(node.getNodesByTagName('hr'), function (n) { if (n.getAttr('class') == 'pagebreak') { var txt = UE.uNode.createText(me.options.pageBreakTag) n.parentNode.insertBefore(txt, n) n.parentNode.removeChild(n) } }) }) /** * 插入分页符 * @command pagebreak * @method execCommand * @param { String } cmd 命令字符串 * @remind 在表格中插入分页符会把表格切分成两部分 * @remind 获取编辑器内的数据时, 编辑器会把分页符转换成“_ueditor_page_break_tag_”字符串, * 以便于提交数据到服务器端后处理分页。 * @example * ```javascript * editor.execCommand( 'pagebreak'); //插入一个hr标签,带有样式类名pagebreak * ``` */ me.commands['pagebreak'] = { execCommand: function () { var range = me.selection.getRange(), hr = me.document.createElement('hr') domUtils.setAttributes(hr, { class: 'pagebreak', noshade: 'noshade', size: '5' }) domUtils.unSelectable(hr) //table单独处理 var node = domUtils.findParentByTagName( range.startContainer, notBreakTags, true ), parents = [], pN if (node) { switch (node.tagName) { case 'TD': pN = node.parentNode if (!pN.previousSibling) { var table = domUtils.findParentByTagName(pN, 'table') // var tableWrapDiv = table.parentNode; // if(tableWrapDiv && tableWrapDiv.nodeType == 1 // && tableWrapDiv.tagName == 'DIV' // && tableWrapDiv.getAttribute('dropdrag') // ){ // domUtils.remove(tableWrapDiv,true); // } table.parentNode.insertBefore(hr, table) parents = domUtils.findParents(hr, true) } else { pN.parentNode.insertBefore(hr, pN) parents = domUtils.findParents(hr) } pN = parents[1] if (hr !== pN) { domUtils.breakParent(hr, pN) } //table要重写绑定一下拖拽 me.fireEvent('afteradjusttable', me.document) } } else { if (!range.collapsed) { range.deleteContents() var start = range.startContainer while ( !domUtils.isBody(start) && domUtils.isBlockElm(start) && domUtils.isEmptyNode(start) ) { range.setStartBefore(start).collapse(true) domUtils.remove(start) start = range.startContainer } } range.insertNode(hr) var pN = hr.parentNode, nextNode while (!domUtils.isBody(pN)) { domUtils.breakParent(hr, pN) nextNode = hr.nextSibling if (nextNode && domUtils.isEmptyBlock(nextNode)) { domUtils.remove(nextNode) } pN = hr.parentNode } nextNode = hr.nextSibling var pre = hr.previousSibling if (isHr(pre)) { domUtils.remove(pre) } else { pre && fillNode(pre) } if (!nextNode) { var p = me.document.createElement('p') hr.parentNode.appendChild(p) domUtils.fillNode(me.document, p) range.setStart(p, 0).collapse(true) } else { if (isHr(nextNode)) { domUtils.remove(nextNode) } else { fillNode(nextNode) } range.setEndAfter(hr).collapse(false) } range.select(true) } } } } // plugins/wordimage.js ///import core ///commands 本地图片引导上传 ///commandsName WordImage ///commandsTitle 本地图片引导上传 ///commandsDialog dialogs\wordimage UE.plugin.register('wordimage', function () { var me = this, images = [] return { commands: { wordimage: { execCommand: function () { var images = domUtils.getElementsByTagName(me.body, 'img') var urlList = [] for (var i = 0, ci; (ci = images[i++]); ) { var url = ci.getAttribute('word_img') url && urlList.push(url) } return urlList }, queryCommandState: function () { images = domUtils.getElementsByTagName(me.body, 'img') for (var i = 0, ci; (ci = images[i++]); ) { if (ci.getAttribute('word_img')) { return 1 } } return -1 }, notNeedUndo: true } }, inputRule: function (root) { utils.each(root.getNodesByTagName('img'), function (img) { var attrs = img.attrs, flag = parseInt(attrs.width) < 128 || parseInt(attrs.height) < 43, opt = me.options, src = opt.UEDITOR_HOME_URL + 'themes/default/images/spacer.gif' if (attrs['src'] && /^(?:(file:\/+))/.test(attrs['src'])) { img.setAttr({ width: attrs.width, height: attrs.height, alt: attrs.alt, word_img: attrs.src, src: src, style: 'background:url(' + (flag ? opt.themePath + opt.theme + '/images/word.gif' : opt.langPath + opt.lang + '/images/localimage.png') + ') no-repeat center center;border:1px solid #ddd' }) } }) } } }) // plugins/dragdrop.js UE.plugins['dragdrop'] = function () { var me = this me.ready(function () { domUtils.on(this.body, 'dragend', function () { var rng = me.selection.getRange() var node = rng.getClosedNode() || me.selection.getStart() if (node && node.tagName == 'IMG') { var pre = node.previousSibling, next while ((next = node.nextSibling)) { if ( next.nodeType == 1 && next.tagName == 'SPAN' && !next.firstChild ) { domUtils.remove(next) } else { break } } if ( ((pre && pre.nodeType == 1 && !domUtils.isEmptyBlock(pre)) || !pre) && (!next || (next && !domUtils.isEmptyBlock(next))) ) { if (pre && pre.tagName == 'P' && !domUtils.isEmptyBlock(pre)) { pre.appendChild(node) domUtils.moveChild(next, pre) domUtils.remove(next) } else if ( next && next.tagName == 'P' && !domUtils.isEmptyBlock(next) ) { next.insertBefore(node, next.firstChild) } if (pre && pre.tagName == 'P' && domUtils.isEmptyBlock(pre)) { domUtils.remove(pre) } if (next && next.tagName == 'P' && domUtils.isEmptyBlock(next)) { domUtils.remove(next) } rng.selectNode(node).select() me.fireEvent('saveScene') } } }) }) me.addListener('keyup', function (type, evt) { var keyCode = evt.keyCode || evt.which if (keyCode == 13) { var rng = me.selection.getRange(), node if ( (node = domUtils.findParentByTagName(rng.startContainer, 'p', true)) ) { if (domUtils.getComputedStyle(node, 'text-align') == 'center') { domUtils.removeStyle(node, 'text-align') } } } }) } // plugins/undo.js /** * undo redo * @file * @since 1.2.6.1 */ /** * 撤销上一次执行的命令 * @command undo * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand( 'undo' ); * ``` */ /** * 重做上一次执行的命令 * @command redo * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand( 'redo' ); * ``` */ UE.plugins['undo'] = function () { var saveSceneTimer var me = this, maxUndoCount = me.options.maxUndoCount || 20, maxInputCount = me.options.maxInputCount || 20, fillchar = new RegExp(domUtils.fillChar + '|', 'gi') // ie会产生多余的 var noNeedFillCharTags = { ol: 1, ul: 1, table: 1, tbody: 1, tr: 1, body: 1 } var orgState = me.options.autoClearEmptyNode function compareAddr(indexA, indexB) { if (indexA.length != indexB.length) return 0 for (var i = 0, l = indexA.length; i < l; i++) { if (indexA[i] != indexB[i]) return 0 } return 1 } function compareRangeAddress(rngAddrA, rngAddrB) { if (rngAddrA.collapsed != rngAddrB.collapsed) { return 0 } if ( !compareAddr(rngAddrA.startAddress, rngAddrB.startAddress) || !compareAddr(rngAddrA.endAddress, rngAddrB.endAddress) ) { return 0 } return 1 } function UndoManager() { this.list = [] this.index = 0 this.hasUndo = false this.hasRedo = false this.undo = function () { if (this.hasUndo) { if (!this.list[this.index - 1] && this.list.length == 1) { this.reset() return } while ( this.list[this.index].content == this.list[this.index - 1].content ) { this.index-- if (this.index == 0) { return this.restore(0) } } this.restore(--this.index) } } this.redo = function () { if (this.hasRedo) { while ( this.list[this.index].content == this.list[this.index + 1].content ) { this.index++ if (this.index == this.list.length - 1) { return this.restore(this.index) } } this.restore(++this.index) } } this.restore = function () { var me = this.editor var scene = this.list[this.index] var root = UE.htmlparser(scene.content.replace(fillchar, '')) me.options.autoClearEmptyNode = false me.filterInputRule(root) me.options.autoClearEmptyNode = orgState //trace:873 //去掉展位符 me.document.body.innerHTML = root.toHtml() me.fireEvent('afterscencerestore') //处理undo后空格不展位的问题 if (browser.ie) { utils.each( domUtils.getElementsByTagName(me.document, 'td th caption p'), function (node) { if (domUtils.isEmptyNode(node)) { domUtils.fillNode(me.document, node) } } ) } try { var rng = new dom.Range(me.document).moveToAddress(scene.address) rng.select( noNeedFillCharTags[rng.startContainer.nodeName.toLowerCase()] ) } catch (e) {} this.update() this.clearKey() //不能把自己reset了 me.fireEvent('reset', true) } this.getScene = function () { var me = this.editor var rng = me.selection.getRange(), rngAddress = rng.createAddress(false, true) me.fireEvent('beforegetscene') var root = UE.htmlparser(me.body.innerHTML) me.options.autoClearEmptyNode = false me.filterOutputRule(root) me.options.autoClearEmptyNode = orgState var cont = root.toHtml() //trace:3461 //这个会引起回退时导致空格丢失的情况 // browser.ie && (cont = cont.replace(/> <').replace(/\s*\s*/g, '>')); me.fireEvent('aftergetscene') return { address: rngAddress, content: cont } } this.save = function (notCompareRange, notSetCursor) { clearTimeout(saveSceneTimer) var currentScene = this.getScene(notSetCursor), lastScene = this.list[this.index] if (lastScene && lastScene.content != currentScene.content) { me.trigger('contentchange') } //内容相同位置相同不存 if ( lastScene && lastScene.content == currentScene.content && (notCompareRange ? 1 : compareRangeAddress(lastScene.address, currentScene.address)) ) { return } this.list = this.list.slice(0, this.index + 1) this.list.push(currentScene) //如果大于最大数量了,就把最前的剔除 if (this.list.length > maxUndoCount) { this.list.shift() } this.index = this.list.length - 1 this.clearKey() //跟新undo/redo状态 this.update() } this.update = function () { this.hasRedo = !!this.list[this.index + 1] this.hasUndo = !!this.list[this.index - 1] } this.reset = function () { this.list = [] this.index = 0 this.hasUndo = false this.hasRedo = false this.clearKey() } this.clearKey = function () { keycont = 0 lastKeyCode = null } } me.undoManger = new UndoManager() me.undoManger.editor = me function saveScene() { this.undoManger.save() } me.addListener('saveScene', function () { var args = Array.prototype.splice.call(arguments, 1) this.undoManger.save.apply(this.undoManger, args) }) // me.addListener('beforeexeccommand', saveScene); // me.addListener('afterexeccommand', saveScene); me.addListener('reset', function (type, exclude) { if (!exclude) { this.undoManger.reset() } }) me.commands['redo'] = me.commands['undo'] = { execCommand: function (cmdName) { this.undoManger[cmdName]() }, queryCommandState: function (cmdName) { return this.undoManger[ 'has' + (cmdName.toLowerCase() == 'undo' ? 'Undo' : 'Redo') ] ? 0 : -1 }, notNeedUndo: 1 } var keys = { // /*Backspace*/ 8:1, /*Delete*/ 46:1, /*Shift*/ 16: 1, /*Ctrl*/ 17: 1, /*Alt*/ 18: 1, 37: 1, 38: 1, 39: 1, 40: 1 }, keycont = 0, lastKeyCode //输入法状态下不计算字符数 var inputType = false me.addListener('ready', function () { domUtils.on(this.body, 'compositionstart', function () { inputType = true }) domUtils.on(this.body, 'compositionend', function () { inputType = false }) }) //快捷键 me.addshortcutkey({ Undo: 'ctrl+90', //undo Redo: 'ctrl+89' //redo }) var isCollapsed = true me.addListener('keydown', function (type, evt) { var me = this var keyCode = evt.keyCode || evt.which if ( !keys[keyCode] && !evt.ctrlKey && !evt.metaKey && !evt.shiftKey && !evt.altKey ) { if (inputType) return if (!me.selection.getRange().collapsed) { me.undoManger.save(false, true) isCollapsed = false return } if (me.undoManger.list.length == 0) { me.undoManger.save(true) } clearTimeout(saveSceneTimer) function save(cont) { cont.undoManger.save(false, true) cont.fireEvent('selectionchange') } saveSceneTimer = setTimeout(function () { if (inputType) { var interalTimer = setInterval(function () { if (!inputType) { save(me) clearInterval(interalTimer) } }, 300) return } save(me) }, 200) lastKeyCode = keyCode keycont++ if (keycont >= maxInputCount) { save(me) } } }) me.addListener('keyup', function (type, evt) { var keyCode = evt.keyCode || evt.which if ( !keys[keyCode] && !evt.ctrlKey && !evt.metaKey && !evt.shiftKey && !evt.altKey ) { if (inputType) return if (!isCollapsed) { this.undoManger.save(false, true) isCollapsed = true } } }) //扩展实例,添加关闭和开启命令undo me.stopCmdUndo = function () { me.__hasEnterExecCommand = true } me.startCmdUndo = function () { me.__hasEnterExecCommand = false } } // plugins/copy.js UE.plugin.register('copy', function () { var me = this function initZeroClipboard() { ZeroClipboard.config({ debug: false, swfPath: me.options.UEDITOR_HOME_URL + 'third-party/zeroclipboard/ZeroClipboard.swf' }) var client = (me.zeroclipboard = new ZeroClipboard()) // 复制内容 client.on('copy', function (e) { var client = e.client, rng = me.selection.getRange(), div = document.createElement('div') div.appendChild(rng.cloneContents()) client.setText(div.innerText || div.textContent) client.setHtml(div.innerHTML) rng.select() }) // hover事件传递到target client.on('mouseover mouseout', function (e) { var target = e.target if (e.type == 'mouseover') { domUtils.addClass(target, 'edui-state-hover') } else if (e.type == 'mouseout') { domUtils.removeClasses(target, 'edui-state-hover') } }) // flash加载不成功 client.on('wrongflash noflash', function () { ZeroClipboard.destroy() }) } return { bindEvents: { ready: function () { if (!browser.ie) { if (window.ZeroClipboard) { initZeroClipboard() } else { utils.loadFile( document, { src: me.options.UEDITOR_HOME_URL + 'third-party/zeroclipboard/ZeroClipboard.js', tag: 'script', type: 'text/javascript', defer: 'defer' }, function () { initZeroClipboard() } ) } } } }, commands: { copy: { execCommand: function (cmd) { if (!me.document.execCommand('copy')) { alert(me.getLang('copymsg')) } } } } } }) // plugins/paste.js ///import core ///import plugins/inserthtml.js ///import plugins/undo.js ///import plugins/serialize.js ///commands 粘贴 ///commandsName PastePlain ///commandsTitle 纯文本粘贴模式 /** * @description 粘贴 * @author zhanyi */ UE.plugins['paste'] = function () { function getClipboardData(callback) { var doc = this.document if (doc.getElementById('baidu_pastebin')) { return } var range = this.selection.getRange(), bk = range.createBookmark(), //创建剪贴的容器div pastebin = doc.createElement('div') pastebin.id = 'baidu_pastebin' // Safari 要求div必须有内容,才能粘贴内容进来 browser.webkit && pastebin.appendChild( doc.createTextNode(domUtils.fillChar + domUtils.fillChar) ) doc.body.appendChild(pastebin) //trace:717 隐藏的span不能得到top //bk.start.innerHTML = ' '; bk.start.style.display = '' pastebin.style.cssText = 'position:absolute;width:1px;height:1px;overflow:hidden;left:-1000px;white-space:nowrap;top:' + //要在现在光标平行的位置加入,否则会出现跳动的问题 domUtils.getXY(bk.start).y + 'px' range.selectNodeContents(pastebin).select(true) setTimeout(function () { if (browser.webkit) { for ( var i = 0, pastebins = doc.querySelectorAll('#baidu_pastebin'), pi; (pi = pastebins[i++]); ) { if (domUtils.isEmptyNode(pi)) { domUtils.remove(pi) } else { pastebin = pi break } } } try { pastebin.parentNode.removeChild(pastebin) } catch (e) {} range.moveToBookmark(bk).select(true) callback(pastebin) }, 0) } var me = this me.setOpt({ retainOnlyLabelPasted: false }) var txtContent, htmlContent, address function getPureHtml(html) { return html.replace( /<(\/?)([\w\-]+)([^>]*)>/gi, function (a, b, tagName, attrs) { tagName = tagName.toLowerCase() if ({ img: 1 }[tagName]) { return a } attrs = attrs.replace( /([\w\-]*?)\s*=\s*(("([^"]*)")|('([^']*)')|([^\s>]+))/gi, function (str, atr, val) { if ( { src: 1, href: 1, name: 1 }[atr.toLowerCase()] ) { return atr + '=' + val + ' ' } return '' } ) if ( { span: 1, div: 1 }[tagName] ) { return '' } else { return '<' + b + tagName + ' ' + utils.trim(attrs) + '>' } } ) } function filter(div) { var html if (div.firstChild) { //去掉cut中添加的边界值 var nodes = domUtils.getElementsByTagName(div, 'span') for (var i = 0, ni; (ni = nodes[i++]); ) { if (ni.id == '_baidu_cut_start' || ni.id == '_baidu_cut_end') { domUtils.remove(ni) } } if (browser.webkit) { var brs = div.querySelectorAll('div br') for (var i = 0, bi; (bi = brs[i++]); ) { var pN = bi.parentNode if (pN.tagName == 'DIV' && pN.childNodes.length == 1) { pN.innerHTML = '


    ' domUtils.remove(pN) } } var divs = div.querySelectorAll('#baidu_pastebin') for (var i = 0, di; (di = divs[i++]); ) { var tmpP = me.document.createElement('p') di.parentNode.insertBefore(tmpP, di) while (di.firstChild) { tmpP.appendChild(di.firstChild) } domUtils.remove(di) } var metas = div.querySelectorAll('meta') for (var i = 0, ci; (ci = metas[i++]); ) { domUtils.remove(ci) } var brs = div.querySelectorAll('br') for (i = 0; (ci = brs[i++]); ) { if (/^apple-/i.test(ci.className)) { domUtils.remove(ci) } } } if (browser.gecko) { var dirtyNodes = div.querySelectorAll('[_moz_dirty]') for (i = 0; (ci = dirtyNodes[i++]); ) { ci.removeAttribute('_moz_dirty') } } if (!browser.ie) { var spans = div.querySelectorAll('span.Apple-style-span') for (var i = 0, ci; (ci = spans[i++]); ) { domUtils.remove(ci, true) } } //ie下使用innerHTML会产生多余的\r\n字符,也会产生 这里过滤掉 html = div.innerHTML //.replace(/>(?:(\s| )*?)<'); //过滤word粘贴过来的冗余属性 html = UE.filterWord(html) //取消了忽略空白的第二个参数,粘贴过来的有些是有空白的,会被套上相关的标签 var root = UE.htmlparser(html) //如果给了过滤规则就先进行过滤 if (me.options.filterRules) { UE.filterNode(root, me.options.filterRules) } //执行默认的处理 me.filterInputRule(root) //针对chrome的处理 if (browser.webkit) { var br = root.lastChild() if (br && br.type == 'element' && br.tagName == 'br') { root.removeChild(br) } utils.each(me.body.querySelectorAll('div'), function (node) { if (domUtils.isEmptyBlock(node)) { domUtils.remove(node, true) } }) } html = { html: root.toHtml() } me.fireEvent('beforepaste', html, root) //抢了默认的粘贴,那后边的内容就不执行了,比如表格粘贴 if (!html.html) { return } root = UE.htmlparser(html.html, true) //如果开启了纯文本模式 if (me.queryCommandState('pasteplain') === 1) { me.execCommand( 'insertHtml', UE.filterNode(root, me.options.filterTxtRules).toHtml(), true ) } else { //文本模式 UE.filterNode(root, me.options.filterTxtRules) txtContent = root.toHtml() //完全模式 htmlContent = html.html address = me.selection.getRange().createAddress(true) me.execCommand( 'insertHtml', me.getOpt('retainOnlyLabelPasted') === true ? getPureHtml(htmlContent) : htmlContent, true ) } me.fireEvent('afterpaste', html) } } me.addListener('pasteTransfer', function (cmd, plainType) { if (address && txtContent && htmlContent && txtContent != htmlContent) { var range = me.selection.getRange() range.moveToAddress(address, true) if (!range.collapsed) { while (!domUtils.isBody(range.startContainer)) { var start = range.startContainer if (start.nodeType == 1) { start = start.childNodes[range.startOffset] if (!start) { range.setStartBefore(range.startContainer) continue } var pre = start.previousSibling if ( pre && pre.nodeType == 3 && new RegExp('^[\n\r\t ' + domUtils.fillChar + ']*$').test( pre.nodeValue ) ) { range.setStartBefore(pre) } } if (range.startOffset == 0) { range.setStartBefore(range.startContainer) } else { break } } while (!domUtils.isBody(range.endContainer)) { var end = range.endContainer if (end.nodeType == 1) { end = end.childNodes[range.endOffset] if (!end) { range.setEndAfter(range.endContainer) continue } var next = end.nextSibling if ( next && next.nodeType == 3 && new RegExp('^[\n\r\t' + domUtils.fillChar + ']*$').test( next.nodeValue ) ) { range.setEndAfter(next) } } if ( range.endOffset == range.endContainer[ range.endContainer.nodeType == 3 ? 'nodeValue' : 'childNodes' ].length ) { range.setEndAfter(range.endContainer) } else { break } } } range.deleteContents() range.select(true) me.__hasEnterExecCommand = true var html = htmlContent if (plainType === 2) { html = getPureHtml(html) } else if (plainType) { html = txtContent } me.execCommand('inserthtml', html, true) me.__hasEnterExecCommand = false var rng = me.selection.getRange() while ( !domUtils.isBody(rng.startContainer) && !rng.startOffset && rng.startContainer[ rng.startContainer.nodeType == 3 ? 'nodeValue' : 'childNodes' ].length ) { rng.setStartBefore(rng.startContainer) } var tmpAddress = rng.createAddress(true) address.endAddress = tmpAddress.startAddress } }) me.addListener('ready', function () { domUtils.on(me.body, 'cut', function () { var range = me.selection.getRange() if (!range.collapsed && me.undoManger) { me.undoManger.save() } }) //ie下beforepaste在点击右键时也会触发,所以用监控键盘才处理 domUtils.on( me.body, browser.ie || browser.opera ? 'keydown' : 'paste', function (e) { if ( (browser.ie || browser.opera) && ((!e.ctrlKey && !e.metaKey) || e.keyCode != '86') ) { return } getClipboardData.call(me, function (div) { filter(div) }) } ) }) me.commands['paste'] = { execCommand: function (cmd) { if (browser.ie) { getClipboardData.call(me, function (div) { filter(div) }) me.document.execCommand('paste') } else { alert(me.getLang('pastemsg')) } } } } // plugins/puretxtpaste.js /** * 纯文本粘贴插件 * @file * @since 1.2.6.1 */ UE.plugins['pasteplain'] = function () { var me = this me.setOpt({ pasteplain: false, filterTxtRules: (function () { function transP(node) { node.tagName = 'p' node.setStyle() } function removeNode(node) { node.parentNode.removeChild(node, true) } return { //直接删除及其字节点内容 '-': 'script style object iframe embed input select', p: { $: {} }, br: { $: {} }, div: function (node) { var tmpNode, p = UE.uNode.createElement('p') while ((tmpNode = node.firstChild())) { if ( tmpNode.type == 'text' || !UE.dom.dtd.$block[tmpNode.tagName] ) { p.appendChild(tmpNode) } else { if (p.firstChild()) { node.parentNode.insertBefore(p, node) p = UE.uNode.createElement('p') } else { node.parentNode.insertBefore(tmpNode, node) } } } if (p.firstChild()) { node.parentNode.insertBefore(p, node) } node.parentNode.removeChild(node) }, ol: removeNode, ul: removeNode, dl: removeNode, dt: removeNode, dd: removeNode, li: removeNode, caption: transP, th: transP, tr: transP, h1: transP, h2: transP, h3: transP, h4: transP, h5: transP, h6: transP, td: function (node) { //没有内容的td直接删掉 var txt = !!node.innerText() if (txt) { node.parentNode.insertAfter( UE.uNode.createText('    '), node ) } node.parentNode.removeChild(node, node.innerText()) } } })() }) //暂时这里支持一下老版本的属性 var pasteplain = me.options.pasteplain /** * 启用或取消纯文本粘贴模式 * @command pasteplain * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.queryCommandState( 'pasteplain' ); * ``` */ /** * 查询当前是否处于纯文本粘贴模式 * @command pasteplain * @method queryCommandState * @param { String } cmd 命令字符串 * @return { int } 如果处于纯文本模式,返回1,否则,返回0 * @example * ```javascript * editor.queryCommandState( 'pasteplain' ); * ``` */ me.commands['pasteplain'] = { queryCommandState: function () { return pasteplain ? 1 : 0 }, execCommand: function () { pasteplain = !pasteplain | 0 }, notNeedUndo: 1 } } // plugins/list.js /** * 有序列表,无序列表插件 * @file * @since 1.2.6.1 */ UE.plugins['list'] = function () { var me = this, notExchange = { TD: 1, PRE: 1, BLOCKQUOTE: 1 } var customStyle = { cn: 'cn-1-', cn1: 'cn-2-', cn2: 'cn-3-', num: 'num-1-', num1: 'num-2-', num2: 'num-3-', dash: 'dash', dot: 'dot' } me.setOpt({ autoTransWordToList: false, insertorderedlist: { num: '', num1: '', num2: '', cn: '', cn1: '', cn2: '', decimal: '', 'lower-alpha': '', 'lower-roman': '', 'upper-alpha': '', 'upper-roman': '' }, insertunorderedlist: { circle: '', disc: '', square: '', dash: '', dot: '' }, listDefaultPaddingLeft: '30', listiconpath: 'http://bs.baidu.com/listicon/', maxListLevel: -1, //-1不限制 disablePInList: false }) function listToArray(list) { var arr = [] for (var p in list) { arr.push(p) } return arr } var listStyle = { OL: listToArray(me.options.insertorderedlist), UL: listToArray(me.options.insertunorderedlist) } var liiconpath = me.options.listiconpath //根据用户配置,调整customStyle for (var s in customStyle) { if ( !me.options.insertorderedlist.hasOwnProperty(s) && !me.options.insertunorderedlist.hasOwnProperty(s) ) { delete customStyle[s] } } me.ready(function () { var customCss = [] for (var p in customStyle) { if (p == 'dash' || p == 'dot') { customCss.push( 'li.list-' + customStyle[p] + '{background-image:url(' + liiconpath + customStyle[p] + '.gif)}' ) customCss.push( 'ul.custom_' + p + '{list-style:none;}ul.custom_' + p + ' li{background-position:0 3px;background-repeat:no-repeat}' ) } else { for (var i = 0; i < 99; i++) { customCss.push( 'li.list-' + customStyle[p] + i + '{background-image:url(' + liiconpath + 'list-' + customStyle[p] + i + '.gif)}' ) } customCss.push( 'ol.custom_' + p + '{list-style:none;}ol.custom_' + p + ' li{background-position:0 3px;background-repeat:no-repeat}' ) } switch (p) { case 'cn': customCss.push('li.list-' + p + '-paddingleft-1{padding-left:25px}') customCss.push('li.list-' + p + '-paddingleft-2{padding-left:40px}') customCss.push('li.list-' + p + '-paddingleft-3{padding-left:55px}') break case 'cn1': customCss.push('li.list-' + p + '-paddingleft-1{padding-left:30px}') customCss.push('li.list-' + p + '-paddingleft-2{padding-left:40px}') customCss.push('li.list-' + p + '-paddingleft-3{padding-left:55px}') break case 'cn2': customCss.push('li.list-' + p + '-paddingleft-1{padding-left:40px}') customCss.push('li.list-' + p + '-paddingleft-2{padding-left:55px}') customCss.push('li.list-' + p + '-paddingleft-3{padding-left:68px}') break case 'num': case 'num1': customCss.push('li.list-' + p + '-paddingleft-1{padding-left:25px}') break case 'num2': customCss.push('li.list-' + p + '-paddingleft-1{padding-left:35px}') customCss.push('li.list-' + p + '-paddingleft-2{padding-left:40px}') break case 'dash': customCss.push('li.list-' + p + '-paddingleft{padding-left:35px}') break case 'dot': customCss.push('li.list-' + p + '-paddingleft{padding-left:20px}') } } customCss.push('.list-paddingleft-1{padding-left:0}') customCss.push( '.list-paddingleft-2{padding-left:' + me.options.listDefaultPaddingLeft + 'px}' ) customCss.push( '.list-paddingleft-3{padding-left:' + me.options.listDefaultPaddingLeft * 2 + 'px}' ) //如果不给宽度会在自定应样式里出现滚动条 utils.cssRule( 'list', 'ol,ul{margin:0;pading:0;' + (browser.ie ? '' : 'width:95%') + '}li{clear:both;}' + customCss.join('\n'), me.document ) }) //单独处理剪切的问题 me.ready(function () { domUtils.on(me.body, 'cut', function () { setTimeout(function () { var rng = me.selection.getRange(), li //trace:3416 if (!rng.collapsed) { if ( (li = domUtils.findParentByTagName( rng.startContainer, 'li', true )) ) { if (!li.nextSibling && domUtils.isEmptyBlock(li)) { var pn = li.parentNode, node if ((node = pn.previousSibling)) { domUtils.remove(pn) rng.setStartAtLast(node).collapse(true) rng.select(true) } else if ((node = pn.nextSibling)) { domUtils.remove(pn) rng.setStartAtFirst(node).collapse(true) rng.select(true) } else { var tmpNode = me.document.createElement('p') domUtils.fillNode(me.document, tmpNode) pn.parentNode.insertBefore(tmpNode, pn) domUtils.remove(pn) rng.setStart(tmpNode, 0).collapse(true) rng.select(true) } } } } }) }) }) function getStyle(node) { var cls = node.className if (domUtils.hasClass(node, /custom_/)) { return cls.match(/custom_(\w+)/)[1] } return domUtils.getStyle(node, 'list-style-type') } me.addListener('beforepaste', function (type, html) { var me = this, rng = me.selection.getRange(), li var root = UE.htmlparser(html.html, true) if ((li = domUtils.findParentByTagName(rng.startContainer, 'li', true))) { var list = li.parentNode, tagName = list.tagName == 'OL' ? 'ul' : 'ol' utils.each(root.getNodesByTagName(tagName), function (n) { n.tagName = list.tagName n.setAttr() if (n.parentNode === root) { type = getStyle(list) || (list.tagName == 'OL' ? 'decimal' : 'disc') } else { var className = n.parentNode.getAttr('class') if (className && /custom_/.test(className)) { type = className.match(/custom_(\w+)/)[1] } else { type = n.parentNode.getStyle('list-style-type') } if (!type) { type = list.tagName == 'OL' ? 'decimal' : 'disc' } } var index = utils.indexOf(listStyle[list.tagName], type) if (n.parentNode !== root) index = index + 1 == listStyle[list.tagName].length ? 0 : index + 1 var currentStyle = listStyle[list.tagName][index] if (customStyle[currentStyle]) { n.setAttr('class', 'custom_' + currentStyle) } else { n.setStyle('list-style-type', currentStyle) } }) } html.html = root.toHtml() }) //导出时,去掉p标签 me.getOpt('disablePInList') === true && me.addOutputRule(function (root) { utils.each(root.getNodesByTagName('li'), function (li) { var newChildrens = [], index = 0 utils.each(li.children, function (n) { if (n.tagName == 'p') { var tmpNode while ((tmpNode = n.children.pop())) { newChildrens.splice(index, 0, tmpNode) tmpNode.parentNode = li lastNode = tmpNode } tmpNode = newChildrens[newChildrens.length - 1] if ( !tmpNode || tmpNode.type != 'element' || tmpNode.tagName != 'br' ) { var br = UE.uNode.createElement('br') br.parentNode = li newChildrens.push(br) } index = newChildrens.length } }) if (newChildrens.length) { li.children = newChildrens } }) }) //进入编辑器的li要套p标签 me.addInputRule(function (root) { utils.each(root.getNodesByTagName('li'), function (li) { var tmpP = UE.uNode.createElement('p') for (var i = 0, ci; (ci = li.children[i]); ) { if (ci.type == 'text' || dtd.p[ci.tagName]) { tmpP.appendChild(ci) } else { if (tmpP.firstChild()) { li.insertBefore(tmpP, ci) tmpP = UE.uNode.createElement('p') i = i + 2 } else { i++ } } } if ((tmpP.firstChild() && !tmpP.parentNode) || !li.firstChild()) { li.appendChild(tmpP) } //trace:3357 //p不能为空 if (!tmpP.firstChild()) { tmpP.innerHTML(browser.ie ? ' ' : '
    ') } //去掉末尾的空白 var p = li.firstChild() var lastChild = p.lastChild() if ( lastChild && lastChild.type == 'text' && /^\s*$/.test(lastChild.data) ) { p.removeChild(lastChild) } }) if (me.options.autoTransWordToList) { var orderlisttype = { num1: /^\d+\)/, decimal: /^\d+\./, 'lower-alpha': /^[a-z]+\)/, 'upper-alpha': /^[A-Z]+\./, cn: /^[\u4E00\u4E8C\u4E09\u56DB\u516d\u4e94\u4e03\u516b\u4e5d]+[\u3001]/, cn2: /^\([\u4E00\u4E8C\u4E09\u56DB\u516d\u4e94\u4e03\u516b\u4e5d]+\)/ }, unorderlisttype = { square: 'n' } function checkListType(content, container) { var span = container.firstChild() if ( span && span.type == 'element' && span.tagName == 'span' && /Wingdings|Symbol/.test(span.getStyle('font-family')) ) { for (var p in unorderlisttype) { if (unorderlisttype[p] == span.data) { return p } } return 'disc' } for (var p in orderlisttype) { if (orderlisttype[p].test(content)) { return p } } } utils.each(root.getNodesByTagName('p'), function (node) { if (node.getAttr('class') != 'MsoListParagraph') { return } //word粘贴过来的会带有margin要去掉,但这样也可能会误命中一些央视 node.setStyle('margin', '') node.setStyle('margin-left', '') node.setAttr('class', '') function appendLi(list, p, type) { if (list.tagName == 'ol') { if (browser.ie) { var first = p.firstChild() if ( first.type == 'element' && first.tagName == 'span' && orderlisttype[type].test(first.innerText()) ) { p.removeChild(first) } } else { p.innerHTML(p.innerHTML().replace(orderlisttype[type], '')) } } else { p.removeChild(p.firstChild()) } var li = UE.uNode.createElement('li') li.appendChild(p) list.appendChild(li) } var tmp = node, type, cacheNode = node if ( node.parentNode.tagName != 'li' && (type = checkListType(node.innerText(), node)) ) { var list = UE.uNode.createElement( me.options.insertorderedlist.hasOwnProperty(type) ? 'ol' : 'ul' ) if (customStyle[type]) { list.setAttr('class', 'custom_' + type) } else { list.setStyle('list-style-type', type) } while ( node && node.parentNode.tagName != 'li' && checkListType(node.innerText(), node) ) { tmp = node.nextSibling() if (!tmp) { node.parentNode.insertBefore(list, node) } appendLi(list, node, type) node = tmp } if (!list.parentNode && node && node.parentNode) { node.parentNode.insertBefore(list, node) } } var span = cacheNode.firstChild() if ( span && span.type == 'element' && span.tagName == 'span' && /^\s*( )+\s*$/.test(span.innerText()) ) { span.parentNode.removeChild(span) } }) } }) //调整索引标签 me.addListener('contentchange', function () { adjustListStyle(me.document) }) function adjustListStyle(doc, ignore) { utils.each(domUtils.getElementsByTagName(doc, 'ol ul'), function (node) { if (!domUtils.inDoc(node, doc)) return var parent = node.parentNode if (parent.tagName == node.tagName) { var nodeStyleType = getStyle(node) || (node.tagName == 'OL' ? 'decimal' : 'disc'), parentStyleType = getStyle(parent) || (parent.tagName == 'OL' ? 'decimal' : 'disc') if (nodeStyleType == parentStyleType) { var styleIndex = utils.indexOf( listStyle[node.tagName], nodeStyleType ) styleIndex = styleIndex + 1 == listStyle[node.tagName].length ? 0 : styleIndex + 1 setListStyle(node, listStyle[node.tagName][styleIndex]) } } var index = 0, type = 2 if (domUtils.hasClass(node, /custom_/)) { if ( !( /[ou]l/i.test(parent.tagName) && domUtils.hasClass(parent, /custom_/) ) ) { type = 1 } } else { if ( /[ou]l/i.test(parent.tagName) && domUtils.hasClass(parent, /custom_/) ) { type = 3 } } var style = domUtils.getStyle(node, 'list-style-type') style && (node.style.cssText = 'list-style-type:' + style) node.className = utils.trim(node.className.replace(/list-paddingleft-\w+/, '')) + ' list-paddingleft-' + type utils.each(domUtils.getElementsByTagName(node, 'li'), function (li) { li.style.cssText && (li.style.cssText = '') if (!li.firstChild) { domUtils.remove(li) return } if (li.parentNode !== node) { return } index++ if (domUtils.hasClass(node, /custom_/)) { var paddingLeft = 1, currentStyle = getStyle(node) if (node.tagName == 'OL') { if (currentStyle) { switch (currentStyle) { case 'cn': case 'cn1': case 'cn2': if ( index > 10 && (index % 10 == 0 || (index > 10 && index < 20)) ) { paddingLeft = 2 } else if (index > 20) { paddingLeft = 3 } break case 'num2': if (index > 9) { paddingLeft = 2 } } } li.className = 'list-' + customStyle[currentStyle] + index + ' ' + 'list-' + currentStyle + '-paddingleft-' + paddingLeft } else { li.className = 'list-' + customStyle[currentStyle] + ' ' + 'list-' + currentStyle + '-paddingleft' } } else { li.className = li.className.replace(/list-[\w\-]+/gi, '') } var className = li.getAttribute('class') if (className !== null && !className.replace(/\s/g, '')) { domUtils.removeAttributes(li, 'class') } }) !ignore && adjustList( node, node.tagName.toLowerCase(), getStyle(node) || domUtils.getStyle(node, 'list-style-type'), true ) }) } function adjustList(list, tag, style, ignoreEmpty) { var nextList = list.nextSibling if ( nextList && nextList.nodeType == 1 && nextList.tagName.toLowerCase() == tag && (getStyle(nextList) || domUtils.getStyle(nextList, 'list-style-type') || (tag == 'ol' ? 'decimal' : 'disc')) == style ) { domUtils.moveChild(nextList, list) if (nextList.childNodes.length == 0) { domUtils.remove(nextList) } } if (nextList && domUtils.isFillChar(nextList)) { domUtils.remove(nextList) } var preList = list.previousSibling if ( preList && preList.nodeType == 1 && preList.tagName.toLowerCase() == tag && (getStyle(preList) || domUtils.getStyle(preList, 'list-style-type') || (tag == 'ol' ? 'decimal' : 'disc')) == style ) { domUtils.moveChild(list, preList) } if (preList && domUtils.isFillChar(preList)) { domUtils.remove(preList) } !ignoreEmpty && domUtils.isEmptyBlock(list) && domUtils.remove(list) if (getStyle(list)) { adjustListStyle(list.ownerDocument, true) } } function setListStyle(list, style) { if (customStyle[style]) { list.className = 'custom_' + style } try { domUtils.setStyle(list, 'list-style-type', style) } catch (e) {} } function clearEmptySibling(node) { var tmpNode = node.previousSibling if (tmpNode && domUtils.isEmptyBlock(tmpNode)) { domUtils.remove(tmpNode) } tmpNode = node.nextSibling if (tmpNode && domUtils.isEmptyBlock(tmpNode)) { domUtils.remove(tmpNode) } } me.addListener('keydown', function (type, evt) { function preventAndSave() { evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false) me.fireEvent('contentchange') me.undoManger && me.undoManger.save() } function findList(node, filterFn) { while (node && !domUtils.isBody(node)) { if (filterFn(node)) { return null } if (node.nodeType == 1 && /[ou]l/i.test(node.tagName)) { return node } node = node.parentNode } return null } var keyCode = evt.keyCode || evt.which if (keyCode == 13 && !evt.shiftKey) { //回车 var rng = me.selection.getRange(), parent = domUtils.findParent( rng.startContainer, function (node) { return domUtils.isBlockElm(node) }, true ), li = domUtils.findParentByTagName(rng.startContainer, 'li', true) if (parent && parent.tagName != 'PRE' && !li) { var html = parent.innerHTML.replace( new RegExp(domUtils.fillChar, 'g'), '' ) if (/^\s*1\s*\.[^\d]/.test(html)) { parent.innerHTML = html.replace(/^\s*1\s*\./, '') rng.setStartAtLast(parent).collapse(true).select() me.__hasEnterExecCommand = true me.execCommand('insertorderedlist') me.__hasEnterExecCommand = false } } var range = me.selection.getRange(), start = findList(range.startContainer, function (node) { return node.tagName == 'TABLE' }), end = range.collapsed ? start : findList(range.endContainer, function (node) { return node.tagName == 'TABLE' }) if (start && end && start === end) { if (!range.collapsed) { start = domUtils.findParentByTagName( range.startContainer, 'li', true ) end = domUtils.findParentByTagName(range.endContainer, 'li', true) if (start && end && start === end) { range.deleteContents() li = domUtils.findParentByTagName( range.startContainer, 'li', true ) if (li && domUtils.isEmptyBlock(li)) { pre = li.previousSibling next = li.nextSibling p = me.document.createElement('p') domUtils.fillNode(me.document, p) parentList = li.parentNode if (pre && next) { range.setStart(next, 0).collapse(true).select(true) domUtils.remove(li) } else { if ((!pre && !next) || !pre) { parentList.parentNode.insertBefore(p, parentList) } else { li.parentNode.parentNode.insertBefore( p, parentList.nextSibling ) } domUtils.remove(li) if (!parentList.firstChild) { domUtils.remove(parentList) } range.setStart(p, 0).setCursor() } preventAndSave() return } } else { var tmpRange = range.cloneRange(), bk = tmpRange.collapse(false).createBookmark() range.deleteContents() tmpRange.moveToBookmark(bk) var li = domUtils.findParentByTagName( tmpRange.startContainer, 'li', true ) clearEmptySibling(li) tmpRange.select() preventAndSave() return } } li = domUtils.findParentByTagName(range.startContainer, 'li', true) if (li) { if (domUtils.isEmptyBlock(li)) { bk = range.createBookmark() var parentList = li.parentNode if (li !== parentList.lastChild) { domUtils.breakParent(li, parentList) clearEmptySibling(li) } else { parentList.parentNode.insertBefore(li, parentList.nextSibling) if (domUtils.isEmptyNode(parentList)) { domUtils.remove(parentList) } } //嵌套不处理 if (!dtd.$list[li.parentNode.tagName]) { if (!domUtils.isBlockElm(li.firstChild)) { p = me.document.createElement('p') li.parentNode.insertBefore(p, li) while (li.firstChild) { p.appendChild(li.firstChild) } domUtils.remove(li) } else { domUtils.remove(li, true) } } range.moveToBookmark(bk).select() } else { var first = li.firstChild if (!first || !domUtils.isBlockElm(first)) { var p = me.document.createElement('p') !li.firstChild && domUtils.fillNode(me.document, p) while (li.firstChild) { p.appendChild(li.firstChild) } li.appendChild(p) first = p } var span = me.document.createElement('span') range.insertNode(span) domUtils.breakParent(span, li) var nextLi = span.nextSibling first = nextLi.firstChild if (!first) { p = me.document.createElement('p') domUtils.fillNode(me.document, p) nextLi.appendChild(p) first = p } if (domUtils.isEmptyNode(first)) { first.innerHTML = '' domUtils.fillNode(me.document, first) } range.setStart(first, 0).collapse(true).shrinkBoundary().select() domUtils.remove(span) var pre = nextLi.previousSibling if (pre && domUtils.isEmptyBlock(pre)) { pre.innerHTML = '

    ' domUtils.fillNode(me.document, pre.firstChild) } } // } preventAndSave() } } } if (keyCode == 8) { //修中ie中li下的问题 range = me.selection.getRange() if (range.collapsed && domUtils.isStartInblock(range)) { tmpRange = range.cloneRange().trimBoundary() li = domUtils.findParentByTagName(range.startContainer, 'li', true) //要在li的最左边,才能处理 if (li && domUtils.isStartInblock(tmpRange)) { start = domUtils.findParentByTagName( range.startContainer, 'p', true ) if (start && start !== li.firstChild) { var parentList = domUtils.findParentByTagName(start, ['ol', 'ul']) domUtils.breakParent(start, parentList) clearEmptySibling(start) me.fireEvent('contentchange') range.setStart(start, 0).setCursor(false, true) me.fireEvent('saveScene') domUtils.preventDefault(evt) return } if (li && (pre = li.previousSibling)) { if (keyCode == 46 && li.childNodes.length) { return } //有可能上边的兄弟节点是个2级菜单,要追加到2级菜单的最后的li if (dtd.$list[pre.tagName]) { pre = pre.lastChild } me.undoManger && me.undoManger.save() first = li.firstChild if (domUtils.isBlockElm(first)) { if (domUtils.isEmptyNode(first)) { // range.setEnd(pre, pre.childNodes.length).shrinkBoundary().collapse().select(true); pre.appendChild(first) range.setStart(first, 0).setCursor(false, true) //first不是唯一的节点 while (li.firstChild) { pre.appendChild(li.firstChild) } } else { span = me.document.createElement('span') range.insertNode(span) //判断pre是否是空的节点,如果是


    类型的空节点,干掉p标签防止它占位 if (domUtils.isEmptyBlock(pre)) { pre.innerHTML = '' } domUtils.moveChild(li, pre) range.setStartBefore(span).collapse(true).select(true) domUtils.remove(span) } } else { if (domUtils.isEmptyNode(li)) { var p = me.document.createElement('p') pre.appendChild(p) range.setStart(p, 0).setCursor() // range.setEnd(pre, pre.childNodes.length).shrinkBoundary().collapse().select(true); } else { range .setEnd(pre, pre.childNodes.length) .collapse() .select(true) while (li.firstChild) { pre.appendChild(li.firstChild) } } } domUtils.remove(li) me.fireEvent('contentchange') me.fireEvent('saveScene') domUtils.preventDefault(evt) return } //trace:980 if (li && !li.previousSibling) { var parentList = li.parentNode var bk = range.createBookmark() if (domUtils.isTagNode(parentList.parentNode, 'ol ul')) { parentList.parentNode.insertBefore(li, parentList) if (domUtils.isEmptyNode(parentList)) { domUtils.remove(parentList) } } else { while (li.firstChild) { parentList.parentNode.insertBefore(li.firstChild, parentList) } domUtils.remove(li) if (domUtils.isEmptyNode(parentList)) { domUtils.remove(parentList) } } range.moveToBookmark(bk).setCursor(false, true) me.fireEvent('contentchange') me.fireEvent('saveScene') domUtils.preventDefault(evt) return } } } } }) me.addListener('keyup', function (type, evt) { var keyCode = evt.keyCode || evt.which if (keyCode == 8) { var rng = me.selection.getRange(), list if ( (list = domUtils.findParentByTagName( rng.startContainer, ['ol', 'ul'], true )) ) { adjustList( list, list.tagName.toLowerCase(), getStyle(list) || domUtils.getComputedStyle(list, 'list-style-type'), true ) } } }) //处理tab键 me.addListener('tabkeydown', function () { var range = me.selection.getRange() //控制级数 function checkLevel(li) { if (me.options.maxListLevel != -1) { var level = li.parentNode, levelNum = 0 while (/[ou]l/i.test(level.tagName)) { levelNum++ level = level.parentNode } if (levelNum >= me.options.maxListLevel) { return true } } } //只以开始为准 //todo 后续改进 var li = domUtils.findParentByTagName(range.startContainer, 'li', true) if (li) { var bk if (range.collapsed) { if (checkLevel(li)) return true var parentLi = li.parentNode, list = me.document.createElement(parentLi.tagName), index = utils.indexOf( listStyle[list.tagName], getStyle(parentLi) || domUtils.getComputedStyle(parentLi, 'list-style-type') ) index = index + 1 == listStyle[list.tagName].length ? 0 : index + 1 var currentStyle = listStyle[list.tagName][index] setListStyle(list, currentStyle) if (domUtils.isStartInblock(range)) { me.fireEvent('saveScene') bk = range.createBookmark() parentLi.insertBefore(list, li) list.appendChild(li) adjustList(list, list.tagName.toLowerCase(), currentStyle) me.fireEvent('contentchange') range.moveToBookmark(bk).select(true) return true } } else { me.fireEvent('saveScene') bk = range.createBookmark() for ( var i = 0, closeList, parents = domUtils.findParents(li), ci; (ci = parents[i++]); ) { if (domUtils.isTagNode(ci, 'ol ul')) { closeList = ci break } } var current = li if (bk.end) { while ( current && !( domUtils.getPosition(current, bk.end) & domUtils.POSITION_FOLLOWING ) ) { if (checkLevel(current)) { current = domUtils.getNextDomNode( current, false, null, function (node) { return node !== closeList } ) continue } var parentLi = current.parentNode, list = me.document.createElement(parentLi.tagName), index = utils.indexOf( listStyle[list.tagName], getStyle(parentLi) || domUtils.getComputedStyle(parentLi, 'list-style-type') ) var currentIndex = index + 1 == listStyle[list.tagName].length ? 0 : index + 1 var currentStyle = listStyle[list.tagName][currentIndex] setListStyle(list, currentStyle) parentLi.insertBefore(list, current) while ( current && !( domUtils.getPosition(current, bk.end) & domUtils.POSITION_FOLLOWING ) ) { li = current.nextSibling list.appendChild(current) if (!li || domUtils.isTagNode(li, 'ol ul')) { if (li) { while ((li = li.firstChild)) { if (li.tagName == 'LI') { break } } } else { li = domUtils.getNextDomNode( current, false, null, function (node) { return node !== closeList } ) } break } current = li } adjustList(list, list.tagName.toLowerCase(), currentStyle) current = li } } me.fireEvent('contentchange') range.moveToBookmark(bk).select() return true } } }) function getLi(start) { while (start && !domUtils.isBody(start)) { if (start.nodeName == 'TABLE') { return null } if (start.nodeName == 'LI') { return start } start = start.parentNode } } /** * 有序列表,与“insertunorderedlist”命令互斥 * @command insertorderedlist * @method execCommand * @param { String } command 命令字符串 * @param { String } style 插入的有序列表类型,值为:decimal,lower-alpha,lower-roman,upper-alpha,upper-roman,cn,cn1,cn2,num,num1,num2 * @example * ```javascript * editor.execCommand( 'insertorderedlist','decimal'); * ``` */ /** * 查询当前选区内容是否有序列表 * @command insertorderedlist * @method queryCommandState * @param { String } cmd 命令字符串 * @return { int } 如果当前选区是有序列表返回1,否则返回0 * @example * ```javascript * editor.queryCommandState( 'insertorderedlist' ); * ``` */ /** * 查询当前选区内容是否有序列表 * @command insertorderedlist * @method queryCommandValue * @param { String } cmd 命令字符串 * @return { String } 返回当前有序列表的类型,值为null或decimal,lower-alpha,lower-roman,upper-alpha,upper-roman,cn,cn1,cn2,num,num1,num2 * @example * ```javascript * editor.queryCommandValue( 'insertorderedlist' ); * ``` */ /** * 无序列表,与“insertorderedlist”命令互斥 * @command insertunorderedlist * @method execCommand * @param { String } command 命令字符串 * @param { String } style 插入的无序列表类型,值为:circle,disc,square,dash,dot * @example * ```javascript * editor.execCommand( 'insertunorderedlist','circle'); * ``` */ /** * 查询当前是否有word文档粘贴进来的图片 * @command insertunorderedlist * @method insertunorderedlist * @param { String } command 命令字符串 * @return { int } 如果当前选区是无序列表返回1,否则返回0 * @example * ```javascript * editor.queryCommandState( 'insertunorderedlist' ); * ``` */ /** * 查询当前选区内容是否有序列表 * @command insertunorderedlist * @method queryCommandValue * @param { String } command 命令字符串 * @return { String } 返回当前无序列表的类型,值为null或circle,disc,square,dash,dot * @example * ```javascript * editor.queryCommandValue( 'insertunorderedlist' ); * ``` */ me.commands['insertorderedlist'] = me.commands['insertunorderedlist'] = { execCommand: function (command, style) { if (!style) { style = command.toLowerCase() == 'insertorderedlist' ? 'decimal' : 'disc' } var me = this, range = this.selection.getRange(), filterFn = function (node) { return node.nodeType == 1 ? node.tagName.toLowerCase() != 'br' : !domUtils.isWhitespace(node) }, tag = command.toLowerCase() == 'insertorderedlist' ? 'ol' : 'ul', frag = me.document.createDocumentFragment() //去掉是因为会出现选到末尾,导致adjustmentBoundary缩到ol/ul的位置 //range.shrinkBoundary();//.adjustmentBoundary(); range.adjustmentBoundary().shrinkBoundary() var bko = range.createBookmark(true), start = getLi(me.document.getElementById(bko.start)), modifyStart = 0, end = getLi(me.document.getElementById(bko.end)), modifyEnd = 0, startParent, endParent, list, tmp if (start || end) { start && (startParent = start.parentNode) if (!bko.end) { end = start } end && (endParent = end.parentNode) if (startParent === endParent) { while (start !== end) { tmp = start start = start.nextSibling if (!domUtils.isBlockElm(tmp.firstChild)) { var p = me.document.createElement('p') while (tmp.firstChild) { p.appendChild(tmp.firstChild) } tmp.appendChild(p) } frag.appendChild(tmp) } tmp = me.document.createElement('span') startParent.insertBefore(tmp, end) if (!domUtils.isBlockElm(end.firstChild)) { p = me.document.createElement('p') while (end.firstChild) { p.appendChild(end.firstChild) } end.appendChild(p) } frag.appendChild(end) domUtils.breakParent(tmp, startParent) if (domUtils.isEmptyNode(tmp.previousSibling)) { domUtils.remove(tmp.previousSibling) } if (domUtils.isEmptyNode(tmp.nextSibling)) { domUtils.remove(tmp.nextSibling) } var nodeStyle = getStyle(startParent) || domUtils.getComputedStyle(startParent, 'list-style-type') || (command.toLowerCase() == 'insertorderedlist' ? 'decimal' : 'disc') if ( startParent.tagName.toLowerCase() == tag && nodeStyle == style ) { for ( var i = 0, ci, tmpFrag = me.document.createDocumentFragment(); (ci = frag.firstChild); ) { if (domUtils.isTagNode(ci, 'ol ul')) { // 删除时,子列表不处理 // utils.each(domUtils.getElementsByTagName(ci,'li'),function(li){ // while(li.firstChild){ // tmpFrag.appendChild(li.firstChild); // } // // }); tmpFrag.appendChild(ci) } else { while (ci.firstChild) { tmpFrag.appendChild(ci.firstChild) domUtils.remove(ci) } } } tmp.parentNode.insertBefore(tmpFrag, tmp) } else { list = me.document.createElement(tag) setListStyle(list, style) list.appendChild(frag) tmp.parentNode.insertBefore(list, tmp) } domUtils.remove(tmp) list && adjustList(list, tag, style) range.moveToBookmark(bko).select() return } //开始 if (start) { while (start) { tmp = start.nextSibling if (domUtils.isTagNode(start, 'ol ul')) { frag.appendChild(start) } else { var tmpfrag = me.document.createDocumentFragment(), hasBlock = 0 while (start.firstChild) { if (domUtils.isBlockElm(start.firstChild)) { hasBlock = 1 } tmpfrag.appendChild(start.firstChild) } if (!hasBlock) { var tmpP = me.document.createElement('p') tmpP.appendChild(tmpfrag) frag.appendChild(tmpP) } else { frag.appendChild(tmpfrag) } domUtils.remove(start) } start = tmp } startParent.parentNode.insertBefore(frag, startParent.nextSibling) if (domUtils.isEmptyNode(startParent)) { range.setStartBefore(startParent) domUtils.remove(startParent) } else { range.setStartAfter(startParent) } modifyStart = 1 } if (end && domUtils.inDoc(endParent, me.document)) { //结束 start = endParent.firstChild while (start && start !== end) { tmp = start.nextSibling if (domUtils.isTagNode(start, 'ol ul')) { frag.appendChild(start) } else { tmpfrag = me.document.createDocumentFragment() hasBlock = 0 while (start.firstChild) { if (domUtils.isBlockElm(start.firstChild)) { hasBlock = 1 } tmpfrag.appendChild(start.firstChild) } if (!hasBlock) { tmpP = me.document.createElement('p') tmpP.appendChild(tmpfrag) frag.appendChild(tmpP) } else { frag.appendChild(tmpfrag) } domUtils.remove(start) } start = tmp } var tmpDiv = domUtils.createElement(me.document, 'div', { tmpDiv: 1 }) domUtils.moveChild(end, tmpDiv) frag.appendChild(tmpDiv) domUtils.remove(end) endParent.parentNode.insertBefore(frag, endParent) range.setEndBefore(endParent) if (domUtils.isEmptyNode(endParent)) { domUtils.remove(endParent) } modifyEnd = 1 } } if (!modifyStart) { range.setStartBefore(me.document.getElementById(bko.start)) } if (bko.end && !modifyEnd) { range.setEndAfter(me.document.getElementById(bko.end)) } range.enlarge(true, function (node) { return notExchange[node.tagName] }) frag = me.document.createDocumentFragment() var bk = range.createBookmark(), current = domUtils.getNextDomNode(bk.start, false, filterFn), tmpRange = range.cloneRange(), tmpNode, block = domUtils.isBlockElm while ( current && current !== bk.end && domUtils.getPosition(current, bk.end) & domUtils.POSITION_PRECEDING ) { if (current.nodeType == 3 || dtd.li[current.tagName]) { if (current.nodeType == 1 && dtd.$list[current.tagName]) { while (current.firstChild) { frag.appendChild(current.firstChild) } tmpNode = domUtils.getNextDomNode(current, false, filterFn) domUtils.remove(current) current = tmpNode continue } tmpNode = current tmpRange.setStartBefore(current) while ( current && current !== bk.end && (!block(current) || domUtils.isBookmarkNode(current)) ) { tmpNode = current current = domUtils.getNextDomNode( current, false, null, function (node) { return !notExchange[node.tagName] } ) } if (current && block(current)) { tmp = domUtils.getNextDomNode(tmpNode, false, filterFn) if (tmp && domUtils.isBookmarkNode(tmp)) { current = domUtils.getNextDomNode(tmp, false, filterFn) tmpNode = tmp } } tmpRange.setEndAfter(tmpNode) current = domUtils.getNextDomNode(tmpNode, false, filterFn) var li = range.document.createElement('li') li.appendChild(tmpRange.extractContents()) if (domUtils.isEmptyNode(li)) { var tmpNode = range.document.createElement('p') while (li.firstChild) { tmpNode.appendChild(li.firstChild) } li.appendChild(tmpNode) } frag.appendChild(li) } else { current = domUtils.getNextDomNode(current, true, filterFn) } } range.moveToBookmark(bk).collapse(true) list = me.document.createElement(tag) setListStyle(list, style) list.appendChild(frag) range.insertNode(list) //当前list上下看能否合并 adjustList(list, tag, style) //去掉冗余的tmpDiv for ( var i = 0, ci, tmpDivs = domUtils.getElementsByTagName(list, 'div'); (ci = tmpDivs[i++]); ) { if (ci.getAttribute('tmpDiv')) { domUtils.remove(ci, true) } } range.moveToBookmark(bko).select() }, queryCommandState: function (command) { var tag = command.toLowerCase() == 'insertorderedlist' ? 'ol' : 'ul' var path = this.selection.getStartElementPath() for (var i = 0, ci; (ci = path[i++]); ) { if (ci.nodeName == 'TABLE') { return 0 } if (tag == ci.nodeName.toLowerCase()) { return 1 } } return 0 }, queryCommandValue: function (command) { var tag = command.toLowerCase() == 'insertorderedlist' ? 'ol' : 'ul' var path = this.selection.getStartElementPath(), node for (var i = 0, ci; (ci = path[i++]); ) { if (ci.nodeName == 'TABLE') { node = null break } if (tag == ci.nodeName.toLowerCase()) { node = ci break } } return node ? getStyle(node) || domUtils.getComputedStyle(node, 'list-style-type') : null } } } // plugins/source.js /** * 源码编辑插件 * @file * @since 1.2.6.1 */ ;(function () { var sourceEditors = { textarea: function (editor, holder) { var textarea = holder.ownerDocument.createElement('textarea') textarea.style.cssText = 'position:absolute;resize:none;width:100%;height:100%;border:0;padding:0;margin:0;overflow-y:auto;' // todo: IE下只有onresize属性可用... 很纠结 if (browser.ie && browser.version < 8) { textarea.style.width = holder.offsetWidth + 'px' textarea.style.height = holder.offsetHeight + 'px' holder.onresize = function () { textarea.style.width = holder.offsetWidth + 'px' textarea.style.height = holder.offsetHeight + 'px' } } holder.appendChild(textarea) return { setContent: function (content) { textarea.value = content }, getContent: function () { return textarea.value }, select: function () { var range if (browser.ie) { range = textarea.createTextRange() range.collapse(true) range.select() } else { //todo: chrome下无法设置焦点 textarea.setSelectionRange(0, 0) textarea.focus() } }, dispose: function () { holder.removeChild(textarea) // todo holder.onresize = null textarea = null holder = null } } }, codemirror: function (editor, holder) { var codeEditor = window.CodeMirror(holder, { mode: 'text/html', tabMode: 'indent', lineNumbers: true, lineWrapping: true }) var dom = codeEditor.getWrapperElement() dom.style.cssText = 'position:absolute;left:0;top:0;width:100%;height:100%;font-family:consolas,"Courier new",monospace;font-size:13px;' codeEditor.getScrollerElement().style.cssText = 'position:absolute;left:0;top:0;width:100%;height:100%;' codeEditor.refresh() return { getCodeMirror: function () { return codeEditor }, setContent: function (content) { codeEditor.setValue(content) }, getContent: function () { return codeEditor.getValue() }, select: function () { codeEditor.focus() }, dispose: function () { holder.removeChild(dom) dom = null codeEditor = null } } } } UE.plugins['source'] = function () { var me = this var opt = this.options var sourceMode = false var sourceEditor var orgSetContent opt.sourceEditor = browser.ie ? 'textarea' : opt.sourceEditor || 'codemirror' me.setOpt({ sourceEditorFirst: false }) function createSourceEditor(holder) { return sourceEditors[ opt.sourceEditor == 'codemirror' && window.CodeMirror ? 'codemirror' : 'textarea' ](me, holder) } var bakCssText //解决在源码模式下getContent不能得到最新的内容问题 var oldGetContent, bakAddress /** * 切换源码模式和编辑模式 * @command source * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand( 'source'); * ``` */ /** * 查询当前编辑区域的状态是源码模式还是可视化模式 * @command source * @method queryCommandState * @param { String } cmd 命令字符串 * @return { int } 如果当前是源码编辑模式,返回1,否则返回0 * @example * ```javascript * editor.queryCommandState( 'source' ); * ``` */ me.commands['source'] = { execCommand: function () { sourceMode = !sourceMode if (sourceMode) { bakAddress = me.selection.getRange().createAddress(false, true) me.undoManger && me.undoManger.save(true) if (browser.gecko) { me.body.contentEditable = false } bakCssText = me.iframe.style.cssText me.iframe.style.cssText += 'position:absolute;left:-32768px;top:-32768px;' me.fireEvent('beforegetcontent') var root = UE.htmlparser(me.body.innerHTML) me.filterOutputRule(root) root.traversal(function (node) { if (node.type == 'element') { switch (node.tagName) { case 'td': case 'th': case 'caption': if (node.children && node.children.length == 1) { if (node.firstChild().tagName == 'br') { node.removeChild(node.firstChild()) } } break case 'pre': node.innerText(node.innerText().replace(/ /g, ' ')) } } }) me.fireEvent('aftergetcontent') var content = root.toHtml(true) sourceEditor = createSourceEditor(me.iframe.parentNode) sourceEditor.setContent(content) orgSetContent = me.setContent me.setContent = function (html) { //这里暂时不触发事件,防止报错 var root = UE.htmlparser(html) me.filterInputRule(root) html = root.toHtml() sourceEditor.setContent(html) } setTimeout(function () { sourceEditor.select() me.addListener('fullscreenchanged', function () { try { sourceEditor.getCodeMirror().refresh() } catch (e) {} }) }) //重置getContent,源码模式下取值也能是最新的数据 oldGetContent = me.getContent me.getContent = function () { return ( sourceEditor.getContent() || '

    ' + (browser.ie ? '' : '
    ') + '

    ' ) } } else { me.iframe.style.cssText = bakCssText var cont = sourceEditor.getContent() || '

    ' + (browser.ie ? '' : '
    ') + '

    ' //处理掉block节点前后的空格,有可能会误命中,暂时不考虑 cont = cont.replace( new RegExp('[\\r\\t\\n ]*]*)>', 'g'), function (a, b) { if (b && !dtd.$inlineWithA[b.toLowerCase()]) { return a.replace(/(^[\n\r\t ]*)|([\n\r\t ]*$)/g, '') } return a.replace(/(^[\n\r\t]*)|([\n\r\t]*$)/g, '') } ) me.setContent = orgSetContent me.setContent(cont) sourceEditor.dispose() sourceEditor = null //还原getContent方法 me.getContent = oldGetContent var first = me.body.firstChild //trace:1106 都删除空了,下边会报错,所以补充一个p占位 if (!first) { me.body.innerHTML = '

    ' + (browser.ie ? '' : '
    ') + '

    ' first = me.body.firstChild } //要在ifm为显示时ff才能取到selection,否则报错 //这里不能比较位置了 me.undoManger && me.undoManger.save(true) if (browser.gecko) { var input = document.createElement('input') input.style.cssText = 'position:absolute;left:0;top:-32768px' document.body.appendChild(input) me.body.contentEditable = false setTimeout(function () { domUtils.setViewportOffset(input, { left: -32768, top: 0 }) input.focus() setTimeout(function () { me.body.contentEditable = true me.selection.getRange().moveToAddress(bakAddress).select(true) domUtils.remove(input) }) }) } else { //ie下有可能报错,比如在代码顶头的情况 try { me.selection.getRange().moveToAddress(bakAddress).select(true) } catch (e) {} } } this.fireEvent('sourcemodechanged', sourceMode) }, queryCommandState: function () { return sourceMode | 0 }, notNeedUndo: 1 } var oldQueryCommandState = me.queryCommandState me.queryCommandState = function (cmdName) { cmdName = cmdName.toLowerCase() if (sourceMode) { //源码模式下可以开启的命令 return cmdName in { source: 1, fullscreen: 1 } ? 1 : -1 } return oldQueryCommandState.apply(this, arguments) } if (opt.sourceEditor == 'codemirror') { me.addListener('ready', function () { utils.loadFile( document, { src: opt.codeMirrorJsUrl || opt.UEDITOR_HOME_URL + 'third-party/codemirror/codemirror.js', tag: 'script', type: 'text/javascript', defer: 'defer' }, function () { if (opt.sourceEditorFirst) { setTimeout(function () { me.execCommand('source') }, 0) } } ) utils.loadFile(document, { tag: 'link', rel: 'stylesheet', type: 'text/css', href: opt.codeMirrorCssUrl || opt.UEDITOR_HOME_URL + 'third-party/codemirror/codemirror.css' }) }) } } })() // plugins/enterkey.js ///import core ///import plugins/undo.js ///commands 设置回车标签p或br ///commandsName EnterKey ///commandsTitle 设置回车标签p或br /** * @description 处理回车 * @author zhanyi */ UE.plugins['enterkey'] = function () { var hTag, me = this, tag = me.options.enterTag me.addListener('keyup', function (type, evt) { var keyCode = evt.keyCode || evt.which if (keyCode == 13) { var range = me.selection.getRange(), start = range.startContainer, doSave //修正在h1-h6里边回车后不能嵌套p的问题 if (!browser.ie) { if (/h\d/i.test(hTag)) { if (browser.gecko) { var h = domUtils.findParentByTagName( start, [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'caption', 'table' ], true ) if (!h) { me.document.execCommand('formatBlock', false, '

    ') doSave = 1 } } else { //chrome remove div if (start.nodeType == 1) { var tmp = me.document.createTextNode(''), div range.insertNode(tmp) div = domUtils.findParentByTagName(tmp, 'div', true) if (div) { var p = me.document.createElement('p') while (div.firstChild) { p.appendChild(div.firstChild) } div.parentNode.insertBefore(p, div) domUtils.remove(div) range.setStartBefore(tmp).setCursor() doSave = 1 } domUtils.remove(tmp) } } if (me.undoManger && doSave) { me.undoManger.save() } } //没有站位符,会出现多行的问题 browser.opera && range.select() } else { me.fireEvent('saveScene', true, true) } } }) me.addListener('keydown', function (type, evt) { var keyCode = evt.keyCode || evt.which if (keyCode == 13) { //回车 if (me.fireEvent('beforeenterkeydown')) { domUtils.preventDefault(evt) return } me.fireEvent('saveScene', true, true) hTag = '' var range = me.selection.getRange() if (!range.collapsed) { //跨td不能删 var start = range.startContainer, end = range.endContainer, startTd = domUtils.findParentByTagName(start, 'td', true), endTd = domUtils.findParentByTagName(end, 'td', true) if ( (startTd && endTd && startTd !== endTd) || (!startTd && endTd) || (startTd && !endTd) ) { evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false) return } } if (tag == 'p') { if (!browser.ie) { start = domUtils.findParentByTagName( range.startContainer, [ 'ol', 'ul', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'caption' ], true ) //opera下执行formatblock会在table的场景下有问题,回车在opera原生支持很好,所以暂时在opera去掉调用这个原生的command //trace:2431 if (!start && !browser.opera) { me.document.execCommand('formatBlock', false, '

    ') if (browser.gecko) { range = me.selection.getRange() start = domUtils.findParentByTagName( range.startContainer, 'p', true ) start && domUtils.removeDirtyAttr(start) } } else { hTag = start.tagName start.tagName.toLowerCase() == 'p' && browser.gecko && domUtils.removeDirtyAttr(start) } } } else { evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false) if (!range.collapsed) { range.deleteContents() start = range.startContainer if ( start.nodeType == 1 && (start = start.childNodes[range.startOffset]) ) { while (start.nodeType == 1) { if (dtd.$empty[start.tagName]) { range.setStartBefore(start).setCursor() if (me.undoManger) { me.undoManger.save() } return false } if (!start.firstChild) { var br = range.document.createElement('br') start.appendChild(br) range.setStart(start, 0).setCursor() if (me.undoManger) { me.undoManger.save() } return false } start = start.firstChild } if ( start === range.startContainer.childNodes[range.startOffset] ) { br = range.document.createElement('br') range.insertNode(br).setCursor() } else { range.setStart(start, 0).setCursor() } } else { br = range.document.createElement('br') range.insertNode(br).setStartAfter(br).setCursor() } } else { br = range.document.createElement('br') range.insertNode(br) var parent = br.parentNode if (parent.lastChild === br) { br.parentNode.insertBefore(br.cloneNode(true), br) range.setStartBefore(br) } else { range.setStartAfter(br) } range.setCursor() } } } }) } // plugins/keystrokes.js /* 处理特殊键的兼容性问题 */ UE.plugins['keystrokes'] = function () { var me = this var collapsed = true me.addListener('keydown', function (type, evt) { var keyCode = evt.keyCode || evt.which, rng = me.selection.getRange() //处理全选的情况 if ( !rng.collapsed && !(evt.ctrlKey || evt.shiftKey || evt.altKey || evt.metaKey) && ((keyCode >= 65 && keyCode <= 90) || (keyCode >= 48 && keyCode <= 57) || (keyCode >= 96 && keyCode <= 111) || { 13: 1, 8: 1, 46: 1 }[keyCode]) ) { var tmpNode = rng.startContainer if (domUtils.isFillChar(tmpNode)) { rng.setStartBefore(tmpNode) } tmpNode = rng.endContainer if (domUtils.isFillChar(tmpNode)) { rng.setEndAfter(tmpNode) } rng.txtToElmBoundary() //结束边界可能放到了br的前边,要把br包含进来 // x[xxx]
    if (rng.endContainer && rng.endContainer.nodeType == 1) { tmpNode = rng.endContainer.childNodes[rng.endOffset] if (tmpNode && domUtils.isBr(tmpNode)) { rng.setEndAfter(tmpNode) } } if (rng.startOffset == 0) { tmpNode = rng.startContainer if (domUtils.isBoundaryNode(tmpNode, 'firstChild')) { tmpNode = rng.endContainer if ( rng.endOffset == (tmpNode.nodeType == 3 ? tmpNode.nodeValue.length : tmpNode.childNodes.length) && domUtils.isBoundaryNode(tmpNode, 'lastChild') ) { me.fireEvent('saveScene') me.body.innerHTML = '

    ' + (browser.ie ? '' : '
    ') + '

    ' rng.setStart(me.body.firstChild, 0).setCursor(false, true) me._selectionChange() return } } } } //处理backspace if (keyCode == keymap.Backspace) { rng = me.selection.getRange() collapsed = rng.collapsed if (me.fireEvent('delkeydown', evt)) { return } var start, end //避免按两次删除才能生效的问题 if (rng.collapsed && rng.inFillChar()) { start = rng.startContainer if (domUtils.isFillChar(start)) { rng.setStartBefore(start).shrinkBoundary(true).collapse(true) domUtils.remove(start) } else { start.nodeValue = start.nodeValue.replace( new RegExp('^' + domUtils.fillChar), '' ) rng.startOffset-- rng.collapse(true).select(true) } } //解决选中control元素不能删除的问题 if ((start = rng.getClosedNode())) { me.fireEvent('saveScene') rng.setStartBefore(start) domUtils.remove(start) rng.setCursor() me.fireEvent('saveScene') domUtils.preventDefault(evt) return } //阻止在table上的删除 if (!browser.ie) { start = domUtils.findParentByTagName( rng.startContainer, 'table', true ) end = domUtils.findParentByTagName(rng.endContainer, 'table', true) if ((start && !end) || (!start && end) || start !== end) { evt.preventDefault() return } } } //处理tab键的逻辑 if (keyCode == keymap.Tab) { //不处理以下标签 var excludeTagNameForTabKey = { ol: 1, ul: 1, table: 1 } //处理组件里的tab按下事件 if (me.fireEvent('tabkeydown', evt)) { domUtils.preventDefault(evt) return } var range = me.selection.getRange() me.fireEvent('saveScene') for ( var i = 0, txt = '', tabSize = me.options.tabSize || 4, tabNode = me.options.tabNode || ' '; i < tabSize; i++ ) { txt += tabNode } var span = me.document.createElement('span') span.innerHTML = txt + domUtils.fillChar if (range.collapsed) { range.insertNode(span.cloneNode(true).firstChild).setCursor(true) } else { var filterFn = function (node) { return ( domUtils.isBlockElm(node) && !excludeTagNameForTabKey[node.tagName.toLowerCase()] ) } //普通的情况 start = domUtils.findParent(range.startContainer, filterFn, true) end = domUtils.findParent(range.endContainer, filterFn, true) if (start && end && start === end) { range.deleteContents() range.insertNode(span.cloneNode(true).firstChild).setCursor(true) } else { var bookmark = range.createBookmark() range.enlarge(true) var bookmark2 = range.createBookmark(), current = domUtils.getNextDomNode( bookmark2.start, false, filterFn ) while ( current && !( domUtils.getPosition(current, bookmark2.end) & domUtils.POSITION_FOLLOWING ) ) { current.insertBefore( span.cloneNode(true).firstChild, current.firstChild ) current = domUtils.getNextDomNode(current, false, filterFn) } range.moveToBookmark(bookmark2).moveToBookmark(bookmark).select() } } domUtils.preventDefault(evt) } //trace:1634 //ff的del键在容器空的时候,也会删除 if (browser.gecko && keyCode == 46) { range = me.selection.getRange() if (range.collapsed) { start = range.startContainer if (domUtils.isEmptyBlock(start)) { var parent = start.parentNode while ( domUtils.getChildCount(parent) == 1 && !domUtils.isBody(parent) ) { start = parent parent = parent.parentNode } if (start === parent.lastChild) evt.preventDefault() return } } } }) me.addListener('keyup', function (type, evt) { var keyCode = evt.keyCode || evt.which, rng, me = this if (keyCode == keymap.Backspace) { if (me.fireEvent('delkeyup')) { return } rng = me.selection.getRange() if (rng.collapsed) { var tmpNode, autoClearTagName = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] if ( (tmpNode = domUtils.findParentByTagName( rng.startContainer, autoClearTagName, true )) ) { if (domUtils.isEmptyBlock(tmpNode)) { var pre = tmpNode.previousSibling if (pre && pre.nodeName != 'TABLE') { domUtils.remove(tmpNode) rng.setStartAtLast(pre).setCursor(false, true) return } else { var next = tmpNode.nextSibling if (next && next.nodeName != 'TABLE') { domUtils.remove(tmpNode) rng.setStartAtFirst(next).setCursor(false, true) return } } } } //处理当删除到body时,要重新给p标签展位 if (domUtils.isBody(rng.startContainer)) { var tmpNode = domUtils.createElement(me.document, 'p', { innerHTML: browser.ie ? domUtils.fillChar : '
    ' }) rng.insertNode(tmpNode).setStart(tmpNode, 0).setCursor(false, true) } } //chrome下如果删除了inline标签,浏览器会有记忆,在输入文字还是会套上刚才删除的标签,所以这里再选一次就不会了 if ( !collapsed && (rng.startContainer.nodeType == 3 || (rng.startContainer.nodeType == 1 && domUtils.isEmptyBlock(rng.startContainer))) ) { if (browser.ie) { var span = rng.document.createElement('span') rng.insertNode(span).setStartBefore(span).collapse(true) rng.select() domUtils.remove(span) } else { rng.select() } } } }) } // plugins/fiximgclick.js ///import core ///commands 修复chrome下图片不能点击的问题,出现八个角可改变大小 ///commandsName FixImgClick ///commandsTitle 修复chrome下图片不能点击的问题,出现八个角可改变大小 //修复chrome下图片不能点击的问题,出现八个角可改变大小 UE.plugins['fiximgclick'] = (function () { var elementUpdated = false function Scale() { this.editor = null this.resizer = null this.cover = null this.doc = document this.prePos = { x: 0, y: 0 } this.startPos = { x: 0, y: 0 } } ;(function () { var rect = [ //[left, top, width, height] [0, 0, -1, -1], [0, 0, 0, -1], [0, 0, 1, -1], [0, 0, -1, 0], [0, 0, 1, 0], [0, 0, -1, 1], [0, 0, 0, 1], [0, 0, 1, 1] ] Scale.prototype = { init: function (editor) { var me = this me.editor = editor me.startPos = this.prePos = { x: 0, y: 0 } me.dragId = -1 var hands = [], cover = (me.cover = document.createElement('div')), resizer = (me.resizer = document.createElement('div')) cover.id = me.editor.ui.id + '_imagescale_cover' cover.style.cssText = 'position:absolute;display:none;z-index:' + me.editor.options.zIndex + ';filter:alpha(opacity=0); opacity:0;background:#CCC;' domUtils.on(cover, 'mousedown click', function () { me.hide() }) for (i = 0; i < 8; i++) { hands.push( '' ) } resizer.id = me.editor.ui.id + '_imagescale' resizer.className = 'edui-editor-imagescale' resizer.innerHTML = hands.join('') resizer.style.cssText += ';display:none;border:1px solid #3b77ff;z-index:' + me.editor.options.zIndex + ';' me.editor.ui.getDom().appendChild(cover) me.editor.ui.getDom().appendChild(resizer) me.initStyle() me.initEvents() }, initStyle: function () { utils.cssRule( 'imagescale', '.edui-editor-imagescale{display:none;position:absolute;border:1px solid #38B2CE;cursor:hand;-webkit-box-sizing: content-box;-moz-box-sizing: content-box;box-sizing: content-box;}' + '.edui-editor-imagescale span{position:absolute;width:6px;height:6px;overflow:hidden;font-size:0px;display:block;background-color:#3C9DD0;}' + '.edui-editor-imagescale .edui-editor-imagescale-hand0{cursor:nw-resize;top:0;margin-top:-4px;left:0;margin-left:-4px;}' + '.edui-editor-imagescale .edui-editor-imagescale-hand1{cursor:n-resize;top:0;margin-top:-4px;left:50%;margin-left:-4px;}' + '.edui-editor-imagescale .edui-editor-imagescale-hand2{cursor:ne-resize;top:0;margin-top:-4px;left:100%;margin-left:-3px;}' + '.edui-editor-imagescale .edui-editor-imagescale-hand3{cursor:w-resize;top:50%;margin-top:-4px;left:0;margin-left:-4px;}' + '.edui-editor-imagescale .edui-editor-imagescale-hand4{cursor:e-resize;top:50%;margin-top:-4px;left:100%;margin-left:-3px;}' + '.edui-editor-imagescale .edui-editor-imagescale-hand5{cursor:sw-resize;top:100%;margin-top:-3px;left:0;margin-left:-4px;}' + '.edui-editor-imagescale .edui-editor-imagescale-hand6{cursor:s-resize;top:100%;margin-top:-3px;left:50%;margin-left:-4px;}' + '.edui-editor-imagescale .edui-editor-imagescale-hand7{cursor:se-resize;top:100%;margin-top:-3px;left:100%;margin-left:-3px;}' ) }, initEvents: function () { var me = this me.startPos.x = me.startPos.y = 0 me.isDraging = false }, _eventHandler: function (e) { var me = this switch (e.type) { case 'mousedown': var hand = e.target || e.srcElement, hand if ( hand.className.indexOf('edui-editor-imagescale-hand') != -1 && me.dragId == -1 ) { me.dragId = hand.className.slice(-1) me.startPos.x = me.prePos.x = e.clientX me.startPos.y = me.prePos.y = e.clientY domUtils.on(me.doc, 'mousemove', me.proxy(me._eventHandler, me)) } break case 'mousemove': if (me.dragId != -1) { me.updateContainerStyle(me.dragId, { x: e.clientX - me.prePos.x, y: e.clientY - me.prePos.y }) me.prePos.x = e.clientX me.prePos.y = e.clientY elementUpdated = true me.updateTargetElement() } break case 'mouseup': if (me.dragId != -1) { me.updateContainerStyle(me.dragId, { x: e.clientX - me.prePos.x, y: e.clientY - me.prePos.y }) me.updateTargetElement() if (me.target.parentNode) me.attachTo(me.target) me.dragId = -1 } domUtils.un(me.doc, 'mousemove', me.proxy(me._eventHandler, me)) //修复只是点击挪动点,但没有改变大小,不应该触发contentchange if (elementUpdated) { elementUpdated = false me.editor.fireEvent('contentchange') } break default: break } }, updateTargetElement: function () { var me = this domUtils.setStyles(me.target, { width: me.resizer.style.width, height: me.resizer.style.height }) me.target.width = parseInt(me.resizer.style.width) me.target.height = parseInt(me.resizer.style.height) me.attachTo(me.target) }, updateContainerStyle: function (dir, offset) { var me = this, dom = me.resizer, tmp if (rect[dir][0] != 0) { tmp = parseInt(dom.style.left) + offset.x dom.style.left = me._validScaledProp('left', tmp) + 'px' } if (rect[dir][1] != 0) { tmp = parseInt(dom.style.top) + offset.y dom.style.top = me._validScaledProp('top', tmp) + 'px' } if (rect[dir][2] != 0) { tmp = dom.clientWidth + rect[dir][2] * offset.x dom.style.width = me._validScaledProp('width', tmp) + 'px' } if (rect[dir][3] != 0) { tmp = dom.clientHeight + rect[dir][3] * offset.y dom.style.height = me._validScaledProp('height', tmp) + 'px' } }, _validScaledProp: function (prop, value) { var ele = this.resizer, wrap = document value = isNaN(value) ? 0 : value switch (prop) { case 'left': return value < 0 ? 0 : value + ele.clientWidth > wrap.clientWidth ? wrap.clientWidth - ele.clientWidth : value case 'top': return value < 0 ? 0 : value + ele.clientHeight > wrap.clientHeight ? wrap.clientHeight - ele.clientHeight : value case 'width': return value <= 0 ? 1 : value + ele.offsetLeft > wrap.clientWidth ? wrap.clientWidth - ele.offsetLeft : value case 'height': return value <= 0 ? 1 : value + ele.offsetTop > wrap.clientHeight ? wrap.clientHeight - ele.offsetTop : value } }, hideCover: function () { this.cover.style.display = 'none' }, showCover: function () { var me = this, editorPos = domUtils.getXY(me.editor.ui.getDom()), iframePos = domUtils.getXY(me.editor.iframe) domUtils.setStyles(me.cover, { width: me.editor.iframe.offsetWidth + 'px', height: me.editor.iframe.offsetHeight + 'px', top: iframePos.y - editorPos.y + 'px', left: iframePos.x - editorPos.x + 'px', position: 'absolute', display: '' }) }, show: function (targetObj) { var me = this me.resizer.style.display = 'block' if (targetObj) me.attachTo(targetObj) domUtils.on(this.resizer, 'mousedown', me.proxy(me._eventHandler, me)) domUtils.on(me.doc, 'mouseup', me.proxy(me._eventHandler, me)) me.showCover() me.editor.fireEvent('afterscaleshow', me) me.editor.fireEvent('saveScene') }, hide: function () { var me = this me.hideCover() me.resizer.style.display = 'none' domUtils.un(me.resizer, 'mousedown', me.proxy(me._eventHandler, me)) domUtils.un(me.doc, 'mouseup', me.proxy(me._eventHandler, me)) me.editor.fireEvent('afterscalehide', me) }, proxy: function (fn, context) { return function (e) { return fn.apply(context || this, arguments) } }, attachTo: function (targetObj) { var me = this, target = (me.target = targetObj), resizer = this.resizer, imgPos = domUtils.getXY(target), iframePos = domUtils.getXY(me.editor.iframe), editorPos = domUtils.getXY(resizer.parentNode) domUtils.setStyles(resizer, { width: target.width + 'px', height: target.height + 'px', left: iframePos.x + imgPos.x - me.editor.document.body.scrollLeft - editorPos.x - parseInt(resizer.style.borderLeftWidth) + 'px', top: iframePos.y + imgPos.y - me.editor.document.body.scrollTop - editorPos.y - parseInt(resizer.style.borderTopWidth) + 'px' }) } } })() return function () { var me = this, imageScale me.setOpt('imageScaleEnabled', true) if (!browser.ie && me.options.imageScaleEnabled) { me.addListener('click', function (type, e) { var range = me.selection.getRange(), img = range.getClosedNode() if ( img && img.tagName == 'IMG' && me.body.contentEditable != 'false' ) { if ( img.className.indexOf('edui-faked-music') != -1 || img.getAttribute('anchorname') || domUtils.hasClass(img, 'loadingclass') || domUtils.hasClass(img, 'loaderrorclass') ) { return } if (!imageScale) { imageScale = new Scale() imageScale.init(me) me.ui.getDom().appendChild(imageScale.resizer) var _keyDownHandler = function (e) { imageScale.hide() if (imageScale.target) me.selection .getRange() .selectNode(imageScale.target) .select() }, _mouseDownHandler = function (e) { var ele = e.target || e.srcElement if ( ele && (ele.className === undefined || ele.className.indexOf('edui-editor-imagescale') == -1) ) { _keyDownHandler(e) } }, timer me.addListener('afterscaleshow', function (e) { me.addListener('beforekeydown', _keyDownHandler) me.addListener('beforemousedown', _mouseDownHandler) domUtils.on(document, 'keydown', _keyDownHandler) domUtils.on(document, 'mousedown', _mouseDownHandler) me.selection.getNative().removeAllRanges() }) me.addListener('afterscalehide', function (e) { me.removeListener('beforekeydown', _keyDownHandler) me.removeListener('beforemousedown', _mouseDownHandler) domUtils.un(document, 'keydown', _keyDownHandler) domUtils.un(document, 'mousedown', _mouseDownHandler) var target = imageScale.target if (target.parentNode) { me.selection.getRange().selectNode(target).select() } }) //TODO 有iframe的情况,mousedown不能往下传。。 domUtils.on(imageScale.resizer, 'mousedown', function (e) { me.selection.getNative().removeAllRanges() var ele = e.target || e.srcElement if ( ele && ele.className.indexOf('edui-editor-imagescale-hand') == -1 ) { timer = setTimeout(function () { imageScale.hide() if (imageScale.target) me.selection.getRange().selectNode(ele).select() }, 200) } }) domUtils.on(imageScale.resizer, 'mouseup', function (e) { var ele = e.target || e.srcElement if ( ele && ele.className.indexOf('edui-editor-imagescale-hand') == -1 ) { clearTimeout(timer) } }) } imageScale.show(img) } else { if (imageScale && imageScale.resizer.style.display != 'none') imageScale.hide() } }) } if (browser.webkit) { me.addListener('click', function (type, e) { if (e.target.tagName == 'IMG' && me.body.contentEditable != 'false') { var range = new dom.Range(me.document) range.selectNode(e.target).select() } }) } } })() // plugins/autolink.js ///import core ///commands 为非ie浏览器自动添加a标签 ///commandsName AutoLink ///commandsTitle 自动增加链接 /** * @description 为非ie浏览器自动添加a标签 * @author zhanyi */ UE.plugin.register( 'autolink', function () { var cont = 0 return !browser.ie ? { bindEvents: { reset: function () { cont = 0 }, keydown: function (type, evt) { var me = this var keyCode = evt.keyCode || evt.which if (keyCode == 32 || keyCode == 13) { var sel = me.selection.getNative(), range = sel.getRangeAt(0).cloneRange(), offset, charCode var start = range.startContainer while (start.nodeType == 1 && range.startOffset > 0) { start = range.startContainer.childNodes[range.startOffset - 1] if (!start) { break } range.setStart( start, start.nodeType == 1 ? start.childNodes.length : start.nodeValue.length ) range.collapse(true) start = range.startContainer } do { if (range.startOffset == 0) { start = range.startContainer.previousSibling while (start && start.nodeType == 1) { start = start.lastChild } if (!start || domUtils.isFillChar(start)) { break } offset = start.nodeValue.length } else { start = range.startContainer offset = range.startOffset } range.setStart(start, offset - 1) charCode = range.toString().charCodeAt(0) } while (charCode != 160 && charCode != 32) if ( range .toString() .replace(new RegExp(domUtils.fillChar, 'g'), '') .match(/(?:https?:\/\/|ssh:\/\/|ftp:\/\/|file:\/|www\.)/i) ) { while (range.toString().length) { if ( /^(?:https?:\/\/|ssh:\/\/|ftp:\/\/|file:\/|www\.)/i.test( range.toString() ) ) { break } try { range.setStart( range.startContainer, range.startOffset + 1 ) } catch (e) { //trace:2121 var start = range.startContainer while (!(next = start.nextSibling)) { if (domUtils.isBody(start)) { return } start = start.parentNode } range.setStart(next, 0) } } //range的开始边界已经在a标签里的不再处理 if ( domUtils.findParentByTagName( range.startContainer, 'a', true ) ) { return } var a = me.document.createElement('a'), text = me.document.createTextNode(' '), href me.undoManger && me.undoManger.save() a.appendChild(range.extractContents()) a.href = a.innerHTML = a.innerHTML.replace(/<[^>]+>/g, '') href = a .getAttribute('href') .replace(new RegExp(domUtils.fillChar, 'g'), '') href = /^(?:https?:\/\/)/gi.test(href) ? href : 'http://' + href a.setAttribute('_src', utils.html(href)) a.href = utils.html(href) range.insertNode(a) a.parentNode.insertBefore(text, a.nextSibling) range.setStart(text, 0) range.collapse(true) sel.removeAllRanges() sel.addRange(range) me.undoManger && me.undoManger.save() } } } } } : {} }, function () { var keyCodes = { 37: 1, 38: 1, 39: 1, 40: 1, 13: 1, 32: 1 } function checkIsCludeLink(node) { if (node.nodeType == 3) { return null } if (node.nodeName == 'A') { return node } var lastChild = node.lastChild while (lastChild) { if (lastChild.nodeName == 'A') { return lastChild } if (lastChild.nodeType == 3) { if (domUtils.isWhitespace(lastChild)) { lastChild = lastChild.previousSibling continue } return null } lastChild = lastChild.lastChild } } browser.ie && this.addListener('keyup', function (cmd, evt) { var me = this, keyCode = evt.keyCode if (keyCodes[keyCode]) { var rng = me.selection.getRange() var start = rng.startContainer if (keyCode == 13) { while ( start && !domUtils.isBody(start) && !domUtils.isBlockElm(start) ) { start = start.parentNode } if (start && !domUtils.isBody(start) && start.nodeName == 'P') { var pre = start.previousSibling if (pre && pre.nodeType == 1) { var pre = checkIsCludeLink(pre) if (pre && !pre.getAttribute('_href')) { domUtils.remove(pre, true) } } } } else if (keyCode == 32) { if (start.nodeType == 3 && /^\s$/.test(start.nodeValue)) { start = start.previousSibling if ( start && start.nodeName == 'A' && !start.getAttribute('_href') ) { domUtils.remove(start, true) } } } else { start = domUtils.findParentByTagName(start, 'a', true) if (start && !start.getAttribute('_href')) { var bk = rng.createBookmark() domUtils.remove(start, true) rng.moveToBookmark(bk).select(true) } } } }) } ) // plugins/autoheight.js ///import core ///commands 当输入内容超过编辑器高度时,编辑器自动增高 ///commandsName AutoHeight,autoHeightEnabled ///commandsTitle 自动增高 /** * @description 自动伸展 * @author zhanyi */ UE.plugins['autoheight'] = function () { var me = this //提供开关,就算加载也可以关闭 me.autoHeightEnabled = me.options.autoHeightEnabled !== false if (!me.autoHeightEnabled) { return } var bakOverflow, lastHeight = 0, options = me.options, currentHeight, timer function adjustHeight() { var me = this clearTimeout(timer) if (isFullscreen) return if ( !me.queryCommandState || (me.queryCommandState && me.queryCommandState('source') != 1) ) { timer = setTimeout(function () { var node = me.body.lastChild while (node && node.nodeType != 1) { node = node.previousSibling } if (node && node.nodeType == 1) { node.style.clear = 'both' currentHeight = Math.max( domUtils.getXY(node).y + node.offsetHeight + 25, Math.max(options.minFrameHeight, options.initialFrameHeight) ) if (currentHeight != lastHeight) { if ( currentHeight !== parseInt(me.iframe.parentNode.style.height) ) { me.iframe.parentNode.style.height = currentHeight + 'px' } me.body.style.height = currentHeight + 'px' lastHeight = currentHeight } domUtils.removeStyle(node, 'clear') } }, 50) } } var isFullscreen me.addListener('fullscreenchanged', function (cmd, f) { isFullscreen = f }) me.addListener('destroy', function () { me.removeListener( 'contentchange afterinserthtml keyup mouseup', adjustHeight ) }) me.enableAutoHeight = function () { var me = this if (!me.autoHeightEnabled) { return } var doc = me.document me.autoHeightEnabled = true bakOverflow = doc.body.style.overflowY doc.body.style.overflowY = 'hidden' me.addListener( 'contentchange afterinserthtml keyup mouseup', adjustHeight ) //ff不给事件算得不对 setTimeout( function () { adjustHeight.call(me) }, browser.gecko ? 100 : 0 ) me.fireEvent('autoheightchanged', me.autoHeightEnabled) } me.disableAutoHeight = function () { me.body.style.overflowY = bakOverflow || '' me.removeListener('contentchange', adjustHeight) me.removeListener('keyup', adjustHeight) me.removeListener('mouseup', adjustHeight) me.autoHeightEnabled = false me.fireEvent('autoheightchanged', me.autoHeightEnabled) } me.on('setHeight', function () { me.disableAutoHeight() }) me.addListener('ready', function () { me.enableAutoHeight() //trace:1764 var timer domUtils.on( browser.ie ? me.body : me.document, browser.webkit ? 'dragover' : 'drop', function () { clearTimeout(timer) timer = setTimeout(function () { //trace:3681 adjustHeight.call(me) }, 100) } ) //修复内容过多时,回到顶部,顶部内容被工具栏遮挡问题 var lastScrollY window.onscroll = function () { if (lastScrollY === null) { lastScrollY = this.scrollY } else if (this.scrollY == 0 && lastScrollY != 0) { me.window.scrollTo(0, 0) lastScrollY = null } } }) } // plugins/autofloat.js ///import core ///commands 悬浮工具栏 ///commandsName AutoFloat,autoFloatEnabled ///commandsTitle 悬浮工具栏 /** * modified by chengchao01 * 注意: 引入此功能后,在IE6下会将body的背景图片覆盖掉! */ UE.plugins['autofloat'] = function () { var me = this, lang = me.getLang() me.setOpt({ topOffset: 0 }) var optsAutoFloatEnabled = me.options.autoFloatEnabled !== false, topOffset = me.options.topOffset //如果不固定toolbar的位置,则直接退出 if (!optsAutoFloatEnabled) { return } var uiUtils = UE.ui.uiUtils, LteIE6 = browser.ie && browser.version <= 6, quirks = browser.quirks function checkHasUI() { if (!UE.ui) { alert(lang.autofloatMsg) return 0 } return 1 } function fixIE6FixedPos() { var docStyle = document.body.style docStyle.backgroundImage = 'url("about:blank")' docStyle.backgroundAttachment = 'fixed' } var bakCssText, placeHolder = document.createElement('div'), toolbarBox, orgTop, getPosition, flag = true //ie7模式下需要偏移 function setFloating() { var toobarBoxPos = domUtils.getXY(toolbarBox), origalFloat = domUtils.getComputedStyle(toolbarBox, 'position'), origalLeft = domUtils.getComputedStyle(toolbarBox, 'left') toolbarBox.style.width = toolbarBox.offsetWidth + 'px' toolbarBox.style.zIndex = me.options.zIndex * 1 + 1 toolbarBox.parentNode.insertBefore(placeHolder, toolbarBox) if (LteIE6 || (quirks && browser.ie)) { if (toolbarBox.style.position != 'absolute') { toolbarBox.style.position = 'absolute' } toolbarBox.style.top = (document.body.scrollTop || document.documentElement.scrollTop) - orgTop + topOffset + 'px' } else { if (browser.ie7Compat && flag) { flag = false toolbarBox.style.left = domUtils.getXY(toolbarBox).x - document.documentElement.getBoundingClientRect().left + 2 + 'px' } if (toolbarBox.style.position != 'fixed') { toolbarBox.style.position = 'fixed' toolbarBox.style.top = topOffset + 'px' ;(origalFloat == 'absolute' || origalFloat == 'relative') && parseFloat(origalLeft) && (toolbarBox.style.left = toobarBoxPos.x + 'px') } } } function unsetFloating() { flag = true if (placeHolder.parentNode) { placeHolder.parentNode.removeChild(placeHolder) } toolbarBox.style.cssText = bakCssText } function updateFloating() { var rect3 = getPosition(me.container) var offset = me.options.toolbarTopOffset || 0 if (rect3.top < 0 && rect3.bottom - toolbarBox.offsetHeight > offset) { setFloating() } else { unsetFloating() } } var defer_updateFloating = utils.defer( function () { updateFloating() }, browser.ie ? 200 : 100, true ) me.addListener('destroy', function () { domUtils.un(window, ['scroll', 'resize'], updateFloating) me.removeListener('keydown', defer_updateFloating) }) me.addListener('ready', function () { if (checkHasUI(me)) { //加载了ui组件,但在new时,没有加载ui,导致编辑器实例上没有ui类,所以这里做判断 if (!me.ui) { return } getPosition = uiUtils.getClientRect toolbarBox = me.ui.getDom('toolbarbox') orgTop = getPosition(toolbarBox).top bakCssText = toolbarBox.style.cssText placeHolder.style.height = toolbarBox.offsetHeight + 'px' if (LteIE6) { fixIE6FixedPos() } domUtils.on(window, ['scroll', 'resize'], updateFloating) me.addListener('keydown', defer_updateFloating) me.addListener('beforefullscreenchange', function (t, enabled) { if (enabled) { unsetFloating() } }) me.addListener('fullscreenchanged', function (t, enabled) { if (!enabled) { updateFloating() } }) me.addListener('sourcemodechanged', function (t, enabled) { setTimeout(function () { updateFloating() }, 0) }) me.addListener('clearDoc', function () { setTimeout(function () { updateFloating() }, 0) }) } }) } // plugins/video.js /** * video插件, 为UEditor提供视频插入支持 * @file * @since 1.2.6.1 */ UE.plugins['video'] = function () { var me = this /** * 创建插入视频字符窜 * @param url 视频地址 * @param width 视频宽度 * @param height 视频高度 * @param align 视频对齐 * @param toEmbed 是否以flash代替显示 * @param addParagraph 是否需要添加P 标签 */ function creatInsertStr(url, width, height, id, align, classname, type) { url = utils.unhtmlForUrl(url) align = utils.unhtml(align) classname = utils.unhtml(classname).trim() width = parseInt(width, 10) || 0 height = parseInt(height, 10) || 0 var str switch (type) { case 'image': str = '' break case 'embed': str = '' break case 'video': var ext = url.substr(url.lastIndexOf('.') + 1) if (ext == 'ogv') ext = 'ogg' str = '' + '' break } return str } function switchImgAndVideo(root, img2video) { utils.each( root.getNodesByTagName(img2video ? 'img' : 'embed video'), function (node) { var className = node.getAttr('class') if (className && className.indexOf('edui-faked-video') != -1) { var html = creatInsertStr( img2video ? node.getAttr('_url') : node.getAttr('src'), node.getAttr('width'), node.getAttr('height'), null, node.getStyle('float') || '', className, img2video ? 'embed' : 'image' ) node.parentNode.replaceChild(UE.uNode.createElement(html), node) } if (className && className.indexOf('edui-upload-video') != -1) { var html = creatInsertStr( img2video ? node.getAttr('_url') : node.getAttr('src'), node.getAttr('width'), node.getAttr('height'), null, node.getStyle('float') || '', className, img2video ? 'video' : 'image' ) node.parentNode.replaceChild(UE.uNode.createElement(html), node) } } ) } me.addOutputRule(function (root) { switchImgAndVideo(root, true) }) me.addInputRule(function (root) { switchImgAndVideo(root) }) /** * 插入视频 * @command insertvideo * @method execCommand * @param { String } cmd 命令字符串 * @param { Object } videoAttr 键值对对象, 描述一个视频的所有属性 * @example * ```javascript * * var videoAttr = { * //视频地址 * url: 'http://www.youku.com/xxx', * //视频宽高值, 单位px * width: 200, * height: 100 * }; * * //editor 是编辑器实例 * //向编辑器插入单个视频 * editor.execCommand( 'insertvideo', videoAttr ); * ``` */ /** * 插入视频 * @command insertvideo * @method execCommand * @param { String } cmd 命令字符串 * @param { Array } videoArr 需要插入的视频的数组, 其中的每一个元素都是一个键值对对象, 描述了一个视频的所有属性 * @example * ```javascript * * var videoAttr1 = { * //视频地址 * url: 'http://www.youku.com/xxx', * //视频宽高值, 单位px * width: 200, * height: 100 * }, * videoAttr2 = { * //视频地址 * url: 'http://www.youku.com/xxx', * //视频宽高值, 单位px * width: 200, * height: 100 * } * * //editor 是编辑器实例 * //该方法将会向编辑器内插入两个视频 * editor.execCommand( 'insertvideo', [ videoAttr1, videoAttr2 ] ); * ``` */ /** * 查询当前光标所在处是否是一个视频 * @command insertvideo * @method queryCommandState * @param { String } cmd 需要查询的命令字符串 * @return { int } 如果当前光标所在处的元素是一个视频对象, 则返回1,否则返回0 * @example * ```javascript * * //editor 是编辑器实例 * editor.queryCommandState( 'insertvideo' ); * ``` */ me.commands['insertvideo'] = { execCommand: function (cmd, videoObjs, type) { videoObjs = utils.isArray(videoObjs) ? videoObjs : [videoObjs] var html = [], id = 'tmpVedio', cl for (var i = 0, vi, len = videoObjs.length; i < len; i++) { vi = videoObjs[i] cl = type == 'upload' ? 'edui-upload-video video-js vjs-default-skin' : 'edui-faked-video' html.push( creatInsertStr( vi.url, vi.width || 420, vi.height || 280, id + i, null, cl, 'image' ) ) } me.execCommand('inserthtml', html.join(''), true) var rng = this.selection.getRange() for (var i = 0, len = videoObjs.length; i < len; i++) { var img = this.document.getElementById('tmpVedio' + i) domUtils.removeAttributes(img, 'id') rng.selectNode(img).select() me.execCommand('imagefloat', videoObjs[i].align) } }, queryCommandState: function () { var img = me.selection.getRange().getClosedNode(), flag = img && (img.className == 'edui-faked-video' || img.className.indexOf('edui-upload-video') != -1) return flag ? 1 : 0 } } } // plugins/table.core.js /** * Created with JetBrains WebStorm. * User: taoqili * Date: 13-1-18 * Time: 上午11:09 * To change this template use File | Settings | File Templates. */ /** * UE表格操作类 * @param table * @constructor */ ;(function () { var UETable = (UE.UETable = function (table) { this.table = table this.indexTable = [] this.selectedTds = [] this.cellsRange = {} this.update(table) }) //===以下为静态工具方法=== UETable.removeSelectedClass = function (cells) { utils.each(cells, function (cell) { domUtils.removeClasses(cell, 'selectTdClass') }) } UETable.addSelectedClass = function (cells) { utils.each(cells, function (cell) { domUtils.addClass(cell, 'selectTdClass') }) } UETable.isEmptyBlock = function (node) { var reg = new RegExp(domUtils.fillChar, 'g') if ( node[browser.ie ? 'innerText' : 'textContent'] .replace(/^\s*$/, '') .replace(reg, '').length > 0 ) { return 0 } for (var i in dtd.$isNotEmpty) if (dtd.$isNotEmpty.hasOwnProperty(i)) { if (node.getElementsByTagName(i).length) { return 0 } } return 1 } UETable.getWidth = function (cell) { if (!cell) return 0 return parseInt(domUtils.getComputedStyle(cell, 'width'), 10) } /** * 获取单元格或者单元格组的“对齐”状态。 如果当前的检测对象是一个单元格组, 只有在满足所有单元格的 水平和竖直 对齐属性都相同的 * 条件时才会返回其状态值,否则将返回null; 如果当前只检测了一个单元格, 则直接返回当前单元格的对齐状态; * @param table cell or table cells , 支持单个单元格dom对象 或者 单元格dom对象数组 * @return { align: 'left' || 'right' || 'center', valign: 'top' || 'middle' || 'bottom' } 或者 null */ UETable.getTableCellAlignState = function (cells) { !utils.isArray(cells) && (cells = [cells]) var result = {}, status = ['align', 'valign'], tempStatus = null, isSame = true //状态是否相同 utils.each(cells, function (cellNode) { utils.each(status, function (currentState) { tempStatus = cellNode.getAttribute(currentState) if (!result[currentState] && tempStatus) { result[currentState] = tempStatus } else if ( !result[currentState] || tempStatus !== result[currentState] ) { isSame = false return false } }) return isSame }) return isSame ? result : null } /** * 根据当前选区获取相关的table信息 * @return {Object} */ UETable.getTableItemsByRange = function (editor) { var start = editor.selection.getStart() //ff下会选中bookmark if ( start && start.id && start.id.indexOf('_baidu_bookmark_start_') === 0 && start.nextSibling ) { start = start.nextSibling } //在table或者td边缘有可能存在选中tr的情况 var cell = start && domUtils.findParentByTagName(start, ['td', 'th'], true), tr = cell && cell.parentNode, caption = start && domUtils.findParentByTagName(start, 'caption', true), table = caption ? caption.parentNode : tr && tr.parentNode.parentNode return { cell: cell, tr: tr, table: table, caption: caption } } UETable.getUETableBySelected = function (editor) { var table = UETable.getTableItemsByRange(editor).table if (table && table.ueTable && table.ueTable.selectedTds.length) { return table.ueTable } return null } UETable.getDefaultValue = function (editor, table) { var borderMap = { thin: '0px', medium: '1px', thick: '2px' }, tableBorder, tdPadding, tdBorder, tmpValue if (!table) { table = editor.document.createElement('table') table.insertRow(0).insertCell(0).innerHTML = 'xxx' editor.body.appendChild(table) var td = table.getElementsByTagName('td')[0] tmpValue = domUtils.getComputedStyle(table, 'border-left-width') tableBorder = parseInt(borderMap[tmpValue] || tmpValue, 10) tmpValue = domUtils.getComputedStyle(td, 'padding-left') tdPadding = parseInt(borderMap[tmpValue] || tmpValue, 10) tmpValue = domUtils.getComputedStyle(td, 'border-left-width') tdBorder = parseInt(borderMap[tmpValue] || tmpValue, 10) domUtils.remove(table) return { tableBorder: tableBorder, tdPadding: tdPadding, tdBorder: tdBorder } } else { td = table.getElementsByTagName('td')[0] tmpValue = domUtils.getComputedStyle(table, 'border-left-width') tableBorder = parseInt(borderMap[tmpValue] || tmpValue, 10) tmpValue = domUtils.getComputedStyle(td, 'padding-left') tdPadding = parseInt(borderMap[tmpValue] || tmpValue, 10) tmpValue = domUtils.getComputedStyle(td, 'border-left-width') tdBorder = parseInt(borderMap[tmpValue] || tmpValue, 10) return { tableBorder: tableBorder, tdPadding: tdPadding, tdBorder: tdBorder } } } /** * 根据当前点击的td或者table获取索引对象 * @param tdOrTable */ UETable.getUETable = function (tdOrTable) { var tag = tdOrTable.tagName.toLowerCase() tdOrTable = tag == 'td' || tag == 'th' || tag == 'caption' ? domUtils.findParentByTagName(tdOrTable, 'table', true) : tdOrTable if (!tdOrTable.ueTable) { tdOrTable.ueTable = new UETable(tdOrTable) } return tdOrTable.ueTable } UETable.cloneCell = function (cell, ignoreMerge, keepPro) { if (!cell || utils.isString(cell)) { return this.table.ownerDocument.createElement(cell || 'td') } var flag = domUtils.hasClass(cell, 'selectTdClass') flag && domUtils.removeClasses(cell, 'selectTdClass') var tmpCell = cell.cloneNode(true) if (ignoreMerge) { tmpCell.rowSpan = tmpCell.colSpan = 1 } //去掉宽高 !keepPro && domUtils.removeAttributes(tmpCell, 'width height') !keepPro && domUtils.removeAttributes(tmpCell, 'style') tmpCell.style.borderLeftStyle = '' tmpCell.style.borderTopStyle = '' tmpCell.style.borderLeftColor = cell.style.borderRightColor tmpCell.style.borderLeftWidth = cell.style.borderRightWidth tmpCell.style.borderTopColor = cell.style.borderBottomColor tmpCell.style.borderTopWidth = cell.style.borderBottomWidth flag && domUtils.addClass(cell, 'selectTdClass') return tmpCell } UETable.prototype = { getMaxRows: function () { var rows = this.table.rows, maxLen = 1 for (var i = 0, row; (row = rows[i]); i++) { var currentMax = 1 for (var j = 0, cj; (cj = row.cells[j++]); ) { currentMax = Math.max(cj.rowSpan || 1, currentMax) } maxLen = Math.max(currentMax + i, maxLen) } return maxLen }, /** * 获取当前表格的最大列数 */ getMaxCols: function () { var rows = this.table.rows, maxLen = 0, cellRows = {} for (var i = 0, row; (row = rows[i]); i++) { var cellsNum = 0 for (var j = 0, cj; (cj = row.cells[j++]); ) { cellsNum += cj.colSpan || 1 if (cj.rowSpan && cj.rowSpan > 1) { for (var k = 1; k < cj.rowSpan; k++) { if (!cellRows['row_' + (i + k)]) { cellRows['row_' + (i + k)] = cj.colSpan || 1 } else { cellRows['row_' + (i + k)]++ } } } } cellsNum += cellRows['row_' + i] || 0 maxLen = Math.max(cellsNum, maxLen) } return maxLen }, getCellColIndex: function (cell) {}, /** * 获取当前cell旁边的单元格, * @param cell * @param right */ getHSideCell: function (cell, right) { try { var cellInfo = this.getCellInfo(cell), previewRowIndex, previewColIndex var len = this.selectedTds.length, range = this.cellsRange //首行或者首列没有前置单元格 if ( (!right && (!len ? !cellInfo.colIndex : !range.beginColIndex)) || (right && (!len ? cellInfo.colIndex == this.colsNum - 1 : range.endColIndex == this.colsNum - 1)) ) return null previewRowIndex = !len ? cellInfo.rowIndex : range.beginRowIndex previewColIndex = !right ? !len ? cellInfo.colIndex < 1 ? 0 : cellInfo.colIndex - 1 : range.beginColIndex - 1 : !len ? cellInfo.colIndex + 1 : range.endColIndex + 1 return this.getCell( this.indexTable[previewRowIndex][previewColIndex].rowIndex, this.indexTable[previewRowIndex][previewColIndex].cellIndex ) } catch (e) { showError(e) } }, getTabNextCell: function (cell, preRowIndex) { var cellInfo = this.getCellInfo(cell), rowIndex = preRowIndex || cellInfo.rowIndex, colIndex = cellInfo.colIndex + 1 + (cellInfo.colSpan - 1), nextCell try { nextCell = this.getCell( this.indexTable[rowIndex][colIndex].rowIndex, this.indexTable[rowIndex][colIndex].cellIndex ) } catch (e) { try { rowIndex = rowIndex * 1 + 1 colIndex = 0 nextCell = this.getCell( this.indexTable[rowIndex][colIndex].rowIndex, this.indexTable[rowIndex][colIndex].cellIndex ) } catch (e) {} } return nextCell }, /** * 获取视觉上的后置单元格 * @param cell * @param bottom */ getVSideCell: function (cell, bottom, ignoreRange) { try { var cellInfo = this.getCellInfo(cell), nextRowIndex, nextColIndex var len = this.selectedTds.length && !ignoreRange, range = this.cellsRange //末行或者末列没有后置单元格 if ( (!bottom && cellInfo.rowIndex == 0) || (bottom && (!len ? cellInfo.rowIndex + cellInfo.rowSpan > this.rowsNum - 1 : range.endRowIndex == this.rowsNum - 1)) ) return null nextRowIndex = !bottom ? !len ? cellInfo.rowIndex - 1 : range.beginRowIndex - 1 : !len ? cellInfo.rowIndex + cellInfo.rowSpan : range.endRowIndex + 1 nextColIndex = !len ? cellInfo.colIndex : range.beginColIndex return this.getCell( this.indexTable[nextRowIndex][nextColIndex].rowIndex, this.indexTable[nextRowIndex][nextColIndex].cellIndex ) } catch (e) { showError(e) } }, /** * 获取相同结束位置的单元格,xOrY指代了是获取x轴相同还是y轴相同 */ getSameEndPosCells: function (cell, xOrY) { try { var flag = xOrY.toLowerCase() === 'x', end = domUtils.getXY(cell)[flag ? 'x' : 'y'] + cell['offset' + (flag ? 'Width' : 'Height')], rows = this.table.rows, cells = null, returns = [] for (var i = 0; i < this.rowsNum; i++) { cells = rows[i].cells for (var j = 0, tmpCell; (tmpCell = cells[j++]); ) { var tmpEnd = domUtils.getXY(tmpCell)[flag ? 'x' : 'y'] + tmpCell['offset' + (flag ? 'Width' : 'Height')] //对应行的td已经被上面行rowSpan了 if (tmpEnd > end && flag) break if (cell == tmpCell || end == tmpEnd) { //只获取单一的单元格 //todo 仅获取单一单元格在特定情况下会造成returns为空,从而影响后续的拖拽实现,修正这个。需考虑性能 if (tmpCell[flag ? 'colSpan' : 'rowSpan'] == 1) { returns.push(tmpCell) } if (flag) break } } } return returns } catch (e) { showError(e) } }, setCellContent: function (cell, content) { cell.innerHTML = content || (browser.ie ? domUtils.fillChar : '
    ') }, cloneCell: UETable.cloneCell, /** * 获取跟当前单元格的右边竖线为左边的所有未合并单元格 */ getSameStartPosXCells: function (cell) { try { var start = domUtils.getXY(cell).x + cell.offsetWidth, rows = this.table.rows, cells, returns = [] for (var i = 0; i < this.rowsNum; i++) { cells = rows[i].cells for (var j = 0, tmpCell; (tmpCell = cells[j++]); ) { var tmpStart = domUtils.getXY(tmpCell).x if (tmpStart > start) break if (tmpStart == start && tmpCell.colSpan == 1) { returns.push(tmpCell) break } } } return returns } catch (e) { showError(e) } }, /** * 更新table对应的索引表 */ update: function (table) { this.table = table || this.table this.selectedTds = [] this.cellsRange = {} this.indexTable = [] var rows = this.table.rows, rowsNum = this.getMaxRows(), dNum = rowsNum - rows.length, colsNum = this.getMaxCols() while (dNum--) { this.table.insertRow(rows.length) } this.rowsNum = rowsNum this.colsNum = colsNum for (var i = 0, len = rows.length; i < len; i++) { this.indexTable[i] = new Array(colsNum) } //填充索引表 for (var rowIndex = 0, row; (row = rows[rowIndex]); rowIndex++) { for ( var cellIndex = 0, cell, cells = row.cells; (cell = cells[cellIndex]); cellIndex++ ) { //修正整行被rowSpan时导致的行数计算错误 if (cell.rowSpan > rowsNum) { cell.rowSpan = rowsNum } var colIndex = cellIndex, rowSpan = cell.rowSpan || 1, colSpan = cell.colSpan || 1 //当已经被上一行rowSpan或者被前一列colSpan了,则跳到下一个单元格进行 while (this.indexTable[rowIndex][colIndex]) colIndex++ for (var j = 0; j < rowSpan; j++) { for (var k = 0; k < colSpan; k++) { this.indexTable[rowIndex + j][colIndex + k] = { rowIndex: rowIndex, cellIndex: cellIndex, colIndex: colIndex, rowSpan: rowSpan, colSpan: colSpan } } } } } //修复残缺td for (j = 0; j < rowsNum; j++) { for (k = 0; k < colsNum; k++) { if (this.indexTable[j][k] === undefined) { row = rows[j] cell = row.cells[row.cells.length - 1] cell = cell ? cell.cloneNode(true) : this.table.ownerDocument.createElement('td') this.setCellContent(cell) if (cell.colSpan !== 1) cell.colSpan = 1 if (cell.rowSpan !== 1) cell.rowSpan = 1 row.appendChild(cell) this.indexTable[j][k] = { rowIndex: j, cellIndex: cell.cellIndex, colIndex: k, rowSpan: 1, colSpan: 1 } } } } //当框选后删除行或者列后撤销,需要重建选区。 var tds = domUtils.getElementsByTagName(this.table, 'td'), selectTds = [] utils.each(tds, function (td) { if (domUtils.hasClass(td, 'selectTdClass')) { selectTds.push(td) } }) if (selectTds.length) { var start = selectTds[0], end = selectTds[selectTds.length - 1], startInfo = this.getCellInfo(start), endInfo = this.getCellInfo(end) this.selectedTds = selectTds this.cellsRange = { beginRowIndex: startInfo.rowIndex, beginColIndex: startInfo.colIndex, endRowIndex: endInfo.rowIndex + endInfo.rowSpan - 1, endColIndex: endInfo.colIndex + endInfo.colSpan - 1 } } //给第一行设置firstRow的样式名称,在排序图标的样式上使用到 if (!domUtils.hasClass(this.table.rows[0], 'firstRow')) { domUtils.addClass(this.table.rows[0], 'firstRow') for (var i = 1; i < this.table.rows.length; i++) { domUtils.removeClasses(this.table.rows[i], 'firstRow') } } }, /** * 获取单元格的索引信息 */ getCellInfo: function (cell) { if (!cell) return var cellIndex = cell.cellIndex, rowIndex = cell.parentNode.rowIndex, rowInfo = this.indexTable[rowIndex], numCols = this.colsNum for (var colIndex = cellIndex; colIndex < numCols; colIndex++) { var cellInfo = rowInfo[colIndex] if ( cellInfo.rowIndex === rowIndex && cellInfo.cellIndex === cellIndex ) { return cellInfo } } }, /** * 根据行列号获取单元格 */ getCell: function (rowIndex, cellIndex) { return ( (rowIndex < this.rowsNum && this.table.rows[rowIndex].cells[cellIndex]) || null ) }, /** * 删除单元格 */ deleteCell: function (cell, rowIndex) { rowIndex = typeof rowIndex == 'number' ? rowIndex : cell.parentNode.rowIndex var row = this.table.rows[rowIndex] row.deleteCell(cell.cellIndex) }, /** * 根据始末两个单元格获取被框选的所有单元格范围 */ getCellsRange: function (cellA, cellB) { function checkRange( beginRowIndex, beginColIndex, endRowIndex, endColIndex ) { var tmpBeginRowIndex = beginRowIndex, tmpBeginColIndex = beginColIndex, tmpEndRowIndex = endRowIndex, tmpEndColIndex = endColIndex, cellInfo, colIndex, rowIndex // 通过indexTable检查是否存在超出TableRange上边界的情况 if (beginRowIndex > 0) { for (colIndex = beginColIndex; colIndex < endColIndex; colIndex++) { cellInfo = me.indexTable[beginRowIndex][colIndex] rowIndex = cellInfo.rowIndex if (rowIndex < beginRowIndex) { tmpBeginRowIndex = Math.min(rowIndex, tmpBeginRowIndex) } } } // 通过indexTable检查是否存在超出TableRange右边界的情况 if (endColIndex < me.colsNum) { for (rowIndex = beginRowIndex; rowIndex < endRowIndex; rowIndex++) { cellInfo = me.indexTable[rowIndex][endColIndex] colIndex = cellInfo.colIndex + cellInfo.colSpan - 1 if (colIndex > endColIndex) { tmpEndColIndex = Math.max(colIndex, tmpEndColIndex) } } } // 检查是否有超出TableRange下边界的情况 if (endRowIndex < me.rowsNum) { for (colIndex = beginColIndex; colIndex < endColIndex; colIndex++) { cellInfo = me.indexTable[endRowIndex][colIndex] rowIndex = cellInfo.rowIndex + cellInfo.rowSpan - 1 if (rowIndex > endRowIndex) { tmpEndRowIndex = Math.max(rowIndex, tmpEndRowIndex) } } } // 检查是否有超出TableRange左边界的情况 if (beginColIndex > 0) { for (rowIndex = beginRowIndex; rowIndex < endRowIndex; rowIndex++) { cellInfo = me.indexTable[rowIndex][beginColIndex] colIndex = cellInfo.colIndex if (colIndex < beginColIndex) { tmpBeginColIndex = Math.min(cellInfo.colIndex, tmpBeginColIndex) } } } //递归调用直至所有完成所有框选单元格的扩展 if ( tmpBeginRowIndex != beginRowIndex || tmpBeginColIndex != beginColIndex || tmpEndRowIndex != endRowIndex || tmpEndColIndex != endColIndex ) { return checkRange( tmpBeginRowIndex, tmpBeginColIndex, tmpEndRowIndex, tmpEndColIndex ) } else { // 不需要扩展TableRange的情况 return { beginRowIndex: beginRowIndex, beginColIndex: beginColIndex, endRowIndex: endRowIndex, endColIndex: endColIndex } } } try { var me = this, cellAInfo = me.getCellInfo(cellA) if (cellA === cellB) { return { beginRowIndex: cellAInfo.rowIndex, beginColIndex: cellAInfo.colIndex, endRowIndex: cellAInfo.rowIndex + cellAInfo.rowSpan - 1, endColIndex: cellAInfo.colIndex + cellAInfo.colSpan - 1 } } var cellBInfo = me.getCellInfo(cellB) // 计算TableRange的四个边 var beginRowIndex = Math.min(cellAInfo.rowIndex, cellBInfo.rowIndex), beginColIndex = Math.min(cellAInfo.colIndex, cellBInfo.colIndex), endRowIndex = Math.max( cellAInfo.rowIndex + cellAInfo.rowSpan - 1, cellBInfo.rowIndex + cellBInfo.rowSpan - 1 ), endColIndex = Math.max( cellAInfo.colIndex + cellAInfo.colSpan - 1, cellBInfo.colIndex + cellBInfo.colSpan - 1 ) return checkRange( beginRowIndex, beginColIndex, endRowIndex, endColIndex ) } catch (e) { //throw e; } }, /** * 依据cellsRange获取对应的单元格集合 */ getCells: function (range) { //每次获取cells之前必须先清除上次的选择,否则会对后续获取操作造成影响 this.clearSelected() var beginRowIndex = range.beginRowIndex, beginColIndex = range.beginColIndex, endRowIndex = range.endRowIndex, endColIndex = range.endColIndex, cellInfo, rowIndex, colIndex, tdHash = {}, returnTds = [] for (var i = beginRowIndex; i <= endRowIndex; i++) { for (var j = beginColIndex; j <= endColIndex; j++) { cellInfo = this.indexTable[i][j] rowIndex = cellInfo.rowIndex colIndex = cellInfo.colIndex // 如果Cells里已经包含了此Cell则跳过 var key = rowIndex + '|' + colIndex if (tdHash[key]) continue tdHash[key] = 1 if ( rowIndex < i || colIndex < j || rowIndex + cellInfo.rowSpan - 1 > endRowIndex || colIndex + cellInfo.colSpan - 1 > endColIndex ) { return null } returnTds.push(this.getCell(rowIndex, cellInfo.cellIndex)) } } return returnTds }, /** * 清理已经选中的单元格 */ clearSelected: function () { UETable.removeSelectedClass(this.selectedTds) this.selectedTds = [] this.cellsRange = {} }, /** * 根据range设置已经选中的单元格 */ setSelected: function (range) { var cells = this.getCells(range) UETable.addSelectedClass(cells) this.selectedTds = cells this.cellsRange = range }, isFullRow: function () { var range = this.cellsRange return range.endColIndex - range.beginColIndex + 1 == this.colsNum }, isFullCol: function () { var range = this.cellsRange, table = this.table, ths = table.getElementsByTagName('th'), rows = range.endRowIndex - range.beginRowIndex + 1 return !ths.length ? rows == this.rowsNum : rows == this.rowsNum || rows == this.rowsNum - 1 }, /** * 获取视觉上的前置单元格,默认是左边,top传入时 * @param cell * @param top */ getNextCell: function (cell, bottom, ignoreRange) { try { var cellInfo = this.getCellInfo(cell), nextRowIndex, nextColIndex var len = this.selectedTds.length && !ignoreRange, range = this.cellsRange //末行或者末列没有后置单元格 if ( (!bottom && cellInfo.rowIndex == 0) || (bottom && (!len ? cellInfo.rowIndex + cellInfo.rowSpan > this.rowsNum - 1 : range.endRowIndex == this.rowsNum - 1)) ) return null nextRowIndex = !bottom ? !len ? cellInfo.rowIndex - 1 : range.beginRowIndex - 1 : !len ? cellInfo.rowIndex + cellInfo.rowSpan : range.endRowIndex + 1 nextColIndex = !len ? cellInfo.colIndex : range.beginColIndex return this.getCell( this.indexTable[nextRowIndex][nextColIndex].rowIndex, this.indexTable[nextRowIndex][nextColIndex].cellIndex ) } catch (e) { showError(e) } }, getPreviewCell: function (cell, top) { try { var cellInfo = this.getCellInfo(cell), previewRowIndex, previewColIndex var len = this.selectedTds.length, range = this.cellsRange //首行或者首列没有前置单元格 if ( (!top && (!len ? !cellInfo.colIndex : !range.beginColIndex)) || (top && (!len ? cellInfo.rowIndex > this.colsNum - 1 : range.endColIndex == this.colsNum - 1)) ) return null previewRowIndex = !top ? !len ? cellInfo.rowIndex : range.beginRowIndex : !len ? cellInfo.rowIndex < 1 ? 0 : cellInfo.rowIndex - 1 : range.beginRowIndex previewColIndex = !top ? !len ? cellInfo.colIndex < 1 ? 0 : cellInfo.colIndex - 1 : range.beginColIndex - 1 : !len ? cellInfo.colIndex : range.endColIndex + 1 return this.getCell( this.indexTable[previewRowIndex][previewColIndex].rowIndex, this.indexTable[previewRowIndex][previewColIndex].cellIndex ) } catch (e) { showError(e) } }, /** * 移动单元格中的内容 */ moveContent: function (cellTo, cellFrom) { if (UETable.isEmptyBlock(cellFrom)) return if (UETable.isEmptyBlock(cellTo)) { cellTo.innerHTML = cellFrom.innerHTML return } var child = cellTo.lastChild if (child.nodeType == 3 || !dtd.$block[child.tagName]) { cellTo.appendChild(cellTo.ownerDocument.createElement('br')) } while ((child = cellFrom.firstChild)) { cellTo.appendChild(child) } }, /** * 向右合并单元格 */ mergeRight: function (cell) { var cellInfo = this.getCellInfo(cell), rightColIndex = cellInfo.colIndex + cellInfo.colSpan, rightCellInfo = this.indexTable[cellInfo.rowIndex][rightColIndex], rightCell = this.getCell( rightCellInfo.rowIndex, rightCellInfo.cellIndex ) //合并 cell.colSpan = cellInfo.colSpan + rightCellInfo.colSpan //被合并的单元格不应存在宽度属性 cell.removeAttribute('width') //移动内容 this.moveContent(cell, rightCell) //删掉被合并的Cell this.deleteCell(rightCell, rightCellInfo.rowIndex) this.update() }, /** * 向下合并单元格 */ mergeDown: function (cell) { var cellInfo = this.getCellInfo(cell), downRowIndex = cellInfo.rowIndex + cellInfo.rowSpan, downCellInfo = this.indexTable[downRowIndex][cellInfo.colIndex], downCell = this.getCell(downCellInfo.rowIndex, downCellInfo.cellIndex) cell.rowSpan = cellInfo.rowSpan + downCellInfo.rowSpan cell.removeAttribute('height') this.moveContent(cell, downCell) this.deleteCell(downCell, downCellInfo.rowIndex) this.update() }, /** * 合并整个range中的内容 */ mergeRange: function () { //由于合并操作可以在任意时刻进行,所以无法通过鼠标位置等信息实时生成range,只能通过缓存实例中的cellsRange对象来访问 var range = this.cellsRange, leftTopCell = this.getCell( range.beginRowIndex, this.indexTable[range.beginRowIndex][range.beginColIndex].cellIndex ) if ( leftTopCell.tagName == 'TH' && range.endRowIndex !== range.beginRowIndex ) { var index = this.indexTable, info = this.getCellInfo(leftTopCell) leftTopCell = this.getCell(1, index[1][info.colIndex].cellIndex) range = this.getCellsRange( leftTopCell, this.getCell( index[this.rowsNum - 1][info.colIndex].rowIndex, index[this.rowsNum - 1][info.colIndex].cellIndex ) ) } // 删除剩余的Cells var cells = this.getCells(range) for (var i = 0, ci; (ci = cells[i++]); ) { if (ci !== leftTopCell) { this.moveContent(leftTopCell, ci) this.deleteCell(ci) } } // 修改左上角Cell的rowSpan和colSpan,并调整宽度属性设置 leftTopCell.rowSpan = range.endRowIndex - range.beginRowIndex + 1 leftTopCell.rowSpan > 1 && leftTopCell.removeAttribute('height') leftTopCell.colSpan = range.endColIndex - range.beginColIndex + 1 leftTopCell.colSpan > 1 && leftTopCell.removeAttribute('width') if (leftTopCell.rowSpan == this.rowsNum && leftTopCell.colSpan != 1) { leftTopCell.colSpan = 1 } if (leftTopCell.colSpan == this.colsNum && leftTopCell.rowSpan != 1) { var rowIndex = leftTopCell.parentNode.rowIndex //解决IE下的表格操作问题 if (this.table.deleteRow) { for ( var i = rowIndex + 1, curIndex = rowIndex + 1, len = leftTopCell.rowSpan; i < len; i++ ) { this.table.deleteRow(curIndex) } } else { for (var i = 0, len = leftTopCell.rowSpan - 1; i < len; i++) { var row = this.table.rows[rowIndex + 1] row.parentNode.removeChild(row) } } leftTopCell.rowSpan = 1 } this.update() }, /** * 插入一行单元格 */ insertRow: function (rowIndex, sourceCell) { var numCols = this.colsNum, table = this.table, row = table.insertRow(rowIndex), cell, isInsertTitle = typeof sourceCell == 'string' && sourceCell.toUpperCase() == 'TH' function replaceTdToTh(colIndex, cell, tableRow) { if (colIndex == 0) { var tr = tableRow.nextSibling || tableRow.previousSibling, th = tr.cells[colIndex] if (th.tagName == 'TH') { th = cell.ownerDocument.createElement('th') th.appendChild(cell.firstChild) tableRow.insertBefore(th, cell) domUtils.remove(cell) } } else { if (cell.tagName == 'TH') { var td = cell.ownerDocument.createElement('td') td.appendChild(cell.firstChild) tableRow.insertBefore(td, cell) domUtils.remove(cell) } } } //首行直接插入,无需考虑部分单元格被rowspan的情况 if (rowIndex == 0 || rowIndex == this.rowsNum) { for (var colIndex = 0; colIndex < numCols; colIndex++) { cell = this.cloneCell(sourceCell, true) this.setCellContent(cell) cell.getAttribute('vAlign') && cell.setAttribute('vAlign', cell.getAttribute('vAlign')) row.appendChild(cell) if (!isInsertTitle) replaceTdToTh(colIndex, cell, row) } } else { var infoRow = this.indexTable[rowIndex], cellIndex = 0 for (colIndex = 0; colIndex < numCols; colIndex++) { var cellInfo = infoRow[colIndex] //如果存在某个单元格的rowspan穿过待插入行的位置,则修改该单元格的rowspan即可,无需插入单元格 if (cellInfo.rowIndex < rowIndex) { cell = this.getCell(cellInfo.rowIndex, cellInfo.cellIndex) cell.rowSpan = cellInfo.rowSpan + 1 } else { cell = this.cloneCell(sourceCell, true) this.setCellContent(cell) row.appendChild(cell) } if (!isInsertTitle) replaceTdToTh(colIndex, cell, row) } } //框选时插入不触发contentchange,需要手动更新索引。 this.update() return row }, /** * 删除一行单元格 * @param rowIndex */ deleteRow: function (rowIndex) { var row = this.table.rows[rowIndex], infoRow = this.indexTable[rowIndex], colsNum = this.colsNum, count = 0 //处理计数 for (var colIndex = 0; colIndex < colsNum; ) { var cellInfo = infoRow[colIndex], cell = this.getCell(cellInfo.rowIndex, cellInfo.cellIndex) if (cell.rowSpan > 1) { if (cellInfo.rowIndex == rowIndex) { var clone = cell.cloneNode(true) clone.rowSpan = cell.rowSpan - 1 clone.innerHTML = '' cell.rowSpan = 1 var nextRowIndex = rowIndex + 1, nextRow = this.table.rows[nextRowIndex], insertCellIndex, preMerged = this.getPreviewMergedCellsNum(nextRowIndex, colIndex) - count if (preMerged < colIndex) { insertCellIndex = colIndex - preMerged - 1 //nextRow.insertCell(insertCellIndex); domUtils.insertAfter(nextRow.cells[insertCellIndex], clone) } else { if (nextRow.cells.length) nextRow.insertBefore(clone, nextRow.cells[0]) } count += 1 //cell.parentNode.removeChild(cell); } } colIndex += cell.colSpan || 1 } var deleteTds = [], cacheMap = {} for (colIndex = 0; colIndex < colsNum; colIndex++) { var tmpRowIndex = infoRow[colIndex].rowIndex, tmpCellIndex = infoRow[colIndex].cellIndex, key = tmpRowIndex + '_' + tmpCellIndex if (cacheMap[key]) continue cacheMap[key] = 1 cell = this.getCell(tmpRowIndex, tmpCellIndex) deleteTds.push(cell) } var mergeTds = [] utils.each(deleteTds, function (td) { if (td.rowSpan == 1) { td.parentNode.removeChild(td) } else { mergeTds.push(td) } }) utils.each(mergeTds, function (td) { td.rowSpan-- }) row.parentNode.removeChild(row) //浏览器方法本身存在bug,采用自定义方法删除 //this.table.deleteRow(rowIndex); this.update() }, insertCol: function (colIndex, sourceCell, defaultValue) { var rowsNum = this.rowsNum, rowIndex = 0, tableRow, cell, backWidth = parseInt( (this.table.offsetWidth - (this.colsNum + 1) * 20 - (this.colsNum + 1)) / (this.colsNum + 1), 10 ), isInsertTitleCol = typeof sourceCell == 'string' && sourceCell.toUpperCase() == 'TH' function replaceTdToTh(rowIndex, cell, tableRow) { if (rowIndex == 0) { var th = cell.nextSibling || cell.previousSibling if (th.tagName == 'TH') { th = cell.ownerDocument.createElement('th') th.appendChild(cell.firstChild) tableRow.insertBefore(th, cell) domUtils.remove(cell) } } else { if (cell.tagName == 'TH') { var td = cell.ownerDocument.createElement('td') td.appendChild(cell.firstChild) tableRow.insertBefore(td, cell) domUtils.remove(cell) } } } var preCell if (colIndex == 0 || colIndex == this.colsNum) { for (; rowIndex < rowsNum; rowIndex++) { tableRow = this.table.rows[rowIndex] preCell = tableRow.cells[colIndex == 0 ? colIndex : tableRow.cells.length] cell = this.cloneCell(sourceCell, true) //tableRow.insertCell(colIndex == 0 ? colIndex : tableRow.cells.length); this.setCellContent(cell) cell.setAttribute('vAlign', cell.getAttribute('vAlign')) preCell && cell.setAttribute('width', preCell.getAttribute('width')) if (!colIndex) { tableRow.insertBefore(cell, tableRow.cells[0]) } else { domUtils.insertAfter( tableRow.cells[tableRow.cells.length - 1], cell ) } if (!isInsertTitleCol) replaceTdToTh(rowIndex, cell, tableRow) } } else { for (; rowIndex < rowsNum; rowIndex++) { var cellInfo = this.indexTable[rowIndex][colIndex] if (cellInfo.colIndex < colIndex) { cell = this.getCell(cellInfo.rowIndex, cellInfo.cellIndex) cell.colSpan = cellInfo.colSpan + 1 } else { tableRow = this.table.rows[rowIndex] preCell = tableRow.cells[cellInfo.cellIndex] cell = this.cloneCell(sourceCell, true) //tableRow.insertCell(cellInfo.cellIndex); this.setCellContent(cell) cell.setAttribute('vAlign', cell.getAttribute('vAlign')) preCell && cell.setAttribute('width', preCell.getAttribute('width')) //防止IE下报错 preCell ? tableRow.insertBefore(cell, preCell) : tableRow.appendChild(cell) } if (!isInsertTitleCol) replaceTdToTh(rowIndex, cell, tableRow) } } //框选时插入不触发contentchange,需要手动更新索引 this.update() this.updateWidth( backWidth, defaultValue || { tdPadding: 10, tdBorder: 1 } ) }, updateWidth: function (width, defaultValue) { var table = this.table, tmpWidth = UETable.getWidth(table) - defaultValue.tdPadding * 2 - defaultValue.tdBorder + width if (tmpWidth < table.ownerDocument.body.offsetWidth) { table.setAttribute('width', tmpWidth) return } var tds = domUtils.getElementsByTagName(this.table, 'td th') utils.each(tds, function (td) { td.setAttribute('width', width) }) }, deleteCol: function (colIndex) { var indexTable = this.indexTable, tableRows = this.table.rows, backTableWidth = this.table.getAttribute('width'), backTdWidth = 0, rowsNum = this.rowsNum, cacheMap = {} for (var rowIndex = 0; rowIndex < rowsNum; ) { var infoRow = indexTable[rowIndex], cellInfo = infoRow[colIndex], key = cellInfo.rowIndex + '_' + cellInfo.colIndex // 跳过已经处理过的Cell if (cacheMap[key]) continue cacheMap[key] = 1 var cell = this.getCell(cellInfo.rowIndex, cellInfo.cellIndex) if (!backTdWidth) backTdWidth = cell && parseInt(cell.offsetWidth / cell.colSpan, 10).toFixed(0) // 如果Cell的colSpan大于1, 就修改colSpan, 否则就删掉这个Cell if (cell.colSpan > 1) { cell.colSpan-- } else { tableRows[rowIndex].deleteCell(cellInfo.cellIndex) } rowIndex += cellInfo.rowSpan || 1 } this.table.setAttribute('width', backTableWidth - backTdWidth) this.update() }, splitToCells: function (cell) { var me = this, cells = this.splitToRows(cell) utils.each(cells, function (cell) { me.splitToCols(cell) }) }, splitToRows: function (cell) { var cellInfo = this.getCellInfo(cell), rowIndex = cellInfo.rowIndex, colIndex = cellInfo.colIndex, results = [] // 修改Cell的rowSpan cell.rowSpan = 1 results.push(cell) // 补齐单元格 for ( var i = rowIndex, endRow = rowIndex + cellInfo.rowSpan; i < endRow; i++ ) { if (i == rowIndex) continue var tableRow = this.table.rows[i], tmpCell = tableRow.insertCell( colIndex - this.getPreviewMergedCellsNum(i, colIndex) ) tmpCell.colSpan = cellInfo.colSpan this.setCellContent(tmpCell) tmpCell.setAttribute('vAlign', cell.getAttribute('vAlign')) tmpCell.setAttribute('align', cell.getAttribute('align')) if (cell.style.cssText) { tmpCell.style.cssText = cell.style.cssText } results.push(tmpCell) } this.update() return results }, getPreviewMergedCellsNum: function (rowIndex, colIndex) { var indexRow = this.indexTable[rowIndex], num = 0 for (var i = 0; i < colIndex; ) { var colSpan = indexRow[i].colSpan, tmpRowIndex = indexRow[i].rowIndex num += colSpan - (tmpRowIndex == rowIndex ? 1 : 0) i += colSpan } return num }, splitToCols: function (cell) { var backWidth = (cell.offsetWidth / cell.colSpan - 22).toFixed(0), cellInfo = this.getCellInfo(cell), rowIndex = cellInfo.rowIndex, colIndex = cellInfo.colIndex, results = [] // 修改Cell的rowSpan cell.colSpan = 1 cell.setAttribute('width', backWidth) results.push(cell) // 补齐单元格 for ( var j = colIndex, endCol = colIndex + cellInfo.colSpan; j < endCol; j++ ) { if (j == colIndex) continue var tableRow = this.table.rows[rowIndex], tmpCell = tableRow.insertCell( this.indexTable[rowIndex][j].cellIndex + 1 ) tmpCell.rowSpan = cellInfo.rowSpan this.setCellContent(tmpCell) tmpCell.setAttribute('vAlign', cell.getAttribute('vAlign')) tmpCell.setAttribute('align', cell.getAttribute('align')) tmpCell.setAttribute('width', backWidth) if (cell.style.cssText) { tmpCell.style.cssText = cell.style.cssText } //处理th的情况 if (cell.tagName == 'TH') { var th = cell.ownerDocument.createElement('th') th.appendChild(tmpCell.firstChild) th.setAttribute('vAlign', cell.getAttribute('vAlign')) th.rowSpan = tmpCell.rowSpan tableRow.insertBefore(th, tmpCell) domUtils.remove(tmpCell) } results.push(tmpCell) } this.update() return results }, isLastCell: function (cell, rowsNum, colsNum) { rowsNum = rowsNum || this.rowsNum colsNum = colsNum || this.colsNum var cellInfo = this.getCellInfo(cell) return ( cellInfo.rowIndex + cellInfo.rowSpan == rowsNum && cellInfo.colIndex + cellInfo.colSpan == colsNum ) }, getLastCell: function (cells) { cells = cells || this.table.getElementsByTagName('td') var firstInfo = this.getCellInfo(cells[0]) var me = this, last = cells[0], tr = last.parentNode, cellsNum = 0, cols = 0, rows utils.each(cells, function (cell) { if (cell.parentNode == tr) cols += cell.colSpan || 1 cellsNum += cell.rowSpan * cell.colSpan || 1 }) rows = cellsNum / cols utils.each(cells, function (cell) { if (me.isLastCell(cell, rows, cols)) { last = cell return false } }) return last }, selectRow: function (rowIndex) { var indexRow = this.indexTable[rowIndex], start = this.getCell(indexRow[0].rowIndex, indexRow[0].cellIndex), end = this.getCell( indexRow[this.colsNum - 1].rowIndex, indexRow[this.colsNum - 1].cellIndex ), range = this.getCellsRange(start, end) this.setSelected(range) }, selectTable: function () { var tds = this.table.getElementsByTagName('td'), range = this.getCellsRange(tds[0], tds[tds.length - 1]) this.setSelected(range) }, setBackground: function (cells, value) { if (typeof value === 'string') { utils.each(cells, function (cell) { cell.style.backgroundColor = value }) } else if (typeof value === 'object') { value = utils.extend( { repeat: true, colorList: ['#ddd', '#fff'] }, value ) var rowIndex = this.getCellInfo(cells[0]).rowIndex, count = 0, colors = value.colorList, getColor = function (list, index, repeat) { return list[index] ? list[index] : repeat ? list[index % list.length] : '' } for (var i = 0, cell; (cell = cells[i++]); ) { var cellInfo = this.getCellInfo(cell) cell.style.backgroundColor = getColor( colors, rowIndex + count == cellInfo.rowIndex ? count : ++count, value.repeat ) } } }, removeBackground: function (cells) { utils.each(cells, function (cell) { cell.style.backgroundColor = '' }) } } function showError(e) {} })() // plugins/table.cmds.js /** * Created with JetBrains PhpStorm. * User: taoqili * Date: 13-2-20 * Time: 下午6:25 * To change this template use File | Settings | File Templates. */ ;(function () { var UT = UE.UETable, getTableItemsByRange = function (editor) { return UT.getTableItemsByRange(editor) }, getUETableBySelected = function (editor) { return UT.getUETableBySelected(editor) }, getDefaultValue = function (editor, table) { return UT.getDefaultValue(editor, table) }, getUETable = function (tdOrTable) { return UT.getUETable(tdOrTable) } UE.commands['inserttable'] = { queryCommandState: function () { return getTableItemsByRange(this).table ? -1 : 0 }, execCommand: function (cmd, opt) { function createTable(opt, tdWidth) { var html = [], rowsNum = opt.numRows, colsNum = opt.numCols for (var r = 0; r < rowsNum; r++) { html.push('') for (var c = 0; c < colsNum; c++) { html.push( '
  • ' + (browser.ie && browser.version < 11 ? domUtils.fillChar : '
    ') + '
    ' + html.join('') + '
    ' } if (!opt) { opt = utils.extend( {}, { numCols: this.options.defaultCols, numRows: this.options.defaultRows, tdvalign: this.options.tdvalign } ) } var me = this var range = this.selection.getRange(), start = range.startContainer, firstParentBlock = domUtils.findParent( start, function (node) { return domUtils.isBlockElm(node) }, true ) || me.body var defaultValue = getDefaultValue(me), tableWidth = firstParentBlock.offsetWidth, tdWidth = Math.floor( tableWidth / opt.numCols - defaultValue.tdPadding * 2 - defaultValue.tdBorder ) //todo其他属性 !opt.tdvalign && (opt.tdvalign = me.options.tdvalign) me.execCommand('inserthtml', createTable(opt, tdWidth)) } } UE.commands['insertparagraphbeforetable'] = { queryCommandState: function () { return getTableItemsByRange(this).cell ? 0 : -1 }, execCommand: function () { var table = getTableItemsByRange(this).table if (table) { var p = this.document.createElement('p') p.innerHTML = browser.ie ? ' ' : '
    ' table.parentNode.insertBefore(p, table) this.selection.getRange().setStart(p, 0).setCursor() } } } UE.commands['deletetable'] = { queryCommandState: function () { var rng = this.selection.getRange() return domUtils.findParentByTagName(rng.startContainer, 'table', true) ? 0 : -1 }, execCommand: function (cmd, table) { var rng = this.selection.getRange() table = table || domUtils.findParentByTagName(rng.startContainer, 'table', true) if (table) { var next = table.nextSibling if (!next) { next = domUtils.createElement(this.document, 'p', { innerHTML: browser.ie ? domUtils.fillChar : '
    ' }) table.parentNode.insertBefore(next, table) } domUtils.remove(table) rng = this.selection.getRange() if (next.nodeType == 3) { rng.setStartBefore(next) } else { rng.setStart(next, 0) } rng.setCursor(false, true) this.fireEvent('tablehasdeleted') } } } UE.commands['cellalign'] = { queryCommandState: function () { return getSelectedArr(this).length ? 0 : -1 }, execCommand: function (cmd, align) { var selectedTds = getSelectedArr(this) if (selectedTds.length) { for (var i = 0, ci; (ci = selectedTds[i++]); ) { ci.setAttribute('align', align) } } } } UE.commands['cellvalign'] = { queryCommandState: function () { return getSelectedArr(this).length ? 0 : -1 }, execCommand: function (cmd, valign) { var selectedTds = getSelectedArr(this) if (selectedTds.length) { for (var i = 0, ci; (ci = selectedTds[i++]); ) { ci.setAttribute('vAlign', valign) } } } } UE.commands['insertcaption'] = { queryCommandState: function () { var table = getTableItemsByRange(this).table if (table) { return table.getElementsByTagName('caption').length == 0 ? 1 : -1 } return -1 }, execCommand: function () { var table = getTableItemsByRange(this).table if (table) { var caption = this.document.createElement('caption') caption.innerHTML = browser.ie ? domUtils.fillChar : '
    ' table.insertBefore(caption, table.firstChild) var range = this.selection.getRange() range.setStart(caption, 0).setCursor() } } } UE.commands['deletecaption'] = { queryCommandState: function () { var rng = this.selection.getRange(), table = domUtils.findParentByTagName(rng.startContainer, 'table') if (table) { return table.getElementsByTagName('caption').length == 0 ? -1 : 1 } return -1 }, execCommand: function () { var rng = this.selection.getRange(), table = domUtils.findParentByTagName(rng.startContainer, 'table') if (table) { domUtils.remove(table.getElementsByTagName('caption')[0]) var range = this.selection.getRange() range.setStart(table.rows[0].cells[0], 0).setCursor() } } } UE.commands['inserttitle'] = { queryCommandState: function () { var table = getTableItemsByRange(this).table if (table) { var firstRow = table.rows[0] return firstRow.cells[ firstRow.cells.length - 1 ].tagName.toLowerCase() != 'th' ? 0 : -1 } return -1 }, execCommand: function () { var table = getTableItemsByRange(this).table if (table) { getUETable(table).insertRow(0, 'th') } var th = table.getElementsByTagName('th')[0] this.selection.getRange().setStart(th, 0).setCursor(false, true) } } UE.commands['deletetitle'] = { queryCommandState: function () { var table = getTableItemsByRange(this).table if (table) { var firstRow = table.rows[0] return firstRow.cells[ firstRow.cells.length - 1 ].tagName.toLowerCase() == 'th' ? 0 : -1 } return -1 }, execCommand: function () { var table = getTableItemsByRange(this).table if (table) { domUtils.remove(table.rows[0]) } var td = table.getElementsByTagName('td')[0] this.selection.getRange().setStart(td, 0).setCursor(false, true) } } UE.commands['inserttitlecol'] = { queryCommandState: function () { var table = getTableItemsByRange(this).table if (table) { var lastRow = table.rows[table.rows.length - 1] return lastRow.getElementsByTagName('th').length ? -1 : 0 } return -1 }, execCommand: function (cmd) { var table = getTableItemsByRange(this).table if (table) { getUETable(table).insertCol(0, 'th') } resetTdWidth(table, this) var th = table.getElementsByTagName('th')[0] this.selection.getRange().setStart(th, 0).setCursor(false, true) } } UE.commands['deletetitlecol'] = { queryCommandState: function () { var table = getTableItemsByRange(this).table if (table) { var lastRow = table.rows[table.rows.length - 1] return lastRow.getElementsByTagName('th').length ? 0 : -1 } return -1 }, execCommand: function () { var table = getTableItemsByRange(this).table if (table) { for (var i = 0; i < table.rows.length; i++) { domUtils.remove(table.rows[i].children[0]) } } resetTdWidth(table, this) var td = table.getElementsByTagName('td')[0] this.selection.getRange().setStart(td, 0).setCursor(false, true) } } UE.commands['mergeright'] = { queryCommandState: function (cmd) { var tableItems = getTableItemsByRange(this), table = tableItems.table, cell = tableItems.cell if (!table || !cell) return -1 var ut = getUETable(table) if (ut.selectedTds.length) return -1 var cellInfo = ut.getCellInfo(cell), rightColIndex = cellInfo.colIndex + cellInfo.colSpan if (rightColIndex >= ut.colsNum) return -1 // 如果处于最右边则不能向右合并 var rightCellInfo = ut.indexTable[cellInfo.rowIndex][rightColIndex], rightCell = table.rows[rightCellInfo.rowIndex].cells[rightCellInfo.cellIndex] if (!rightCell || cell.tagName != rightCell.tagName) return -1 // TH和TD不能相互合并 // 当且仅当两个Cell的开始列号和结束列号一致时能进行合并 return rightCellInfo.rowIndex == cellInfo.rowIndex && rightCellInfo.rowSpan == cellInfo.rowSpan ? 0 : -1 }, execCommand: function (cmd) { var rng = this.selection.getRange(), bk = rng.createBookmark(true) var cell = getTableItemsByRange(this).cell, ut = getUETable(cell) ut.mergeRight(cell) rng.moveToBookmark(bk).select() } } UE.commands['mergedown'] = { queryCommandState: function (cmd) { var tableItems = getTableItemsByRange(this), table = tableItems.table, cell = tableItems.cell if (!table || !cell) return -1 var ut = getUETable(table) if (ut.selectedTds.length) return -1 var cellInfo = ut.getCellInfo(cell), downRowIndex = cellInfo.rowIndex + cellInfo.rowSpan if (downRowIndex >= ut.rowsNum) return -1 // 如果处于最下边则不能向下合并 var downCellInfo = ut.indexTable[downRowIndex][cellInfo.colIndex], downCell = table.rows[downCellInfo.rowIndex].cells[downCellInfo.cellIndex] if (!downCell || cell.tagName != downCell.tagName) return -1 // TH和TD不能相互合并 // 当且仅当两个Cell的开始列号和结束列号一致时能进行合并 return downCellInfo.colIndex == cellInfo.colIndex && downCellInfo.colSpan == cellInfo.colSpan ? 0 : -1 }, execCommand: function () { var rng = this.selection.getRange(), bk = rng.createBookmark(true) var cell = getTableItemsByRange(this).cell, ut = getUETable(cell) ut.mergeDown(cell) rng.moveToBookmark(bk).select() } } UE.commands['mergecells'] = { queryCommandState: function () { return getUETableBySelected(this) ? 0 : -1 }, execCommand: function () { var ut = getUETableBySelected(this) if (ut && ut.selectedTds.length) { var cell = ut.selectedTds[0] ut.mergeRange() var rng = this.selection.getRange() if (domUtils.isEmptyBlock(cell)) { rng.setStart(cell, 0).collapse(true) } else { rng.selectNodeContents(cell) } rng.select() } } } UE.commands['insertrow'] = { queryCommandState: function () { var tableItems = getTableItemsByRange(this), cell = tableItems.cell return cell && (cell.tagName == 'TD' || (cell.tagName == 'TH' && tableItems.tr !== tableItems.table.rows[0])) && getUETable(tableItems.table).rowsNum < this.options.maxRowNum ? 0 : -1 }, execCommand: function () { var rng = this.selection.getRange(), bk = rng.createBookmark(true) var tableItems = getTableItemsByRange(this), cell = tableItems.cell, table = tableItems.table, ut = getUETable(table), cellInfo = ut.getCellInfo(cell) //ut.insertRow(!ut.selectedTds.length ? cellInfo.rowIndex:ut.cellsRange.beginRowIndex,''); if (!ut.selectedTds.length) { ut.insertRow(cellInfo.rowIndex, cell) } else { var range = ut.cellsRange for ( var i = 0, len = range.endRowIndex - range.beginRowIndex + 1; i < len; i++ ) { ut.insertRow(range.beginRowIndex, cell) } } rng.moveToBookmark(bk).select() if (table.getAttribute('interlaced') === 'enabled') this.fireEvent('interlacetable', table) } } //后插入行 UE.commands['insertrownext'] = { queryCommandState: function () { var tableItems = getTableItemsByRange(this), cell = tableItems.cell return cell && cell.tagName == 'TD' && getUETable(tableItems.table).rowsNum < this.options.maxRowNum ? 0 : -1 }, execCommand: function () { var rng = this.selection.getRange(), bk = rng.createBookmark(true) var tableItems = getTableItemsByRange(this), cell = tableItems.cell, table = tableItems.table, ut = getUETable(table), cellInfo = ut.getCellInfo(cell) //ut.insertRow(!ut.selectedTds.length? cellInfo.rowIndex + cellInfo.rowSpan : ut.cellsRange.endRowIndex + 1,''); if (!ut.selectedTds.length) { ut.insertRow(cellInfo.rowIndex + cellInfo.rowSpan, cell) } else { var range = ut.cellsRange for ( var i = 0, len = range.endRowIndex - range.beginRowIndex + 1; i < len; i++ ) { ut.insertRow(range.endRowIndex + 1, cell) } } rng.moveToBookmark(bk).select() if (table.getAttribute('interlaced') === 'enabled') this.fireEvent('interlacetable', table) } } UE.commands['deleterow'] = { queryCommandState: function () { var tableItems = getTableItemsByRange(this) return tableItems.cell ? 0 : -1 }, execCommand: function () { var cell = getTableItemsByRange(this).cell, ut = getUETable(cell), cellsRange = ut.cellsRange, cellInfo = ut.getCellInfo(cell), preCell = ut.getVSideCell(cell), nextCell = ut.getVSideCell(cell, true), rng = this.selection.getRange() if (utils.isEmptyObject(cellsRange)) { ut.deleteRow(cellInfo.rowIndex) } else { for ( var i = cellsRange.beginRowIndex; i < cellsRange.endRowIndex + 1; i++ ) { ut.deleteRow(cellsRange.beginRowIndex) } } var table = ut.table if (!table.getElementsByTagName('td').length) { var nextSibling = table.nextSibling domUtils.remove(table) if (nextSibling) { rng.setStart(nextSibling, 0).setCursor(false, true) } } else { if ( cellInfo.rowSpan == 1 || cellInfo.rowSpan == cellsRange.endRowIndex - cellsRange.beginRowIndex + 1 ) { if (nextCell || preCell) rng.selectNodeContents(nextCell || preCell).setCursor(false, true) } else { var newCell = ut.getCell( cellInfo.rowIndex, ut.indexTable[cellInfo.rowIndex][cellInfo.colIndex].cellIndex ) if (newCell) rng.selectNodeContents(newCell).setCursor(false, true) } } if (table.getAttribute('interlaced') === 'enabled') this.fireEvent('interlacetable', table) } } UE.commands['insertcol'] = { queryCommandState: function (cmd) { var tableItems = getTableItemsByRange(this), cell = tableItems.cell return cell && (cell.tagName == 'TD' || (cell.tagName == 'TH' && cell !== tableItems.tr.cells[0])) && getUETable(tableItems.table).colsNum < this.options.maxColNum ? 0 : -1 }, execCommand: function (cmd) { var rng = this.selection.getRange(), bk = rng.createBookmark(true) if (this.queryCommandState(cmd) == -1) return var cell = getTableItemsByRange(this).cell, ut = getUETable(cell), cellInfo = ut.getCellInfo(cell) //ut.insertCol(!ut.selectedTds.length ? cellInfo.colIndex:ut.cellsRange.beginColIndex); if (!ut.selectedTds.length) { ut.insertCol(cellInfo.colIndex, cell) } else { var range = ut.cellsRange for ( var i = 0, len = range.endColIndex - range.beginColIndex + 1; i < len; i++ ) { ut.insertCol(range.beginColIndex, cell) } } rng.moveToBookmark(bk).select(true) } } UE.commands['insertcolnext'] = { queryCommandState: function () { var tableItems = getTableItemsByRange(this), cell = tableItems.cell return cell && getUETable(tableItems.table).colsNum < this.options.maxColNum ? 0 : -1 }, execCommand: function () { var rng = this.selection.getRange(), bk = rng.createBookmark(true) var cell = getTableItemsByRange(this).cell, ut = getUETable(cell), cellInfo = ut.getCellInfo(cell) //ut.insertCol(!ut.selectedTds.length ? cellInfo.colIndex + cellInfo.colSpan:ut.cellsRange.endColIndex +1); if (!ut.selectedTds.length) { ut.insertCol(cellInfo.colIndex + cellInfo.colSpan, cell) } else { var range = ut.cellsRange for ( var i = 0, len = range.endColIndex - range.beginColIndex + 1; i < len; i++ ) { ut.insertCol(range.endColIndex + 1, cell) } } rng.moveToBookmark(bk).select() } } UE.commands['deletecol'] = { queryCommandState: function () { var tableItems = getTableItemsByRange(this) return tableItems.cell ? 0 : -1 }, execCommand: function () { var cell = getTableItemsByRange(this).cell, ut = getUETable(cell), range = ut.cellsRange, cellInfo = ut.getCellInfo(cell), preCell = ut.getHSideCell(cell), nextCell = ut.getHSideCell(cell, true) if (utils.isEmptyObject(range)) { ut.deleteCol(cellInfo.colIndex) } else { for (var i = range.beginColIndex; i < range.endColIndex + 1; i++) { ut.deleteCol(range.beginColIndex) } } var table = ut.table, rng = this.selection.getRange() if (!table.getElementsByTagName('td').length) { var nextSibling = table.nextSibling domUtils.remove(table) if (nextSibling) { rng.setStart(nextSibling, 0).setCursor(false, true) } } else { if (domUtils.inDoc(cell, this.document)) { rng.setStart(cell, 0).setCursor(false, true) } else { if (nextCell && domUtils.inDoc(nextCell, this.document)) { rng.selectNodeContents(nextCell).setCursor(false, true) } else { if (preCell && domUtils.inDoc(preCell, this.document)) { rng.selectNodeContents(preCell).setCursor(true, true) } } } } } } UE.commands['splittocells'] = { queryCommandState: function () { var tableItems = getTableItemsByRange(this), cell = tableItems.cell if (!cell) return -1 var ut = getUETable(tableItems.table) if (ut.selectedTds.length > 0) return -1 return cell && (cell.colSpan > 1 || cell.rowSpan > 1) ? 0 : -1 }, execCommand: function () { var rng = this.selection.getRange(), bk = rng.createBookmark(true) var cell = getTableItemsByRange(this).cell, ut = getUETable(cell) ut.splitToCells(cell) rng.moveToBookmark(bk).select() } } UE.commands['splittorows'] = { queryCommandState: function () { var tableItems = getTableItemsByRange(this), cell = tableItems.cell if (!cell) return -1 var ut = getUETable(tableItems.table) if (ut.selectedTds.length > 0) return -1 return cell && cell.rowSpan > 1 ? 0 : -1 }, execCommand: function () { var rng = this.selection.getRange(), bk = rng.createBookmark(true) var cell = getTableItemsByRange(this).cell, ut = getUETable(cell) ut.splitToRows(cell) rng.moveToBookmark(bk).select() } } UE.commands['splittocols'] = { queryCommandState: function () { var tableItems = getTableItemsByRange(this), cell = tableItems.cell if (!cell) return -1 var ut = getUETable(tableItems.table) if (ut.selectedTds.length > 0) return -1 return cell && cell.colSpan > 1 ? 0 : -1 }, execCommand: function () { var rng = this.selection.getRange(), bk = rng.createBookmark(true) var cell = getTableItemsByRange(this).cell, ut = getUETable(cell) ut.splitToCols(cell) rng.moveToBookmark(bk).select() } } UE.commands['adaptbytext'] = UE.commands['adaptbywindow'] = { queryCommandState: function () { return getTableItemsByRange(this).table ? 0 : -1 }, execCommand: function (cmd) { var tableItems = getTableItemsByRange(this), table = tableItems.table if (table) { if (cmd == 'adaptbywindow') { resetTdWidth(table, this) } else { var cells = domUtils.getElementsByTagName(table, 'td th') utils.each(cells, function (cell) { cell.removeAttribute('width') }) table.removeAttribute('width') } } } } //平均分配各列 UE.commands['averagedistributecol'] = { queryCommandState: function () { var ut = getUETableBySelected(this) if (!ut) return -1 return ut.isFullRow() || ut.isFullCol() ? 0 : -1 }, execCommand: function (cmd) { var me = this, ut = getUETableBySelected(me) function getAverageWidth() { var tb = ut.table, averageWidth, sumWidth = 0, colsNum = 0, tbAttr = getDefaultValue(me, tb) if (ut.isFullRow()) { sumWidth = tb.offsetWidth colsNum = ut.colsNum } else { var begin = ut.cellsRange.beginColIndex, end = ut.cellsRange.endColIndex, node for (var i = begin; i <= end; ) { node = ut.selectedTds[i] sumWidth += node.offsetWidth i += node.colSpan colsNum += 1 } } averageWidth = Math.ceil(sumWidth / colsNum) - tbAttr.tdBorder * 2 - tbAttr.tdPadding * 2 return averageWidth } function setAverageWidth(averageWidth) { utils.each( domUtils.getElementsByTagName(ut.table, 'th'), function (node) { node.setAttribute('width', '') } ) var cells = ut.isFullRow() ? domUtils.getElementsByTagName(ut.table, 'td') : ut.selectedTds utils.each(cells, function (node) { if (node.colSpan == 1) { node.setAttribute('width', averageWidth) } }) } if (ut && ut.selectedTds.length) { setAverageWidth(getAverageWidth()) } } } //平均分配各行 UE.commands['averagedistributerow'] = { queryCommandState: function () { var ut = getUETableBySelected(this) if (!ut) return -1 if (ut.selectedTds && /th/gi.test(ut.selectedTds[0].tagName)) return -1 return ut.isFullRow() || ut.isFullCol() ? 0 : -1 }, execCommand: function (cmd) { var me = this, ut = getUETableBySelected(me) function getAverageHeight() { var averageHeight, rowNum, sumHeight = 0, tb = ut.table, tbAttr = getDefaultValue(me, tb), tdpadding = parseInt( domUtils.getComputedStyle( tb.getElementsByTagName('td')[0], 'padding-top' ) ) if (ut.isFullCol()) { var captionArr = domUtils.getElementsByTagName(tb, 'caption'), thArr = domUtils.getElementsByTagName(tb, 'th'), captionHeight, thHeight if (captionArr.length > 0) { captionHeight = captionArr[0].offsetHeight } if (thArr.length > 0) { thHeight = thArr[0].offsetHeight } sumHeight = tb.offsetHeight - (captionHeight || 0) - (thHeight || 0) rowNum = thArr.length == 0 ? ut.rowsNum : ut.rowsNum - 1 } else { var begin = ut.cellsRange.beginRowIndex, end = ut.cellsRange.endRowIndex, count = 0, trs = domUtils.getElementsByTagName(tb, 'tr') for (var i = begin; i <= end; i++) { sumHeight += trs[i].offsetHeight count += 1 } rowNum = count } //ie8下是混杂模式 if (browser.ie && browser.version < 9) { averageHeight = Math.ceil(sumHeight / rowNum) } else { averageHeight = Math.ceil(sumHeight / rowNum) - tbAttr.tdBorder * 2 - tdpadding * 2 } return averageHeight } function setAverageHeight(averageHeight) { var cells = ut.isFullCol() ? domUtils.getElementsByTagName(ut.table, 'td') : ut.selectedTds utils.each(cells, function (node) { if (node.rowSpan == 1) { node.setAttribute('height', averageHeight) } }) } if (ut && ut.selectedTds.length) { setAverageHeight(getAverageHeight()) } } } //单元格对齐方式 UE.commands['cellalignment'] = { queryCommandState: function () { return getTableItemsByRange(this).table ? 0 : -1 }, execCommand: function (cmd, data) { var me = this, ut = getUETableBySelected(me) if (!ut) { var start = me.selection.getStart(), cell = start && domUtils.findParentByTagName(start, ['td', 'th', 'caption'], true) if (!/caption/gi.test(cell.tagName)) { domUtils.setAttributes(cell, data) } else { cell.style.textAlign = data.align cell.style.verticalAlign = data.vAlign } me.selection.getRange().setCursor(true) } else { utils.each(ut.selectedTds, function (cell) { domUtils.setAttributes(cell, data) }) } }, /** * 查询当前点击的单元格的对齐状态, 如果当前已经选择了多个单元格, 则会返回所有单元格经过统一协调过后的状态 * @see UE.UETable.getTableCellAlignState */ queryCommandValue: function (cmd) { var activeMenuCell = getTableItemsByRange(this).cell if (!activeMenuCell) { activeMenuCell = getSelectedArr(this)[0] } if (!activeMenuCell) { return null } else { //获取同时选中的其他单元格 var cells = UE.UETable.getUETable(activeMenuCell).selectedTds !cells.length && (cells = activeMenuCell) return UE.UETable.getTableCellAlignState(cells) } } } //表格对齐方式 UE.commands['tablealignment'] = { queryCommandState: function () { if (browser.ie && browser.version < 8) { return -1 } return getTableItemsByRange(this).table ? 0 : -1 }, execCommand: function (cmd, value) { var me = this, start = me.selection.getStart(), table = start && domUtils.findParentByTagName(start, ['table'], true) if (table) { table.setAttribute('align', value) } } } //表格属性 UE.commands['edittable'] = { queryCommandState: function () { return getTableItemsByRange(this).table ? 0 : -1 }, execCommand: function (cmd, color) { var rng = this.selection.getRange(), table = domUtils.findParentByTagName(rng.startContainer, 'table') if (table) { var arr = domUtils .getElementsByTagName(table, 'td') .concat( domUtils.getElementsByTagName(table, 'th'), domUtils.getElementsByTagName(table, 'caption') ) utils.each(arr, function (node) { node.style.borderColor = color }) } } } //单元格属性 UE.commands['edittd'] = { queryCommandState: function () { return getTableItemsByRange(this).table ? 0 : -1 }, execCommand: function (cmd, bkColor) { var me = this, ut = getUETableBySelected(me) if (!ut) { var start = me.selection.getStart(), cell = start && domUtils.findParentByTagName(start, ['td', 'th', 'caption'], true) if (cell) { cell.style.backgroundColor = bkColor } } else { utils.each(ut.selectedTds, function (cell) { cell.style.backgroundColor = bkColor }) } } } UE.commands['settablebackground'] = { queryCommandState: function () { return getSelectedArr(this).length > 1 ? 0 : -1 }, execCommand: function (cmd, value) { var cells, ut cells = getSelectedArr(this) ut = getUETable(cells[0]) ut.setBackground(cells, value) } } UE.commands['cleartablebackground'] = { queryCommandState: function () { var cells = getSelectedArr(this) if (!cells.length) return -1 for (var i = 0, cell; (cell = cells[i++]); ) { if (cell.style.backgroundColor !== '') return 0 } return -1 }, execCommand: function () { var cells = getSelectedArr(this), ut = getUETable(cells[0]) ut.removeBackground(cells) } } UE.commands['interlacetable'] = UE.commands['uninterlacetable'] = { queryCommandState: function (cmd) { var table = getTableItemsByRange(this).table if (!table) return -1 var interlaced = table.getAttribute('interlaced') if (cmd == 'interlacetable') { //TODO 待定 //是否需要待定,如果设置,则命令只能单次执行成功,但反射具备toggle效果;否则可以覆盖前次命令,但反射将不存在toggle效果 return interlaced === 'enabled' ? -1 : 0 } else { return !interlaced || interlaced === 'disabled' ? -1 : 0 } }, execCommand: function (cmd, classList) { var table = getTableItemsByRange(this).table if (cmd == 'interlacetable') { table.setAttribute('interlaced', 'enabled') this.fireEvent('interlacetable', table, classList) } else { table.setAttribute('interlaced', 'disabled') this.fireEvent('uninterlacetable', table) } } } UE.commands['setbordervisible'] = { queryCommandState: function (cmd) { var table = getTableItemsByRange(this).table if (!table) return -1 return 0 }, execCommand: function () { var table = getTableItemsByRange(this).table utils.each(domUtils.getElementsByTagName(table, 'td'), function (td) { td.style.borderWidth = '1px' td.style.borderStyle = 'solid' }) } } function resetTdWidth(table, editor) { var tds = domUtils.getElementsByTagName(table, 'td th') utils.each(tds, function (td) { td.removeAttribute('width') }) table.setAttribute( 'width', getTableWidth(editor, true, getDefaultValue(editor, table)) ) var tdsWidths = [] setTimeout(function () { utils.each(tds, function (td) { td.colSpan == 1 && tdsWidths.push(td.offsetWidth) }) utils.each(tds, function (td, i) { td.colSpan == 1 && td.setAttribute('width', tdsWidths[i] + '') }) }, 0) } function getTableWidth(editor, needIEHack, defaultValue) { var body = editor.body return ( body.offsetWidth - (needIEHack ? parseInt(domUtils.getComputedStyle(body, 'margin-left'), 10) * 2 : 0) - defaultValue.tableBorder * 2 - (editor.options.offsetWidth || 0) ) } function getSelectedArr(editor) { var cell = getTableItemsByRange(editor).cell if (cell) { var ut = getUETable(cell) return ut.selectedTds.length ? ut.selectedTds : [cell] } else { return [] } } })() // plugins/table.action.js /** * Created with JetBrains PhpStorm. * User: taoqili * Date: 12-10-12 * Time: 上午10:05 * To change this template use File | Settings | File Templates. */ UE.plugins['table'] = function () { var me = this, tabTimer = null, //拖动计时器 tableDragTimer = null, //双击计时器 tableResizeTimer = null, //单元格最小宽度 cellMinWidth = 5, isInResizeBuffer = false, //单元格边框大小 cellBorderWidth = 5, //鼠标偏移距离 offsetOfTableCell = 10, //记录在有限时间内的点击状态, 共有3个取值, 0, 1, 2。 0代表未初始化, 1代表单击了1次,2代表2次 singleClickState = 0, userActionStatus = null, //双击允许的时间范围 dblclickTime = 360, UT = UE.UETable, getUETable = function (tdOrTable) { return UT.getUETable(tdOrTable) }, getUETableBySelected = function (editor) { return UT.getUETableBySelected(editor) }, getDefaultValue = function (editor, table) { return UT.getDefaultValue(editor, table) }, removeSelectedClass = function (cells) { return UT.removeSelectedClass(cells) } function showError(e) { // throw e; } me.ready(function () { var me = this var orgGetText = me.selection.getText me.selection.getText = function () { var table = getUETableBySelected(me) if (table) { var str = '' utils.each(table.selectedTds, function (td) { str += td[browser.ie ? 'innerText' : 'textContent'] }) return str } else { return orgGetText.call(me.selection) } } }) //处理拖动及框选相关方法 var startTd = null, //鼠标按下时的锚点td currentTd = null, //当前鼠标经过时的td onDrag = '', //指示当前拖动状态,其值可为"","h","v" ,分别表示未拖动状态,横向拖动状态,纵向拖动状态,用于鼠标移动过程中的判断 onBorder = false, //检测鼠标按下时是否处在单元格边缘位置 dragButton = null, dragOver = false, dragLine = null, //模拟的拖动线 dragTd = null //发生拖动的目标td var mousedown = false, //todo 判断混乱模式 needIEHack = true me.setOpt({ maxColNum: 20, maxRowNum: 100, defaultCols: 5, defaultRows: 5, tdvalign: 'top', cursorpath: me.options.UEDITOR_HOME_URL + 'themes/default/images/cursor_', tableDragable: false, classList: [ 'ue-table-interlace-color-single', 'ue-table-interlace-color-double' ] }) me.getUETable = getUETable var commands = { deletetable: 1, inserttable: 1, cellvalign: 1, insertcaption: 1, deletecaption: 1, inserttitle: 1, deletetitle: 1, mergeright: 1, mergedown: 1, mergecells: 1, insertrow: 1, insertrownext: 1, deleterow: 1, insertcol: 1, insertcolnext: 1, deletecol: 1, splittocells: 1, splittorows: 1, splittocols: 1, adaptbytext: 1, adaptbywindow: 1, adaptbycustomer: 1, insertparagraph: 1, insertparagraphbeforetable: 1, averagedistributecol: 1, averagedistributerow: 1 } me.ready(function () { utils.cssRule( 'table', //选中的td上的样式 '.selectTdClass{background-color:#edf5fa !important}' + 'table.noBorderTable td,table.noBorderTable th,table.noBorderTable caption{border:1px dashed #ddd !important}' + //插入的表格的默认样式 'table{margin-bottom:10px;border-collapse:collapse;display:table;}' + 'td,th{padding: 5px 10px;border: 1px solid #DDD;}' + 'caption{border:1px dashed #DDD;border-bottom:0;padding:3px;text-align:center;}' + 'th{border-top:1px solid #BBB;background-color:#F7F7F7;}' + 'table tr.firstRow th{border-top-width:2px;}' + '.ue-table-interlace-color-single{ background-color: #fcfcfc; } .ue-table-interlace-color-double{ background-color: #f7faff; }' + 'td p{margin:0;padding:0;}', me.document ) var tableCopyList, isFullCol, isFullRow //注册del/backspace事件 me.addListener('keydown', function (cmd, evt) { var me = this var keyCode = evt.keyCode || evt.which if (keyCode == 8) { var ut = getUETableBySelected(me) if (ut && ut.selectedTds.length) { if (ut.isFullCol()) { me.execCommand('deletecol') } else if (ut.isFullRow()) { me.execCommand('deleterow') } else { me.fireEvent('delcells') } domUtils.preventDefault(evt) } var caption = domUtils.findParentByTagName( me.selection.getStart(), 'caption', true ), range = me.selection.getRange() if (range.collapsed && caption && isEmptyBlock(caption)) { me.fireEvent('saveScene') var table = caption.parentNode domUtils.remove(caption) if (table) { range.setStart(table.rows[0].cells[0], 0).setCursor(false, true) } me.fireEvent('saveScene') } } if (keyCode == 46) { ut = getUETableBySelected(me) if (ut) { me.fireEvent('saveScene') for (var i = 0, ci; (ci = ut.selectedTds[i++]); ) { domUtils.fillNode(me.document, ci) } me.fireEvent('saveScene') domUtils.preventDefault(evt) } } if (keyCode == 13) { var rng = me.selection.getRange(), caption = domUtils.findParentByTagName( rng.startContainer, 'caption', true ) if (caption) { var table = domUtils.findParentByTagName(caption, 'table') if (!rng.collapsed) { rng.deleteContents() me.fireEvent('saveScene') } else { if (caption) { rng.setStart(table.rows[0].cells[0], 0).setCursor(false, true) } } domUtils.preventDefault(evt) return } if (rng.collapsed) { var table = domUtils.findParentByTagName( rng.startContainer, 'table' ) if (table) { var cell = table.rows[0].cells[0], start = domUtils.findParentByTagName( me.selection.getStart(), ['td', 'th'], true ), preNode = table.previousSibling if ( cell === start && (!preNode || (preNode.nodeType == 1 && preNode.tagName == 'TABLE')) && domUtils.isStartInblock(rng) ) { var first = domUtils.findParent( me.selection.getStart(), function (n) { return domUtils.isBlockElm(n) }, true ) if ( first && (/t(h|d)/i.test(first.tagName) || first === start.firstChild) ) { me.execCommand('insertparagraphbeforetable') domUtils.preventDefault(evt) } } } } } if ((evt.ctrlKey || evt.metaKey) && evt.keyCode == '67') { tableCopyList = null var ut = getUETableBySelected(me) if (ut) { var tds = ut.selectedTds isFullCol = ut.isFullCol() isFullRow = ut.isFullRow() tableCopyList = [[ut.cloneCell(tds[0], null, true)]] for (var i = 1, ci; (ci = tds[i]); i++) { if (ci.parentNode !== tds[i - 1].parentNode) { tableCopyList.push([ut.cloneCell(ci, null, true)]) } else { tableCopyList[tableCopyList.length - 1].push( ut.cloneCell(ci, null, true) ) } } } } }) me.addListener('tablehasdeleted', function () { toggleDraggableState(this, false, '', null) if (dragButton) domUtils.remove(dragButton) }) me.addListener('beforepaste', function (cmd, html) { var me = this var rng = me.selection.getRange() if (domUtils.findParentByTagName(rng.startContainer, 'caption', true)) { var div = me.document.createElement('div') div.innerHTML = html.html //trace:3729 html.html = div[browser.ie9below ? 'innerText' : 'textContent'] return } var table = getUETableBySelected(me) if (tableCopyList) { me.fireEvent('saveScene') var rng = me.selection.getRange() var td = domUtils.findParentByTagName( rng.startContainer, ['td', 'th'], true ), tmpNode, preNode if (td) { var ut = getUETable(td) if (isFullRow) { var rowIndex = ut.getCellInfo(td).rowIndex if (td.tagName == 'TH') { rowIndex++ } for (var i = 0, ci; (ci = tableCopyList[i++]); ) { var tr = ut.insertRow(rowIndex++, 'td') for (var j = 0, cj; (cj = ci[j]); j++) { var cell = tr.cells[j] if (!cell) { cell = tr.insertCell(j) } cell.innerHTML = cj.innerHTML cj.getAttribute('width') && cell.setAttribute('width', cj.getAttribute('width')) cj.getAttribute('vAlign') && cell.setAttribute('vAlign', cj.getAttribute('vAlign')) cj.getAttribute('align') && cell.setAttribute('align', cj.getAttribute('align')) cj.style.cssText && (cell.style.cssText = cj.style.cssText) } for (var j = 0, cj; (cj = tr.cells[j]); j++) { if (!ci[j]) break cj.innerHTML = ci[j].innerHTML ci[j].getAttribute('width') && cj.setAttribute('width', ci[j].getAttribute('width')) ci[j].getAttribute('vAlign') && cj.setAttribute('vAlign', ci[j].getAttribute('vAlign')) ci[j].getAttribute('align') && cj.setAttribute('align', ci[j].getAttribute('align')) ci[j].style.cssText && (cj.style.cssText = ci[j].style.cssText) } } } else { if (isFullCol) { cellInfo = ut.getCellInfo(td) var maxColNum = 0 for (var j = 0, ci = tableCopyList[0], cj; (cj = ci[j++]); ) { maxColNum += cj.colSpan || 1 } me.__hasEnterExecCommand = true for (i = 0; i < maxColNum; i++) { me.execCommand('insertcol') } me.__hasEnterExecCommand = false td = ut.table.rows[0].cells[cellInfo.cellIndex] if (td.tagName == 'TH') { td = ut.table.rows[1].cells[cellInfo.cellIndex] } } for (var i = 0, ci; (ci = tableCopyList[i++]); ) { tmpNode = td for (var j = 0, cj; (cj = ci[j++]); ) { if (td) { td.innerHTML = cj.innerHTML //todo 定制处理 cj.getAttribute('width') && td.setAttribute('width', cj.getAttribute('width')) cj.getAttribute('vAlign') && td.setAttribute('vAlign', cj.getAttribute('vAlign')) cj.getAttribute('align') && td.setAttribute('align', cj.getAttribute('align')) cj.style.cssText && (td.style.cssText = cj.style.cssText) preNode = td td = td.nextSibling } else { var cloneTd = cj.cloneNode(true) domUtils.removeAttributes(cloneTd, [ 'class', 'rowSpan', 'colSpan' ]) preNode.parentNode.appendChild(cloneTd) } } td = ut.getNextCell(tmpNode, true, true) if (!tableCopyList[i]) break if (!td) { var cellInfo = ut.getCellInfo(tmpNode) ut.table.insertRow(ut.table.rows.length) ut.update() td = ut.getVSideCell(tmpNode, true) } } } ut.update() } else { table = me.document.createElement('table') for (var i = 0, ci; (ci = tableCopyList[i++]); ) { var tr = table.insertRow(table.rows.length) for (var j = 0, cj; (cj = ci[j++]); ) { cloneTd = UT.cloneCell(cj, null, true) domUtils.removeAttributes(cloneTd, ['class']) tr.appendChild(cloneTd) } if (j == 2 && cloneTd.rowSpan > 1) { cloneTd.rowSpan = 1 } } var defaultValue = getDefaultValue(me), width = me.body.offsetWidth - (needIEHack ? parseInt( domUtils.getComputedStyle(me.body, 'margin-left'), 10 ) * 2 : 0) - defaultValue.tableBorder * 2 - (me.options.offsetWidth || 0) me.execCommand( 'insertHTML', '' + table.innerHTML .replace(/>\s*<') .replace(/\bth\b/gi, 'td') + '
    ' ) } me.fireEvent('contentchange') me.fireEvent('saveScene') html.html = '' return true } else { var div = me.document.createElement('div'), tables div.innerHTML = html.html tables = div.getElementsByTagName('table') if (domUtils.findParentByTagName(me.selection.getStart(), 'table')) { utils.each(tables, function (t) { domUtils.remove(t) }) if ( domUtils.findParentByTagName( me.selection.getStart(), 'caption', true ) ) { div.innerHTML = div[browser.ie ? 'innerText' : 'textContent'] } } else { utils.each(tables, function (table) { removeStyleSize(table, true) domUtils.removeAttributes(table, ['style', 'border']) utils.each( domUtils.getElementsByTagName(table, 'td'), function (td) { if (isEmptyBlock(td)) { domUtils.fillNode(me.document, td) } removeStyleSize(td, true) // domUtils.removeAttributes(td, ['style']) } ) }) } html.html = div.innerHTML } }) me.addListener('afterpaste', function () { utils.each( domUtils.getElementsByTagName(me.body, 'table'), function (table) { if (table.offsetWidth > me.body.offsetWidth) { var defaultValue = getDefaultValue(me, table) table.style.width = me.body.offsetWidth - (needIEHack ? parseInt( domUtils.getComputedStyle(me.body, 'margin-left'), 10 ) * 2 : 0) - defaultValue.tableBorder * 2 - (me.options.offsetWidth || 0) + 'px' } } ) }) me.addListener('blur', function () { tableCopyList = null }) var timer me.addListener('keydown', function () { clearTimeout(timer) timer = setTimeout(function () { var rng = me.selection.getRange(), cell = domUtils.findParentByTagName( rng.startContainer, ['th', 'td'], true ) if (cell) { var table = cell.parentNode.parentNode.parentNode if (table.offsetWidth > table.getAttribute('width')) { cell.style.wordBreak = 'break-all' } } }, 100) }) me.addListener('selectionchange', function () { toggleDraggableState(me, false, '', null) }) //内容变化时触发索引更新 //todo 可否考虑标记检测,如果不涉及表格的变化就不进行索引重建和更新 me.addListener('contentchange', function () { var me = this //尽可能排除一些不需要更新的状况 hideDragLine(me) if (getUETableBySelected(me)) return var rng = me.selection.getRange() var start = rng.startContainer start = domUtils.findParentByTagName(start, ['td', 'th'], true) utils.each( domUtils.getElementsByTagName(me.document, 'table'), function (table) { if (me.fireEvent('excludetable', table) === true) return table.ueTable = new UT(table) //trace:3742 // utils.each(domUtils.getElementsByTagName(me.document, 'td'), function (td) { // // if (domUtils.isEmptyBlock(td) && td !== start) { // domUtils.fillNode(me.document, td); // if (browser.ie && browser.version == 6) { // td.innerHTML = ' ' // } // } // }); // utils.each(domUtils.getElementsByTagName(me.document, 'th'), function (th) { // if (domUtils.isEmptyBlock(th) && th !== start) { // domUtils.fillNode(me.document, th); // if (browser.ie && browser.version == 6) { // th.innerHTML = ' ' // } // } // }); table.onmouseover = function () { me.fireEvent('tablemouseover', table) } table.onmousemove = function () { me.fireEvent('tablemousemove', table) me.options.tableDragable && toggleDragButton(true, this, me) utils.defer(function () { me.fireEvent('contentchange', 50) }, true) } table.onmouseout = function () { me.fireEvent('tablemouseout', table) toggleDraggableState(me, false, '', null) hideDragLine(me) } table.onclick = function (evt) { evt = me.window.event || evt var target = getParentTdOrTh(evt.target || evt.srcElement) if (!target) return var ut = getUETable(target), table = ut.table, cellInfo = ut.getCellInfo(target), cellsRange, rng = me.selection.getRange() // if ("topLeft" == inPosition(table, mouseCoords(evt))) { // cellsRange = ut.getCellsRange(ut.table.rows[0].cells[0], ut.getLastCell()); // ut.setSelected(cellsRange); // return; // } // if ("bottomRight" == inPosition(table, mouseCoords(evt))) { // // return; // } if (inTableSide(table, target, evt, true)) { var endTdCol = ut.getCell( ut.indexTable[ut.rowsNum - 1][cellInfo.colIndex].rowIndex, ut.indexTable[ut.rowsNum - 1][cellInfo.colIndex].cellIndex ) if (evt.shiftKey && ut.selectedTds.length) { if (ut.selectedTds[0] !== endTdCol) { cellsRange = ut.getCellsRange(ut.selectedTds[0], endTdCol) ut.setSelected(cellsRange) } else { rng && rng.selectNodeContents(endTdCol).select() } } else { if (target !== endTdCol) { cellsRange = ut.getCellsRange(target, endTdCol) ut.setSelected(cellsRange) } else { rng && rng.selectNodeContents(endTdCol).select() } } return } if (inTableSide(table, target, evt)) { var endTdRow = ut.getCell( ut.indexTable[cellInfo.rowIndex][ut.colsNum - 1].rowIndex, ut.indexTable[cellInfo.rowIndex][ut.colsNum - 1].cellIndex ) if (evt.shiftKey && ut.selectedTds.length) { if (ut.selectedTds[0] !== endTdRow) { cellsRange = ut.getCellsRange(ut.selectedTds[0], endTdRow) ut.setSelected(cellsRange) } else { rng && rng.selectNodeContents(endTdRow).select() } } else { if (target !== endTdRow) { cellsRange = ut.getCellsRange(target, endTdRow) ut.setSelected(cellsRange) } else { rng && rng.selectNodeContents(endTdRow).select() } } } } } ) switchBorderColor(me, true) }) domUtils.on(me.document, 'mousemove', mouseMoveEvent) domUtils.on(me.document, 'mouseout', function (evt) { var target = evt.target || evt.srcElement if (target.tagName == 'TABLE') { toggleDraggableState(me, false, '', null) } }) /** * 表格隔行变色 */ me.addListener('interlacetable', function (type, table, classList) { if (!table) return var me = this, rows = table.rows, len = rows.length, getClass = function (list, index, repeat) { return list[index] ? list[index] : repeat ? list[index % list.length] : '' } for (var i = 0; i < len; i++) { rows[i].className = getClass( classList || me.options.classList, i, true ) } }) me.addListener('uninterlacetable', function (type, table) { if (!table) return var me = this, rows = table.rows, classList = me.options.classList, len = rows.length for (var i = 0; i < len; i++) { domUtils.removeClasses(rows[i], classList) } }) me.addListener('mousedown', mouseDownEvent) me.addListener('mouseup', mouseUpEvent) //拖动的时候触发mouseup domUtils.on(me.body, 'dragstart', function (evt) { mouseUpEvent.call(me, 'dragstart', evt) }) me.addOutputRule(function (root) { utils.each(root.getNodesByTagName('div'), function (n) { if (n.getAttr('id') == 'ue_tableDragLine') { n.parentNode.removeChild(n) } }) }) var currentRowIndex = 0 me.addListener('mousedown', function () { currentRowIndex = 0 }) me.addListener('tabkeydown', function () { var range = this.selection.getRange(), common = range.getCommonAncestor(true, true), table = domUtils.findParentByTagName(common, 'table') if (table) { if (domUtils.findParentByTagName(common, 'caption', true)) { var cell = domUtils.getElementsByTagName(table, 'th td') if (cell && cell.length) { range.setStart(cell[0], 0).setCursor(false, true) } } else { var cell = domUtils.findParentByTagName(common, ['td', 'th'], true), ua = getUETable(cell) currentRowIndex = cell.rowSpan > 1 ? currentRowIndex : ua.getCellInfo(cell).rowIndex var nextCell = ua.getTabNextCell(cell, currentRowIndex) if (nextCell) { if (isEmptyBlock(nextCell)) { range.setStart(nextCell, 0).setCursor(false, true) } else { range.selectNodeContents(nextCell).select() } } else { me.fireEvent('saveScene') me.__hasEnterExecCommand = true this.execCommand('insertrownext') me.__hasEnterExecCommand = false range = this.selection.getRange() range .setStart(table.rows[table.rows.length - 1].cells[0], 0) .setCursor() me.fireEvent('saveScene') } } return true } }) browser.ie && me.addListener('selectionchange', function () { toggleDraggableState(this, false, '', null) }) me.addListener('keydown', function (type, evt) { var me = this //处理在表格的最后一个输入tab产生新的表格 var keyCode = evt.keyCode || evt.which if (keyCode == 8 || keyCode == 46) { return } var notCtrlKey = !evt.ctrlKey && !evt.metaKey && !evt.shiftKey && !evt.altKey notCtrlKey && removeSelectedClass(domUtils.getElementsByTagName(me.body, 'td')) var ut = getUETableBySelected(me) if (!ut) return notCtrlKey && ut.clearSelected() }) me.addListener('beforegetcontent', function () { switchBorderColor(this, false) browser.ie && utils.each( this.document.getElementsByTagName('caption'), function (ci) { if (domUtils.isEmptyNode(ci)) { ci.innerHTML = ' ' } } ) }) me.addListener('aftergetcontent', function () { switchBorderColor(this, true) }) me.addListener('getAllHtml', function () { removeSelectedClass(me.document.getElementsByTagName('td')) }) //修正全屏状态下插入的表格宽度在非全屏状态下撑开编辑器的情况 me.addListener('fullscreenchanged', function (type, fullscreen) { if (!fullscreen) { var ratio = this.body.offsetWidth / document.body.offsetWidth, tables = domUtils.getElementsByTagName(this.body, 'table') utils.each(tables, function (table) { if (table.offsetWidth < me.body.offsetWidth) return false var tds = domUtils.getElementsByTagName(table, 'td'), backWidths = [] utils.each(tds, function (td) { backWidths.push(td.offsetWidth) }) for (var i = 0, td; (td = tds[i]); i++) { td.setAttribute('width', Math.floor(backWidths[i] * ratio)) } table.setAttribute( 'width', Math.floor(getTableWidth(me, needIEHack, getDefaultValue(me))) ) }) } }) //重写execCommand命令,用于处理框选时的处理 var oldExecCommand = me.execCommand me.execCommand = function (cmd, datatat) { var me = this, args = arguments cmd = cmd.toLowerCase() var ut = getUETableBySelected(me), tds, range = new dom.Range(me.document), cmdFun = me.commands[cmd] || UE.commands[cmd], result if (!cmdFun) return if ( ut && !commands[cmd] && !cmdFun.notNeedUndo && !me.__hasEnterExecCommand ) { me.__hasEnterExecCommand = true me.fireEvent('beforeexeccommand', cmd) tds = ut.selectedTds var lastState = -2, lastValue = -2, value, state for (var i = 0, td; (td = tds[i]); i++) { if (isEmptyBlock(td)) { range.setStart(td, 0).setCursor(false, true) } else { range.selectNode(td).select(true) } state = me.queryCommandState(cmd) value = me.queryCommandValue(cmd) if (state != -1) { if (lastState !== state || lastValue !== value) { me._ignoreContentChange = true result = oldExecCommand.apply(me, arguments) me._ignoreContentChange = false } lastState = me.queryCommandState(cmd) lastValue = me.queryCommandValue(cmd) if (domUtils.isEmptyBlock(td)) { domUtils.fillNode(me.document, td) } } } range.setStart(tds[0], 0).shrinkBoundary(true).setCursor(false, true) me.fireEvent('contentchange') me.fireEvent('afterexeccommand', cmd) me.__hasEnterExecCommand = false me._selectionChange() } else { result = oldExecCommand.apply(me, arguments) } return result } }) /** * 删除obj的宽高style,改成属性宽高 * @param obj * @param replaceToProperty */ function removeStyleSize(obj, replaceToProperty) { removeStyle(obj, 'width', true) removeStyle(obj, 'height', true) } function removeStyle(obj, styleName, replaceToProperty) { if (obj.style[styleName]) { replaceToProperty && obj.setAttribute(styleName, parseInt(obj.style[styleName], 10)) obj.style[styleName] = '' } } function getParentTdOrTh(ele) { if (ele.tagName == 'TD' || ele.tagName == 'TH') return ele var td if ( (td = domUtils.findParentByTagName(ele, 'td', true) || domUtils.findParentByTagName(ele, 'th', true)) ) return td return null } function isEmptyBlock(node) { var reg = new RegExp(domUtils.fillChar, 'g') if ( node[browser.ie ? 'innerText' : 'textContent'] .replace(/^\s*$/, '') .replace(reg, '').length > 0 ) { return 0 } for (var n in dtd.$isNotEmpty) { if (node.getElementsByTagName(n).length) { return 0 } } return 1 } function mouseCoords(evt) { if (evt.pageX || evt.pageY) { return { x: evt.pageX, y: evt.pageY } } return { x: evt.clientX + me.document.body.scrollLeft - me.document.body.clientLeft, y: evt.clientY + me.document.body.scrollTop - me.document.body.clientTop } } function mouseMoveEvent(evt) { if (isEditorDisabled()) { return } try { //普通状态下鼠标移动 var target = getParentTdOrTh(evt.target || evt.srcElement), pos //区分用户的行为是拖动还是双击 if (isInResizeBuffer) { me.body.style.webkitUserSelect = 'none' if ( Math.abs(userActionStatus.x - evt.clientX) > offsetOfTableCell || Math.abs(userActionStatus.y - evt.clientY) > offsetOfTableCell ) { clearTableDragTimer() isInResizeBuffer = false singleClickState = 0 //drag action tableBorderDrag(evt) } } //修改单元格大小时的鼠标移动 if (onDrag && dragTd) { singleClickState = 0 me.body.style.webkitUserSelect = 'none' me.selection .getNative() [browser.ie9below ? 'empty' : 'removeAllRanges']() pos = mouseCoords(evt) toggleDraggableState(me, true, onDrag, pos, target) if (onDrag == 'h') { dragLine.style.left = getPermissionX(dragTd, evt) + 'px' } else if (onDrag == 'v') { dragLine.style.top = getPermissionY(dragTd, evt) + 'px' } return } //当鼠标处于table上时,修改移动过程中的光标状态 if (target) { //针对使用table作为容器的组件不触发拖拽效果 if (me.fireEvent('excludetable', target) === true) return pos = mouseCoords(evt) var state = getRelation(target, pos), table = domUtils.findParentByTagName(target, 'table', true) if (inTableSide(table, target, evt, true)) { if (me.fireEvent('excludetable', table) === true) return me.body.style.cursor = 'url(' + me.options.cursorpath + 'h.png),pointer' } else if (inTableSide(table, target, evt)) { if (me.fireEvent('excludetable', table) === true) return me.body.style.cursor = 'url(' + me.options.cursorpath + 'v.png),pointer' } else { me.body.style.cursor = 'text' var curCell = target if (/\d/.test(state)) { state = state.replace(/\d/, '') target = getUETable(target).getPreviewCell(target, state == 'v') } //位于第一行的顶部或者第一列的左边时不可拖动 toggleDraggableState( me, target ? !!state : false, target ? state : '', pos, target ) } } else { toggleDragButton(false, table, me) } } catch (e) { showError(e) } } var dragButtonTimer function toggleDragButton(show, table, editor) { if (!show) { if (dragOver) return dragButtonTimer = setTimeout(function () { !dragOver && dragButton && dragButton.parentNode && dragButton.parentNode.removeChild(dragButton) }, 2000) } else { createDragButton(table, editor) } } function createDragButton(table, editor) { var pos = domUtils.getXY(table), doc = table.ownerDocument if (dragButton && dragButton.parentNode) return dragButton dragButton = doc.createElement('div') dragButton.contentEditable = false dragButton.innerHTML = '' dragButton.style.cssText = 'width:15px;height:15px;background-image:url(' + editor.options.UEDITOR_HOME_URL + 'dialogs/table/dragicon.png);position: absolute;cursor:move;top:' + (pos.y - 15) + 'px;left:' + pos.x + 'px;' domUtils.unSelectable(dragButton) dragButton.onmouseover = function (evt) { dragOver = true } dragButton.onmouseout = function (evt) { dragOver = false } domUtils.on(dragButton, 'click', function (type, evt) { doClick(evt, this) }) domUtils.on(dragButton, 'dblclick', function (type, evt) { doDblClick(evt) }) domUtils.on(dragButton, 'dragstart', function (type, evt) { domUtils.preventDefault(evt) }) var timer function doClick(evt, button) { // 部分浏览器下需要清理 clearTimeout(timer) timer = setTimeout(function () { editor.fireEvent('tableClicked', table, button) }, 300) } function doDblClick(evt) { clearTimeout(timer) var ut = getUETable(table), start = table.rows[0].cells[0], end = ut.getLastCell(), range = ut.getCellsRange(start, end) editor.selection.getRange().setStart(start, 0).setCursor(false, true) ut.setSelected(range) } doc.body.appendChild(dragButton) } // function inPosition(table, pos) { // var tablePos = domUtils.getXY(table), // width = table.offsetWidth, // height = table.offsetHeight; // if (pos.x - tablePos.x < 5 && pos.y - tablePos.y < 5) { // return "topLeft"; // } else if (tablePos.x + width - pos.x < 5 && tablePos.y + height - pos.y < 5) { // return "bottomRight"; // } // } function inTableSide(table, cell, evt, top) { var pos = mouseCoords(evt), state = getRelation(cell, pos) if (top) { var caption = table.getElementsByTagName('caption')[0], capHeight = caption ? caption.offsetHeight : 0 return state == 'v1' && pos.y - domUtils.getXY(table).y - capHeight < 8 } else { return state == 'h1' && pos.x - domUtils.getXY(table).x < 8 } } /** * 获取拖动时允许的X轴坐标 * @param dragTd * @param evt */ function getPermissionX(dragTd, evt) { var ut = getUETable(dragTd) if (ut) { var preTd = ut.getSameEndPosCells(dragTd, 'x')[0], nextTd = ut.getSameStartPosXCells(dragTd)[0], mouseX = mouseCoords(evt).x, left = (preTd ? domUtils.getXY(preTd).x : domUtils.getXY(ut.table).x) + 20, right = nextTd ? domUtils.getXY(nextTd).x + nextTd.offsetWidth - 20 : me.body.offsetWidth + 5 || parseInt(domUtils.getComputedStyle(me.body, 'width'), 10) left += cellMinWidth right -= cellMinWidth return mouseX < left ? left : mouseX > right ? right : mouseX } } /** * 获取拖动时允许的Y轴坐标 */ function getPermissionY(dragTd, evt) { try { var top = domUtils.getXY(dragTd).y, mousePosY = mouseCoords(evt).y return mousePosY < top ? top : mousePosY } catch (e) { showError(e) } } /** * 移动状态切换 */ function toggleDraggableState(editor, draggable, dir, mousePos, cell) { try { editor.body.style.cursor = dir == 'h' ? 'col-resize' : dir == 'v' ? 'row-resize' : 'text' if (browser.ie) { if (dir && !mousedown && !getUETableBySelected(editor)) { getDragLine(editor, editor.document) showDragLineAt(dir, cell) } else { hideDragLine(editor) } } onBorder = draggable } catch (e) { showError(e) } } /** * 获取与UETable相关的resize line * @param uetable UETable对象 */ function getResizeLineByUETable() { var lineId = '_UETableResizeLine', line = this.document.getElementById(lineId) if (!line) { line = this.document.createElement('div') line.id = lineId line.contnetEditable = false line.setAttribute('unselectable', 'on') var styles = { width: 2 * cellBorderWidth + 1 + 'px', position: 'absolute', 'z-index': 100000, cursor: 'col-resize', background: 'red', display: 'none' } //切换状态 line.onmouseout = function () { this.style.display = 'none' } utils.extend(line.style, styles) this.document.body.appendChild(line) } return line } /** * 更新resize-line */ function updateResizeLine(cell, uetable) { var line = getResizeLineByUETable.call(this), table = uetable.table, styles = { top: domUtils.getXY(table).y + 'px', left: domUtils.getXY(cell).x + cell.offsetWidth - cellBorderWidth + 'px', display: 'block', height: table.offsetHeight + 'px' } utils.extend(line.style, styles) } /** * 显示resize-line */ function showResizeLine(cell) { var uetable = getUETable(cell) updateResizeLine.call(this, cell, uetable) } /** * 获取鼠标与当前单元格的相对位置 * @param ele * @param mousePos */ function getRelation(ele, mousePos) { var elePos = domUtils.getXY(ele) if (!elePos) { return '' } if (elePos.x + ele.offsetWidth - mousePos.x < cellBorderWidth) { return 'h' } if (mousePos.x - elePos.x < cellBorderWidth) { return 'h1' } if (elePos.y + ele.offsetHeight - mousePos.y < cellBorderWidth) { return 'v' } if (mousePos.y - elePos.y < cellBorderWidth) { return 'v1' } return '' } function mouseDownEvent(type, evt) { if (isEditorDisabled()) { return } userActionStatus = { x: evt.clientX, y: evt.clientY } //右键菜单单独处理 if (evt.button == 2) { var ut = getUETableBySelected(me), flag = false if (ut) { var td = getTargetTd(me, evt) utils.each(ut.selectedTds, function (ti) { if (ti === td) { flag = true } }) if (!flag) { removeSelectedClass(domUtils.getElementsByTagName(me.body, 'th td')) ut.clearSelected() } else { td = ut.selectedTds[0] setTimeout(function () { me.selection.getRange().setStart(td, 0).setCursor(false, true) }, 0) } } } else { tableClickHander(evt) } } //清除表格的计时器 function clearTableTimer() { tabTimer && clearTimeout(tabTimer) tabTimer = null } //双击收缩 function tableDbclickHandler(evt) { singleClickState = 0 evt = evt || me.window.event var target = getParentTdOrTh(evt.target || evt.srcElement) if (target) { var h if ((h = getRelation(target, mouseCoords(evt)))) { hideDragLine(me) if (h == 'h1') { h = 'h' if ( inTableSide( domUtils.findParentByTagName(target, 'table'), target, evt ) ) { me.execCommand('adaptbywindow') } else { target = getUETable(target).getPreviewCell(target) if (target) { var rng = me.selection.getRange() rng.selectNodeContents(target).setCursor(true, true) } } } if (h == 'h') { var ut = getUETable(target), table = ut.table, cells = getCellsByMoveBorder(target, table, true) cells = extractArray(cells, 'left') ut.width = ut.offsetWidth var oldWidth = [], newWidth = [] utils.each(cells, function (cell) { oldWidth.push(cell.offsetWidth) }) utils.each(cells, function (cell) { cell.removeAttribute('width') }) window.setTimeout(function () { //是否允许改变 var changeable = true utils.each(cells, function (cell, index) { var width = cell.offsetWidth if (width > oldWidth[index]) { changeable = false return false } newWidth.push(width) }) var change = changeable ? newWidth : oldWidth utils.each(cells, function (cell, index) { cell.width = change[index] - getTabcellSpace() }) }, 0) // minWidth -= cellMinWidth; // // table.removeAttribute("width"); // utils.each(cells, function (cell) { // cell.style.width = ""; // cell.width -= minWidth; // }); } } } } function tableClickHander(evt) { removeSelectedClass(domUtils.getElementsByTagName(me.body, 'td th')) //trace:3113 //选中单元格,点击table外部,不会清掉table上挂的ueTable,会引起getUETableBySelected方法返回值 utils.each(me.document.getElementsByTagName('table'), function (t) { t.ueTable = null }) startTd = getTargetTd(me, evt) if (!startTd) return var table = domUtils.findParentByTagName(startTd, 'table', true) ut = getUETable(table) ut && ut.clearSelected() //判断当前鼠标状态 if (!onBorder) { me.document.body.style.webkitUserSelect = '' mousedown = true me.addListener('mouseover', mouseOverEvent) } else { //边框上的动作处理 borderActionHandler(evt) } } //处理表格边框上的动作, 这里做延时处理,避免两种动作互相影响 function borderActionHandler(evt) { if (browser.ie) { evt = reconstruct(evt) } clearTableDragTimer() //是否正在等待resize的缓冲中 isInResizeBuffer = true tableDragTimer = setTimeout(function () { tableBorderDrag(evt) }, dblclickTime) } function extractArray(originArr, key) { var result = [], tmp = null for (var i = 0, len = originArr.length; i < len; i++) { tmp = originArr[i][key] if (tmp) { result.push(tmp) } } return result } function clearTableDragTimer() { tableDragTimer && clearTimeout(tableDragTimer) tableDragTimer = null } function reconstruct(obj) { var attrs = [ 'pageX', 'pageY', 'clientX', 'clientY', 'srcElement', 'target' ], newObj = {} if (obj) { for (var i = 0, key, val; (key = attrs[i]); i++) { val = obj[key] val && (newObj[key] = val) } } return newObj } //边框拖动 function tableBorderDrag(evt) { isInResizeBuffer = false startTd = evt.target || evt.srcElement if (!startTd) return var state = getRelation(startTd, mouseCoords(evt)) if (/\d/.test(state)) { state = state.replace(/\d/, '') startTd = getUETable(startTd).getPreviewCell(startTd, state == 'v') } hideDragLine(me) getDragLine(me, me.document) me.fireEvent('saveScene') showDragLineAt(state, startTd) mousedown = true //拖动开始 onDrag = state dragTd = startTd } function mouseUpEvent(type, evt) { if (isEditorDisabled()) { return } clearTableDragTimer() isInResizeBuffer = false if (onBorder) { singleClickState = ++singleClickState % 3 userActionStatus = { x: evt.clientX, y: evt.clientY } tableResizeTimer = setTimeout(function () { singleClickState > 0 && singleClickState-- }, dblclickTime) if (singleClickState === 2) { singleClickState = 0 tableDbclickHandler(evt) return } } if (evt.button == 2) return var me = this //清除表格上原生跨选问题 var range = me.selection.getRange(), start = domUtils.findParentByTagName( range.startContainer, 'table', true ), end = domUtils.findParentByTagName(range.endContainer, 'table', true) if (start || end) { if (start === end) { start = domUtils.findParentByTagName( range.startContainer, ['td', 'th', 'caption'], true ) end = domUtils.findParentByTagName( range.endContainer, ['td', 'th', 'caption'], true ) if (start !== end) { me.selection.clearRange() } } else { me.selection.clearRange() } } mousedown = false me.document.body.style.webkitUserSelect = '' //拖拽状态下的mouseUP if (onDrag && dragTd) { me.selection .getNative() [browser.ie9below ? 'empty' : 'removeAllRanges']() singleClickState = 0 dragLine = me.document.getElementById('ue_tableDragLine') // trace 3973 if (dragLine) { var dragTdPos = domUtils.getXY(dragTd), dragLinePos = domUtils.getXY(dragLine) switch (onDrag) { case 'h': changeColWidth(dragTd, dragLinePos.x - dragTdPos.x) break case 'v': changeRowHeight( dragTd, dragLinePos.y - dragTdPos.y - dragTd.offsetHeight ) break default: } onDrag = '' dragTd = null hideDragLine(me) me.fireEvent('saveScene') return } } //正常状态下的mouseup if (!startTd) { var target = domUtils.findParentByTagName( evt.target || evt.srcElement, 'td', true ) if (!target) target = domUtils.findParentByTagName( evt.target || evt.srcElement, 'th', true ) if (target && (target.tagName == 'TD' || target.tagName == 'TH')) { if (me.fireEvent('excludetable', target) === true) return range = new dom.Range(me.document) range.setStart(target, 0).setCursor(false, true) } } else { var ut = getUETable(startTd), cell = ut ? ut.selectedTds[0] : null if (cell) { range = new dom.Range(me.document) if (domUtils.isEmptyBlock(cell)) { range.setStart(cell, 0).setCursor(false, true) } else { range .selectNodeContents(cell) .shrinkBoundary() .setCursor(false, true) } } else { range = me.selection.getRange().shrinkBoundary() if (!range.collapsed) { var start = domUtils.findParentByTagName( range.startContainer, ['td', 'th'], true ), end = domUtils.findParentByTagName( range.endContainer, ['td', 'th'], true ) //在table里边的不能清除 if ( (start && !end) || (!start && end) || (start && end && start !== end) ) { range.setCursor(false, true) } } } startTd = null me.removeListener('mouseover', mouseOverEvent) } me._selectionChange(250, evt) } function mouseOverEvent(type, evt) { if (isEditorDisabled()) { return } var me = this, tar = evt.target || evt.srcElement currentTd = domUtils.findParentByTagName(tar, 'td', true) || domUtils.findParentByTagName(tar, 'th', true) //需要判断两个TD是否位于同一个表格内 if ( startTd && currentTd && ((startTd.tagName == 'TD' && currentTd.tagName == 'TD') || (startTd.tagName == 'TH' && currentTd.tagName == 'TH')) && domUtils.findParentByTagName(startTd, 'table') == domUtils.findParentByTagName(currentTd, 'table') ) { var ut = getUETable(currentTd) if (startTd != currentTd) { me.document.body.style.webkitUserSelect = 'none' me.selection .getNative() [browser.ie9below ? 'empty' : 'removeAllRanges']() var range = ut.getCellsRange(startTd, currentTd) ut.setSelected(range) } else { me.document.body.style.webkitUserSelect = '' ut.clearSelected() } } evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false) } function setCellHeight(cell, height, backHeight) { var lineHight = parseInt( domUtils.getComputedStyle(cell, 'line-height'), 10 ), tmpHeight = backHeight + height height = tmpHeight < lineHight ? lineHight : tmpHeight if (cell.style.height) cell.style.height = '' cell.rowSpan == 1 ? cell.setAttribute('height', height) : cell.removeAttribute && cell.removeAttribute('height') } function getWidth(cell) { if (!cell) return 0 return parseInt(domUtils.getComputedStyle(cell, 'width'), 10) } function changeColWidth(cell, changeValue) { var ut = getUETable(cell) if (ut) { //根据当前移动的边框获取相关的单元格 var table = ut.table, cells = getCellsByMoveBorder(cell, table) table.style.width = '' table.removeAttribute('width') //修正改变量 changeValue = correctChangeValue(changeValue, cell, cells) if (cell.nextSibling) { var i = 0 utils.each(cells, function (cellGroup) { cellGroup.left.width = +cellGroup.left.width + changeValue cellGroup.right && (cellGroup.right.width = +cellGroup.right.width - changeValue) }) } else { utils.each(cells, function (cellGroup) { cellGroup.left.width -= -changeValue }) } } } function isEditorDisabled() { return me.body.contentEditable === 'false' } function changeRowHeight(td, changeValue) { if (Math.abs(changeValue) < 10) return var ut = getUETable(td) if (ut) { var cells = ut.getSameEndPosCells(td, 'y'), //备份需要连带变化的td的原始高度,否则后期无法获取正确的值 backHeight = cells[0] ? cells[0].offsetHeight : 0 for (var i = 0, cell; (cell = cells[i++]); ) { setCellHeight(cell, changeValue, backHeight) } } } /** * 获取调整单元格大小的相关单元格 * @isContainMergeCell 返回的结果中是否包含发生合并后的单元格 */ function getCellsByMoveBorder(cell, table, isContainMergeCell) { if (!table) { table = domUtils.findParentByTagName(cell, 'table') } if (!table) { return null } //获取到该单元格所在行的序列号 var index = domUtils.getNodeIndex(cell), temp = cell, rows = table.rows, colIndex = 0 while (temp) { //获取到当前单元格在未发生单元格合并时的序列 if (temp.nodeType === 1) { colIndex += temp.colSpan || 1 } temp = temp.previousSibling } temp = null //记录想关的单元格 var borderCells = [] utils.each(rows, function (tabRow) { var cells = tabRow.cells, currIndex = 0 utils.each(cells, function (tabCell) { currIndex += tabCell.colSpan || 1 if (currIndex === colIndex) { borderCells.push({ left: tabCell, right: tabCell.nextSibling || null }) return false } else if (currIndex > colIndex) { if (isContainMergeCell) { borderCells.push({ left: tabCell }) } return false } }) }) return borderCells } /** * 通过给定的单元格集合获取最小的单元格width */ function getMinWidthByTableCells(cells) { var minWidth = Number.MAX_VALUE for (var i = 0, curCell; (curCell = cells[i]); i++) { minWidth = Math.min( minWidth, curCell.width || getTableCellWidth(curCell) ) } return minWidth } function correctChangeValue(changeValue, relatedCell, cells) { //为单元格的paading预留空间 changeValue -= getTabcellSpace() if (changeValue < 0) { return 0 } changeValue -= getTableCellWidth(relatedCell) //确定方向 var direction = changeValue < 0 ? 'left' : 'right' changeValue = Math.abs(changeValue) //只关心非最后一个单元格就可以 utils.each(cells, function (cellGroup) { var curCell = cellGroup[direction] //为单元格保留最小空间 if (curCell) { changeValue = Math.min( changeValue, getTableCellWidth(curCell) - cellMinWidth ) } }) //修正越界 changeValue = changeValue < 0 ? 0 : changeValue return direction === 'left' ? -changeValue : changeValue } function getTableCellWidth(cell) { var width = 0, //偏移纠正量 offset = 0, width = cell.offsetWidth - getTabcellSpace() //最后一个节点纠正一下 if (!cell.nextSibling) { width -= getTableCellOffset(cell) } width = width < 0 ? 0 : width try { cell.width = width } catch (e) {} return width } /** * 获取单元格所在表格的最末单元格的偏移量 */ function getTableCellOffset(cell) { tab = domUtils.findParentByTagName(cell, 'table', false) if (tab.offsetVal === undefined) { var prev = cell.previousSibling if (prev) { //最后一个单元格和前一个单元格的width diff结果 如果恰好为一个border width, 则条件成立 tab.offsetVal = cell.offsetWidth - prev.offsetWidth === UT.borderWidth ? UT.borderWidth : 0 } else { tab.offsetVal = 0 } } return tab.offsetVal } function getTabcellSpace() { if (UT.tabcellSpace === undefined) { var cell = null, tab = me.document.createElement('table'), tbody = me.document.createElement('tbody'), trow = me.document.createElement('tr'), tabcell = me.document.createElement('td'), mirror = null tabcell.style.cssText = 'border: 0;' tabcell.width = 1 trow.appendChild(tabcell) trow.appendChild((mirror = tabcell.cloneNode(false))) tbody.appendChild(trow) tab.appendChild(tbody) tab.style.cssText = 'visibility: hidden;' me.body.appendChild(tab) UT.paddingSpace = tabcell.offsetWidth - 1 var tmpTabWidth = tab.offsetWidth tabcell.style.cssText = '' mirror.style.cssText = '' UT.borderWidth = (tab.offsetWidth - tmpTabWidth) / 3 UT.tabcellSpace = UT.paddingSpace + UT.borderWidth me.body.removeChild(tab) } getTabcellSpace = function () { return UT.tabcellSpace } return UT.tabcellSpace } function getDragLine(editor, doc) { if (mousedown) return dragLine = editor.document.createElement('div') domUtils.setAttributes(dragLine, { id: 'ue_tableDragLine', unselectable: 'on', contenteditable: false, onresizestart: 'return false', ondragstart: 'return false', onselectstart: 'return false', style: 'background-color:blue;position:absolute;padding:0;margin:0;background-image:none;border:0px none;opacity:0;filter:alpha(opacity=0)' }) editor.body.appendChild(dragLine) } function hideDragLine(editor) { if (mousedown) return var line while ((line = editor.document.getElementById('ue_tableDragLine'))) { domUtils.remove(line) } } /** * 依据state(v|h)在cell位置显示横线 * @param state * @param cell */ function showDragLineAt(state, cell) { if (!cell) return var table = domUtils.findParentByTagName(cell, 'table'), caption = table.getElementsByTagName('caption'), width = table.offsetWidth, height = table.offsetHeight - (caption.length > 0 ? caption[0].offsetHeight : 0), tablePos = domUtils.getXY(table), cellPos = domUtils.getXY(cell), css switch (state) { case 'h': css = 'height:' + height + 'px;top:' + (tablePos.y + (caption.length > 0 ? caption[0].offsetHeight : 0)) + 'px;left:' + (cellPos.x + cell.offsetWidth) dragLine.style.cssText = css + 'px;position: absolute;display:block;background-color:blue;width:1px;border:0; color:blue;opacity:.3;filter:alpha(opacity=30)' break case 'v': css = 'width:' + width + 'px;left:' + tablePos.x + 'px;top:' + (cellPos.y + cell.offsetHeight) //必须加上border:0和color:blue,否则低版ie不支持背景色显示 dragLine.style.cssText = css + 'px;overflow:hidden;position: absolute;display:block;background-color:blue;height:1px;border:0;color:blue;opacity:.2;filter:alpha(opacity=20)' break default: } } /** * 当表格边框颜色为白色时设置为虚线,true为添加虚线 * @param editor * @param flag */ function switchBorderColor(editor, flag) { var tableArr = domUtils.getElementsByTagName(editor.body, 'table'), color for (var i = 0, node; (node = tableArr[i++]); ) { var td = domUtils.getElementsByTagName(node, 'td') if (td[0]) { if (flag) { color = td[0].style.borderColor.replace(/\s/g, '') if (/(#ffffff)|(rgb\(255,255,255\))/gi.test(color)) domUtils.addClass(node, 'noBorderTable') } else { domUtils.removeClasses(node, 'noBorderTable') } } } } function getTableWidth(editor, needIEHack, defaultValue) { var body = editor.body return ( body.offsetWidth - (needIEHack ? parseInt(domUtils.getComputedStyle(body, 'margin-left'), 10) * 2 : 0) - defaultValue.tableBorder * 2 - (editor.options.offsetWidth || 0) ) } /** * 获取当前拖动的单元格 */ function getTargetTd(editor, evt) { var target = domUtils.findParentByTagName( evt.target || evt.srcElement, ['td', 'th'], true ), dir = null if (!target) { return null } dir = getRelation(target, mouseCoords(evt)) //如果有前一个节点, 需要做一个修正, 否则可能会得到一个错误的td if (!target) { return null } if (dir === 'h1' && target.previousSibling) { var position = domUtils.getXY(target), cellWidth = target.offsetWidth if (Math.abs(position.x + cellWidth - evt.clientX) > cellWidth / 3) { target = target.previousSibling } } else if (dir === 'v1' && target.parentNode.previousSibling) { var position = domUtils.getXY(target), cellHeight = target.offsetHeight if (Math.abs(position.y + cellHeight - evt.clientY) > cellHeight / 3) { target = target.parentNode.previousSibling.firstChild } } //排除了非td内部以及用于代码高亮部分的td return target && !(editor.fireEvent('excludetable', target) === true) ? target : null } } // plugins/table.sort.js /** * Created with JetBrains PhpStorm. * User: Jinqn * Date: 13-10-12 * Time: 上午10:20 * To change this template use File | Settings | File Templates. */ UE.UETable.prototype.sortTable = function (sortByCellIndex, compareFn) { var table = this.table, rows = table.rows, trArray = [], flag = rows[0].cells[0].tagName === 'TH', lastRowIndex = 0 if (this.selectedTds.length) { var range = this.cellsRange, len = range.endRowIndex + 1 for (var i = range.beginRowIndex; i < len; i++) { trArray[i] = rows[i] } trArray.splice(0, range.beginRowIndex) lastRowIndex = range.endRowIndex + 1 === this.rowsNum ? 0 : range.endRowIndex + 1 } else { for (var i = 0, len = rows.length; i < len; i++) { trArray[i] = rows[i] } } var Fn = { reversecurrent: function (td1, td2) { return 1 }, orderbyasc: function (td1, td2) { var value1 = td1.innerText || td1.textContent, value2 = td2.innerText || td2.textContent return value1.localeCompare(value2) }, reversebyasc: function (td1, td2) { var value1 = td1.innerHTML, value2 = td2.innerHTML return value2.localeCompare(value1) }, orderbynum: function (td1, td2) { var value1 = td1[browser.ie ? 'innerText' : 'textContent'].match(/\d+/), value2 = td2[browser.ie ? 'innerText' : 'textContent'].match(/\d+/) if (value1) value1 = +value1[0] if (value2) value2 = +value2[0] return (value1 || 0) - (value2 || 0) }, reversebynum: function (td1, td2) { var value1 = td1[browser.ie ? 'innerText' : 'textContent'].match(/\d+/), value2 = td2[browser.ie ? 'innerText' : 'textContent'].match(/\d+/) if (value1) value1 = +value1[0] if (value2) value2 = +value2[0] return (value2 || 0) - (value1 || 0) } } //对表格设置排序的标记data-sort-type table.setAttribute( 'data-sort-type', compareFn && typeof compareFn === 'string' && Fn[compareFn] ? compareFn : '' ) //th不参与排序 flag && trArray.splice(0, 1) trArray = utils.sort(trArray, function (tr1, tr2) { var result if (compareFn && typeof compareFn === 'function') { result = compareFn.call( this, tr1.cells[sortByCellIndex], tr2.cells[sortByCellIndex] ) } else if (compareFn && typeof compareFn === 'number') { result = 1 } else if (compareFn && typeof compareFn === 'string' && Fn[compareFn]) { result = Fn[compareFn].call( this, tr1.cells[sortByCellIndex], tr2.cells[sortByCellIndex] ) } else { result = Fn['orderbyasc'].call( this, tr1.cells[sortByCellIndex], tr2.cells[sortByCellIndex] ) } return result }) var fragment = table.ownerDocument.createDocumentFragment() for (var j = 0, len = trArray.length; j < len; j++) { fragment.appendChild(trArray[j]) } var tbody = table.getElementsByTagName('tbody')[0] if (!lastRowIndex) { tbody.appendChild(fragment) } else { tbody.insertBefore( fragment, rows[lastRowIndex - range.endRowIndex + range.beginRowIndex - 1] ) } } UE.plugins['tablesort'] = function () { var me = this, UT = UE.UETable, getUETable = function (tdOrTable) { return UT.getUETable(tdOrTable) }, getTableItemsByRange = function (editor) { return UT.getTableItemsByRange(editor) } me.ready(function () { //添加表格可排序的样式 utils.cssRule( 'tablesort', 'table.sortEnabled tr.firstRow th,table.sortEnabled tr.firstRow td{padding-right:20px;background-repeat: no-repeat;background-position: center right;' + ' background-image:url(' + me.options.themePath + me.options.theme + '/images/sortable.png);}', me.document ) //做单元格合并操作时,清除可排序标识 me.addListener('afterexeccommand', function (type, cmd) { if (cmd == 'mergeright' || cmd == 'mergedown' || cmd == 'mergecells') { this.execCommand('disablesort') } }) }) //表格排序 UE.commands['sorttable'] = { queryCommandState: function () { var me = this, tableItems = getTableItemsByRange(me) if (!tableItems.cell) return -1 var table = tableItems.table, cells = table.getElementsByTagName('td') for (var i = 0, cell; (cell = cells[i++]); ) { if (cell.rowSpan != 1 || cell.colSpan != 1) return -1 } return 0 }, execCommand: function (cmd, fn) { var me = this, range = me.selection.getRange(), bk = range.createBookmark(true), tableItems = getTableItemsByRange(me), cell = tableItems.cell, ut = getUETable(tableItems.table), cellInfo = ut.getCellInfo(cell) ut.sortTable(cellInfo.cellIndex, fn) range.moveToBookmark(bk) try { range.select() } catch (e) {} } } //设置表格可排序,清除表格可排序 UE.commands['enablesort'] = UE.commands['disablesort'] = { queryCommandState: function (cmd) { var table = getTableItemsByRange(this).table if (table && cmd == 'enablesort') { var cells = domUtils.getElementsByTagName(table, 'th td') for (var i = 0; i < cells.length; i++) { if ( cells[i].getAttribute('colspan') > 1 || cells[i].getAttribute('rowspan') > 1 ) return -1 } } return !table ? -1 : (cmd == 'enablesort') ^ (table.getAttribute('data-sort') != 'sortEnabled') ? -1 : 0 }, execCommand: function (cmd) { var table = getTableItemsByRange(this).table table.setAttribute( 'data-sort', cmd == 'enablesort' ? 'sortEnabled' : 'sortDisabled' ) cmd == 'enablesort' ? domUtils.addClass(table, 'sortEnabled') : domUtils.removeClasses(table, 'sortEnabled') } } } // plugins/contextmenu.js ///import core ///commands 右键菜单 ///commandsName ContextMenu ///commandsTitle 右键菜单 /** * 右键菜单 * @function * @name baidu.editor.plugins.contextmenu * @author zhanyi */ UE.plugins['contextmenu'] = function () { var me = this me.setOpt('enableContextMenu', true) if (me.getOpt('enableContextMenu') === false) { return } var lang = me.getLang('contextMenu'), menu, items = me.options.contextMenu || [ { label: lang['selectall'], cmdName: 'selectall' }, { label: lang.cleardoc, cmdName: 'cleardoc', exec: function () { if (confirm(lang.confirmclear)) { this.execCommand('cleardoc') } } }, '-', { label: lang.unlink, cmdName: 'unlink' }, '-', { group: lang.paragraph, icon: 'justifyjustify', subMenu: [ { label: lang.justifyleft, cmdName: 'justify', value: 'left' }, { label: lang.justifyright, cmdName: 'justify', value: 'right' }, { label: lang.justifycenter, cmdName: 'justify', value: 'center' }, { label: lang.justifyjustify, cmdName: 'justify', value: 'justify' } ] }, '-', { group: lang.table, icon: 'table', subMenu: [ { label: lang.inserttable, cmdName: 'inserttable' }, { label: lang.deletetable, cmdName: 'deletetable' }, '-', { label: lang.deleterow, cmdName: 'deleterow' }, { label: lang.deletecol, cmdName: 'deletecol' }, { label: lang.insertcol, cmdName: 'insertcol' }, { label: lang.insertcolnext, cmdName: 'insertcolnext' }, { label: lang.insertrow, cmdName: 'insertrow' }, { label: lang.insertrownext, cmdName: 'insertrownext' }, '-', { label: lang.insertcaption, cmdName: 'insertcaption' }, { label: lang.deletecaption, cmdName: 'deletecaption' }, { label: lang.inserttitle, cmdName: 'inserttitle' }, { label: lang.deletetitle, cmdName: 'deletetitle' }, { label: lang.inserttitlecol, cmdName: 'inserttitlecol' }, { label: lang.deletetitlecol, cmdName: 'deletetitlecol' }, '-', { label: lang.mergecells, cmdName: 'mergecells' }, { label: lang.mergeright, cmdName: 'mergeright' }, { label: lang.mergedown, cmdName: 'mergedown' }, '-', { label: lang.splittorows, cmdName: 'splittorows' }, { label: lang.splittocols, cmdName: 'splittocols' }, { label: lang.splittocells, cmdName: 'splittocells' }, '-', { label: lang.averageDiseRow, cmdName: 'averagedistributerow' }, { label: lang.averageDisCol, cmdName: 'averagedistributecol' }, '-', { label: lang.edittd, cmdName: 'edittd', exec: function () { if (UE.ui['edittd']) { new UE.ui['edittd'](this) } this.getDialog('edittd').open() } }, { label: lang.edittable, cmdName: 'edittable', exec: function () { if (UE.ui['edittable']) { new UE.ui['edittable'](this) } this.getDialog('edittable').open() } }, { label: lang.setbordervisible, cmdName: 'setbordervisible' } ] }, { group: lang.tablesort, icon: 'tablesort', subMenu: [ { label: lang.enablesort, cmdName: 'enablesort' }, { label: lang.disablesort, cmdName: 'disablesort' }, '-', { label: lang.reversecurrent, cmdName: 'sorttable', value: 'reversecurrent' }, { label: lang.orderbyasc, cmdName: 'sorttable', value: 'orderbyasc' }, { label: lang.reversebyasc, cmdName: 'sorttable', value: 'reversebyasc' }, { label: lang.orderbynum, cmdName: 'sorttable', value: 'orderbynum' }, { label: lang.reversebynum, cmdName: 'sorttable', value: 'reversebynum' } ] }, { group: lang.borderbk, icon: 'borderBack', subMenu: [ { label: lang.setcolor, cmdName: 'interlacetable', exec: function () { this.execCommand('interlacetable') } }, { label: lang.unsetcolor, cmdName: 'uninterlacetable', exec: function () { this.execCommand('uninterlacetable') } }, { label: lang.setbackground, cmdName: 'settablebackground', exec: function () { this.execCommand('settablebackground', { repeat: true, colorList: ['#bbb', '#ccc'] }) } }, { label: lang.unsetbackground, cmdName: 'cleartablebackground', exec: function () { this.execCommand('cleartablebackground') } }, { label: lang.redandblue, cmdName: 'settablebackground', exec: function () { this.execCommand('settablebackground', { repeat: true, colorList: ['red', 'blue'] }) } }, { label: lang.threecolorgradient, cmdName: 'settablebackground', exec: function () { this.execCommand('settablebackground', { repeat: true, colorList: ['#aaa', '#bbb', '#ccc'] }) } } ] }, { group: lang.aligntd, icon: 'aligntd', subMenu: [ { cmdName: 'cellalignment', value: { align: 'left', vAlign: 'top' } }, { cmdName: 'cellalignment', value: { align: 'center', vAlign: 'top' } }, { cmdName: 'cellalignment', value: { align: 'right', vAlign: 'top' } }, { cmdName: 'cellalignment', value: { align: 'left', vAlign: 'middle' } }, { cmdName: 'cellalignment', value: { align: 'center', vAlign: 'middle' } }, { cmdName: 'cellalignment', value: { align: 'right', vAlign: 'middle' } }, { cmdName: 'cellalignment', value: { align: 'left', vAlign: 'bottom' } }, { cmdName: 'cellalignment', value: { align: 'center', vAlign: 'bottom' } }, { cmdName: 'cellalignment', value: { align: 'right', vAlign: 'bottom' } } ] }, { group: lang.aligntable, icon: 'aligntable', subMenu: [ { cmdName: 'tablealignment', className: 'left', label: lang.tableleft, value: 'left' }, { cmdName: 'tablealignment', className: 'center', label: lang.tablecenter, value: 'center' }, { cmdName: 'tablealignment', className: 'right', label: lang.tableright, value: 'right' } ] }, '-', { label: lang.insertparagraphbefore, cmdName: 'insertparagraph', value: true }, { label: lang.insertparagraphafter, cmdName: 'insertparagraph' }, { label: lang['copy'], cmdName: 'copy' }, { label: lang['paste'], cmdName: 'paste' } ] if (!items.length) { return } var uiUtils = UE.ui.uiUtils me.addListener('contextmenu', function (type, evt) { var offset = uiUtils.getViewportOffsetByEvent(evt) me.fireEvent('beforeselectionchange') if (menu) { menu.destroy() } for (var i = 0, ti, contextItems = []; (ti = items[i]); i++) { var last ;(function (item) { if (item == '-') { if ( (last = contextItems[contextItems.length - 1]) && last !== '-' ) { contextItems.push('-') } } else if (item.hasOwnProperty('group')) { for (var j = 0, cj, subMenu = []; (cj = item.subMenu[j]); j++) { ;(function (subItem) { if (subItem == '-') { if ((last = subMenu[subMenu.length - 1]) && last !== '-') { subMenu.push('-') } else { subMenu.splice(subMenu.length - 1) } } else { if ( (me.commands[subItem.cmdName] || UE.commands[subItem.cmdName] || subItem.query) && (subItem.query ? subItem.query() : me.queryCommandState(subItem.cmdName)) > -1 ) { subMenu.push({ label: subItem.label || me.getLang( 'contextMenu.' + subItem.cmdName + (subItem.value || '') ) || '', className: 'edui-for-' + subItem.cmdName + (subItem.className ? ' edui-for-' + subItem.cmdName + '-' + subItem.className : ''), onclick: subItem.exec ? function () { subItem.exec.call(me) } : function () { me.execCommand(subItem.cmdName, subItem.value) } }) } } })(cj) } if (subMenu.length) { function getLabel() { switch (item.icon) { case 'table': return me.getLang('contextMenu.table') case 'justifyjustify': return me.getLang('contextMenu.paragraph') case 'aligntd': return me.getLang('contextMenu.aligntd') case 'aligntable': return me.getLang('contextMenu.aligntable') case 'tablesort': return lang.tablesort case 'borderBack': return lang.borderbk default: return '' } } contextItems.push({ //todo 修正成自动获取方式 label: getLabel(), className: 'edui-for-' + item.icon, subMenu: { items: subMenu, editor: me } }) } } else { //有可能commmand没有加载右键不能出来,或者没有command也想能展示出来添加query方法 if ( (me.commands[item.cmdName] || UE.commands[item.cmdName] || item.query) && (item.query ? item.query.call(me) : me.queryCommandState(item.cmdName)) > -1 ) { contextItems.push({ label: item.label || me.getLang('contextMenu.' + item.cmdName), className: 'edui-for-' + (item.icon ? item.icon : item.cmdName + (item.value || '')), onclick: item.exec ? function () { item.exec.call(me) } : function () { me.execCommand(item.cmdName, item.value) } }) } } })(ti) } if (contextItems[contextItems.length - 1] == '-') { contextItems.pop() } menu = new UE.ui.Menu({ items: contextItems, className: 'edui-contextmenu', editor: me }) menu.render() menu.showAt(offset) me.fireEvent('aftershowcontextmenu', menu) domUtils.preventDefault(evt) if (browser.ie) { var ieRange try { ieRange = me.selection.getNative().createRange() } catch (e) { return } if (ieRange.item) { var range = new dom.Range(me.document) range.selectNode(ieRange.item(0)).select(true, true) } } }) // 添加复制的flash按钮 me.addListener('aftershowcontextmenu', function (type, menu) { if (me.zeroclipboard) { var items = menu.items for (var key in items) { if (items[key].className == 'edui-for-copy') { me.zeroclipboard.clip(items[key].getDom()) } } } }) } // plugins/shortcutmenu.js ///import core ///commands 弹出菜单 // commandsName popupmenu ///commandsTitle 弹出菜单 /** * 弹出菜单 * @function * @name baidu.editor.plugins.popupmenu * @author xuheng */ UE.plugins['shortcutmenu'] = function () { var me = this, menu, items = me.options.shortcutMenu || [] if (!items.length) { return } me.addListener('contextmenu mouseup', function (type, e) { var me = this, customEvt = { type: type, target: e.target || e.srcElement, screenX: e.screenX, screenY: e.screenY, clientX: e.clientX, clientY: e.clientY } setTimeout(function () { var rng = me.selection.getRange() if (rng.collapsed === false || type == 'contextmenu') { if (!menu) { menu = new baidu.editor.ui.ShortCutMenu({ editor: me, items: items, theme: me.options.theme, className: 'edui-shortcutmenu' }) menu.render() me.fireEvent('afterrendershortcutmenu', menu) } menu.show(customEvt, !!UE.plugins['contextmenu']) } }) if (type == 'contextmenu') { domUtils.preventDefault(e) if (browser.ie9below) { var ieRange try { ieRange = me.selection.getNative().createRange() } catch (e) { return } if (ieRange.item) { var range = new dom.Range(me.document) range.selectNode(ieRange.item(0)).select(true, true) } } } }) me.addListener('keydown', function (type) { if (type == 'keydown') { menu && !menu.isHidden && menu.hide() } }) } // plugins/basestyle.js /** * B、I、sub、super命令支持 * @file * @since 1.2.6.1 */ UE.plugins['basestyle'] = function () { /** * 字体加粗 * @command bold * @param { String } cmd 命令字符串 * @remind 对已加粗的文本内容执行该命令, 将取消加粗 * @method execCommand * @example * ```javascript * //editor是编辑器实例 * //对当前选中的文本内容执行加粗操作 * //第一次执行, 文本内容加粗 * editor.execCommand( 'bold' ); * * //第二次执行, 文本内容取消加粗 * editor.execCommand( 'bold' ); * ``` */ /** * 字体倾斜 * @command italic * @method execCommand * @param { String } cmd 命令字符串 * @remind 对已倾斜的文本内容执行该命令, 将取消倾斜 * @example * ```javascript * //editor是编辑器实例 * //对当前选中的文本内容执行斜体操作 * //第一次操作, 文本内容将变成斜体 * editor.execCommand( 'italic' ); * * //再次对同一文本内容执行, 则文本内容将恢复正常 * editor.execCommand( 'italic' ); * ``` */ /** * 下标文本,与“superscript”命令互斥 * @command subscript * @method execCommand * @remind 把选中的文本内容切换成下标文本, 如果当前选中的文本已经是下标, 则该操作会把文本内容还原成正常文本 * @param { String } cmd 命令字符串 * @example * ```javascript * //editor是编辑器实例 * //对当前选中的文本内容执行下标操作 * //第一次操作, 文本内容将变成下标文本 * editor.execCommand( 'subscript' ); * * //再次对同一文本内容执行, 则文本内容将恢复正常 * editor.execCommand( 'subscript' ); * ``` */ /** * 上标文本,与“subscript”命令互斥 * @command superscript * @method execCommand * @remind 把选中的文本内容切换成上标文本, 如果当前选中的文本已经是上标, 则该操作会把文本内容还原成正常文本 * @param { String } cmd 命令字符串 * @example * ```javascript * //editor是编辑器实例 * //对当前选中的文本内容执行上标操作 * //第一次操作, 文本内容将变成上标文本 * editor.execCommand( 'superscript' ); * * //再次对同一文本内容执行, 则文本内容将恢复正常 * editor.execCommand( 'superscript' ); * ``` */ var basestyles = { bold: ['strong', 'b'], italic: ['em', 'i'], subscript: ['sub'], superscript: ['sup'] }, getObj = function (editor, tagNames) { return domUtils.filterNodeList( editor.selection.getStartElementPath(), tagNames ) }, me = this //添加快捷键 me.addshortcutkey({ Bold: 'ctrl+66', //^B Italic: 'ctrl+73', //^I Underline: 'ctrl+85' //^U }) me.addInputRule(function (root) { utils.each(root.getNodesByTagName('b i'), function (node) { switch (node.tagName) { case 'b': node.tagName = 'strong' break case 'i': node.tagName = 'em' } }) }) for (var style in basestyles) { ;(function (cmd, tagNames) { me.commands[cmd] = { execCommand: function (cmdName) { var range = me.selection.getRange(), obj = getObj(this, tagNames) if (range.collapsed) { if (obj) { var tmpText = me.document.createTextNode('') range.insertNode(tmpText).removeInlineStyle(tagNames) range.setStartBefore(tmpText) domUtils.remove(tmpText) } else { var tmpNode = range.document.createElement(tagNames[0]) if (cmdName == 'superscript' || cmdName == 'subscript') { tmpText = me.document.createTextNode('') range .insertNode(tmpText) .removeInlineStyle(['sub', 'sup']) .setStartBefore(tmpText) .collapse(true) } range.insertNode(tmpNode).setStart(tmpNode, 0) } range.collapse(true) } else { if (cmdName == 'superscript' || cmdName == 'subscript') { if (!obj || obj.tagName.toLowerCase() != cmdName) { range.removeInlineStyle(['sub', 'sup']) } } obj ? range.removeInlineStyle(tagNames) : range.applyInlineStyle(tagNames[0]) } range.select() }, queryCommandState: function () { return getObj(this, tagNames) ? 1 : 0 } } })(style, basestyles[style]) } } // plugins/elementpath.js /** * 选取路径命令 * @file */ UE.plugins['elementpath'] = function () { var currentLevel, tagNames, me = this me.setOpt('elementPathEnabled', true) if (!me.options.elementPathEnabled) { return } me.commands['elementpath'] = { execCommand: function (cmdName, level) { var start = tagNames[level], range = me.selection.getRange() currentLevel = level * 1 range.selectNode(start).select() }, queryCommandValue: function () { //产生一个副本,不能修改原来的startElementPath; var parents = [].concat(this.selection.getStartElementPath()).reverse(), names = [] tagNames = parents for (var i = 0, ci; (ci = parents[i]); i++) { if (ci.nodeType == 3) { continue } var name = ci.tagName.toLowerCase() if (name == 'img' && ci.getAttribute('anchorname')) { name = 'anchor' } names[i] = name if (currentLevel == i) { currentLevel = -1 break } } return names } } } // plugins/formatmatch.js /** * 格式刷,只格式inline的 * @file * @since 1.2.6.1 */ /** * 格式刷 * @command formatmatch * @method execCommand * @remind 该操作不能复制段落格式 * @param { String } cmd 命令字符串 * @example * ```javascript * //editor是编辑器实例 * //获取格式刷 * editor.execCommand( 'formatmatch' ); * ``` */ UE.plugins['formatmatch'] = function () { var me = this, list = [], img, flag = 0 me.addListener('reset', function () { list = [] flag = 0 }) function addList(type, evt) { if (browser.webkit) { var target = evt.target.tagName == 'IMG' ? evt.target : null } function addFormat(range) { if (text) { range.selectNode(text) } return range.applyInlineStyle(list[list.length - 1].tagName, null, list) } me.undoManger && me.undoManger.save() var range = me.selection.getRange(), imgT = target || range.getClosedNode() if (img && imgT && imgT.tagName == 'IMG') { //trace:964 imgT.style.cssText += ';float:' + (img.style.cssFloat || img.style.styleFloat || 'none') + ';display:' + (img.style.display || 'inline') img = null } else { if (!img) { var collapsed = range.collapsed if (collapsed) { var text = me.document.createTextNode('match') range.insertNode(text).select() } me.__hasEnterExecCommand = true //不能把block上的属性干掉 //trace:1553 var removeFormatAttributes = me.options.removeFormatAttributes me.options.removeFormatAttributes = '' me.execCommand('removeformat') me.options.removeFormatAttributes = removeFormatAttributes me.__hasEnterExecCommand = false //trace:969 range = me.selection.getRange() if (list.length) { addFormat(range) } if (text) { range.setStartBefore(text).collapse(true) } range.select() text && domUtils.remove(text) } } me.undoManger && me.undoManger.save() me.removeListener('mouseup', addList) flag = 0 } me.commands['formatmatch'] = { execCommand: function (cmdName) { if (flag) { flag = 0 list = [] me.removeListener('mouseup', addList) return } var range = me.selection.getRange() img = range.getClosedNode() if (!img || img.tagName != 'IMG') { range.collapse(true).shrinkBoundary() var start = range.startContainer list = domUtils.findParents(start, true, function (node) { return !domUtils.isBlockElm(node) && node.nodeType == 1 }) //a不能加入格式刷, 并且克隆节点 for (var i = 0, ci; (ci = list[i]); i++) { if (ci.tagName == 'A') { list.splice(i, 1) break } } } me.addListener('mouseup', addList) flag = 1 }, queryCommandState: function () { return flag }, notNeedUndo: 1 } } // plugins/searchreplace.js ///import core ///commands 查找替换 ///commandsName SearchReplace ///commandsTitle 查询替换 ///commandsDialog dialogs\searchreplace /** * @description 查找替换 * @author zhanyi */ UE.plugin.register('searchreplace', function () { var me = this var _blockElm = { table: 1, tbody: 1, tr: 1, ol: 1, ul: 1 } function findTextInString(textContent, opt, currentIndex) { var str = opt.searchStr if (opt.dir == -1) { textContent = textContent.split('').reverse().join('') str = str.split('').reverse().join('') currentIndex = textContent.length - currentIndex } var reg = new RegExp(str, 'g' + (opt.casesensitive ? '' : 'i')), match while ((match = reg.exec(textContent))) { if (match.index >= currentIndex) { return opt.dir == -1 ? textContent.length - match.index - opt.searchStr.length : match.index } } return -1 } function findTextBlockElm(node, currentIndex, opt) { var textContent, index, methodName = opt.all || opt.dir == 1 ? 'getNextDomNode' : 'getPreDomNode' if (domUtils.isBody(node)) { node = node.firstChild } var first = 1 while (node) { textContent = node.nodeType == 3 ? node.nodeValue : node[browser.ie ? 'innerText' : 'textContent'] index = findTextInString(textContent, opt, currentIndex) first = 0 if (index != -1) { return { node: node, index: index } } node = domUtils[methodName](node) while (node && _blockElm[node.nodeName.toLowerCase()]) { node = domUtils[methodName](node, true) } if (node) { currentIndex = opt.dir == -1 ? (node.nodeType == 3 ? node.nodeValue : node[browser.ie ? 'innerText' : 'textContent'] ).length : 0 } } } function findNTextInBlockElm(node, index, str) { var currentIndex = 0, currentNode = node.firstChild, currentNodeLength = 0, result while (currentNode) { if (currentNode.nodeType == 3) { currentNodeLength = currentNode.nodeValue.replace( /(^[\t\r\n]+)|([\t\r\n]+$)/, '' ).length currentIndex += currentNodeLength if (currentIndex >= index) { return { node: currentNode, index: currentNodeLength - (currentIndex - index) } } } else if (!dtd.$empty[currentNode.tagName]) { currentNodeLength = currentNode[ browser.ie ? 'innerText' : 'textContent' ].replace(/(^[\t\r\n]+)|([\t\r\n]+$)/, '').length currentIndex += currentNodeLength if (currentIndex >= index) { result = findNTextInBlockElm( currentNode, currentNodeLength - (currentIndex - index), str ) if (result) { return result } } } currentNode = domUtils.getNextDomNode(currentNode) } } function searchReplace(me, opt) { var rng = me.selection.getRange(), startBlockNode, searchStr = opt.searchStr, span = me.document.createElement('span') span.innerHTML = '$$ueditor_searchreplace_key$$' rng.shrinkBoundary(true) //判断是不是第一次选中 if (!rng.collapsed) { rng.select() var rngText = me.selection.getText() if ( new RegExp( '^' + opt.searchStr + '$', opt.casesensitive ? '' : 'i' ).test(rngText) ) { if (opt.replaceStr != undefined) { replaceText(rng, opt.replaceStr) rng.select() return true } else { rng.collapse(opt.dir == -1) } } } rng.insertNode(span) rng.enlargeToBlockElm(true) startBlockNode = rng.startContainer var currentIndex = startBlockNode[ browser.ie ? 'innerText' : 'textContent' ].indexOf('$$ueditor_searchreplace_key$$') rng.setStartBefore(span) domUtils.remove(span) var result = findTextBlockElm(startBlockNode, currentIndex, opt) if (result) { var rngStart = findNTextInBlockElm(result.node, result.index, searchStr) var rngEnd = findNTextInBlockElm( result.node, result.index + searchStr.length, searchStr ) rng .setStart(rngStart.node, rngStart.index) .setEnd(rngEnd.node, rngEnd.index) if (opt.replaceStr !== undefined) { replaceText(rng, opt.replaceStr) } rng.select() return true } else { rng.setCursor() } } function replaceText(rng, str) { str = me.document.createTextNode(str) rng.deleteContents().insertNode(str) } return { commands: { searchreplace: { execCommand: function (cmdName, opt) { utils.extend( opt, { all: false, casesensitive: false, dir: 1 }, true ) var num = 0 if (opt.all) { var rng = me.selection.getRange(), first = me.body.firstChild if (first && first.nodeType == 1) { rng.setStart(first, 0) rng.shrinkBoundary(true) } else if (first.nodeType == 3) { rng.setStartBefore(first) } rng.collapse(true).select(true) if (opt.replaceStr !== undefined) { me.fireEvent('saveScene') } while (searchReplace(this, opt)) { num++ } if (num) { me.fireEvent('saveScene') } } else { if (opt.replaceStr !== undefined) { me.fireEvent('saveScene') } if (searchReplace(this, opt)) { num++ } if (num) { me.fireEvent('saveScene') } } return num }, notNeedUndo: 1 } } } }) // plugins/customstyle.js /** * 自定义样式 * @file * @since 1.2.6.1 */ /** * 根据config配置文件里“customstyle”选项的值对匹配的标签执行样式替换。 * @command customstyle * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand( 'customstyle' ); * ``` */ UE.plugins['customstyle'] = function () { var me = this me.setOpt({ customstyle: [ { tag: 'h1', name: 'tc', style: 'font-size:32px;font-weight:bold;border-bottom:#ccc 2px solid;padding:0 4px 0 0;text-align:center;margin:0 0 20px 0;' }, { tag: 'h1', name: 'tl', style: 'font-size:32px;font-weight:bold;border-bottom:#ccc 2px solid;padding:0 4px 0 0;text-align:left;margin:0 0 10px 0;' }, { tag: 'span', name: 'im', style: 'font-size:16px;font-style:italic;font-weight:bold;line-height:18px;' }, { tag: 'span', name: 'hi', style: 'font-size:16px;font-style:italic;font-weight:bold;color:rgb(51, 153, 204);line-height:18px;' } ] }) me.commands['customstyle'] = { execCommand: function (cmdName, obj) { var me = this, tagName = obj.tag, node = domUtils.findParent( me.selection.getStart(), function (node) { return node.getAttribute('label') }, true ), range, bk, tmpObj = {} for (var p in obj) { if (obj[p] !== undefined) tmpObj[p] = obj[p] } delete tmpObj.tag if (node && node.getAttribute('label') == obj.label) { range = this.selection.getRange() bk = range.createBookmark() if (range.collapsed) { //trace:1732 删掉自定义标签,要有p来回填站位 if (dtd.$block[node.tagName]) { var fillNode = me.document.createElement('p') domUtils.moveChild(node, fillNode) node.parentNode.insertBefore(fillNode, node) domUtils.remove(node) } else { domUtils.remove(node, true) } } else { var common = domUtils.getCommonAncestor(bk.start, bk.end), nodes = domUtils.getElementsByTagName(common, tagName) if (new RegExp(tagName, 'i').test(common.tagName)) { nodes.push(common) } for (var i = 0, ni; (ni = nodes[i++]); ) { if (ni.getAttribute('label') == obj.label) { var ps = domUtils.getPosition(ni, bk.start), pe = domUtils.getPosition(ni, bk.end) if ( (ps & domUtils.POSITION_FOLLOWING || ps & domUtils.POSITION_CONTAINS) && (pe & domUtils.POSITION_PRECEDING || pe & domUtils.POSITION_CONTAINS) ) if (dtd.$block[tagName]) { var fillNode = me.document.createElement('p') domUtils.moveChild(ni, fillNode) ni.parentNode.insertBefore(fillNode, ni) } domUtils.remove(ni, true) } } node = domUtils.findParent( common, function (node) { return node.getAttribute('label') == obj.label }, true ) if (node) { domUtils.remove(node, true) } } range.moveToBookmark(bk).select() } else { if (dtd.$block[tagName]) { this.execCommand('paragraph', tagName, tmpObj, 'customstyle') range = me.selection.getRange() if (!range.collapsed) { range.collapse() node = domUtils.findParent( me.selection.getStart(), function (node) { return node.getAttribute('label') == obj.label }, true ) var pNode = me.document.createElement('p') domUtils.insertAfter(node, pNode) domUtils.fillNode(me.document, pNode) range.setStart(pNode, 0).setCursor() } } else { range = me.selection.getRange() if (range.collapsed) { node = me.document.createElement(tagName) domUtils.setAttributes(node, tmpObj) range.insertNode(node).setStart(node, 0).setCursor() return } bk = range.createBookmark() range.applyInlineStyle(tagName, tmpObj).moveToBookmark(bk).select() } } }, queryCommandValue: function () { var parent = domUtils.filterNodeList( this.selection.getStartElementPath(), function (node) { return node.getAttribute('label') } ) return parent ? parent.getAttribute('label') : '' } } //当去掉customstyle是,如果是块元素,用p代替 me.addListener('keyup', function (type, evt) { var keyCode = evt.keyCode || evt.which if (keyCode == 32 || keyCode == 13) { var range = me.selection.getRange() if (range.collapsed) { var node = domUtils.findParent( me.selection.getStart(), function (node) { return node.getAttribute('label') }, true ) if (node && dtd.$block[node.tagName] && domUtils.isEmptyNode(node)) { var p = me.document.createElement('p') domUtils.insertAfter(node, p) domUtils.fillNode(me.document, p) domUtils.remove(node) range.setStart(p, 0).setCursor() } } } }) } // plugins/catchremoteimage.js ///import core ///commands 远程图片抓取 ///commandsName catchRemoteImage,catchremoteimageenable ///commandsTitle 远程图片抓取 /** * 远程图片抓取,当开启本插件时所有不符合本地域名的图片都将被抓取成为本地服务器上的图片 */ UE.plugins['catchremoteimage'] = function () { var me = this, ajax = UE.ajax /* 设置默认值 */ if (me.options.catchRemoteImageEnable === false) return me.setOpt({ catchRemoteImageEnable: false }) me.addListener('afterpaste', function () { me.fireEvent('catchRemoteImage') }) me.addListener('catchRemoteImage', function () { var catcherLocalDomain = me.getOpt('catcherLocalDomain'), catcherActionUrl = me.getActionUrl(me.getOpt('catcherActionName')), catcherUrlPrefix = me.getOpt('catcherUrlPrefix'), catcherFieldName = me.getOpt('catcherFieldName') var remoteImages = [], imgs = domUtils.getElementsByTagName(me.document, 'img'), test = function (src, urls) { if (src.indexOf(location.host) != -1 || /(^\.)|(^\/)/.test(src)) { return true } if (urls) { for (var j = 0, url; (url = urls[j++]); ) { if (src.indexOf(url) !== -1) { return true } } } return false } for (var i = 0, ci; (ci = imgs[i++]); ) { if (ci.getAttribute('word_img')) { continue } var src = ci.getAttribute('_src') || ci.src || '' if (/^(https?|ftp):/i.test(src) && !test(src, catcherLocalDomain)) { remoteImages.push(src) } } if (remoteImages.length) { catchremoteimage(remoteImages, { //成功抓取 success: function (r) { try { var info = r.state !== undefined ? r : eval('(' + r.responseText + ')') } catch (e) { return } /* 获取源路径和新路径 */ var i, j, ci, cj, oldSrc, newSrc, list = info.list for (i = 0; (ci = imgs[i++]); ) { oldSrc = ci.getAttribute('_src') || ci.src || '' for (j = 0; (cj = list[j++]); ) { if (oldSrc == cj.source && cj.state == 'SUCCESS') { //抓取失败时不做替换处理 newSrc = catcherUrlPrefix + cj.url domUtils.setAttributes(ci, { src: newSrc, _src: newSrc }) break } } } me.fireEvent('catchremotesuccess') }, //回调失败,本次请求超时 error: function () { me.fireEvent('catchremoteerror') } }) } function catchremoteimage(imgs, callbacks) { var params = utils.serializeParam(me.queryCommandValue('serverparam')) || '', url = utils.formatUrl( catcherActionUrl + (catcherActionUrl.indexOf('?') == -1 ? '?' : '&') + params ), isJsonp = utils.isCrossDomainUrl(url), opt = { method: 'POST', dataType: isJsonp ? 'jsonp' : '', timeout: 60000, //单位:毫秒,回调请求超时设置。目标用户如果网速不是很快的话此处建议设置一个较大的数值 onsuccess: callbacks['success'], onerror: callbacks['error'] } opt[catcherFieldName] = imgs ajax.request(url, opt) } }) } // plugins/snapscreen.js /** * 截屏插件,为UEditor提供插入支持 * @file * @since 1.4.2 */ UE.plugin.register('snapscreen', function () { var me = this var snapplugin function getLocation(url) { var search, a = document.createElement('a'), params = utils.serializeParam(me.queryCommandValue('serverparam')) || '' a.href = url if (browser.ie) { a.href = a.href } search = a.search if (params) { search = search + (search.indexOf('?') == -1 ? '?' : '&') + params search = search.replace(/[&]+/gi, '&') } return { port: a.port, hostname: a.hostname, path: a.pathname + search || +a.hash } } return { commands: { /** * 字体背景颜色 * @command snapscreen * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand('snapscreen'); * ``` */ snapscreen: { execCommand: function (cmd) { var url, local, res var lang = me.getLang('snapScreen_plugin') if (!snapplugin) { var container = me.container var doc = me.container.ownerDocument || me.container.document snapplugin = doc.createElement('object') try { snapplugin.type = 'application/x-pluginbaidusnap' } catch (e) { return } snapplugin.style.cssText = 'position:absolute;left:-9999px;width:0;height:0;' snapplugin.setAttribute('width', '0') snapplugin.setAttribute('height', '0') container.appendChild(snapplugin) } function onSuccess(rs) { try { rs = eval('(' + rs + ')') if (rs.state == 'SUCCESS') { var opt = me.options me.execCommand('insertimage', { src: opt.snapscreenUrlPrefix + rs.url, _src: opt.snapscreenUrlPrefix + rs.url, alt: rs.title || '', floatStyle: opt.snapscreenImgAlign }) } else { alert(rs.state) } } catch (e) { alert(lang.callBackErrorMsg) } } url = me.getActionUrl(me.getOpt('snapscreenActionName')) local = getLocation(url) setTimeout(function () { try { res = snapplugin.saveSnapshot( local.hostname, local.path, local.port ) } catch (e) { me.ui._dialogs['snapscreenDialog'].open() return } onSuccess(res) }, 50) }, queryCommandState: function () { return navigator.userAgent.indexOf('Windows', 0) != -1 ? 0 : -1 } } } } }) // plugins/insertparagraph.js /** * 插入段落 * @file * @since 1.2.6.1 */ /** * 插入段落 * @command insertparagraph * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * //editor是编辑器实例 * editor.execCommand( 'insertparagraph' ); * ``` */ UE.commands['insertparagraph'] = { execCommand: function (cmdName, front) { var me = this, range = me.selection.getRange(), start = range.startContainer, tmpNode while (start) { if (domUtils.isBody(start)) { break } tmpNode = start start = start.parentNode } if (tmpNode) { var p = me.document.createElement('p') if (front) { tmpNode.parentNode.insertBefore(p, tmpNode) } else { tmpNode.parentNode.insertBefore(p, tmpNode.nextSibling) } domUtils.fillNode(me.document, p) range.setStart(p, 0).setCursor(false, true) } } } // plugins/webapp.js /** * 百度应用 * @file * @since 1.2.6.1 */ /** * 插入百度应用 * @command webapp * @method execCommand * @remind 需要百度APPKey * @remind 百度应用主页: http://app.baidu.com/ * @param { Object } appOptions 应用所需的参数项, 支持的key有: title=>应用标题, width=>应用容器宽度, * height=>应用容器高度,logo=>应用logo,url=>应用地址 * @example * ```javascript * //editor是编辑器实例 * //在编辑器里插入一个“植物大战僵尸”的APP * editor.execCommand( 'webapp' , { * title: '植物大战僵尸', * width: 560, * height: 465, * logo: '应用展示的图片', * url: '百度应用的地址' * } ); * ``` */ //UE.plugins['webapp'] = function () { // var me = this; // function createInsertStr( obj, toIframe, addParagraph ) { // return !toIframe ? // (addParagraph ? '

    ' : '') + '' + // (addParagraph ? '

    ' : '') // : // ''; // } // // function switchImgAndIframe( img2frame ) { // var tmpdiv, // nodes = domUtils.getElementsByTagName( me.document, !img2frame ? "iframe" : "img" ); // for ( var i = 0, node; node = nodes[i++]; ) { // if ( node.className != "edui-faked-webapp" ){ // continue; // } // tmpdiv = me.document.createElement( "div" ); // tmpdiv.innerHTML = createInsertStr( img2frame ? {url:node.getAttribute( "_url" ), width:node.width, height:node.height,title:node.title,logo:node.style.backgroundImage.replace("url(","").replace(")","")} : {url:node.getAttribute( "src", 2 ),title:node.title, width:node.width, height:node.height,logo:node.getAttribute("logo_url")}, img2frame ? true : false,false ); // node.parentNode.replaceChild( tmpdiv.firstChild, node ); // } // } // // me.addListener( "beforegetcontent", function () { // switchImgAndIframe( true ); // } ); // me.addListener( 'aftersetcontent', function () { // switchImgAndIframe( false ); // } ); // me.addListener( 'aftergetcontent', function ( cmdName ) { // if ( cmdName == 'aftergetcontent' && me.queryCommandState( 'source' ) ){ // return; // } // switchImgAndIframe( false ); // } ); // // me.commands['webapp'] = { // execCommand:function ( cmd, obj ) { // me.execCommand( "inserthtml", createInsertStr( obj, false,true ) ); // } // }; //}; UE.plugin.register('webapp', function () { var me = this function createInsertStr(obj, toEmbed) { return !toEmbed ? '' : '' } return { outputRule: function (root) { utils.each(root.getNodesByTagName('img'), function (node) { var html if (node.getAttr('class') == 'edui-faked-webapp') { html = createInsertStr( { title: node.getAttr('title'), width: node.getAttr('width'), height: node.getAttr('height'), align: node.getAttr('align'), cssfloat: node.getStyle('float'), url: node.getAttr('_url'), logo: node.getAttr('_logo_url') }, true ) var embed = UE.uNode.createElement(html) node.parentNode.replaceChild(embed, node) } }) }, inputRule: function (root) { utils.each(root.getNodesByTagName('iframe'), function (node) { if (node.getAttr('class') == 'edui-faked-webapp') { var img = UE.uNode.createElement( createInsertStr({ title: node.getAttr('title'), width: node.getAttr('width'), height: node.getAttr('height'), align: node.getAttr('align'), cssfloat: node.getStyle('float'), url: node.getAttr('src'), logo: node.getAttr('logo_url') }) ) node.parentNode.replaceChild(img, node) } }) }, commands: { /** * 插入百度应用 * @command webapp * @method execCommand * @remind 需要百度APPKey * @remind 百度应用主页: http://app.baidu.com/ * @param { Object } appOptions 应用所需的参数项, 支持的key有: title=>应用标题, width=>应用容器宽度, * height=>应用容器高度,logo=>应用logo,url=>应用地址 * @example * ```javascript * //editor是编辑器实例 * //在编辑器里插入一个“植物大战僵尸”的APP * editor.execCommand( 'webapp' , { * title: '植物大战僵尸', * width: 560, * height: 465, * logo: '应用展示的图片', * url: '百度应用的地址' * } ); * ``` */ webapp: { execCommand: function (cmd, obj) { var me = this, str = createInsertStr( utils.extend(obj, { align: 'none' }), false ) me.execCommand('inserthtml', str) }, queryCommandState: function () { var me = this, img = me.selection.getRange().getClosedNode(), flag = img && img.className == 'edui-faked-webapp' return flag ? 1 : 0 } } } } }) // plugins/template.js ///import core ///import plugins\inserthtml.js ///import plugins\cleardoc.js ///commands 模板 ///commandsName template ///commandsTitle 模板 ///commandsDialog dialogs\template UE.plugins['template'] = function () { UE.commands['template'] = { execCommand: function (cmd, obj) { obj.html && this.execCommand('inserthtml', obj.html) } } this.addListener('click', function (type, evt) { var el = evt.target || evt.srcElement, range = this.selection.getRange() var tnode = domUtils.findParent( el, function (node) { if (node.className && domUtils.hasClass(node, 'ue_t')) { return node } }, true ) tnode && range.selectNode(tnode).shrinkBoundary().select() }) this.addListener('keydown', function (type, evt) { var range = this.selection.getRange() if (!range.collapsed) { if (!evt.ctrlKey && !evt.metaKey && !evt.shiftKey && !evt.altKey) { var tnode = domUtils.findParent( range.startContainer, function (node) { if (node.className && domUtils.hasClass(node, 'ue_t')) { return node } }, true ) if (tnode) { domUtils.removeClasses(tnode, ['ue_t']) } } } }) } // plugins/music.js /** * 插入音乐命令 * @file */ UE.plugin.register('music', function () { var me = this function creatInsertStr(url, width, height, align, cssfloat, toEmbed) { return !toEmbed ? '' : '' } return { outputRule: function (root) { utils.each(root.getNodesByTagName('img'), function (node) { var html if (node.getAttr('class') == 'edui-faked-music') { var cssfloat = node.getStyle('float') var align = node.getAttr('align') html = creatInsertStr( node.getAttr('_url'), node.getAttr('width'), node.getAttr('height'), align, cssfloat, true ) var embed = UE.uNode.createElement(html) node.parentNode.replaceChild(embed, node) } }) }, inputRule: function (root) { utils.each(root.getNodesByTagName('embed'), function (node) { if (node.getAttr('class') == 'edui-faked-music') { var cssfloat = node.getStyle('float') var align = node.getAttr('align') html = creatInsertStr( node.getAttr('src'), node.getAttr('width'), node.getAttr('height'), align, cssfloat, false ) var img = UE.uNode.createElement(html) node.parentNode.replaceChild(img, node) } }) }, commands: { /** * 插入音乐 * @command music * @method execCommand * @param { Object } musicOptions 插入音乐的参数项, 支持的key有: url=>音乐地址; * width=>音乐容器宽度;height=>音乐容器高度;align=>音乐文件的对齐方式, 可选值有: left, center, right, none * @example * ```javascript * //editor是编辑器实例 * //在编辑器里插入一个“植物大战僵尸”的APP * editor.execCommand( 'music' , { * width: 400, * height: 95, * align: "center", * url: "音乐地址" * } ); * ``` */ music: { execCommand: function (cmd, musicObj) { var me = this, str = creatInsertStr( musicObj.url, musicObj.width || 400, musicObj.height || 95, 'none', false ) me.execCommand('inserthtml', str) }, queryCommandState: function () { var me = this, img = me.selection.getRange().getClosedNode(), flag = img && img.className == 'edui-faked-music' return flag ? 1 : 0 } } } } }) // plugins/autoupload.js /** * @description * 1.拖放文件到编辑区域,自动上传并插入到选区 * 2.插入粘贴板的图片,自动上传并插入到选区 * @author Jinqn * @date 2013-10-14 */ UE.plugin.register('autoupload', function () { function sendAndInsertFile(file, editor) { var me = editor //模拟数据 var fieldName, urlPrefix, maxSize, allowFiles, actionUrl, loadingHtml, errorHandler, successHandler, filetype = /image\/\w+/i.test(file.type) ? 'image' : 'file', loadingId = 'loading_' + (+new Date()).toString(36) fieldName = me.getOpt(filetype + 'FieldName') urlPrefix = me.getOpt(filetype + 'UrlPrefix') maxSize = me.getOpt(filetype + 'MaxSize') allowFiles = me.getOpt(filetype + 'AllowFiles') actionUrl = me.getActionUrl(me.getOpt(filetype + 'ActionName')) errorHandler = function (title) { var loader = me.document.getElementById(loadingId) loader && domUtils.remove(loader) me.fireEvent('showmessage', { id: loadingId, content: title, type: 'error', timeout: 4000 }) } if (filetype == 'image') { loadingHtml = '' successHandler = function (data) { var link = urlPrefix + data.url, loader = me.document.getElementById(loadingId) if (loader) { loader.setAttribute('src', link) loader.setAttribute('_src', link) loader.setAttribute('title', data.title || '') loader.setAttribute('alt', data.original || '') loader.removeAttribute('id') domUtils.removeClasses(loader, 'loadingclass') } } } else { loadingHtml = '

    ' + '' + '

    ' successHandler = function (data) { var link = urlPrefix + data.url, loader = me.document.getElementById(loadingId) var rng = me.selection.getRange(), bk = rng.createBookmark() rng.selectNode(loader).select() me.execCommand('insertfile', { url: link }) rng.moveToBookmark(bk).select() } } /* 插入loading的占位符 */ me.execCommand('inserthtml', loadingHtml) /* 判断后端配置是否没有加载成功 */ if (!me.getOpt(filetype + 'ActionName')) { errorHandler(me.getLang('autoupload.errorLoadConfig')) return } /* 判断文件大小是否超出限制 */ if (file.size > maxSize) { errorHandler(me.getLang('autoupload.exceedSizeError')) return } /* 判断文件格式是否超出允许 */ var fileext = file.name ? file.name.substr(file.name.lastIndexOf('.')) : '' if ( (fileext && filetype != 'image') || (allowFiles && (allowFiles.join('') + '.').indexOf(fileext.toLowerCase() + '.') == -1) ) { errorHandler(me.getLang('autoupload.exceedTypeError')) return } /* 创建Ajax并提交 */ var xhr = new XMLHttpRequest(), fd = new FormData(), params = utils.serializeParam(me.queryCommandValue('serverparam')) || '', url = utils.formatUrl( actionUrl + (actionUrl.indexOf('?') == -1 ? '?' : '&') + params ) fd.append( fieldName, file, file.name || 'blob.' + file.type.substr('image/'.length) ) fd.append('type', 'ajax') xhr.open('post', url, true) xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest') xhr.addEventListener('load', function (e) { try { var json = new Function('return ' + utils.trim(e.target.response))() if (json.state == 'SUCCESS' && json.url) { successHandler(json) } else { errorHandler(json.state) } } catch (er) { errorHandler(me.getLang('autoupload.loadError')) } }) xhr.send(fd) } function getPasteImage(e) { return e.clipboardData && e.clipboardData.items && e.clipboardData.items.length == 1 && /^image\//.test(e.clipboardData.items[0].type) ? e.clipboardData.items : null } function getDropImage(e) { return e.dataTransfer && e.dataTransfer.files ? e.dataTransfer.files : null } return { outputRule: function (root) { utils.each(root.getNodesByTagName('img'), function (n) { if ( /\b(loaderrorclass)|(bloaderrorclass)\b/.test(n.getAttr('class')) ) { n.parentNode.removeChild(n) } }) utils.each(root.getNodesByTagName('p'), function (n) { if (/\bloadpara\b/.test(n.getAttr('class'))) { n.parentNode.removeChild(n) } }) }, bindEvents: { //插入粘贴板的图片,拖放插入图片 ready: function (e) { var me = this if (window.FormData && window.FileReader) { domUtils.on(me.body, 'paste drop', function (e) { var hasImg = false, items //获取粘贴板文件列表或者拖放文件列表 items = e.type == 'paste' ? getPasteImage(e) : getDropImage(e) if (items) { var len = items.length, file while (len--) { file = items[len] if (file.getAsFile) file = file.getAsFile() if (file && file.size > 0) { sendAndInsertFile(file, me) hasImg = true } } hasImg && e.preventDefault() } }) //取消拖放图片时出现的文字光标位置提示 domUtils.on(me.body, 'dragover', function (e) { if (e.dataTransfer.types[0] == 'Files') { e.preventDefault() } }) //设置loading的样式 utils.cssRule( 'loading', ".loadingclass{display:inline-block;cursor:default;background: url('" + this.options.themePath + this.options.theme + "/images/loading.gif') no-repeat center center transparent;border:1px solid #cccccc;margin-left:1px;height: 22px;width: 22px;}\n" + ".loaderrorclass{display:inline-block;cursor:default;background: url('" + this.options.themePath + this.options.theme + "/images/loaderror.png') no-repeat center center transparent;border:1px solid #cccccc;margin-right:1px;height: 22px;width: 22px;" + '}', this.document ) } } } } }) // plugins/autosave.js UE.plugin.register('autosave', function () { var me = this, //无限循环保护 lastSaveTime = new Date(), //最小保存间隔时间 MIN_TIME = 20, //auto save key saveKey = null function save(editor) { var saveData if (new Date() - lastSaveTime < MIN_TIME) { return } if (!editor.hasContents()) { //这里不能调用命令来删除, 会造成事件死循环 saveKey && me.removePreferences(saveKey) return } lastSaveTime = new Date() editor._saveFlag = null saveData = me.body.innerHTML if ( editor.fireEvent('beforeautosave', { content: saveData }) === false ) { return } me.setPreferences(saveKey, saveData) editor.fireEvent('afterautosave', { content: saveData }) } return { defaultOptions: { //默认间隔时间 saveInterval: 500, enableAutoSave: true // HaoChuan9421 }, bindEvents: { ready: function () { var _suffix = '-drafts-data', key = null if (me.key) { key = me.key + _suffix } else { key = (me.container.parentNode.id || 'ue-common') + _suffix } //页面地址+编辑器ID 保持唯一 saveKey = (location.protocol + location.host + location.pathname).replace( /[.:\/]/g, '_' ) + key }, contentchange: function () { // HaoChuan9421 if (!me.getOpt('enableAutoSave')) { return } if (!saveKey) { return } if (me._saveFlag) { window.clearTimeout(me._saveFlag) } if (me.options.saveInterval > 0) { me._saveFlag = window.setTimeout(function () { save(me) }, me.options.saveInterval) } else { save(me) } } }, commands: { clearlocaldata: { execCommand: function (cmd, name) { if (saveKey && me.getPreferences(saveKey)) { me.removePreferences(saveKey) } }, notNeedUndo: true, ignoreContentChange: true }, getlocaldata: { execCommand: function (cmd, name) { return saveKey ? me.getPreferences(saveKey) || '' : '' }, notNeedUndo: true, ignoreContentChange: true }, drafts: { execCommand: function (cmd, name) { if (saveKey) { me.body.innerHTML = me.getPreferences(saveKey) || '

    ' + domUtils.fillHtml + '

    ' me.focus(true) } }, queryCommandState: function () { return saveKey ? (me.getPreferences(saveKey) === null ? -1 : 0) : -1 }, notNeedUndo: true, ignoreContentChange: true } } } }) // plugins/charts.js UE.plugin.register('charts', function () { var me = this return { bindEvents: { chartserror: function () {} }, commands: { charts: { execCommand: function (cmd, data) { var tableNode = domUtils.findParentByTagName( this.selection.getRange().startContainer, 'table', true ), flagText = [], config = {} if (!tableNode) { return false } if (!validData(tableNode)) { me.fireEvent('chartserror') return false } config.title = data.title || '' config.subTitle = data.subTitle || '' config.xTitle = data.xTitle || '' config.yTitle = data.yTitle || '' config.suffix = data.suffix || '' config.tip = data.tip || '' //数据对齐方式 config.dataFormat = data.tableDataFormat || '' //图表类型 config.chartType = data.chartType || 0 for (var key in config) { if (!config.hasOwnProperty(key)) { continue } flagText.push(key + ':' + config[key]) } tableNode.setAttribute('data-chart', flagText.join(';')) domUtils.addClass(tableNode, 'edui-charts-table') }, queryCommandState: function (cmd, name) { var tableNode = domUtils.findParentByTagName( this.selection.getRange().startContainer, 'table', true ) return tableNode && validData(tableNode) ? 0 : -1 } } }, inputRule: function (root) { utils.each(root.getNodesByTagName('table'), function (tableNode) { if (tableNode.getAttr('data-chart') !== undefined) { tableNode.setAttr('style') } }) }, outputRule: function (root) { utils.each(root.getNodesByTagName('table'), function (tableNode) { if (tableNode.getAttr('data-chart') !== undefined) { tableNode.setAttr('style', 'display: none;') } }) } } function validData(table) { var firstRows = null, cellCount = 0 //行数不够 if (table.rows.length < 2) { return false } //列数不够 if (table.rows[0].cells.length < 2) { return false } //第一行所有cell必须是th firstRows = table.rows[0].cells cellCount = firstRows.length for (var i = 0, cell; (cell = firstRows[i]); i++) { if (cell.tagName.toLowerCase() !== 'th') { return false } } for (var i = 1, row; (row = table.rows[i]); i++) { //每行单元格数不匹配, 返回false if (row.cells.length != cellCount) { return false } //第一列不是th也返回false if (row.cells[0].tagName.toLowerCase() !== 'th') { return false } for (var j = 1, cell; (cell = row.cells[j]); j++) { var value = utils.trim(cell.innerText || cell.textContent || '') value = value .replace(new RegExp(UE.dom.domUtils.fillChar, 'g'), '') .replace(/^\s+|\s+$/g, '') //必须是数字 if (!/^\d*\.?\d+$/.test(value)) { return false } } } return true } }) // plugins/section.js /** * 目录大纲支持插件 * @file * @since 1.3.0 */ UE.plugin.register('section', function () { /* 目录节点对象 */ function Section(option) { this.tag = '' ;(this.level = -1), (this.dom = null) this.nextSection = null this.previousSection = null this.parentSection = null this.startAddress = [] this.endAddress = [] this.children = [] } function getSection(option) { var section = new Section() return utils.extend(section, option) } function getNodeFromAddress(startAddress, root) { var current = root for (var i = 0; i < startAddress.length; i++) { if (!current.childNodes) return null current = current.childNodes[startAddress[i]] } return current } var me = this return { bindMultiEvents: { type: 'aftersetcontent afterscencerestore', handler: function () { me.fireEvent('updateSections') } }, bindEvents: { /* 初始化、拖拽、粘贴、执行setcontent之后 */ ready: function () { me.fireEvent('updateSections') domUtils.on(me.body, 'drop paste', function () { me.fireEvent('updateSections') }) }, /* 执行paragraph命令之后 */ afterexeccommand: function (type, cmd) { if (cmd == 'paragraph') { me.fireEvent('updateSections') } }, /* 部分键盘操作,触发updateSections事件 */ keyup: function (type, e) { var me = this, range = me.selection.getRange() if (range.collapsed != true) { me.fireEvent('updateSections') } else { var keyCode = e.keyCode || e.which if (keyCode == 13 || keyCode == 8 || keyCode == 46) { me.fireEvent('updateSections') } } } }, commands: { getsections: { execCommand: function (cmd, levels) { var levelFn = levels || ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] for (var i = 0; i < levelFn.length; i++) { if (typeof levelFn[i] == 'string') { levelFn[i] = (function (fn) { return function (node) { return node.tagName == fn.toUpperCase() } })(levelFn[i]) } else if (typeof levelFn[i] != 'function') { levelFn[i] = function (node) { return null } } } function getSectionLevel(node) { for (var i = 0; i < levelFn.length; i++) { if (levelFn[i](node)) return i } return -1 } var me = this, Directory = getSection({ level: -1, title: 'root' }), previous = Directory function traversal(node, Directory) { var level, tmpSection = null, parent, child, children = node.childNodes for (var i = 0, len = children.length; i < len; i++) { child = children[i] level = getSectionLevel(child) if (level >= 0) { var address = me.selection .getRange() .selectNode(child) .createAddress(true).startAddress, current = getSection({ tag: child.tagName, title: child.innerText || child.textContent || '', level: level, dom: child, startAddress: utils.clone(address, []), endAddress: utils.clone(address, []), children: [] }) previous.nextSection = current current.previousSection = previous parent = previous while (level <= parent.level) { parent = parent.parentSection } current.parentSection = parent parent.children.push(current) tmpSection = previous = current } else { child.nodeType === 1 && traversal(child, Directory) tmpSection && tmpSection.endAddress[tmpSection.endAddress.length - 1]++ } } } traversal(me.body, Directory) return Directory }, notNeedUndo: true }, movesection: { execCommand: function (cmd, sourceSection, targetSection, isAfter) { var me = this, targetAddress, target if (!sourceSection || !targetSection || targetSection.level == -1) return targetAddress = isAfter ? targetSection.endAddress : targetSection.startAddress target = getNodeFromAddress(targetAddress, me.body) /* 判断目标地址是否被源章节包含 */ if ( !targetAddress || !target || isContainsAddress( sourceSection.startAddress, sourceSection.endAddress, targetAddress ) ) return var startNode = getNodeFromAddress( sourceSection.startAddress, me.body ), endNode = getNodeFromAddress(sourceSection.endAddress, me.body), current, nextNode if (isAfter) { current = endNode while ( current && !( domUtils.getPosition(startNode, current) & domUtils.POSITION_FOLLOWING ) ) { nextNode = current.previousSibling domUtils.insertAfter(target, current) if (current == startNode) break current = nextNode } } else { current = startNode while ( current && !( domUtils.getPosition(current, endNode) & domUtils.POSITION_FOLLOWING ) ) { nextNode = current.nextSibling target.parentNode.insertBefore(current, target) if (current == endNode) break current = nextNode } } me.fireEvent('updateSections') /* 获取地址的包含关系 */ function isContainsAddress( startAddress, endAddress, addressTarget ) { var isAfterStartAddress = false, isBeforeEndAddress = false for (var i = 0; i < startAddress.length; i++) { if (i >= addressTarget.length) break if (addressTarget[i] > startAddress[i]) { isAfterStartAddress = true break } else if (addressTarget[i] < startAddress[i]) { break } } for (var i = 0; i < endAddress.length; i++) { if (i >= addressTarget.length) break if (addressTarget[i] < startAddress[i]) { isBeforeEndAddress = true break } else if (addressTarget[i] > startAddress[i]) { break } } return isAfterStartAddress && isBeforeEndAddress } } }, deletesection: { execCommand: function (cmd, section, keepChildren) { var me = this if (!section) return function getNodeFromAddress(startAddress) { var current = me.body for (var i = 0; i < startAddress.length; i++) { if (!current.childNodes) return null current = current.childNodes[startAddress[i]] } return current } var startNode = getNodeFromAddress(section.startAddress), endNode = getNodeFromAddress(section.endAddress), current = startNode, nextNode if (!keepChildren) { while ( current && domUtils.inDoc(endNode, me.document) && !( domUtils.getPosition(current, endNode) & domUtils.POSITION_FOLLOWING ) ) { nextNode = current.nextSibling domUtils.remove(current) current = nextNode } } else { domUtils.remove(current) } me.fireEvent('updateSections') } }, selectsection: { execCommand: function (cmd, section) { if (!section && !section.dom) return false var me = this, range = me.selection.getRange(), address = { startAddress: utils.clone(section.startAddress, []), endAddress: utils.clone(section.endAddress, []) } address.endAddress[address.endAddress.length - 1]++ range.moveToAddress(address).select().scrollToView() return true }, notNeedUndo: true }, scrolltosection: { execCommand: function (cmd, section) { if (!section && !section.dom) return false var me = this, range = me.selection.getRange(), address = { startAddress: section.startAddress, endAddress: section.endAddress } address.endAddress[address.endAddress.length - 1]++ range.moveToAddress(address).scrollToView() return true }, notNeedUndo: true } } } }) // plugins/simpleupload.js /** * @description * 简单上传:点击按钮,直接选择文件上传。 * 原 UEditor 作者使用了 form 表单 + iframe 的方式上传 * 但由于同源策略的限制,父页面无法访问跨域的 iframe 内容 * 导致无法获取接口返回的数据,使得单图上传无法在跨域的情况下使用 * 这里改为普通的XHR上传,兼容到IE10+ * @author HaoChuan9421 * @date 2018-12-20 */ UE.plugin.register('simpleupload', function () { var me = this, containerBtn, timestrap = (+new Date()).toString(36) function initUploadBtn() { var w = containerBtn.offsetWidth || 20, h = containerBtn.offsetHeight || 20, btnStyle = 'display:block;width:' + w + 'px;height:' + h + 'px;overflow:hidden;border:0;margin:0;padding:0;position:absolute;top:0;left:0;filter:alpha(opacity=0);-moz-opacity:0;-khtml-opacity: 0;opacity: 0;cursor:pointer;' var form = document.createElement('form') var input = document.createElement('input') form.id = 'edui_form_' + timestrap form.enctype = 'multipart/form-data' form.style = btnStyle input.id = 'edui_input_' + timestrap input.type = 'file' input.accept = 'image/*' input.name = me.options.imageFieldName input.style = btnStyle form.appendChild(input) containerBtn.appendChild(form) input.addEventListener('change', function (event) { if (!input.value) return var loadingId = 'loading_' + (+new Date()).toString(36) var imageActionUrl = me.getActionUrl(me.getOpt('imageActionName')) var params = utils.serializeParam(me.queryCommandValue('serverparam')) || '' var action = utils.formatUrl( imageActionUrl + (imageActionUrl.indexOf('?') == -1 ? '?' : '&') + params ) var allowFiles = me.getOpt('imageAllowFiles') console.log(action, 'action', 'allowFiles', allowFiles) me.focus() me.execCommand( 'inserthtml', '' ) function showErrorLoader(title) { if (loadingId) { var loader = me.document.getElementById(loadingId) loader && domUtils.remove(loader) me.fireEvent('showmessage', { id: loadingId, content: title, type: 'error', timeout: 4000 }) } } /* 判断后端配置是否没有加载成功 */ if (!me.getOpt('imageActionName')) { showErrorLoader(me.getLang('autoupload.errorLoadConfig')) return } // 判断文件格式是否错误 var filename = input.value, fileext = filename ? filename.substr(filename.lastIndexOf('.')) : '' if ( !fileext || (allowFiles && (allowFiles.join('') + '.').indexOf(fileext.toLowerCase() + '.') == -1) ) { showErrorLoader(me.getLang('simpleupload.exceedTypeError')) return } console.log('filename', input.value) console.log('准备上传action', action, input) let formData = new FormData() let files = input.files[0] formData.append('file', files) var xhr = new XMLHttpRequest() // xhr.setRequestHeader('Content-Type', 'multipart/form-data') xhr.open('post', action, true) xhr.send(formData) if ( me.options.headers && Object.prototype.toString.apply(me.options.headers) === '[object Object]' ) { for (var key in me.options.headers) { xhr.setRequestHeader(key, me.options.headers[key]) } } xhr.onload = function () { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) { var res = JSON.parse(xhr.responseText) var link = res.data.url if (res.code == 200 && res.data.url) { loader = me.document.getElementById(loadingId) loader.setAttribute('src', link) loader.setAttribute('_src', link) loader.setAttribute('title', res.title || '') loader.setAttribute('alt', res.original || '') loader.removeAttribute('id') domUtils.removeClasses(loader, 'loadingclass') me.fireEvent('contentchange') } else { showErrorLoader(res.state) } } else { console.log('请求失败') showErrorLoader(me.getLang('simpleupload.loadError')) } } xhr.onerror = function () { console.log('请求失败onerror') showErrorLoader(me.getLang('simpleupload.loadError')) } form.reset() }) } return { bindEvents: { ready: function () { //设置loading的样式 utils.cssRule( 'loading', ".loadingclass{display:inline-block;cursor:default;background: url('" + this.options.themePath + this.options.theme + "/images/loading.gif') no-repeat center center transparent;border:1px solid #cccccc;margin-right:1px;height: 22px;width: 22px;}\n" + ".loaderrorclass{display:inline-block;cursor:default;background: url('" + this.options.themePath + this.options.theme + "/images/loaderror.png') no-repeat center center transparent;border:1px solid #cccccc;margin-right:1px;height: 22px;width: 22px;" + '}', this.document ) }, /* 初始化简单上传按钮 */ simpleuploadbtnready: function (type, container) { containerBtn = container me.afterConfigReady(initUploadBtn) } }, outputRule: function (root) { utils.each(root.getNodesByTagName('img'), function (n) { if ( /\b(loaderrorclass)|(bloaderrorclass)\b/.test(n.getAttr('class')) ) { n.parentNode.removeChild(n) } }) } } }) // plugins/serverparam.js /** * 服务器提交的额外参数列表设置插件 * @file * @since 1.2.6.1 */ UE.plugin.register('serverparam', function () { var me = this, serverParam = {} return { commands: { /** * 修改服务器提交的额外参数列表,清除所有项 * @command serverparam * @method execCommand * @param { String } cmd 命令字符串 * @example * ```javascript * editor.execCommand('serverparam'); * editor.queryCommandValue('serverparam'); //返回空 * ``` */ /** * 修改服务器提交的额外参数列表,删除指定项 * @command serverparam * @method execCommand * @param { String } cmd 命令字符串 * @param { String } key 要清除的属性 * @example * ```javascript * editor.execCommand('serverparam', 'name'); //删除属性name * ``` */ /** * 修改服务器提交的额外参数列表,使用键值添加项 * @command serverparam * @method execCommand * @param { String } cmd 命令字符串 * @param { String } key 要添加的属性 * @param { String } value 要添加属性的值 * @example * ```javascript * editor.execCommand('serverparam', 'name', 'hello'); * editor.queryCommandValue('serverparam'); //返回对象 {'name': 'hello'} * ``` */ /** * 修改服务器提交的额外参数列表,传入键值对对象添加多项 * @command serverparam * @method execCommand * @param { String } cmd 命令字符串 * @param { Object } key 传入的键值对对象 * @example * ```javascript * editor.execCommand('serverparam', {'name': 'hello'}); * editor.queryCommandValue('serverparam'); //返回对象 {'name': 'hello'} * ``` */ /** * 修改服务器提交的额外参数列表,使用自定义函数添加多项 * @command serverparam * @method execCommand * @param { String } cmd 命令字符串 * @param { Function } key 自定义获取参数的函数 * @example * ```javascript * editor.execCommand('serverparam', function(editor){ * return {'key': 'value'}; * }); * editor.queryCommandValue('serverparam'); //返回对象 {'key': 'value'} * ``` */ /** * 获取服务器提交的额外参数列表 * @command serverparam * @method queryCommandValue * @param { String } cmd 命令字符串 * @example * ```javascript * editor.queryCommandValue( 'serverparam' ); //返回对象 {'key': 'value'} * ``` */ serverparam: { execCommand: function (cmd, key, value) { if (key === undefined || key === null) { //不传参数,清空列表 serverParam = {} } else if (utils.isString(key)) { //传入键值 if (value === undefined || value === null) { delete serverParam[key] } else { serverParam[key] = value } } else if (utils.isObject(key)) { //传入对象,覆盖列表项 utils.extend(serverParam, key, true) } else if (utils.isFunction(key)) { //传入函数,添加列表项 utils.extend(serverParam, key(), true) } }, queryCommandValue: function () { return serverParam || {} } } } } }) // plugins/insertfile.js /** * 插入附件 */ UE.plugin.register('insertfile', function () { var me = this function getFileIcon(url) { var ext = url.substr(url.lastIndexOf('.') + 1).toLowerCase(), maps = { rar: 'icon_rar.gif', zip: 'icon_rar.gif', tar: 'icon_rar.gif', gz: 'icon_rar.gif', bz2: 'icon_rar.gif', doc: 'icon_doc.gif', docx: 'icon_doc.gif', pdf: 'icon_pdf.gif', mp3: 'icon_mp3.gif', xls: 'icon_xls.gif', chm: 'icon_chm.gif', ppt: 'icon_ppt.gif', pptx: 'icon_ppt.gif', avi: 'icon_mv.gif', rmvb: 'icon_mv.gif', wmv: 'icon_mv.gif', flv: 'icon_mv.gif', swf: 'icon_mv.gif', rm: 'icon_mv.gif', exe: 'icon_exe.gif', psd: 'icon_psd.gif', txt: 'icon_txt.gif', jpg: 'icon_jpg.gif', png: 'icon_jpg.gif', jpeg: 'icon_jpg.gif', gif: 'icon_jpg.gif', ico: 'icon_jpg.gif', bmp: 'icon_jpg.gif' } return maps[ext] ? maps[ext] : maps['txt'] } return { commands: { insertfile: { execCommand: function (command, filelist) { filelist = utils.isArray(filelist) ? filelist : [filelist] var i, item, icon, title, html = '', URL = me.getOpt('UEDITOR_HOME_URL'), iconDir = URL + (URL.substr(URL.length - 1) == '/' ? '' : '/') + 'dialogs/attachment/fileTypeImages/' for (i = 0; i < filelist.length; i++) { item = filelist[i] icon = iconDir + getFileIcon(item.url) title = item.title || item.url.substr(item.url.lastIndexOf('/') + 1) html += '

    ' + '' + '' + title + '' + '

    ' } me.execCommand('insertHtml', html) } } } } }) // plugins/xssFilter.js /** * @file xssFilter.js * @desc xss过滤器 * @author robbenmu */ UE.plugins.xssFilter = function () { var config = UEDITOR_CONFIG var whitList = config.whitList function filter(node) { var tagName = node.tagName var attrs = node.attrs if (!whitList.hasOwnProperty(tagName)) { node.parentNode.removeChild(node) return false } UE.utils.each(attrs, function (val, key) { if (whitList[tagName].indexOf(key) === -1) { node.setAttr(key) } }) } // 添加inserthtml\paste等操作用的过滤规则 if (whitList && config.xssFilterRules) { this.options.filterRules = (function () { var result = {} UE.utils.each(whitList, function (val, key) { result[key] = function (node) { return filter(node) } }) return result })() } var tagList = [] UE.utils.each(whitList, function (val, key) { tagList.push(key) }) // 添加input过滤规则 // if (whitList && config.inputXssFilter) { this.addInputRule(function (root) { root.traversal(function (node) { if (node.type !== 'element') { return false } filter(node) }) }) } // 添加output过滤规则 // if (whitList && config.outputXssFilter) { this.addOutputRule(function (root) { root.traversal(function (node) { if (node.type !== 'element') { return false } filter(node) }) }) } } // ui/ui.js var baidu = baidu || {} baidu.editor = baidu.editor || {} UE.ui = baidu.editor.ui = {} // ui/uiutils.js ;(function () { var browser = baidu.editor.browser, domUtils = baidu.editor.dom.domUtils var magic = '$EDITORUI' var root = (window[magic] = {}) var uidMagic = 'ID' + magic var uidCount = 0 var uiUtils = (baidu.editor.ui.uiUtils = { uid: function (obj) { return obj ? obj[uidMagic] || (obj[uidMagic] = ++uidCount) : ++uidCount }, hook: function (fn, callback) { var dg if (fn && fn._callbacks) { dg = fn } else { dg = function () { var q if (fn) { q = fn.apply(this, arguments) } var callbacks = dg._callbacks var k = callbacks.length while (k--) { var r = callbacks[k].apply(this, arguments) if (q === undefined) { q = r } } return q } dg._callbacks = [] } dg._callbacks.push(callback) return dg }, createElementByHtml: function (html) { var el = document.createElement('div') el.innerHTML = html el = el.firstChild el.parentNode.removeChild(el) return el }, getViewportElement: function () { return browser.ie && browser.quirks ? document.body : document.documentElement }, getClientRect: function (element) { var bcr //trace IE6下在控制编辑器显隐时可能会报错,catch一下 try { bcr = element.getBoundingClientRect() } catch (e) { bcr = { left: 0, top: 0, height: 0, width: 0 } } var rect = { left: Math.round(bcr.left), top: Math.round(bcr.top), height: Math.round(bcr.bottom - bcr.top), width: Math.round(bcr.right - bcr.left) } var doc while ( (doc = element.ownerDocument) !== document && (element = domUtils.getWindow(doc).frameElement) ) { bcr = element.getBoundingClientRect() rect.left += bcr.left rect.top += bcr.top } rect.bottom = rect.top + rect.height rect.right = rect.left + rect.width return rect }, getViewportRect: function () { var viewportEl = uiUtils.getViewportElement() var width = (window.innerWidth || viewportEl.clientWidth) | 0 var height = (window.innerHeight || viewportEl.clientHeight) | 0 return { left: 0, top: 0, height: height, width: width, bottom: height, right: width } }, setViewportOffset: function (element, offset) { var rect var fixedLayer = uiUtils.getFixedLayer() if (element.parentNode === fixedLayer) { element.style.left = offset.left + 'px' element.style.top = offset.top + 'px' } else { domUtils.setViewportOffset(element, offset) } }, getEventOffset: function (evt) { var el = evt.target || evt.srcElement var rect = uiUtils.getClientRect(el) var offset = uiUtils.getViewportOffsetByEvent(evt) return { left: offset.left - rect.left, top: offset.top - rect.top } }, getViewportOffsetByEvent: function (evt) { var el = evt.target || evt.srcElement var frameEl = domUtils.getWindow(el).frameElement var offset = { left: evt.clientX, top: evt.clientY } if (frameEl && el.ownerDocument !== document) { var rect = uiUtils.getClientRect(frameEl) offset.left += rect.left offset.top += rect.top } return offset }, setGlobal: function (id, obj) { root[id] = obj return magic + '["' + id + '"]' }, unsetGlobal: function (id) { delete root[id] }, copyAttributes: function (tgt, src) { var attributes = src.attributes var k = attributes.length while (k--) { var attrNode = attributes[k] if ( attrNode.nodeName != 'style' && attrNode.nodeName != 'class' && (!browser.ie || attrNode.specified) ) { tgt.setAttribute(attrNode.nodeName, attrNode.nodeValue) } } if (src.className) { domUtils.addClass(tgt, src.className) } if (src.style.cssText) { tgt.style.cssText += ';' + src.style.cssText } }, removeStyle: function (el, styleName) { if (el.style.removeProperty) { el.style.removeProperty(styleName) } else if (el.style.removeAttribute) { el.style.removeAttribute(styleName) } else throw '' }, contains: function (elA, elB) { return ( elA && elB && (elA === elB ? false : elA.contains ? elA.contains(elB) : elA.compareDocumentPosition(elB) & 16) ) }, startDrag: function (evt, callbacks, doc) { var doc = doc || document var startX = evt.clientX var startY = evt.clientY function handleMouseMove(evt) { var x = evt.clientX - startX var y = evt.clientY - startY callbacks.ondragmove(x, y, evt) if (evt.stopPropagation) { evt.stopPropagation() } else { evt.cancelBubble = true } } if (doc.addEventListener) { function handleMouseUp(evt) { doc.removeEventListener('mousemove', handleMouseMove, true) doc.removeEventListener('mouseup', handleMouseUp, true) window.removeEventListener('mouseup', handleMouseUp, true) callbacks.ondragstop() } doc.addEventListener('mousemove', handleMouseMove, true) doc.addEventListener('mouseup', handleMouseUp, true) window.addEventListener('mouseup', handleMouseUp, true) evt.preventDefault() } else { var elm = evt.srcElement elm.setCapture() function releaseCaptrue() { elm.releaseCapture() elm.detachEvent('onmousemove', handleMouseMove) elm.detachEvent('onmouseup', releaseCaptrue) elm.detachEvent('onlosecaptrue', releaseCaptrue) callbacks.ondragstop() } elm.attachEvent('onmousemove', handleMouseMove) elm.attachEvent('onmouseup', releaseCaptrue) elm.attachEvent('onlosecaptrue', releaseCaptrue) evt.returnValue = false } callbacks.ondragstart() }, getFixedLayer: function () { var layer = document.getElementById('edui_fixedlayer') if (layer == null) { layer = document.createElement('div') layer.id = 'edui_fixedlayer' document.body.appendChild(layer) if (browser.ie && browser.version <= 8) { layer.style.position = 'absolute' bindFixedLayer() setTimeout(updateFixedOffset) } else { layer.style.position = 'fixed' } layer.style.left = '0' layer.style.top = '0' layer.style.width = '0' layer.style.height = '0' layer.style.zIndex = '3005' } return layer }, makeUnselectable: function (element) { if (browser.opera || (browser.ie && browser.version < 9)) { element.unselectable = 'on' if (element.hasChildNodes()) { for (var i = 0; i < element.childNodes.length; i++) { if (element.childNodes[i].nodeType == 1) { uiUtils.makeUnselectable(element.childNodes[i]) } } } } else { if (element.style.MozUserSelect !== undefined) { element.style.MozUserSelect = 'none' } else if (element.style.WebkitUserSelect !== undefined) { element.style.WebkitUserSelect = 'none' } else if (element.style.KhtmlUserSelect !== undefined) { element.style.KhtmlUserSelect = 'none' } } } }) function updateFixedOffset() { var layer = document.getElementById('edui_fixedlayer') uiUtils.setViewportOffset(layer, { left: 0, top: 0 }) // layer.style.display = 'none'; // layer.style.display = 'block'; //#trace: 1354 // setTimeout(updateFixedOffset); } function bindFixedLayer(adjOffset) { domUtils.on(window, 'scroll', updateFixedOffset) domUtils.on( window, 'resize', baidu.editor.utils.defer(updateFixedOffset, 0, true) ) } })() // ui/uibase.js ;(function () { var utils = baidu.editor.utils, uiUtils = baidu.editor.ui.uiUtils, EventBase = baidu.editor.EventBase, UIBase = (baidu.editor.ui.UIBase = function () {}) UIBase.prototype = { className: '', uiName: '', initOptions: function (options) { var me = this for (var k in options) { me[k] = options[k] } this.id = this.id || 'edui' + uiUtils.uid() }, initUIBase: function () { this._globalKey = utils.unhtml(uiUtils.setGlobal(this.id, this)) }, render: function (holder) { var html = this.renderHtml() var el = uiUtils.createElementByHtml(html) //by xuheng 给每个node添加class var list = domUtils.getElementsByTagName(el, '*') var theme = 'edui-' + (this.theme || this.editor.options.theme) var layer = document.getElementById('edui_fixedlayer') for (var i = 0, node; (node = list[i++]); ) { domUtils.addClass(node, theme) } domUtils.addClass(el, theme) if (layer) { layer.className = '' domUtils.addClass(layer, theme) } var seatEl = this.getDom() if (seatEl != null) { seatEl.parentNode.replaceChild(el, seatEl) uiUtils.copyAttributes(el, seatEl) } else { if (typeof holder == 'string') { holder = document.getElementById(holder) } holder = holder || uiUtils.getFixedLayer() domUtils.addClass(holder, theme) holder.appendChild(el) } this.postRender() }, getDom: function (name) { if (!name) { return document.getElementById(this.id) } else { return document.getElementById(this.id + '_' + name) } }, postRender: function () { this.fireEvent('postrender') }, getHtmlTpl: function () { return '' }, formatHtml: function (tpl) { var prefix = 'edui-' + this.uiName return tpl .replace(/##/g, this.id) .replace(/%%-/g, this.uiName ? prefix + '-' : '') .replace(/%%/g, (this.uiName ? prefix : '') + ' ' + this.className) .replace(/\$\$/g, this._globalKey) }, renderHtml: function () { return this.formatHtml(this.getHtmlTpl()) }, dispose: function () { var box = this.getDom() if (box) baidu.editor.dom.domUtils.remove(box) uiUtils.unsetGlobal(this.id) } } utils.inherits(UIBase, EventBase) })() // ui/separator.js ;(function () { var utils = baidu.editor.utils, UIBase = baidu.editor.ui.UIBase, Separator = (baidu.editor.ui.Separator = function (options) { this.initOptions(options) this.initSeparator() }) Separator.prototype = { uiName: 'separator', initSeparator: function () { this.initUIBase() }, getHtmlTpl: function () { return '
    ' } } utils.inherits(Separator, UIBase) })() // ui/mask.js ///import core ///import uicore ;(function () { var utils = baidu.editor.utils, domUtils = baidu.editor.dom.domUtils, UIBase = baidu.editor.ui.UIBase, uiUtils = baidu.editor.ui.uiUtils var Mask = (baidu.editor.ui.Mask = function (options) { this.initOptions(options) this.initUIBase() }) Mask.prototype = { getHtmlTpl: function () { return '
    ' }, postRender: function () { var me = this domUtils.on(window, 'resize', function () { setTimeout(function () { if (!me.isHidden()) { me._fill() } }) }) }, show: function (zIndex) { this._fill() this.getDom().style.display = '' this.getDom().style.zIndex = zIndex }, hide: function () { this.getDom().style.display = 'none' this.getDom().style.zIndex = '' }, isHidden: function () { return this.getDom().style.display == 'none' }, _onMouseDown: function () { return false }, _onClick: function (e, target) { this.fireEvent('click', e, target) }, _fill: function () { var el = this.getDom() var vpRect = uiUtils.getViewportRect() el.style.width = vpRect.width + 'px' el.style.height = vpRect.height + 'px' } } utils.inherits(Mask, UIBase) })() // ui/popup.js ///import core ///import uicore ;(function () { var utils = baidu.editor.utils, uiUtils = baidu.editor.ui.uiUtils, domUtils = baidu.editor.dom.domUtils, UIBase = baidu.editor.ui.UIBase, Popup = (baidu.editor.ui.Popup = function (options) { this.initOptions(options) this.initPopup() }) var allPopups = [] function closeAllPopup(evt, el) { for (var i = 0; i < allPopups.length; i++) { var pop = allPopups[i] if (!pop.isHidden()) { if (pop.queryAutoHide(el) !== false) { if ( evt && /scroll/gi.test(evt.type) && pop.className == 'edui-wordpastepop' ) return pop.hide() } } } if (allPopups.length) pop.editor.fireEvent('afterhidepop') } Popup.postHide = closeAllPopup var ANCHOR_CLASSES = [ 'edui-anchor-topleft', 'edui-anchor-topright', 'edui-anchor-bottomleft', 'edui-anchor-bottomright' ] Popup.prototype = { SHADOW_RADIUS: 5, content: null, _hidden: false, autoRender: true, canSideLeft: true, canSideUp: true, initPopup: function () { this.initUIBase() allPopups.push(this) }, getHtmlTpl: function () { return ( '
    ' + '
    ' + ' ' + '
    ' + '
    ' + this.getContentHtmlTpl() + '
    ' + '
    ' + '
    ' ) }, getContentHtmlTpl: function () { if (this.content) { if (typeof this.content == 'string') { return this.content } return this.content.renderHtml() } else { return '' } }, _UIBase_postRender: UIBase.prototype.postRender, postRender: function () { if (this.content instanceof UIBase) { this.content.postRender() } //捕获鼠标滚轮 if (this.captureWheel && !this.captured) { this.captured = true var winHeight = (document.documentElement.clientHeight || document.body.clientHeight) - 80, _height = this.getDom().offsetHeight, _top = uiUtils.getClientRect(this.combox.getDom()).top, content = this.getDom('content'), ifr = this.getDom('body').getElementsByTagName('iframe'), me = this ifr.length && (ifr = ifr[0]) while (_top + _height > winHeight) { _height -= 30 } content.style.height = _height + 'px' //同步更改iframe高度 ifr && (ifr.style.height = _height + 'px') //阻止在combox上的鼠标滚轮事件, 防止用户的正常操作被误解 if (window.XMLHttpRequest) { domUtils.on( content, 'onmousewheel' in document.body ? 'mousewheel' : 'DOMMouseScroll', function (e) { if (e.preventDefault) { e.preventDefault() } else { e.returnValue = false } if (e.wheelDelta) { content.scrollTop -= (e.wheelDelta / 120) * 60 } else { content.scrollTop -= (e.detail / -3) * 60 } } ) } else { //ie6 domUtils.on(this.getDom(), 'mousewheel', function (e) { e.returnValue = false me.getDom('content').scrollTop -= (e.wheelDelta / 120) * 60 }) } } this.fireEvent('postRenderAfter') this.hide(true) this._UIBase_postRender() }, _doAutoRender: function () { if (!this.getDom() && this.autoRender) { this.render() } }, mesureSize: function () { var box = this.getDom('content') return uiUtils.getClientRect(box) }, fitSize: function () { if (this.captureWheel && this.sized) { return this.__size } this.sized = true var popBodyEl = this.getDom('body') popBodyEl.style.width = '' popBodyEl.style.height = '' var size = this.mesureSize() if (this.captureWheel) { popBodyEl.style.width = -(-20 - size.width) + 'px' var height = parseInt(this.getDom('content').style.height, 10) !window.isNaN(height) && (size.height = height) } else { popBodyEl.style.width = size.width + 'px' } popBodyEl.style.height = size.height + 'px' this.__size = size this.captureWheel && (this.getDom('content').style.overflow = 'auto') return size }, showAnchor: function (element, hoz) { this.showAnchorRect(uiUtils.getClientRect(element), hoz) }, showAnchorRect: function (rect, hoz, adj) { this._doAutoRender() var vpRect = uiUtils.getViewportRect() this.getDom().style.visibility = 'hidden' this._show() var popSize = this.fitSize() var sideLeft, sideUp, left, top if (hoz) { sideLeft = this.canSideLeft && rect.right + popSize.width > vpRect.right && rect.left > popSize.width sideUp = this.canSideUp && rect.top + popSize.height > vpRect.bottom && rect.bottom > popSize.height left = sideLeft ? rect.left - popSize.width : rect.right top = sideUp ? rect.bottom - popSize.height : rect.top } else { sideLeft = this.canSideLeft && rect.right + popSize.width > vpRect.right && rect.left > popSize.width sideUp = this.canSideUp && rect.top + popSize.height > vpRect.bottom && rect.bottom > popSize.height left = sideLeft ? rect.right - popSize.width : rect.left top = sideUp ? rect.top - popSize.height : rect.bottom } var popEl = this.getDom() uiUtils.setViewportOffset(popEl, { left: left, top: top }) domUtils.removeClasses(popEl, ANCHOR_CLASSES) popEl.className += ' ' + ANCHOR_CLASSES[(sideUp ? 1 : 0) * 2 + (sideLeft ? 1 : 0)] if (this.editor) { popEl.style.zIndex = this.editor.container.style.zIndex * 1 + 10 baidu.editor.ui.uiUtils.getFixedLayer().style.zIndex = popEl.style.zIndex - 1 } this.getDom().style.visibility = 'visible' }, showAt: function (offset) { var left = offset.left var top = offset.top var rect = { left: left, top: top, right: left, bottom: top, height: 0, width: 0 } this.showAnchorRect(rect, false, true) }, _show: function () { if (this._hidden) { var box = this.getDom() box.style.display = '' this._hidden = false // if (box.setActive) { // box.setActive(); // } this.fireEvent('show') } }, isHidden: function () { return this._hidden }, show: function () { this._doAutoRender() this._show() }, hide: function (notNofity) { if (!this._hidden && this.getDom()) { this.getDom().style.display = 'none' this._hidden = true if (!notNofity) { this.fireEvent('hide') } } }, queryAutoHide: function (el) { return !el || !uiUtils.contains(this.getDom(), el) } } utils.inherits(Popup, UIBase) domUtils.on(document, 'mousedown', function (evt) { var el = evt.target || evt.srcElement closeAllPopup(evt, el) }) domUtils.on(window, 'scroll', function (evt, el) { closeAllPopup(evt, el) }) })() // ui/colorpicker.js ///import core ///import uicore ;(function () { var utils = baidu.editor.utils, UIBase = baidu.editor.ui.UIBase, ColorPicker = (baidu.editor.ui.ColorPicker = function (options) { this.initOptions(options) this.noColorText = this.noColorText || this.editor.getLang('clearColor') this.initUIBase() }) ColorPicker.prototype = { getHtmlTpl: function () { return genColorPicker(this.noColorText, this.editor) }, _onTableClick: function (evt) { var tgt = evt.target || evt.srcElement var color = tgt.getAttribute('data-color') if (color) { this.fireEvent('pickcolor', color) } }, _onTableOver: function (evt) { var tgt = evt.target || evt.srcElement var color = tgt.getAttribute('data-color') if (color) { this.getDom('preview').style.backgroundColor = color } }, _onTableOut: function () { this.getDom('preview').style.backgroundColor = '' }, _onPickNoColor: function () { this.fireEvent('picknocolor') } } utils.inherits(ColorPicker, UIBase) var COLORS = ( 'ffffff,000000,eeece1,1f497d,4f81bd,c0504d,9bbb59,8064a2,4bacc6,f79646,' + 'f2f2f2,7f7f7f,ddd9c3,c6d9f0,dbe5f1,f2dcdb,ebf1dd,e5e0ec,dbeef3,fdeada,' + 'd8d8d8,595959,c4bd97,8db3e2,b8cce4,e5b9b7,d7e3bc,ccc1d9,b7dde8,fbd5b5,' + 'bfbfbf,3f3f3f,938953,548dd4,95b3d7,d99694,c3d69b,b2a2c7,92cddc,fac08f,' + 'a5a5a5,262626,494429,17365d,366092,953734,76923c,5f497a,31859b,e36c09,' + '7f7f7f,0c0c0c,1d1b10,0f243e,244061,632423,4f6128,3f3151,205867,974806,' + 'c00000,ff0000,ffc000,ffff00,92d050,00b050,00b0f0,0070c0,002060,7030a0,' ).split(',') function genColorPicker(noColorText, editor) { var html = '
    ' + '
    ' + '
    ' + '
    ' + noColorText + '
    ' + '
    ' + '' + '' + '' for (var i = 0; i < COLORS.length; i++) { if (i && i % 10 === 0) { html += '' + (i == 60 ? '' : '') + '' } html += i < 70 ? '' : '' } html += '
    ' + editor.getLang('themeColor') + '
    ' + editor.getLang('standardColor') + '
    ' return html } })() // ui/tablepicker.js ///import core ///import uicore ;(function () { var utils = baidu.editor.utils, uiUtils = baidu.editor.ui.uiUtils, UIBase = baidu.editor.ui.UIBase var TablePicker = (baidu.editor.ui.TablePicker = function (options) { this.initOptions(options) this.initTablePicker() }) TablePicker.prototype = { defaultNumRows: 10, defaultNumCols: 10, maxNumRows: 20, maxNumCols: 20, numRows: 10, numCols: 10, lengthOfCellSide: 22, initTablePicker: function () { this.initUIBase() }, getHtmlTpl: function () { var me = this return ( '
    ' + '
    ' + '
    ' + '' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' ) }, _UIBase_render: UIBase.prototype.render, render: function (holder) { this._UIBase_render(holder) this.getDom('label').innerHTML = '0' + this.editor.getLang('t_row') + ' x 0' + this.editor.getLang('t_col') }, _track: function (numCols, numRows) { var style = this.getDom('overlay').style var sideLen = this.lengthOfCellSide style.width = numCols * sideLen + 'px' style.height = numRows * sideLen + 'px' var label = this.getDom('label') label.innerHTML = numCols + this.editor.getLang('t_col') + ' x ' + numRows + this.editor.getLang('t_row') this.numCols = numCols this.numRows = numRows }, _onMouseOver: function (evt, el) { var rel = evt.relatedTarget || evt.fromElement if (!uiUtils.contains(el, rel) && el !== rel) { this.getDom('label').innerHTML = '0' + this.editor.getLang('t_col') + ' x 0' + this.editor.getLang('t_row') this.getDom('overlay').style.visibility = '' } }, _onMouseOut: function (evt, el) { var rel = evt.relatedTarget || evt.toElement if (!uiUtils.contains(el, rel) && el !== rel) { this.getDom('label').innerHTML = '0' + this.editor.getLang('t_col') + ' x 0' + this.editor.getLang('t_row') this.getDom('overlay').style.visibility = 'hidden' } }, _onMouseMove: function (evt, el) { var style = this.getDom('overlay').style var offset = uiUtils.getEventOffset(evt) var sideLen = this.lengthOfCellSide var numCols = Math.ceil(offset.left / sideLen) var numRows = Math.ceil(offset.top / sideLen) this._track(numCols, numRows) }, _onClick: function () { this.fireEvent('picktable', this.numCols, this.numRows) } } utils.inherits(TablePicker, UIBase) })() // ui/stateful.js ;(function () { var browser = baidu.editor.browser, domUtils = baidu.editor.dom.domUtils, uiUtils = baidu.editor.ui.uiUtils var TPL_STATEFUL = 'onmousedown="$$.Stateful_onMouseDown(event, this);"' + ' onmouseup="$$.Stateful_onMouseUp(event, this);"' + (browser.ie ? ' onmouseenter="$$.Stateful_onMouseEnter(event, this);"' + ' onmouseleave="$$.Stateful_onMouseLeave(event, this);"' : ' onmouseover="$$.Stateful_onMouseOver(event, this);"' + ' onmouseout="$$.Stateful_onMouseOut(event, this);"') baidu.editor.ui.Stateful = { alwalysHoverable: false, target: null, //目标元素和this指向dom不一样 Stateful_init: function () { this._Stateful_dGetHtmlTpl = this.getHtmlTpl this.getHtmlTpl = this.Stateful_getHtmlTpl }, Stateful_getHtmlTpl: function () { var tpl = this._Stateful_dGetHtmlTpl() // 使用function避免$转义 return tpl.replace(/stateful/g, function () { return TPL_STATEFUL }) }, Stateful_onMouseEnter: function (evt, el) { this.target = el if (!this.isDisabled() || this.alwalysHoverable) { this.addState('hover') this.fireEvent('over') } }, Stateful_onMouseLeave: function (evt, el) { if (!this.isDisabled() || this.alwalysHoverable) { this.removeState('hover') this.removeState('active') this.fireEvent('out') } }, Stateful_onMouseOver: function (evt, el) { var rel = evt.relatedTarget if (!uiUtils.contains(el, rel) && el !== rel) { this.Stateful_onMouseEnter(evt, el) } }, Stateful_onMouseOut: function (evt, el) { var rel = evt.relatedTarget if (!uiUtils.contains(el, rel) && el !== rel) { this.Stateful_onMouseLeave(evt, el) } }, Stateful_onMouseDown: function (evt, el) { if (!this.isDisabled()) { this.addState('active') } }, Stateful_onMouseUp: function (evt, el) { if (!this.isDisabled()) { this.removeState('active') } }, Stateful_postRender: function () { if (this.disabled && !this.hasState('disabled')) { this.addState('disabled') } }, hasState: function (state) { return domUtils.hasClass(this.getStateDom(), 'edui-state-' + state) }, addState: function (state) { if (!this.hasState(state)) { this.getStateDom().className += ' edui-state-' + state } }, removeState: function (state) { if (this.hasState(state)) { domUtils.removeClasses(this.getStateDom(), ['edui-state-' + state]) } }, getStateDom: function () { return this.getDom('state') }, isChecked: function () { return this.hasState('checked') }, setChecked: function (checked) { if (!this.isDisabled() && checked) { this.addState('checked') } else { this.removeState('checked') } }, isDisabled: function () { return this.hasState('disabled') }, setDisabled: function (disabled) { if (disabled) { this.removeState('hover') this.removeState('checked') this.removeState('active') this.addState('disabled') } else { this.removeState('disabled') } } } })() // ui/button.js ///import core ///import uicore ///import ui/stateful.js ;(function () { var utils = baidu.editor.utils, UIBase = baidu.editor.ui.UIBase, Stateful = baidu.editor.ui.Stateful, Button = (baidu.editor.ui.Button = function (options) { if (options.name) { var btnName = options.name var cssRules = options.cssRules if (!options.className) { options.className = 'edui-for-' + btnName } options.cssRules = '.edui-default .edui-for-' + btnName + ' .edui-icon {' + cssRules + '}' } this.initOptions(options) this.initButton() }) Button.prototype = { uiName: 'button', label: '', title: '', showIcon: true, showText: true, cssRules: '', initButton: function () { this.initUIBase() this.Stateful_init() if (this.cssRules) { utils.cssRule('edui-customize-' + this.name + '-style', this.cssRules) } }, getHtmlTpl: function () { return ( '
    ' + '
    ' + '
    ' + (this.showIcon ? '
    ' : '') + (this.showText ? '
    ' + this.label + '
    ' : '') + '
    ' + '
    ' + '
    ' ) }, postRender: function () { this.Stateful_postRender() this.setDisabled(this.disabled) }, _onMouseDown: function (e) { var target = e.target || e.srcElement, tagName = target && target.tagName && target.tagName.toLowerCase() if (tagName == 'input' || tagName == 'object' || tagName == 'object') { return false } }, _onClick: function () { if (!this.isDisabled()) { this.fireEvent('click') } }, setTitle: function (text) { var label = this.getDom('label') label.innerHTML = text } } utils.inherits(Button, UIBase) utils.extend(Button.prototype, Stateful) })() // ui/splitbutton.js ///import core ///import uicore ///import ui/stateful.js ;(function () { var utils = baidu.editor.utils, uiUtils = baidu.editor.ui.uiUtils, domUtils = baidu.editor.dom.domUtils, UIBase = baidu.editor.ui.UIBase, Stateful = baidu.editor.ui.Stateful, SplitButton = (baidu.editor.ui.SplitButton = function (options) { this.initOptions(options) this.initSplitButton() }) SplitButton.prototype = { popup: null, uiName: 'splitbutton', title: '', initSplitButton: function () { this.initUIBase() this.Stateful_init() var me = this if (this.popup != null) { var popup = this.popup this.popup = null this.setPopup(popup) } }, _UIBase_postRender: UIBase.prototype.postRender, postRender: function () { this.Stateful_postRender() this._UIBase_postRender() }, setPopup: function (popup) { if (this.popup === popup) return if (this.popup != null) { this.popup.dispose() } popup.addListener('show', utils.bind(this._onPopupShow, this)) popup.addListener('hide', utils.bind(this._onPopupHide, this)) popup.addListener( 'postrender', utils.bind(function () { popup .getDom('body') .appendChild( uiUtils.createElementByHtml( '
    ' ) ) popup.getDom().className += ' ' + this.className }, this) ) this.popup = popup }, _onPopupShow: function () { this.addState('opened') }, _onPopupHide: function () { this.removeState('opened') }, getHtmlTpl: function () { return ( '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' ) }, showPopup: function () { // 当popup往上弹出的时候,做特殊处理 var rect = uiUtils.getClientRect(this.getDom()) rect.top -= this.popup.SHADOW_RADIUS rect.height += this.popup.SHADOW_RADIUS this.popup.showAnchorRect(rect) }, _onArrowClick: function (event, el) { if (!this.isDisabled()) { this.showPopup() } }, _onButtonClick: function () { if (!this.isDisabled()) { this.fireEvent('buttonclick') } } } utils.inherits(SplitButton, UIBase) utils.extend(SplitButton.prototype, Stateful, true) })() // ui/colorbutton.js ///import core ///import uicore ///import ui/colorpicker.js ///import ui/popup.js ///import ui/splitbutton.js ;(function () { var utils = baidu.editor.utils, uiUtils = baidu.editor.ui.uiUtils, ColorPicker = baidu.editor.ui.ColorPicker, Popup = baidu.editor.ui.Popup, SplitButton = baidu.editor.ui.SplitButton, ColorButton = (baidu.editor.ui.ColorButton = function (options) { this.initOptions(options) this.initColorButton() }) ColorButton.prototype = { initColorButton: function () { var me = this this.popup = new Popup({ content: new ColorPicker({ noColorText: me.editor.getLang('clearColor'), editor: me.editor, onpickcolor: function (t, color) { me._onPickColor(color) }, onpicknocolor: function (t, color) { me._onPickNoColor(color) } }), editor: me.editor }) this.initSplitButton() }, _SplitButton_postRender: SplitButton.prototype.postRender, postRender: function () { this._SplitButton_postRender() this.getDom('button_body').appendChild( uiUtils.createElementByHtml( '
    ' ) ) this.getDom().className += ' edui-colorbutton' }, setColor: function (color) { this.getDom('colorlump').style.backgroundColor = color this.color = color }, _onPickColor: function (color) { if (this.fireEvent('pickcolor', color) !== false) { this.setColor(color) this.popup.hide() } }, _onPickNoColor: function (color) { if (this.fireEvent('picknocolor') !== false) { this.popup.hide() } } } utils.inherits(ColorButton, SplitButton) })() // ui/tablebutton.js ///import core ///import uicore ///import ui/popup.js ///import ui/tablepicker.js ///import ui/splitbutton.js ;(function () { var utils = baidu.editor.utils, Popup = baidu.editor.ui.Popup, TablePicker = baidu.editor.ui.TablePicker, SplitButton = baidu.editor.ui.SplitButton, TableButton = (baidu.editor.ui.TableButton = function (options) { this.initOptions(options) this.initTableButton() }) TableButton.prototype = { initTableButton: function () { var me = this this.popup = new Popup({ content: new TablePicker({ editor: me.editor, onpicktable: function (t, numCols, numRows) { me._onPickTable(numCols, numRows) } }), editor: me.editor }) this.initSplitButton() }, _onPickTable: function (numCols, numRows) { if (this.fireEvent('picktable', numCols, numRows) !== false) { this.popup.hide() } } } utils.inherits(TableButton, SplitButton) })() // ui/autotypesetpicker.js ///import core ///import uicore ;(function () { var utils = baidu.editor.utils, UIBase = baidu.editor.ui.UIBase var AutoTypeSetPicker = (baidu.editor.ui.AutoTypeSetPicker = function ( options ) { this.initOptions(options) this.initAutoTypeSetPicker() }) AutoTypeSetPicker.prototype = { initAutoTypeSetPicker: function () { this.initUIBase() }, getHtmlTpl: function () { var me = this.editor, opt = me.options.autotypeset, lang = me.getLang('autoTypeSet') var textAlignInputName = 'textAlignValue' + me.uid, imageBlockInputName = 'imageBlockLineValue' + me.uid, symbolConverInputName = 'symbolConverValue' + me.uid return ( '
    ' + '
    ' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
    ' + lang.mergeLine + '' + lang.delLine + '
    ' + lang.removeFormat + '' + lang.indent + '
    ' + lang.alignment + '' + '' + me.getLang('justifyleft') + '' + me.getLang('justifycenter') + '' + me.getLang('justifyright') + '
    ' + lang.imageFloat + '' + '' + me.getLang('default') + '' + me.getLang('justifyleft') + '' + me.getLang('justifycenter') + '' + me.getLang('justifyright') + '
    ' + lang.removeFontsize + '' + lang.removeFontFamily + '
    ' + lang.removeHtml + '
    ' + lang.pasteFilter + '
    ' + lang.symbol + '' + '' + lang.bdc2sb + '' + lang.tobdc + '' + '
    ' + '
    ' + '
    ' ) }, _UIBase_render: UIBase.prototype.render } utils.inherits(AutoTypeSetPicker, UIBase) })() // ui/autotypesetbutton.js ///import core ///import uicore ///import ui/popup.js ///import ui/autotypesetpicker.js ///import ui/splitbutton.js ;(function () { var utils = baidu.editor.utils, Popup = baidu.editor.ui.Popup, AutoTypeSetPicker = baidu.editor.ui.AutoTypeSetPicker, SplitButton = baidu.editor.ui.SplitButton, AutoTypeSetButton = (baidu.editor.ui.AutoTypeSetButton = function ( options ) { this.initOptions(options) this.initAutoTypeSetButton() }) function getPara(me) { var opt = {}, cont = me.getDom(), editorId = me.editor.uid, inputType = null, attrName = null, ipts = domUtils.getElementsByTagName(cont, 'input') for (var i = ipts.length - 1, ipt; (ipt = ipts[i--]); ) { inputType = ipt.getAttribute('type') if (inputType == 'checkbox') { attrName = ipt.getAttribute('name') opt[attrName] && delete opt[attrName] if (ipt.checked) { var attrValue = document.getElementById( attrName + 'Value' + editorId ) if (attrValue) { if (/input/gi.test(attrValue.tagName)) { opt[attrName] = attrValue.value } else { var iptChilds = attrValue.getElementsByTagName('input') for ( var j = iptChilds.length - 1, iptchild; (iptchild = iptChilds[j--]); ) { if (iptchild.checked) { opt[attrName] = iptchild.value break } } } } else { opt[attrName] = true } } else { opt[attrName] = false } } else { opt[ipt.getAttribute('value')] = ipt.checked } } var selects = domUtils.getElementsByTagName(cont, 'select') for (var i = 0, si; (si = selects[i++]); ) { var attr = si.getAttribute('name') opt[attr] = opt[attr] ? si.value : '' } utils.extend(me.editor.options.autotypeset, opt) me.editor.setPreferences('autotypeset', opt) } AutoTypeSetButton.prototype = { initAutoTypeSetButton: function () { var me = this this.popup = new Popup({ //传入配置参数 content: new AutoTypeSetPicker({ editor: me.editor }), editor: me.editor, hide: function () { if (!this._hidden && this.getDom()) { getPara(this) this.getDom().style.display = 'none' this._hidden = true this.fireEvent('hide') } } }) var flag = 0 this.popup.addListener('postRenderAfter', function () { var popupUI = this if (flag) return var cont = this.getDom(), btn = cont.getElementsByTagName('button')[0] btn.onclick = function () { getPara(popupUI) me.editor.execCommand('autotypeset') popupUI.hide() } domUtils.on(cont, 'click', function (e) { var target = e.target || e.srcElement, editorId = me.editor.uid if (target && target.tagName == 'INPUT') { // 点击图片浮动的checkbox,去除对应的radio if ( target.name == 'imageBlockLine' || target.name == 'textAlign' || target.name == 'symbolConver' ) { var checked = target.checked, radioTd = document.getElementById( target.name + 'Value' + editorId ), radios = radioTd.getElementsByTagName('input'), defalutSelect = { imageBlockLine: 'none', textAlign: 'left', symbolConver: 'tobdc' } for (var i = 0; i < radios.length; i++) { if (checked) { if (radios[i].value == defalutSelect[target.name]) { radios[i].checked = 'checked' } } else { radios[i].checked = false } } } // 点击radio,选中对应的checkbox if ( target.name == 'imageBlockLineValue' + editorId || target.name == 'textAlignValue' + editorId || target.name == 'bdc' ) { var checkboxs = target.parentNode.previousSibling.getElementsByTagName( 'input' ) checkboxs && (checkboxs[0].checked = true) } getPara(popupUI) } }) flag = 1 }) this.initSplitButton() } } utils.inherits(AutoTypeSetButton, SplitButton) })() // ui/cellalignpicker.js ///import core ///import uicore ;(function () { var utils = baidu.editor.utils, Popup = baidu.editor.ui.Popup, Stateful = baidu.editor.ui.Stateful, UIBase = baidu.editor.ui.UIBase /** * 该参数将新增一个参数: selected, 参数类型为一个Object, 形如{ 'align': 'center', 'valign': 'top' }, 表示单元格的初始 * 对齐状态为: 竖直居上,水平居中; 其中 align的取值为:'center', 'left', 'right'; valign的取值为: 'top', 'middle', 'bottom' * @update 2013/4/2 hancong03@baidu.com */ var CellAlignPicker = (baidu.editor.ui.CellAlignPicker = function ( options ) { this.initOptions(options) this.initSelected() this.initCellAlignPicker() }) CellAlignPicker.prototype = { //初始化选中状态, 该方法将根据传递进来的参数获取到应该选中的对齐方式图标的索引 initSelected: function () { var status = { valign: { top: 0, middle: 1, bottom: 2 }, align: { left: 0, center: 1, right: 2 }, count: 3 }, result = -1 if (this.selected) { this.selectedIndex = status.valign[this.selected.valign] * status.count + status.align[this.selected.align] } }, initCellAlignPicker: function () { this.initUIBase() this.Stateful_init() }, getHtmlTpl: function () { var alignType = ['left', 'center', 'right'], COUNT = 9, tempClassName = null, tempIndex = -1, tmpl = [] for (var i = 0; i < COUNT; i++) { tempClassName = this.selectedIndex === i ? ' class="edui-cellalign-selected" ' : '' tempIndex = i % 3 tempIndex === 0 && tmpl.push('') tmpl.push( '
    ' ) tempIndex === 2 && tmpl.push('') } return ( '
    ' + '
    ' + '' + tmpl.join('') + '
    ' + '
    ' + '
    ' ) }, getStateDom: function () { return this.target }, _onClick: function (evt) { var target = evt.target || evt.srcElement if (/icon/.test(target.className)) { this.items[target.parentNode.getAttribute('index')].onclick() Popup.postHide(evt) } }, _UIBase_render: UIBase.prototype.render } utils.inherits(CellAlignPicker, UIBase) utils.extend(CellAlignPicker.prototype, Stateful, true) })() // ui/pastepicker.js ///import core ///import uicore ;(function () { var utils = baidu.editor.utils, Stateful = baidu.editor.ui.Stateful, uiUtils = baidu.editor.ui.uiUtils, UIBase = baidu.editor.ui.UIBase var PastePicker = (baidu.editor.ui.PastePicker = function (options) { this.initOptions(options) this.initPastePicker() }) PastePicker.prototype = { initPastePicker: function () { this.initUIBase() this.Stateful_init() }, getHtmlTpl: function () { return ( '
    ' + '
    ' + '
    ' + this.editor.getLang('pasteOpt') + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '' ) }, getStateDom: function () { return this.target }, format: function (param) { this.editor.ui._isTransfer = true this.editor.fireEvent('pasteTransfer', param) }, _onClick: function (cur) { var node = domUtils.getNextDomNode(cur), screenHt = uiUtils.getViewportRect().height, subPop = uiUtils.getClientRect(node) if (subPop.top + subPop.height > screenHt) node.style.top = -subPop.height - cur.offsetHeight + 'px' else node.style.top = '' if (/hidden/gi.test(domUtils.getComputedStyle(node, 'visibility'))) { node.style.visibility = 'visible' domUtils.addClass(cur, 'edui-state-opened') } else { node.style.visibility = 'hidden' domUtils.removeClasses(cur, 'edui-state-opened') } }, _UIBase_render: UIBase.prototype.render } utils.inherits(PastePicker, UIBase) utils.extend(PastePicker.prototype, Stateful, true) })() // ui/toolbar.js ;(function () { var utils = baidu.editor.utils, uiUtils = baidu.editor.ui.uiUtils, UIBase = baidu.editor.ui.UIBase, Toolbar = (baidu.editor.ui.Toolbar = function (options) { this.initOptions(options) this.initToolbar() }) Toolbar.prototype = { items: null, initToolbar: function () { this.items = this.items || [] this.initUIBase() }, add: function (item, index) { if (index === undefined) { this.items.push(item) } else { this.items.splice(index, 0, item) } }, getHtmlTpl: function () { var buff = [] for (var i = 0; i < this.items.length; i++) { buff[i] = this.items[i].renderHtml() } return ( '
    ' + buff.join('') + '
    ' ) }, postRender: function () { var box = this.getDom() for (var i = 0; i < this.items.length; i++) { this.items[i].postRender() } uiUtils.makeUnselectable(box) }, _onMouseDown: function (e) { var target = e.target || e.srcElement, tagName = target && target.tagName && target.tagName.toLowerCase() if (tagName == 'input' || tagName == 'object' || tagName == 'object') { return false } } } utils.inherits(Toolbar, UIBase) })() // ui/menu.js ///import core ///import uicore ///import ui\popup.js ///import ui\stateful.js ;(function () { var utils = baidu.editor.utils, domUtils = baidu.editor.dom.domUtils, uiUtils = baidu.editor.ui.uiUtils, UIBase = baidu.editor.ui.UIBase, Popup = baidu.editor.ui.Popup, Stateful = baidu.editor.ui.Stateful, CellAlignPicker = baidu.editor.ui.CellAlignPicker, Menu = (baidu.editor.ui.Menu = function (options) { this.initOptions(options) this.initMenu() }) var menuSeparator = { renderHtml: function () { return '
    ' }, postRender: function () {}, queryAutoHide: function () { return true } } Menu.prototype = { items: null, uiName: 'menu', initMenu: function () { this.items = this.items || [] this.initPopup() this.initItems() }, initItems: function () { for (var i = 0; i < this.items.length; i++) { var item = this.items[i] if (item == '-') { this.items[i] = this.getSeparator() } else if (!(item instanceof MenuItem)) { item.editor = this.editor item.theme = this.editor.options.theme this.items[i] = this.createItem(item) } } }, getSeparator: function () { return menuSeparator }, createItem: function (item) { //新增一个参数menu, 该参数存储了menuItem所对应的menu引用 item.menu = this return new MenuItem(item) }, _Popup_getContentHtmlTpl: Popup.prototype.getContentHtmlTpl, getContentHtmlTpl: function () { if (this.items.length == 0) { return this._Popup_getContentHtmlTpl() } var buff = [] for (var i = 0; i < this.items.length; i++) { var item = this.items[i] buff[i] = item.renderHtml() } return '
    ' + buff.join('') + '
    ' }, _Popup_postRender: Popup.prototype.postRender, postRender: function () { var me = this for (var i = 0; i < this.items.length; i++) { var item = this.items[i] item.ownerMenu = this item.postRender() } domUtils.on(this.getDom(), 'mouseover', function (evt) { evt = evt || event var rel = evt.relatedTarget || evt.fromElement var el = me.getDom() if (!uiUtils.contains(el, rel) && el !== rel) { me.fireEvent('over') } }) this._Popup_postRender() }, queryAutoHide: function (el) { if (el) { if (uiUtils.contains(this.getDom(), el)) { return false } for (var i = 0; i < this.items.length; i++) { var item = this.items[i] if (item.queryAutoHide(el) === false) { return false } } } }, clearItems: function () { for (var i = 0; i < this.items.length; i++) { var item = this.items[i] clearTimeout(item._showingTimer) clearTimeout(item._closingTimer) if (item.subMenu) { item.subMenu.destroy() } } this.items = [] }, destroy: function () { if (this.getDom()) { domUtils.remove(this.getDom()) } this.clearItems() }, dispose: function () { this.destroy() } } utils.inherits(Menu, Popup) /** * @update 2013/04/03 hancong03 新增一个参数menu, 该参数存储了menuItem所对应的menu引用 * @type {Function} */ var MenuItem = (baidu.editor.ui.MenuItem = function (options) { this.initOptions(options) this.initUIBase() this.Stateful_init() if (this.subMenu && !(this.subMenu instanceof Menu)) { if (options.className && options.className.indexOf('aligntd') != -1) { var me = this //获取单元格对齐初始状态 this.subMenu.selected = this.editor.queryCommandValue('cellalignment') this.subMenu = new Popup({ content: new CellAlignPicker(this.subMenu), parentMenu: me, editor: me.editor, destroy: function () { if (this.getDom()) { domUtils.remove(this.getDom()) } } }) this.subMenu.addListener('postRenderAfter', function () { domUtils.on(this.getDom(), 'mouseover', function () { me.addState('opened') }) }) } else { this.subMenu = new Menu(this.subMenu) } } }) MenuItem.prototype = { label: '', subMenu: null, ownerMenu: null, uiName: 'menuitem', alwalysHoverable: true, getHtmlTpl: function () { return ( '
    ' + '
    ' + this.renderLabelHtml() + '
    ' + '
    ' ) }, postRender: function () { var me = this this.addListener('over', function () { me.ownerMenu.fireEvent('submenuover', me) if (me.subMenu) { me.delayShowSubMenu() } }) if (this.subMenu) { this.getDom().className += ' edui-hassubmenu' this.subMenu.render() this.addListener('out', function () { me.delayHideSubMenu() }) this.subMenu.addListener('over', function () { clearTimeout(me._closingTimer) me._closingTimer = null me.addState('opened') }) this.ownerMenu.addListener('hide', function () { me.hideSubMenu() }) this.ownerMenu.addListener('submenuover', function (t, subMenu) { if (subMenu !== me) { me.delayHideSubMenu() } }) this.subMenu._bakQueryAutoHide = this.subMenu.queryAutoHide this.subMenu.queryAutoHide = function (el) { if (el && uiUtils.contains(me.getDom(), el)) { return false } return this._bakQueryAutoHide(el) } } this.getDom().style.tabIndex = '-1' uiUtils.makeUnselectable(this.getDom()) this.Stateful_postRender() }, delayShowSubMenu: function () { var me = this if (!me.isDisabled()) { me.addState('opened') clearTimeout(me._showingTimer) clearTimeout(me._closingTimer) me._closingTimer = null me._showingTimer = setTimeout(function () { me.showSubMenu() }, 250) } }, delayHideSubMenu: function () { var me = this if (!me.isDisabled()) { me.removeState('opened') clearTimeout(me._showingTimer) if (!me._closingTimer) { me._closingTimer = setTimeout(function () { if (!me.hasState('opened')) { me.hideSubMenu() } me._closingTimer = null }, 400) } } }, renderLabelHtml: function () { return ( '
    ' + '
    ' + '
    ' + (this.label || '') + '
    ' ) }, getStateDom: function () { return this.getDom() }, queryAutoHide: function (el) { if (this.subMenu && this.hasState('opened')) { return this.subMenu.queryAutoHide(el) } }, _onClick: function (event, this_) { if (this.hasState('disabled')) return if (this.fireEvent('click', event, this_) !== false) { if (this.subMenu) { this.showSubMenu() } else { Popup.postHide(event) } } }, showSubMenu: function () { var rect = uiUtils.getClientRect(this.getDom()) rect.right -= 5 rect.left += 2 rect.width -= 7 rect.top -= 4 rect.bottom += 4 rect.height += 8 this.subMenu.showAnchorRect(rect, true, true) }, hideSubMenu: function () { this.subMenu.hide() } } utils.inherits(MenuItem, UIBase) utils.extend(MenuItem.prototype, Stateful, true) })() // ui/combox.js ///import core ///import uicore ///import ui/menu.js ///import ui/splitbutton.js ;(function () { // todo: menu和item提成通用list var utils = baidu.editor.utils, uiUtils = baidu.editor.ui.uiUtils, Menu = baidu.editor.ui.Menu, SplitButton = baidu.editor.ui.SplitButton, Combox = (baidu.editor.ui.Combox = function (options) { this.initOptions(options) this.initCombox() }) Combox.prototype = { uiName: 'combox', onbuttonclick: function () { this.showPopup() }, initCombox: function () { var me = this this.items = this.items || [] for (var i = 0; i < this.items.length; i++) { var item = this.items[i] item.uiName = 'listitem' item.index = i item.onclick = function () { me.selectByIndex(this.index) } } this.popup = new Menu({ items: this.items, uiName: 'list', editor: this.editor, captureWheel: true, combox: this }) this.initSplitButton() }, _SplitButton_postRender: SplitButton.prototype.postRender, postRender: function () { this._SplitButton_postRender() this.setLabel(this.label || '') this.setValue(this.initValue || '') }, showPopup: function () { var rect = uiUtils.getClientRect(this.getDom()) rect.top += 1 rect.bottom -= 1 rect.height -= 2 this.popup.showAnchorRect(rect) }, getValue: function () { return this.value }, setValue: function (value) { var index = this.indexByValue(value) if (index != -1) { this.selectedIndex = index this.setLabel(this.items[index].label) this.value = this.items[index].value } else { this.selectedIndex = -1 this.setLabel(this.getLabelForUnknowValue(value)) this.value = value } }, setLabel: function (label) { this.getDom('button_body').innerHTML = label this.label = label }, getLabelForUnknowValue: function (value) { return value }, indexByValue: function (value) { for (var i = 0; i < this.items.length; i++) { if (value == this.items[i].value) { return i } } return -1 }, getItem: function (index) { return this.items[index] }, selectByIndex: function (index) { if ( index < this.items.length && this.fireEvent('select', index) !== false ) { this.selectedIndex = index this.value = this.items[index].value this.setLabel(this.items[index].label) } } } utils.inherits(Combox, SplitButton) })() // ui/dialog.js ///import core ///import uicore ///import ui/mask.js ///import ui/button.js ;(function () { var utils = baidu.editor.utils, domUtils = baidu.editor.dom.domUtils, uiUtils = baidu.editor.ui.uiUtils, Mask = baidu.editor.ui.Mask, UIBase = baidu.editor.ui.UIBase, Button = baidu.editor.ui.Button, Dialog = (baidu.editor.ui.Dialog = function (options) { if (options.name) { var name = options.name var cssRules = options.cssRules if (!options.className) { options.className = 'edui-for-' + name } if (cssRules) { options.cssRules = '.edui-default .edui-for-' + name + ' .edui-dialog-content {' + cssRules + '}' } } this.initOptions( utils.extend( { autoReset: true, draggable: true, onok: function () {}, oncancel: function () {}, onclose: function (t, ok) { return ok ? this.onok() : this.oncancel() }, //是否控制dialog中的scroll事件, 默认为不阻止 holdScroll: false }, options ) ) this.initDialog() }) var modalMask var dragMask var activeDialog Dialog.prototype = { draggable: false, uiName: 'dialog', initDialog: function () { var me = this, theme = this.editor.options.theme if (this.cssRules) { utils.cssRule('edui-customize-' + this.name + '-style', this.cssRules) } this.initUIBase() this.modalMask = modalMask || (modalMask = new Mask({ className: 'edui-dialog-modalmask', theme: theme, onclick: function () { activeDialog && activeDialog.close(false) } })) this.dragMask = dragMask || (dragMask = new Mask({ className: 'edui-dialog-dragmask', theme: theme })) this.closeButton = new Button({ className: 'edui-dialog-closebutton', title: me.closeDialog, theme: theme, onclick: function () { me.close(false) } }) this.fullscreen && this.initResizeEvent() if (this.buttons) { for (var i = 0; i < this.buttons.length; i++) { if (!(this.buttons[i] instanceof Button)) { this.buttons[i] = new Button( utils.extend( this.buttons[i], { editor: this.editor }, true ) ) } } } }, initResizeEvent: function () { var me = this domUtils.on(window, 'resize', function () { if (me._hidden || me._hidden === undefined) { return } if (me.__resizeTimer) { window.clearTimeout(me.__resizeTimer) } me.__resizeTimer = window.setTimeout(function () { me.__resizeTimer = null var dialogWrapNode = me.getDom(), contentNode = me.getDom('content'), wrapRect = UE.ui.uiUtils.getClientRect(dialogWrapNode), contentRect = UE.ui.uiUtils.getClientRect(contentNode), vpRect = uiUtils.getViewportRect() contentNode.style.width = vpRect.width - wrapRect.width + contentRect.width + 'px' contentNode.style.height = vpRect.height - wrapRect.height + contentRect.height + 'px' dialogWrapNode.style.width = vpRect.width + 'px' dialogWrapNode.style.height = vpRect.height + 'px' me.fireEvent('resize') }, 100) }) }, fitSize: function () { var popBodyEl = this.getDom('body') // if (!(baidu.editor.browser.ie && baidu.editor.browser.version == 7)) { // uiUtils.removeStyle(popBodyEl, 'width'); // uiUtils.removeStyle(popBodyEl, 'height'); // } var size = this.mesureSize() popBodyEl.style.width = size.width + 'px' popBodyEl.style.height = size.height + 'px' return size }, safeSetOffset: function (offset) { var me = this var el = me.getDom() var vpRect = uiUtils.getViewportRect() var rect = uiUtils.getClientRect(el) var left = offset.left if (left + rect.width > vpRect.right) { left = vpRect.right - rect.width } var top = offset.top if (top + rect.height > vpRect.bottom) { top = vpRect.bottom - rect.height } el.style.left = Math.max(left, 0) + 'px' el.style.top = Math.max(top, 0) + 'px' }, showAtCenter: function () { var vpRect = uiUtils.getViewportRect() if (!this.fullscreen) { this.getDom().style.display = '' var popSize = this.fitSize() var titleHeight = this.getDom('titlebar').offsetHeight | 0 var left = vpRect.width / 2 - popSize.width / 2 var top = vpRect.height / 2 - (popSize.height - titleHeight) / 2 - titleHeight var popEl = this.getDom() this.safeSetOffset({ left: Math.max(left | 0, 0), top: Math.max(top | 0, 0) }) if (!domUtils.hasClass(popEl, 'edui-state-centered')) { popEl.className += ' edui-state-centered' } } else { var dialogWrapNode = this.getDom(), contentNode = this.getDom('content') dialogWrapNode.style.display = 'block' var wrapRect = UE.ui.uiUtils.getClientRect(dialogWrapNode), contentRect = UE.ui.uiUtils.getClientRect(contentNode) dialogWrapNode.style.left = '-100000px' contentNode.style.width = vpRect.width - wrapRect.width + contentRect.width + 'px' contentNode.style.height = vpRect.height - wrapRect.height + contentRect.height + 'px' dialogWrapNode.style.width = vpRect.width + 'px' dialogWrapNode.style.height = vpRect.height + 'px' dialogWrapNode.style.left = 0 //保存环境的overflow值 this._originalContext = { html: { overflowX: document.documentElement.style.overflowX, overflowY: document.documentElement.style.overflowY }, body: { overflowX: document.body.style.overflowX, overflowY: document.body.style.overflowY } } document.documentElement.style.overflowX = 'hidden' document.documentElement.style.overflowY = 'hidden' document.body.style.overflowX = 'hidden' document.body.style.overflowY = 'hidden' } this._show() }, getContentHtml: function () { var contentHtml = '' if (typeof this.content == 'string') { contentHtml = this.content } else if (this.iframeUrl) { contentHtml = '' } return contentHtml }, getHtmlTpl: function () { var footHtml = '' if (this.buttons) { var buff = [] for (var i = 0; i < this.buttons.length; i++) { buff[i] = this.buttons[i].renderHtml() } footHtml = '
    ' + '
    ' + buff.join('') + '
    ' + '
    ' } return ( '
    ' + '
    ' + '
    ' + '
    ' + '' + (this.title || '') + '' + '
    ' + this.closeButton.renderHtml() + '
    ' + '
    ' + (this.autoReset ? '' : this.getContentHtml()) + '
    ' + footHtml + '
    ' ) }, postRender: function () { // todo: 保持居中/记住上次关闭位置选项 if (!this.modalMask.getDom()) { this.modalMask.render() this.modalMask.hide() } if (!this.dragMask.getDom()) { this.dragMask.render() this.dragMask.hide() } var me = this this.addListener('show', function () { me.modalMask.show(this.getDom().style.zIndex - 2) }) this.addListener('hide', function () { me.modalMask.hide() }) if (this.buttons) { for (var i = 0; i < this.buttons.length; i++) { this.buttons[i].postRender() } } domUtils.on(window, 'resize', function () { setTimeout(function () { if (!me.isHidden()) { me.safeSetOffset(uiUtils.getClientRect(me.getDom())) } }) }) //hold住scroll事件,防止dialog的滚动影响页面 // if( this.holdScroll ) { // // if( !me.iframeUrl ) { // domUtils.on( document.getElementById( me.id + "_iframe"), !browser.gecko ? "mousewheel" : "DOMMouseScroll", function(e){ // domUtils.preventDefault(e); // } ); // } else { // me.addListener('dialogafterreset', function(){ // window.setTimeout(function(){ // var iframeWindow = document.getElementById( me.id + "_iframe").contentWindow; // // if( browser.ie ) { // // var timer = window.setInterval(function(){ // // if( iframeWindow.document && iframeWindow.document.body ) { // window.clearInterval( timer ); // timer = null; // domUtils.on( iframeWindow.document.body, !browser.gecko ? "mousewheel" : "DOMMouseScroll", function(e){ // domUtils.preventDefault(e); // } ); // } // // }, 100); // // } else { // domUtils.on( iframeWindow, !browser.gecko ? "mousewheel" : "DOMMouseScroll", function(e){ // domUtils.preventDefault(e); // } ); // } // // }, 1); // }); // } // // } this._hide() }, mesureSize: function () { var body = this.getDom('body') var width = uiUtils.getClientRect(this.getDom('content')).width var dialogBodyStyle = body.style dialogBodyStyle.width = width return uiUtils.getClientRect(body) }, _onTitlebarMouseDown: function (evt, el) { if (this.draggable) { var rect var vpRect = uiUtils.getViewportRect() var me = this uiUtils.startDrag(evt, { ondragstart: function () { rect = uiUtils.getClientRect(me.getDom()) me.getDom('contmask').style.visibility = 'visible' me.dragMask.show(me.getDom().style.zIndex - 1) }, ondragmove: function (x, y) { var left = rect.left + x var top = rect.top + y me.safeSetOffset({ left: left, top: top }) }, ondragstop: function () { me.getDom('contmask').style.visibility = 'hidden' domUtils.removeClasses(me.getDom(), ['edui-state-centered']) me.dragMask.hide() } }) } }, reset: function () { this.getDom('content').innerHTML = this.getContentHtml() this.fireEvent('dialogafterreset') }, _show: function () { if (this._hidden) { this.getDom().style.display = '' //要高过编辑器的zindxe this.editor.container.style.zIndex && (this.getDom().style.zIndex = this.editor.container.style.zIndex * 1 + 10) this._hidden = false this.fireEvent('show') baidu.editor.ui.uiUtils.getFixedLayer().style.zIndex = this.getDom().style.zIndex - 4 } }, isHidden: function () { return this._hidden }, _hide: function () { if (!this._hidden) { var wrapNode = this.getDom() wrapNode.style.display = 'none' wrapNode.style.zIndex = '' wrapNode.style.width = '' wrapNode.style.height = '' this._hidden = true this.fireEvent('hide') } }, open: function () { if (this.autoReset) { //有可能还没有渲染 try { this.reset() } catch (e) { this.render() this.open() } } this.showAtCenter() if (this.iframeUrl) { try { this.getDom('iframe').focus() } catch (ex) {} } activeDialog = this }, _onCloseButtonClick: function (evt, el) { this.close(false) }, close: function (ok) { if (this.fireEvent('close', ok) !== false) { //还原环境 if (this.fullscreen) { document.documentElement.style.overflowX = this._originalContext.html.overflowX document.documentElement.style.overflowY = this._originalContext.html.overflowY document.body.style.overflowX = this._originalContext.body.overflowX document.body.style.overflowY = this._originalContext.body.overflowY delete this._originalContext } this._hide() //销毁content var content = this.getDom('content') var iframe = this.getDom('iframe') if (content && iframe) { var doc = iframe.contentDocument || iframe.contentWindow.document doc && (doc.body.innerHTML = '') domUtils.remove(content) } } } } utils.inherits(Dialog, UIBase) })() // ui/menubutton.js ///import core ///import uicore ///import ui/menu.js ///import ui/splitbutton.js ;(function () { var utils = baidu.editor.utils, Menu = baidu.editor.ui.Menu, SplitButton = baidu.editor.ui.SplitButton, MenuButton = (baidu.editor.ui.MenuButton = function (options) { this.initOptions(options) this.initMenuButton() }) MenuButton.prototype = { initMenuButton: function () { var me = this this.uiName = 'menubutton' this.popup = new Menu({ items: me.items, className: me.className, editor: me.editor }) this.popup.addListener('show', function () { var list = this for (var i = 0; i < list.items.length; i++) { list.items[i].removeState('checked') if (list.items[i].value == me._value) { list.items[i].addState('checked') this.value = me._value } } }) this.initSplitButton() }, setValue: function (value) { this._value = value } } utils.inherits(MenuButton, SplitButton) })() // ui/multiMenu.js ///import core ///import uicore ///commands 表情 ;(function () { var utils = baidu.editor.utils, Popup = baidu.editor.ui.Popup, SplitButton = baidu.editor.ui.SplitButton, MultiMenuPop = (baidu.editor.ui.MultiMenuPop = function (options) { this.initOptions(options) this.initMultiMenu() }) MultiMenuPop.prototype = { initMultiMenu: function () { var me = this this.popup = new Popup({ content: '', editor: me.editor, iframe_rendered: false, onshow: function () { if (!this.iframe_rendered) { this.iframe_rendered = true this.getDom('content').innerHTML = '' me.editor.container.style.zIndex && (this.getDom().style.zIndex = me.editor.container.style.zIndex * 1 + 1) } } // canSideUp:false, // canSideLeft:false }) this.onbuttonclick = function () { this.showPopup() } this.initSplitButton() } } utils.inherits(MultiMenuPop, SplitButton) })() // ui/shortcutmenu.js ;(function () { var UI = baidu.editor.ui, UIBase = UI.UIBase, uiUtils = UI.uiUtils, utils = baidu.editor.utils, domUtils = baidu.editor.dom.domUtils var allMenus = [], //存储所有快捷菜单 timeID, isSubMenuShow = false //是否有子pop显示 var ShortCutMenu = (UI.ShortCutMenu = function (options) { this.initOptions(options) this.initShortCutMenu() }) ShortCutMenu.postHide = hideAllMenu ShortCutMenu.prototype = { isHidden: true, SPACE: 5, initShortCutMenu: function () { this.items = this.items || [] this.initUIBase() this.initItems() this.initEvent() allMenus.push(this) }, initEvent: function () { var me = this, doc = me.editor.document domUtils.on(doc, 'mousemove', function (e) { if (me.isHidden === false) { //有pop显示就不隐藏快捷菜单 if (me.getSubMenuMark() || me.eventType == 'contextmenu') return var flag = true, el = me.getDom(), wt = el.offsetWidth, ht = el.offsetHeight, distanceX = wt / 2 + me.SPACE, //距离中心X标准 distanceY = ht / 2, //距离中心Y标准 x = Math.abs(e.screenX - me.left), //离中心距离横坐标 y = Math.abs(e.screenY - me.top) //离中心距离纵坐标 clearTimeout(timeID) timeID = setTimeout(function () { if (y > 0 && y < distanceY) { me.setOpacity(el, '1') } else if (y > distanceY && y < distanceY + 70) { me.setOpacity(el, '0.5') flag = false } else if (y > distanceY + 70 && y < distanceY + 140) { me.hide() } if (flag && x > 0 && x < distanceX) { me.setOpacity(el, '1') } else if (x > distanceX && x < distanceX + 70) { me.setOpacity(el, '0.5') } else if (x > distanceX + 70 && x < distanceX + 140) { me.hide() } }) } }) //ie\ff下 mouseout不准 if (browser.chrome) { domUtils.on(doc, 'mouseout', function (e) { var relatedTgt = e.relatedTarget || e.toElement if (relatedTgt == null || relatedTgt.tagName == 'HTML') { me.hide() } }) } me.editor.addListener('afterhidepop', function () { if (!me.isHidden) { isSubMenuShow = true } }) }, initItems: function () { if (utils.isArray(this.items)) { for (var i = 0, len = this.items.length; i < len; i++) { var item = this.items[i].toLowerCase() if (UI[item]) { this.items[i] = new UI[item](this.editor) this.items[i].className += ' edui-shortcutsubmenu ' } } } }, setOpacity: function (el, value) { if (browser.ie && browser.version < 9) { el.style.filter = 'alpha(opacity = ' + parseFloat(value) * 100 + ');' } else { el.style.opacity = value } }, getSubMenuMark: function () { isSubMenuShow = false var layerEle = uiUtils.getFixedLayer() var list = domUtils.getElementsByTagName( layerEle, 'div', function (node) { return domUtils.hasClass(node, 'edui-shortcutsubmenu edui-popup') } ) for (var i = 0, node; (node = list[i++]); ) { if (node.style.display != 'none') { isSubMenuShow = true } } return isSubMenuShow }, show: function (e, hasContextmenu) { var me = this, offset = {}, el = this.getDom(), fixedlayer = uiUtils.getFixedLayer() function setPos(offset) { if (offset.left < 0) { offset.left = 0 } if (offset.top < 0) { offset.top = 0 } el.style.cssText = 'position:absolute;left:' + offset.left + 'px;top:' + offset.top + 'px;' } function setPosByCxtMenu(menu) { if (!menu.tagName) { menu = menu.getDom() } offset.left = parseInt(menu.style.left) offset.top = parseInt(menu.style.top) offset.top -= el.offsetHeight + 15 setPos(offset) } me.eventType = e.type el.style.cssText = 'display:block;left:-9999px' if (e.type == 'contextmenu' && hasContextmenu) { var menu = domUtils.getElementsByTagName( fixedlayer, 'div', 'edui-contextmenu' )[0] if (menu) { setPosByCxtMenu(menu) } else { me.editor.addListener( 'aftershowcontextmenu', function (type, menu) { setPosByCxtMenu(menu) } ) } } else { offset = uiUtils.getViewportOffsetByEvent(e) offset.top -= el.offsetHeight + me.SPACE offset.left += me.SPACE + 20 setPos(offset) me.setOpacity(el, 0.2) } me.isHidden = false me.left = e.screenX + el.offsetWidth / 2 - me.SPACE me.top = e.screenY - el.offsetHeight / 2 - me.SPACE if (me.editor) { el.style.zIndex = me.editor.container.style.zIndex * 1 + 10 fixedlayer.style.zIndex = el.style.zIndex - 1 } }, hide: function () { if (this.getDom()) { this.getDom().style.display = 'none' } this.isHidden = true }, postRender: function () { if (utils.isArray(this.items)) { for (var i = 0, item; (item = this.items[i++]); ) { item.postRender() } } }, getHtmlTpl: function () { var buff if (utils.isArray(this.items)) { buff = [] for (var i = 0; i < this.items.length; i++) { buff[i] = this.items[i].renderHtml() } buff = buff.join('') } else { buff = this.items } return ( '
    ' + buff + '
    ' ) } } utils.inherits(ShortCutMenu, UIBase) function hideAllMenu(e) { var tgt = e.target || e.srcElement, cur = domUtils.findParent( tgt, function (node) { return ( domUtils.hasClass(node, 'edui-shortcutmenu') || domUtils.hasClass(node, 'edui-popup') ) }, true ) if (!cur) { for (var i = 0, menu; (menu = allMenus[i++]); ) { menu.hide() } } } domUtils.on(document, 'mousedown', function (e) { hideAllMenu(e) }) domUtils.on(window, 'scroll', function (e) { hideAllMenu(e) }) })() // ui/breakline.js ;(function () { var utils = baidu.editor.utils, UIBase = baidu.editor.ui.UIBase, Breakline = (baidu.editor.ui.Breakline = function (options) { this.initOptions(options) this.initSeparator() }) Breakline.prototype = { uiName: 'Breakline', initSeparator: function () { this.initUIBase() }, getHtmlTpl: function () { return '
    ' } } utils.inherits(Breakline, UIBase) })() // ui/message.js ///import core ///import uicore ;(function () { var utils = baidu.editor.utils, domUtils = baidu.editor.dom.domUtils, UIBase = baidu.editor.ui.UIBase, Message = (baidu.editor.ui.Message = function (options) { this.initOptions(options) this.initMessage() }) Message.prototype = { initMessage: function () { this.initUIBase() }, getHtmlTpl: function () { return ( '
    ' + '
    ×
    ' + '
    ' + ' ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' ) }, reset: function (opt) { var me = this if (!opt.keepshow) { clearTimeout(this.timer) me.timer = setTimeout(function () { me.hide() }, opt.timeout || 4000) } opt.content !== undefined && me.setContent(opt.content) opt.type !== undefined && me.setType(opt.type) me.show() }, postRender: function () { var me = this, closer = this.getDom('closer') closer && domUtils.on(closer, 'click', function () { me.hide() }) }, setContent: function (content) { this.getDom('content').innerHTML = content }, setType: function (type) { type = type || 'info' var body = this.getDom('body') body.className = body.className.replace( /edui-message-type-[\w-]+/, 'edui-message-type-' + type ) }, getContent: function () { return this.getDom('content').innerHTML }, getType: function () { var arr = this.getDom('body').match(/edui-message-type-([\w-]+)/) return arr ? arr[1] : '' }, show: function () { this.getDom().style.display = 'block' }, hide: function () { var dom = this.getDom() if (dom) { dom.style.display = 'none' dom.parentNode && dom.parentNode.removeChild(dom) } } } utils.inherits(Message, UIBase) })() // adapter/editorui.js //ui跟编辑器的适配層 //那个按钮弹出是dialog,是下拉筐等都是在这个js中配置 //自己写的ui也要在这里配置,放到baidu.editor.ui下边,当编辑器实例化的时候会根据ueditor.config中的toolbars找到相应的进行实例化 ;(function () { var utils = baidu.editor.utils var editorui = baidu.editor.ui var _Dialog = editorui.Dialog editorui.buttons = {} editorui.Dialog = function (options) { var dialog = new _Dialog(options) dialog.addListener('hide', function () { if (dialog.editor) { var editor = dialog.editor try { if (browser.gecko) { var y = editor.window.scrollY, x = editor.window.scrollX editor.body.focus() editor.window.scrollTo(x, y) } else { editor.focus() } } catch (ex) {} } }) return dialog } var iframeUrlMap = { anchor: '~/dialogs/anchor/anchor.html', insertimage: '~/dialogs/image/image.html', link: '~/dialogs/link/link.html', spechars: '~/dialogs/spechars/spechars.html', searchreplace: '~/dialogs/searchreplace/searchreplace.html', map: '~/dialogs/map/map.html', gmap: '~/dialogs/gmap/gmap.html', insertvideo: '~/dialogs/video/video.html', help: '~/dialogs/help/help.html', preview: '~/dialogs/preview/preview.html', emotion: '~/dialogs/emotion/emotion.html', wordimage: '~/dialogs/wordimage/wordimage.html', attachment: '~/dialogs/attachment/attachment.html', insertframe: '~/dialogs/insertframe/insertframe.html', edittip: '~/dialogs/table/edittip.html', edittable: '~/dialogs/table/edittable.html', edittd: '~/dialogs/table/edittd.html', webapp: '~/dialogs/webapp/webapp.html', snapscreen: '~/dialogs/snapscreen/snapscreen.html', scrawl: '~/dialogs/scrawl/scrawl.html', music: '~/dialogs/music/music.html', template: '~/dialogs/template/template.html', background: '~/dialogs/background/background.html', charts: '~/dialogs/charts/charts.html' } //为工具栏添加按钮,以下都是统一的按钮触发命令,所以写在一起 var btnCmds = [ 'undo', 'redo', 'formatmatch', 'bold', 'italic', 'underline', 'fontborder', 'touppercase', 'tolowercase', 'strikethrough', 'subscript', 'superscript', 'source', 'indent', 'outdent', 'blockquote', 'pasteplain', 'pagebreak', 'selectall', 'print', 'horizontal', 'removeformat', 'time', 'date', 'unlink', 'insertparagraphbeforetable', 'insertrow', 'insertcol', 'mergeright', 'mergedown', 'deleterow', 'deletecol', 'splittorows', 'splittocols', 'splittocells', 'mergecells', 'deletetable', 'drafts' ] for (var i = 0, ci; (ci = btnCmds[i++]); ) { ci = ci.toLowerCase() editorui[ci] = (function (cmd) { return function (editor) { var ui = new editorui.Button({ className: 'edui-for-' + cmd, title: editor.options.labelMap[cmd] || editor.getLang('labelMap.' + cmd) || '', onclick: function () { editor.execCommand(cmd) }, theme: editor.options.theme, showText: false }) editorui.buttons[cmd] = ui editor.addListener( 'selectionchange', function (type, causeByUi, uiReady) { var state = editor.queryCommandState(cmd) if (state == -1) { ui.setDisabled(true) ui.setChecked(false) } else { if (!uiReady) { ui.setDisabled(false) ui.setChecked(state) } } } ) return ui } })(ci) } //清除文档 editorui.cleardoc = function (editor) { var ui = new editorui.Button({ className: 'edui-for-cleardoc', title: editor.options.labelMap.cleardoc || editor.getLang('labelMap.cleardoc') || '', theme: editor.options.theme, onclick: function () { if (confirm(editor.getLang('confirmClear'))) { editor.execCommand('cleardoc') } } }) editorui.buttons['cleardoc'] = ui editor.addListener('selectionchange', function () { ui.setDisabled(editor.queryCommandState('cleardoc') == -1) }) return ui } //排版,图片排版,文字方向 var typeset = { justify: ['left', 'right', 'center', 'justify'], imagefloat: ['none', 'left', 'center', 'right'], directionality: ['ltr', 'rtl'] } for (var p in typeset) { ;(function (cmd, val) { for (var i = 0, ci; (ci = val[i++]); ) { ;(function (cmd2) { editorui[cmd.replace('float', '') + cmd2] = function (editor) { var ui = new editorui.Button({ className: 'edui-for-' + cmd.replace('float', '') + cmd2, title: editor.options.labelMap[cmd.replace('float', '') + cmd2] || editor.getLang( 'labelMap.' + cmd.replace('float', '') + cmd2 ) || '', theme: editor.options.theme, onclick: function () { editor.execCommand(cmd, cmd2) } }) editorui.buttons[cmd] = ui editor.addListener( 'selectionchange', function (type, causeByUi, uiReady) { ui.setDisabled(editor.queryCommandState(cmd) == -1) ui.setChecked( editor.queryCommandValue(cmd) == cmd2 && !uiReady ) } ) return ui } })(ci) } })(p, typeset[p]) } //字体颜色和背景颜色 for (var i = 0, ci; (ci = ['backcolor', 'forecolor'][i++]); ) { editorui[ci] = (function (cmd) { return function (editor) { var ui = new editorui.ColorButton({ className: 'edui-for-' + cmd, color: 'default', title: editor.options.labelMap[cmd] || editor.getLang('labelMap.' + cmd) || '', editor: editor, onpickcolor: function (t, color) { editor.execCommand(cmd, color) }, onpicknocolor: function () { editor.execCommand(cmd, 'default') this.setColor('transparent') this.color = 'default' }, onbuttonclick: function () { editor.execCommand(cmd, this.color) } }) editorui.buttons[cmd] = ui editor.addListener('selectionchange', function () { ui.setDisabled(editor.queryCommandState(cmd) == -1) }) return ui } })(ci) } var dialogBtns = { noOk: ['searchreplace', 'help', 'spechars', 'webapp', 'preview'], ok: [ 'attachment', 'anchor', 'link', 'insertimage', 'map', 'gmap', 'insertframe', 'wordimage', 'insertvideo', 'insertframe', 'edittip', 'edittable', 'edittd', 'scrawl', 'template', 'music', 'background', 'charts' ] } for (var p in dialogBtns) { ;(function (type, vals) { for (var i = 0, ci; (ci = vals[i++]); ) { //todo opera下存在问题 if (browser.opera && ci === 'searchreplace') { continue } ;(function (cmd) { editorui[cmd] = function (editor, iframeUrl, title) { iframeUrl = iframeUrl || (editor.options.iframeUrlMap || {})[cmd] || iframeUrlMap[cmd] title = editor.options.labelMap[cmd] || editor.getLang('labelMap.' + cmd) || '' var dialog //没有iframeUrl不创建dialog if (iframeUrl) { dialog = new editorui.Dialog( utils.extend( { iframeUrl: editor.ui.mapUrl(iframeUrl), editor: editor, className: 'edui-for-' + cmd, title: title, holdScroll: cmd === 'insertimage', fullscreen: /charts|preview/.test(cmd), closeDialog: editor.getLang('closeDialog') }, type == 'ok' ? { buttons: [ { className: 'edui-okbutton', label: editor.getLang('ok'), editor: editor, onclick: function () { dialog.close(true) } }, { className: 'edui-cancelbutton', label: editor.getLang('cancel'), editor: editor, onclick: function () { dialog.close(false) } } ] } : {} ) ) editor.ui._dialogs[cmd + 'Dialog'] = dialog } var ui = new editorui.Button({ className: 'edui-for-' + cmd, title: title, onclick: function () { if (dialog) { switch (cmd) { case 'wordimage': var images = editor.execCommand('wordimage') if (images && images.length) { dialog.render() dialog.open() } break case 'scrawl': if (editor.queryCommandState('scrawl') != -1) { dialog.render() dialog.open() } break default: dialog.render() dialog.open() } } }, theme: editor.options.theme, disabled: (cmd == 'scrawl' && editor.queryCommandState('scrawl') == -1) || cmd == 'charts' }) editorui.buttons[cmd] = ui editor.addListener('selectionchange', function () { //只存在于右键菜单而无工具栏按钮的ui不需要检测状态 var unNeedCheckState = { edittable: 1 } if (cmd in unNeedCheckState) return var state = editor.queryCommandState(cmd) if (ui.getDom()) { ui.setDisabled(state == -1) ui.setChecked(state) } }) return ui } })(ci.toLowerCase()) } })(p, dialogBtns[p]) } editorui.snapscreen = function (editor, iframeUrl, title) { title = editor.options.labelMap['snapscreen'] || editor.getLang('labelMap.snapscreen') || '' var ui = new editorui.Button({ className: 'edui-for-snapscreen', title: title, onclick: function () { editor.execCommand('snapscreen') }, theme: editor.options.theme }) editorui.buttons['snapscreen'] = ui iframeUrl = iframeUrl || (editor.options.iframeUrlMap || {})['snapscreen'] || iframeUrlMap['snapscreen'] if (iframeUrl) { var dialog = new editorui.Dialog({ iframeUrl: editor.ui.mapUrl(iframeUrl), editor: editor, className: 'edui-for-snapscreen', title: title, buttons: [ { className: 'edui-okbutton', label: editor.getLang('ok'), editor: editor, onclick: function () { dialog.close(true) } }, { className: 'edui-cancelbutton', label: editor.getLang('cancel'), editor: editor, onclick: function () { dialog.close(false) } } ] }) dialog.render() editor.ui._dialogs['snapscreenDialog'] = dialog } editor.addListener('selectionchange', function () { ui.setDisabled(editor.queryCommandState('snapscreen') == -1) }) return ui } editorui.insertcode = function (editor, list, title) { list = editor.options['insertcode'] || [] title = editor.options.labelMap['insertcode'] || editor.getLang('labelMap.insertcode') || '' // if (!list.length) return; var items = [] utils.each(list, function (key, val) { items.push({ label: key, value: val, theme: editor.options.theme, renderLabelHtml: function () { return ( '
    ' + (this.label || '') + '
    ' ) } }) }) var ui = new editorui.Combox({ editor: editor, items: items, onselect: function (t, index) { editor.execCommand('insertcode', this.items[index].value) }, onbuttonclick: function () { this.showPopup() }, title: title, initValue: title, className: 'edui-for-insertcode', indexByValue: function (value) { if (value) { for (var i = 0, ci; (ci = this.items[i]); i++) { if (ci.value.indexOf(value) != -1) return i } } return -1 } }) editorui.buttons['insertcode'] = ui editor.addListener( 'selectionchange', function (type, causeByUi, uiReady) { if (!uiReady) { var state = editor.queryCommandState('insertcode') if (state == -1) { ui.setDisabled(true) } else { ui.setDisabled(false) var value = editor.queryCommandValue('insertcode') if (!value) { ui.setValue(title) return } //trace:1871 ie下从源码模式切换回来时,字体会带单引号,而且会有逗号 value && (value = value.replace(/['"]/g, '').split(',')[0]) ui.setValue(value) } } } ) return ui } editorui.fontfamily = function (editor, list, title) { list = editor.options['fontfamily'] || [] title = editor.options.labelMap['fontfamily'] || editor.getLang('labelMap.fontfamily') || '' if (!list.length) return for (var i = 0, ci, items = []; (ci = list[i]); i++) { var langLabel = editor.getLang('fontfamily')[ci.name] || '' ;(function (key, val) { items.push({ label: key, value: val, theme: editor.options.theme, renderLabelHtml: function () { return ( '
    ' + (this.label || '') + '
    ' ) } }) })(ci.label || langLabel, ci.val) } var ui = new editorui.Combox({ editor: editor, items: items, onselect: function (t, index) { editor.execCommand('FontFamily', this.items[index].value) }, onbuttonclick: function () { this.showPopup() }, title: title, initValue: title, className: 'edui-for-fontfamily', indexByValue: function (value) { if (value) { for (var i = 0, ci; (ci = this.items[i]); i++) { if (ci.value.indexOf(value) != -1) return i } } return -1 } }) editorui.buttons['fontfamily'] = ui editor.addListener( 'selectionchange', function (type, causeByUi, uiReady) { if (!uiReady) { var state = editor.queryCommandState('FontFamily') if (state == -1) { ui.setDisabled(true) } else { ui.setDisabled(false) var value = editor.queryCommandValue('FontFamily') //trace:1871 ie下从源码模式切换回来时,字体会带单引号,而且会有逗号 value && (value = value.replace(/['"]/g, '').split(',')[0]) ui.setValue(value) } } } ) return ui } editorui.fontsize = function (editor, list, title) { title = editor.options.labelMap['fontsize'] || editor.getLang('labelMap.fontsize') || '' list = list || editor.options['fontsize'] || [] if (!list.length) return var items = [] for (var i = 0; i < list.length; i++) { var size = list[i] + 'px' items.push({ label: size, value: size, theme: editor.options.theme, renderLabelHtml: function () { return ( '
    ' + (this.label || '') + '
    ' ) } }) } var ui = new editorui.Combox({ editor: editor, items: items, title: title, initValue: title, onselect: function (t, index) { editor.execCommand('FontSize', this.items[index].value) }, onbuttonclick: function () { this.showPopup() }, className: 'edui-for-fontsize' }) editorui.buttons['fontsize'] = ui editor.addListener( 'selectionchange', function (type, causeByUi, uiReady) { if (!uiReady) { var state = editor.queryCommandState('FontSize') if (state == -1) { ui.setDisabled(true) } else { ui.setDisabled(false) ui.setValue(editor.queryCommandValue('FontSize')) } } } ) return ui } editorui.paragraph = function (editor, list, title) { title = editor.options.labelMap['paragraph'] || editor.getLang('labelMap.paragraph') || '' list = editor.options['paragraph'] || [] if (utils.isEmptyObject(list)) return var items = [] for (var i in list) { items.push({ value: i, label: list[i] || editor.getLang('paragraph')[i], theme: editor.options.theme, renderLabelHtml: function () { return ( '
    ' + (this.label || '') + '
    ' ) } }) } var ui = new editorui.Combox({ editor: editor, items: items, title: title, initValue: title, className: 'edui-for-paragraph', onselect: function (t, index) { editor.execCommand('Paragraph', this.items[index].value) }, onbuttonclick: function () { this.showPopup() } }) editorui.buttons['paragraph'] = ui editor.addListener( 'selectionchange', function (type, causeByUi, uiReady) { if (!uiReady) { var state = editor.queryCommandState('Paragraph') if (state == -1) { ui.setDisabled(true) } else { ui.setDisabled(false) var value = editor.queryCommandValue('Paragraph') var index = ui.indexByValue(value) if (index != -1) { ui.setValue(value) } else { ui.setValue(ui.initValue) } } } } ) return ui } //自定义标题 editorui.customstyle = function (editor) { var list = editor.options['customstyle'] || [], title = editor.options.labelMap['customstyle'] || editor.getLang('labelMap.customstyle') || '' if (!list.length) return var langCs = editor.getLang('customstyle') for (var i = 0, items = [], t; (t = list[i++]); ) { ;(function (t) { var ck = {} ck.label = t.label ? t.label : langCs[t.name] ck.style = t.style ck.className = t.className ck.tag = t.tag items.push({ label: ck.label, value: ck, theme: editor.options.theme, renderLabelHtml: function () { return ( '
    ' + '<' + ck.tag + ' ' + (ck.className ? ' class="' + ck.className + '"' : '') + (ck.style ? ' style="' + ck.style + '"' : '') + '>' + ck.label + '' + '
    ' ) } }) })(t) } var ui = new editorui.Combox({ editor: editor, items: items, title: title, initValue: title, className: 'edui-for-customstyle', onselect: function (t, index) { editor.execCommand('customstyle', this.items[index].value) }, onbuttonclick: function () { this.showPopup() }, indexByValue: function (value) { for (var i = 0, ti; (ti = this.items[i++]); ) { if (ti.label == value) { return i - 1 } } return -1 } }) editorui.buttons['customstyle'] = ui editor.addListener( 'selectionchange', function (type, causeByUi, uiReady) { if (!uiReady) { var state = editor.queryCommandState('customstyle') if (state == -1) { ui.setDisabled(true) } else { ui.setDisabled(false) var value = editor.queryCommandValue('customstyle') var index = ui.indexByValue(value) if (index != -1) { ui.setValue(value) } else { ui.setValue(ui.initValue) } } } } ) return ui } editorui.inserttable = function (editor, iframeUrl, title) { title = editor.options.labelMap['inserttable'] || editor.getLang('labelMap.inserttable') || '' var ui = new editorui.TableButton({ editor: editor, title: title, className: 'edui-for-inserttable', onpicktable: function (t, numCols, numRows) { editor.execCommand('InsertTable', { numRows: numRows, numCols: numCols, border: 1 }) }, onbuttonclick: function () { this.showPopup() } }) editorui.buttons['inserttable'] = ui editor.addListener('selectionchange', function () { ui.setDisabled(editor.queryCommandState('inserttable') == -1) }) return ui } editorui.lineheight = function (editor) { var val = editor.options.lineheight || [] if (!val.length) return for (var i = 0, ci, items = []; (ci = val[i++]); ) { items.push({ //todo:写死了 label: ci, value: ci, theme: editor.options.theme, onclick: function () { editor.execCommand('lineheight', this.value) } }) } var ui = new editorui.MenuButton({ editor: editor, className: 'edui-for-lineheight', title: editor.options.labelMap['lineheight'] || editor.getLang('labelMap.lineheight') || '', items: items, onbuttonclick: function () { var value = editor.queryCommandValue('LineHeight') || this.value editor.execCommand('LineHeight', value) } }) editorui.buttons['lineheight'] = ui editor.addListener('selectionchange', function () { var state = editor.queryCommandState('LineHeight') if (state == -1) { ui.setDisabled(true) } else { ui.setDisabled(false) var value = editor.queryCommandValue('LineHeight') value && ui.setValue((value + '').replace(/cm/, '')) ui.setChecked(state) } }) return ui } var rowspacings = ['top', 'bottom'] for (var r = 0, ri; (ri = rowspacings[r++]); ) { ;(function (cmd) { editorui['rowspacing' + cmd] = function (editor) { var val = editor.options['rowspacing' + cmd] || [] if (!val.length) return null for (var i = 0, ci, items = []; (ci = val[i++]); ) { items.push({ label: ci, value: ci, theme: editor.options.theme, onclick: function () { editor.execCommand('rowspacing', this.value, cmd) } }) } var ui = new editorui.MenuButton({ editor: editor, className: 'edui-for-rowspacing' + cmd, title: editor.options.labelMap['rowspacing' + cmd] || editor.getLang('labelMap.rowspacing' + cmd) || '', items: items, onbuttonclick: function () { var value = editor.queryCommandValue('rowspacing', cmd) || this.value editor.execCommand('rowspacing', value, cmd) } }) editorui.buttons[cmd] = ui editor.addListener('selectionchange', function () { var state = editor.queryCommandState('rowspacing', cmd) if (state == -1) { ui.setDisabled(true) } else { ui.setDisabled(false) var value = editor.queryCommandValue('rowspacing', cmd) value && ui.setValue((value + '').replace(/%/, '')) ui.setChecked(state) } }) return ui } })(ri) } //有序,无序列表 var lists = ['insertorderedlist', 'insertunorderedlist'] for (var l = 0, cl; (cl = lists[l++]); ) { ;(function (cmd) { editorui[cmd] = function (editor) { var vals = editor.options[cmd], _onMenuClick = function () { editor.execCommand(cmd, this.value) }, items = [] for (var i in vals) { items.push({ label: vals[i] || editor.getLang()[cmd][i] || '', value: i, theme: editor.options.theme, onclick: _onMenuClick }) } var ui = new editorui.MenuButton({ editor: editor, className: 'edui-for-' + cmd, title: editor.getLang('labelMap.' + cmd) || '', items: items, onbuttonclick: function () { var value = editor.queryCommandValue(cmd) || this.value editor.execCommand(cmd, value) } }) editorui.buttons[cmd] = ui editor.addListener('selectionchange', function () { var state = editor.queryCommandState(cmd) if (state == -1) { ui.setDisabled(true) } else { ui.setDisabled(false) var value = editor.queryCommandValue(cmd) ui.setValue(value) ui.setChecked(state) } }) return ui } })(cl) } editorui.fullscreen = function (editor, title) { title = editor.options.labelMap['fullscreen'] || editor.getLang('labelMap.fullscreen') || '' var ui = new editorui.Button({ className: 'edui-for-fullscreen', title: title, theme: editor.options.theme, onclick: function () { if (editor.ui) { editor.ui.setFullScreen(!editor.ui.isFullScreen()) } this.setChecked(editor.ui.isFullScreen()) } }) editorui.buttons['fullscreen'] = ui editor.addListener('selectionchange', function () { var state = editor.queryCommandState('fullscreen') ui.setDisabled(state == -1) ui.setChecked(editor.ui.isFullScreen()) }) return ui } // 表情 editorui['emotion'] = function (editor, iframeUrl) { var cmd = 'emotion' var ui = new editorui.MultiMenuPop({ title: editor.options.labelMap[cmd] || editor.getLang('labelMap.' + cmd + '') || '', editor: editor, className: 'edui-for-' + cmd, iframeUrl: editor.ui.mapUrl( iframeUrl || (editor.options.iframeUrlMap || {})[cmd] || iframeUrlMap[cmd] ) }) editorui.buttons[cmd] = ui editor.addListener('selectionchange', function () { ui.setDisabled(editor.queryCommandState(cmd) == -1) }) return ui } editorui.autotypeset = function (editor) { var ui = new editorui.AutoTypeSetButton({ editor: editor, title: editor.options.labelMap['autotypeset'] || editor.getLang('labelMap.autotypeset') || '', className: 'edui-for-autotypeset', onbuttonclick: function () { editor.execCommand('autotypeset') } }) editorui.buttons['autotypeset'] = ui editor.addListener('selectionchange', function () { ui.setDisabled(editor.queryCommandState('autotypeset') == -1) }) return ui } /* 简单上传插件 */ editorui['simpleupload'] = function (editor) { var name = 'simpleupload', ui = new editorui.Button({ className: 'edui-for-' + name, title: editor.options.labelMap[name] || editor.getLang('labelMap.' + name) || '', onclick: function () {}, theme: editor.options.theme, showText: false }) editorui.buttons[name] = ui editor.addListener('ready', function () { var b = ui.getDom('body'), iconSpan = b.children[0] editor.fireEvent('simpleuploadbtnready', iconSpan) }) editor.addListener( 'selectionchange', function (type, causeByUi, uiReady) { var state = editor.queryCommandState(name) if (state == -1) { ui.setDisabled(true) ui.setChecked(false) } else { if (!uiReady) { ui.setDisabled(false) ui.setChecked(state) } } } ) return ui } })() // adapter/editor.js ///import core ///commands 全屏 ///commandsName FullScreen ///commandsTitle 全屏 ;(function () { var utils = baidu.editor.utils, uiUtils = baidu.editor.ui.uiUtils, UIBase = baidu.editor.ui.UIBase, domUtils = baidu.editor.dom.domUtils var nodeStack = [] function EditorUI(options) { this.initOptions(options) this.initEditorUI() } EditorUI.prototype = { uiName: 'editor', initEditorUI: function () { this.editor.ui = this this._dialogs = {} this.initUIBase() this._initToolbars() var editor = this.editor, me = this editor.addListener('ready', function () { //提供getDialog方法 editor.getDialog = function (name) { return editor.ui._dialogs[name + 'Dialog'] } domUtils.on(editor.window, 'scroll', function (evt) { baidu.editor.ui.Popup.postHide(evt) }) //提供编辑器实时宽高(全屏时宽高不变化) editor.ui._actualFrameWidth = editor.options.initialFrameWidth UE.browser.ie && UE.browser.version === 6 && editor.container.ownerDocument.execCommand( 'BackgroundImageCache', false, true ) //display bottom-bar label based on config if (editor.options.elementPathEnabled) { editor.ui.getDom('elementpath').innerHTML = '
    ' + editor.getLang('elementPathTip') + ':
    ' } if (editor.options.wordCount) { function countFn() { setCount(editor, me) domUtils.un(editor.document, 'click', arguments.callee) } domUtils.on(editor.document, 'click', countFn) editor.ui.getDom('wordcount').innerHTML = editor.getLang('wordCountTip') } editor.ui._scale() if (editor.options.scaleEnabled) { if (editor.autoHeightEnabled) { editor.disableAutoHeight() } me.enableScale() } else { me.disableScale() } if ( !editor.options.elementPathEnabled && !editor.options.wordCount && !editor.options.scaleEnabled ) { editor.ui.getDom('elementpath').style.display = 'none' editor.ui.getDom('wordcount').style.display = 'none' editor.ui.getDom('scale').style.display = 'none' } if (!editor.selection.isFocus()) return editor.fireEvent('selectionchange', false, true) }) editor.addListener('mousedown', function (t, evt) { var el = evt.target || evt.srcElement baidu.editor.ui.Popup.postHide(evt, el) baidu.editor.ui.ShortCutMenu.postHide(evt) }) editor.addListener('delcells', function () { if (UE.ui['edittip']) { new UE.ui['edittip'](editor) } editor.getDialog('edittip').open() }) var pastePop, isPaste = false, timer editor.addListener('afterpaste', function () { if (editor.queryCommandState('pasteplain')) return if (baidu.editor.ui.PastePicker) { pastePop = new baidu.editor.ui.Popup({ content: new baidu.editor.ui.PastePicker({ editor: editor }), editor: editor, className: 'edui-wordpastepop' }) pastePop.render() } isPaste = true }) editor.addListener('afterinserthtml', function () { clearTimeout(timer) timer = setTimeout(function () { if (pastePop && (isPaste || editor.ui._isTransfer)) { if (pastePop.isHidden()) { var span = domUtils.createElement(editor.document, 'span', { style: 'line-height:0px;', innerHTML: '\ufeff' }), range = editor.selection.getRange() range.insertNode(span) var tmp = getDomNode(span, 'firstChild', 'previousSibling') tmp && pastePop.showAnchor(tmp.nodeType == 3 ? tmp.parentNode : tmp) domUtils.remove(span) } else { pastePop.show() } delete editor.ui._isTransfer isPaste = false } }, 200) }) editor.addListener('contextmenu', function (t, evt) { baidu.editor.ui.Popup.postHide(evt) }) editor.addListener('keydown', function (t, evt) { if (pastePop) pastePop.dispose(evt) var keyCode = evt.keyCode || evt.which if (evt.altKey && keyCode == 90) { UE.ui.buttons['fullscreen'].onclick() } }) editor.addListener('wordcount', function (type) { setCount(this, me) }) function setCount(editor, ui) { editor.setOpt({ wordCount: true, maximumWords: 10000, wordCountMsg: editor.options.wordCountMsg || editor.getLang('wordCountMsg'), wordOverFlowMsg: editor.options.wordOverFlowMsg || editor.getLang('wordOverFlowMsg') }) var opt = editor.options, max = opt.maximumWords, msg = opt.wordCountMsg, errMsg = opt.wordOverFlowMsg, countDom = ui.getDom('wordcount') if (!opt.wordCount) { return } var count = editor.getContentLength(true) if (count > max) { countDom.innerHTML = errMsg editor.fireEvent('wordcountoverflow') } else { countDom.innerHTML = msg .replace('{#leave}', max - count) .replace('{#count}', count) } } editor.addListener('selectionchange', function () { if (editor.options.elementPathEnabled) { me[ (editor.queryCommandState('elementpath') == -1 ? 'dis' : 'en') + 'ableElementPath' ]() } if (editor.options.scaleEnabled) { me[ (editor.queryCommandState('scale') == -1 ? 'dis' : 'en') + 'ableScale' ]() } }) var popup = new baidu.editor.ui.Popup({ editor: editor, content: '', className: 'edui-bubble', _onEditButtonClick: function () { this.hide() editor.ui._dialogs.linkDialog.open() }, _onImgEditButtonClick: function (name) { this.hide() editor.ui._dialogs[name] && editor.ui._dialogs[name].open() }, _onImgSetFloat: function (value) { this.hide() editor.execCommand('imagefloat', value) }, _setIframeAlign: function (value) { var frame = popup.anchorEl var newFrame = frame.cloneNode(true) switch (value) { case -2: newFrame.setAttribute('align', '') break case -1: newFrame.setAttribute('align', 'left') break case 1: newFrame.setAttribute('align', 'right') break } frame.parentNode.insertBefore(newFrame, frame) domUtils.remove(frame) popup.anchorEl = newFrame popup.showAnchor(popup.anchorEl) }, _updateIframe: function () { var frame = (editor._iframe = popup.anchorEl) if (domUtils.hasClass(frame, 'ueditor_baidumap')) { editor.selection.getRange().selectNode(frame).select() editor.ui._dialogs.mapDialog.open() popup.hide() } else { editor.ui._dialogs.insertframeDialog.open() popup.hide() } }, _onRemoveButtonClick: function (cmdName) { editor.execCommand(cmdName) this.hide() }, queryAutoHide: function (el) { if (el && el.ownerDocument == editor.document) { if ( el.tagName.toLowerCase() == 'img' || domUtils.findParentByTagName(el, 'a', true) ) { return el !== popup.anchorEl } } return baidu.editor.ui.Popup.prototype.queryAutoHide.call(this, el) } }) popup.render() if (editor.options.imagePopup) { editor.addListener('mouseover', function (t, evt) { evt = evt || window.event var el = evt.target || evt.srcElement if ( editor.ui._dialogs.insertframeDialog && /iframe/gi.test(el.tagName) ) { var html = popup.formatHtml( '' + editor.getLang('property') + ': ' + editor.getLang('default') + '  ' + editor.getLang('justifyleft') + '  ' + editor.getLang('justifyright') + '  ' + ' ' + editor.getLang('modify') + '' ) if (html) { popup.getDom('content').innerHTML = html popup.anchorEl = el popup.showAnchor(popup.anchorEl) } else { popup.hide() } } }) editor.addListener('selectionchange', function (t, causeByUi) { if (!causeByUi) return var html = '', str = '', img = editor.selection.getRange().getClosedNode(), dialogs = editor.ui._dialogs if (img && img.tagName == 'IMG') { var dialogName = 'insertimageDialog' if ( img.className.indexOf('edui-faked-video') != -1 || img.className.indexOf('edui-upload-video') != -1 ) { dialogName = 'insertvideoDialog' } if (img.className.indexOf('edui-faked-webapp') != -1) { dialogName = 'webappDialog' } if (img.src.indexOf('http://api.map.baidu.com') != -1) { dialogName = 'mapDialog' } if (img.className.indexOf('edui-faked-music') != -1) { dialogName = 'musicDialog' } if ( img.src.indexOf('http://maps.google.com/maps/api/staticmap') != -1 ) { dialogName = 'gmapDialog' } if (img.getAttribute('anchorname')) { dialogName = 'anchorDialog' html = popup.formatHtml( '' + editor.getLang('property') + ': ' + editor.getLang('modify') + '  ' + '' + editor.getLang('delete') + '' ) } if (img.getAttribute('word_img')) { //todo 放到dialog去做查询 editor.word_img = [img.getAttribute('word_img')] dialogName = 'wordimageDialog' } if ( domUtils.hasClass(img, 'loadingclass') || domUtils.hasClass(img, 'loaderrorclass') ) { dialogName = '' } if (!dialogs[dialogName]) { return } str = '' + editor.getLang('property') + ': ' + '' + editor.getLang('default') + '  ' + '' + editor.getLang('justifyleft') + '  ' + '' + editor.getLang('justifyright') + '  ' + '' + editor.getLang('justifycenter') + '  ' + '' + editor.getLang('modify') + '' !html && (html = popup.formatHtml(str)) } if (editor.ui._dialogs.linkDialog) { var link = editor.queryCommandValue('link') var url if ( link && (url = link.getAttribute('_href') || link.getAttribute('href', 2)) ) { var txt = url if (url.length > 30) { txt = url.substring(0, 20) + '...' } if (html) { html += '
    ' } html += popup.formatHtml( '' + editor.getLang('anthorMsg') + ': ' + txt + '' + ' ' + editor.getLang('modify') + '' + ' ' + editor.getLang('clear') + '' ) popup.showAnchor(link) } } if (html) { popup.getDom('content').innerHTML = html popup.anchorEl = img || link popup.showAnchor(popup.anchorEl) } else { popup.hide() } }) } }, _initToolbars: function () { var editor = this.editor var toolbars = this.toolbars || [] var toolbarUis = [] for (var i = 0; i < toolbars.length; i++) { var toolbar = toolbars[i] var toolbarUi = new baidu.editor.ui.Toolbar({ theme: editor.options.theme }) for (var j = 0; j < toolbar.length; j++) { var toolbarItem = toolbar[j] var toolbarItemUi = null if (typeof toolbarItem == 'string') { toolbarItem = toolbarItem.toLowerCase() if (toolbarItem == '|') { toolbarItem = 'Separator' } if (toolbarItem == '||') { toolbarItem = 'Breakline' } if (baidu.editor.ui[toolbarItem]) { toolbarItemUi = new baidu.editor.ui[toolbarItem](editor) } //fullscreen这里单独处理一下,放到首行去 if (toolbarItem == 'fullscreen') { if (toolbarUis && toolbarUis[0]) { toolbarUis[0].items.splice(0, 0, toolbarItemUi) } else { toolbarItemUi && toolbarUi.items.splice(0, 0, toolbarItemUi) } continue } } else { toolbarItemUi = toolbarItem } if (toolbarItemUi && toolbarItemUi.id) { toolbarUi.add(toolbarItemUi) } } toolbarUis[i] = toolbarUi } //接受外部定制的UI(修复因 utils.each 无法准确的循环出对象的全部元素而导致的自定义 UI 不符合预期的 BUG by HaoChuan9421) // utils.each(UE._customizeUI,function(obj,key){ // var itemUI,index; // if(obj.id && obj.id != editor.key){ // return false; // } // itemUI = obj.execFn.call(editor,editor,key); // if(itemUI){ // index = obj.index; // if(index === undefined){ // index = toolbarUi.items.length; // } // toolbarUi.add(itemUI,index) // } // }); for (var key in UE._customizeUI) { var obj = UE._customizeUI[key] var itemUI, index if (!obj.id || obj.id == editor.key) { itemUI = obj.execFn.call(editor, editor, key) if (itemUI) { index = obj.index if (index === undefined) { index = toolbarUi.items.length } toolbarUi.add(itemUI, index) } } } this.toolbars = toolbarUis }, getHtmlTpl: function () { return ( '
    ' + '
    ' + (this.toolbars.length ? '
    ' + this.renderToolbarBoxHtml() + '
    ' : '') + '' + '
    ' + '
    ' + '
    ' + '
    ' + //modify wdcount by matao '
    ' + '' + '' + '' + '
    ' + '
    ' + '
    ' ) }, showWordImageDialog: function () { this._dialogs['wordimageDialog'].open() }, renderToolbarBoxHtml: function () { var buff = [] for (var i = 0; i < this.toolbars.length; i++) { buff.push(this.toolbars[i].renderHtml()) } return buff.join('') }, setFullScreen: function (fullscreen) { var editor = this.editor, container = editor.container.parentNode.parentNode if (this._fullscreen != fullscreen) { this._fullscreen = fullscreen this.editor.fireEvent('beforefullscreenchange', fullscreen) if (baidu.editor.browser.gecko) { var bk = editor.selection.getRange().createBookmark() } if (fullscreen) { while (container.tagName != 'BODY') { var position = baidu.editor.dom.domUtils.getComputedStyle( container, 'position' ) nodeStack.push(position) container.style.position = 'static' container = container.parentNode } this._bakHtmlOverflow = document.documentElement.style.overflow this._bakBodyOverflow = document.body.style.overflow this._bakAutoHeight = this.editor.autoHeightEnabled this._bakScrollTop = Math.max( document.documentElement.scrollTop, document.body.scrollTop ) this._bakEditorContaninerWidth = editor.iframe.parentNode.offsetWidth if (this._bakAutoHeight) { //当全屏时不能执行自动长高 editor.autoHeightEnabled = false this.editor.disableAutoHeight() } document.documentElement.style.overflow = 'hidden' //修复,滚动条不收起的问题 window.scrollTo(0, window.scrollY) this._bakCssText = this.getDom().style.cssText this._bakCssText1 = this.getDom('iframeholder').style.cssText editor.iframe.parentNode.style.width = '' this._updateFullScreen() } else { while (container.tagName != 'BODY') { container.style.position = nodeStack.shift() container = container.parentNode } this.getDom().style.cssText = this._bakCssText this.getDom('iframeholder').style.cssText = this._bakCssText1 if (this._bakAutoHeight) { editor.autoHeightEnabled = true this.editor.enableAutoHeight() } document.documentElement.style.overflow = this._bakHtmlOverflow document.body.style.overflow = this._bakBodyOverflow editor.iframe.parentNode.style.width = this._bakEditorContaninerWidth + 'px' window.scrollTo(0, this._bakScrollTop) } if (browser.gecko && editor.body.contentEditable === 'true') { var input = document.createElement('input') document.body.appendChild(input) editor.body.contentEditable = false setTimeout(function () { input.focus() setTimeout(function () { editor.body.contentEditable = true editor.fireEvent('fullscreenchanged', fullscreen) editor.selection.getRange().moveToBookmark(bk).select(true) baidu.editor.dom.domUtils.remove(input) fullscreen && window.scroll(0, 0) }, 0) }, 0) } if (editor.body.contentEditable === 'true') { this.editor.fireEvent('fullscreenchanged', fullscreen) this.triggerLayout() } } }, _updateFullScreen: function () { if (this._fullscreen) { var vpRect = uiUtils.getViewportRect() this.getDom().style.cssText = 'border:0;position:absolute;left:0;top:' + (this.editor.options.topOffset || 0) + 'px;width:' + vpRect.width + 'px;height:' + vpRect.height + 'px;z-index:' + (this.getDom().style.zIndex * 1 + 100) uiUtils.setViewportOffset(this.getDom(), { left: 0, top: this.editor.options.topOffset || 0 }) this.editor.setHeight( vpRect.height - this.getDom('toolbarbox').offsetHeight - this.getDom('bottombar').offsetHeight - (this.editor.options.topOffset || 0), true ) //不手动调一下,会导致全屏失效 if (browser.gecko) { try { window.onresize() } catch (e) {} } } }, _updateElementPath: function () { var bottom = this.getDom('elementpath'), list if ( this.elementPathEnabled && (list = this.editor.queryCommandValue('elementpath')) ) { var buff = [] for (var i = 0, ci; (ci = list[i]); i++) { buff[i] = this.formatHtml( '' + ci + '' ) } bottom.innerHTML = '
    ' + this.editor.getLang('elementPathTip') + ': ' + buff.join(' > ') + '
    ' } else { bottom.style.display = 'none' } }, disableElementPath: function () { var bottom = this.getDom('elementpath') bottom.innerHTML = '' bottom.style.display = 'none' this.elementPathEnabled = false }, enableElementPath: function () { var bottom = this.getDom('elementpath') bottom.style.display = '' this.elementPathEnabled = true this._updateElementPath() }, _scale: function () { var doc = document, editor = this.editor, editorHolder = editor.container, editorDocument = editor.document, toolbarBox = this.getDom('toolbarbox'), bottombar = this.getDom('bottombar'), scale = this.getDom('scale'), scalelayer = this.getDom('scalelayer') var isMouseMove = false, position = null, minEditorHeight = 0, minEditorWidth = editor.options.minFrameWidth, pageX = 0, pageY = 0, scaleWidth = 0, scaleHeight = 0 function down() { position = domUtils.getXY(editorHolder) if (!minEditorHeight) { minEditorHeight = editor.options.minFrameHeight + toolbarBox.offsetHeight + bottombar.offsetHeight } scalelayer.style.cssText = 'position:absolute;left:0;display:;top:0;background-color:#41ABFF;opacity:0.4;filter: Alpha(opacity=40);width:' + editorHolder.offsetWidth + 'px;height:' + editorHolder.offsetHeight + 'px;z-index:' + (editor.options.zIndex + 1) domUtils.on(doc, 'mousemove', move) domUtils.on(editorDocument, 'mouseup', up) domUtils.on(doc, 'mouseup', up) } var me = this //by xuheng 全屏时关掉缩放 this.editor.addListener('fullscreenchanged', function (e, fullScreen) { if (fullScreen) { me.disableScale() } else { if (me.editor.options.scaleEnabled) { me.enableScale() var tmpNode = me.editor.document.createElement('span') me.editor.body.appendChild(tmpNode) me.editor.body.style.height = Math.max( domUtils.getXY(tmpNode).y, me.editor.iframe.offsetHeight - 20 ) + 'px' domUtils.remove(tmpNode) } } }) function move(event) { clearSelection() var e = event || window.event pageX = e.pageX || doc.documentElement.scrollLeft + e.clientX pageY = e.pageY || doc.documentElement.scrollTop + e.clientY scaleWidth = pageX - position.x scaleHeight = pageY - position.y if (scaleWidth >= minEditorWidth) { isMouseMove = true scalelayer.style.width = scaleWidth + 'px' } if (scaleHeight >= minEditorHeight) { isMouseMove = true scalelayer.style.height = scaleHeight + 'px' } } function up() { if (isMouseMove) { isMouseMove = false editor.ui._actualFrameWidth = scalelayer.offsetWidth - 2 editorHolder.style.width = editor.ui._actualFrameWidth + 'px' editor.setHeight( scalelayer.offsetHeight - bottombar.offsetHeight - toolbarBox.offsetHeight - 2, true ) } if (scalelayer) { scalelayer.style.display = 'none' } clearSelection() domUtils.un(doc, 'mousemove', move) domUtils.un(editorDocument, 'mouseup', up) domUtils.un(doc, 'mouseup', up) } function clearSelection() { if (browser.ie) doc.selection.clear() else window.getSelection().removeAllRanges() } this.enableScale = function () { //trace:2868 if (editor.queryCommandState('source') == 1) return scale.style.display = '' this.scaleEnabled = true domUtils.on(scale, 'mousedown', down) } this.disableScale = function () { scale.style.display = 'none' this.scaleEnabled = false domUtils.un(scale, 'mousedown', down) } }, isFullScreen: function () { return this._fullscreen }, postRender: function () { UIBase.prototype.postRender.call(this) for (var i = 0; i < this.toolbars.length; i++) { this.toolbars[i].postRender() } var me = this var timerId, domUtils = baidu.editor.dom.domUtils, updateFullScreenTime = function () { clearTimeout(timerId) timerId = setTimeout(function () { me._updateFullScreen() }) } domUtils.on(window, 'resize', updateFullScreenTime) me.addListener('destroy', function () { domUtils.un(window, 'resize', updateFullScreenTime) clearTimeout(timerId) }) }, showToolbarMsg: function (msg, flag) { this.getDom('toolbarmsg_label').innerHTML = msg this.getDom('toolbarmsg').style.display = '' // if (!flag) { var w = this.getDom('upload_dialog') w.style.display = 'none' } }, hideToolbarMsg: function () { this.getDom('toolbarmsg').style.display = 'none' }, mapUrl: function (url) { return url ? url.replace('~/', this.editor.options.UEDITOR_HOME_URL || '') : '' }, triggerLayout: function () { var dom = this.getDom() if (dom.style.zoom == '1') { dom.style.zoom = '100%' } else { dom.style.zoom = '1' } } } utils.inherits(EditorUI, baidu.editor.ui.UIBase) var instances = {} UE.ui.Editor = function (options) { var editor = new UE.Editor(options) editor.options.editor = editor utils.loadFile(document, { href: editor.options.themePath + editor.options.theme + '/css/ueditor.css', tag: 'link', type: 'text/css', rel: 'stylesheet' }) var oldRender = editor.render editor.render = function (holder) { if (holder.constructor === String) { editor.key = holder instances[holder] = editor } utils.domReady(function () { editor.langIsReady ? renderUI() : editor.addListener('langReady', renderUI) function renderUI() { editor.setOpt({ labelMap: editor.options.labelMap || editor.getLang('labelMap') }) new EditorUI(editor.options) if (holder) { if (holder.constructor === String) { holder = document.getElementById(holder) } holder && holder.getAttribute('name') && (editor.options.textarea = holder.getAttribute('name')) if (holder && /script|textarea/gi.test(holder.tagName)) { var newDiv = document.createElement('div') holder.parentNode.insertBefore(newDiv, holder) var cont = holder.value || holder.innerHTML editor.options.initialContent = /^[\t\r\n ]*$/.test(cont) ? editor.options.initialContent : cont .replace(/>[\n\r\t]+([ ]{4})+/g, '>') .replace(/[\n\r\t]+([ ]{4})+[\n\r\t]+<') holder.className && (newDiv.className = holder.className) holder.style.cssText && (newDiv.style.cssText = holder.style.cssText) if (/textarea/i.test(holder.tagName)) { editor.textarea = holder editor.textarea.style.display = 'none' } else { holder.parentNode.removeChild(holder) } if (holder.id) { newDiv.id = holder.id domUtils.removeAttributes(holder, 'id') } holder = newDiv holder.innerHTML = '' } } domUtils.addClass(holder, 'edui-' + editor.options.theme) editor.ui.render(holder) var opt = editor.options //给实例添加一个编辑器的容器引用 editor.container = editor.ui.getDom() var parents = domUtils.findParents(holder, true) var displays = [] for (var i = 0, ci; (ci = parents[i]); i++) { displays[i] = ci.style.display ci.style.display = 'block' } if (opt.initialFrameWidth) { opt.minFrameWidth = opt.initialFrameWidth } else { opt.minFrameWidth = opt.initialFrameWidth = holder.offsetWidth var styleWidth = holder.style.width if (/%$/.test(styleWidth)) { opt.initialFrameWidth = styleWidth } } if (opt.initialFrameHeight) { opt.minFrameHeight = opt.initialFrameHeight } else { opt.initialFrameHeight = opt.minFrameHeight = holder.offsetHeight } for (var i = 0, ci; (ci = parents[i]); i++) { ci.style.display = displays[i] } //编辑器最外容器设置了高度,会导致,编辑器不占位 //todo 先去掉,没有找到原因 if (holder.style.height) { holder.style.height = '' } editor.container.style.width = opt.initialFrameWidth + (/%$/.test(opt.initialFrameWidth) ? '' : 'px') editor.container.style.zIndex = opt.zIndex oldRender.call(editor, editor.ui.getDom('iframeholder')) editor.fireEvent('afteruiready') } }) } return editor } /** * @file * @name UE * @short UE * @desc UEditor的顶部命名空间 */ /** * @name getEditor * @since 1.2.4+ * @grammar UE.getEditor(id,[opt]) => Editor实例 * @desc 提供一个全局的方法得到编辑器实例 * * * ''id'' 放置编辑器的容器id, 如果容器下的编辑器已经存在,就直接返回 * * ''opt'' 编辑器的可选参数 * @example * UE.getEditor('containerId',{onready:function(){//创建一个编辑器实例 * this.setContent('hello') * }}); * UE.getEditor('containerId'); //返回刚创建的实例 * */ UE.getEditor = function (id, opt) { var editor = instances[id] if (!editor) { editor = instances[id] = new UE.ui.Editor(opt) editor.render(id) } return editor } UE.delEditor = function (id) { var editor if ((editor = instances[id])) { editor.key && editor.destroy() delete instances[id] } } UE.registerUI = function (uiName, fn, index, editorId) { utils.each(uiName.split(/\s+/), function (name) { UE._customizeUI[name] = { id: editorId, execFn: fn, index: index } }) } })() // adapter/message.js UE.registerUI('message', function (editor) { var editorui = baidu.editor.ui var Message = editorui.Message var holder var _messageItems = [] var me = editor me.addListener('ready', function () { holder = document.getElementById(me.ui.id + '_message_holder') updateHolderPos() // HaoChuan9421 // setTimeout(function(){ // updateHolderPos(); // }, 500); }) me.addListener('showmessage', function (type, opt) { opt = utils.isString(opt) ? { content: opt } : opt var message = new Message({ timeout: opt.timeout, type: opt.type, content: opt.content, keepshow: opt.keepshow, editor: me }), mid = opt.id || 'msg_' + (+new Date()).toString(36) message.render(holder) _messageItems[mid] = message message.reset(opt) updateHolderPos() return mid }) me.addListener('updatemessage', function (type, id, opt) { opt = utils.isString(opt) ? { content: opt } : opt var message = _messageItems[id] message.render(holder) message && message.reset(opt) }) me.addListener('hidemessage', function (type, id) { var message = _messageItems[id] message && message.hide() }) function updateHolderPos() { var toolbarbox = me.ui.getDom('toolbarbox') if (toolbarbox) { holder.style.top = toolbarbox.offsetHeight + 3 + 'px' } holder.style.zIndex = Math.max(me.options.zIndex, me.iframe.style.zIndex) + 1 } }) // adapter/autosave.js UE.registerUI('autosave', function (editor) { var timer = null, uid = null editor.on('afterautosave', function () { clearTimeout(timer) timer = setTimeout(function () { if (uid) { editor.trigger('hidemessage', uid) } uid = editor.trigger('showmessage', { content: editor.getLang('autosave.success'), timeout: 2000 }) }, 2000) }) }) })()