◆幾個問題javascript
1.官網源代碼默認換行自增p標籤嵌套br,這樣就致使每次修改編輯的內容大量增大,最後致使數據庫字段長度超出php
解決: enterTag: 'br' 。這樣就不會自增p了css
//實例化編輯器 var ue = UE.getEditor('editor', { initialFrameWidth: "100%", //初始化寬度 initialFrameHeight: 400, //初始化高度 enterTag: 'br' });
2.上傳的接口跨域問題 。(修改源碼)html
3.默認傳多圖會壓縮,順序也是亂的。要改配置文件。java
4...忘了。好多問題node
最後的源碼爲:ueditor.all.jspython
/*! * UEditor * version: ueditor * build: Wed Aug 10 2016 11:06:16 GMT+0800 (CST) */ (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 * <ul> * <li>IE系列返回值爲5,6,7,8,9,10等</li> * <li>gecko系列會返回10900,158900等</li> * <li>webkit系列會返回其build號 (如 522等)</li> * </ul> * @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 = '<body>&</body>'; * * //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: <body>&</body> * 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(' ') + ' ></' + obj.tag + '>'); 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 * <body> * * <div id="test"> * </div> * * <script type="text/javascript"> * * //output: DIV, BODY * var filterNode = UE.dom.domUtils.findParent( document.getElementById( "test" ), function ( node ) { * * console.log( node.tagName ); * return false; * * }, true ); * * </script> * </body> * ``` */ 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 * <div id="test"> * <div id="child">你好</div> * </div> * <script> * UE.dom.domUtils.remove( document.body, false ); * //output: false * console.log( document.getElementById( "child" ) !== null ); * </script> * ``` */ /** * 刪除節點node,並根據keepChildren的值決定是否保留子節點 * @method remove * @param { Node } node 須要刪除的節點對象 * @param { Boolean } keepChildren 是否須要保留子節點 * @return { Node } 返回剛刪除的節點對象 * @example * ```html * <div id="test"> * <div id="child">你好</div> * </div> * <script> * UE.dom.domUtils.remove( document.body, true ); * //output: true * console.log( document.getElementById( "child" ) !== null ); * </script> * ``` */ 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 * <body> * <div id="test"> * <span></span> * </div> * <i>xxx</i> * </body> * <script> * * //output: i節點 * console.log( UE.dom.domUtils.getNextDomNode( document.getElementById( "test" ) ) ); * * </script> * ``` * @example * ```html * <body> * <div> * <span></span> * <i id="test">xxx</i> * </div> * <b>xxx</b> * </body> * <script> * * //因爲id爲test的i節點以後沒有兄弟節點, 則查找其父節點(div)後面的兄弟節點 * //output: b節點 * console.log( UE.dom.domUtils.getNextDomNode( document.getElementById( "test" ) ) ); * * </script> * ``` */ /** * 取得node節點的下一個兄弟節點, 若是startFromChild的值爲ture,則先獲取其子節點, * 若是有子節點則直接返回第一個子節點;若是沒有子節點或者startFromChild的值爲false, * 則執行<a href="#UE.dom.domUtils.getNextDomNode(Node)">getNextDomNode(Node node)</a>的查找過程。 * @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 * <span id="_baidu_bookmark_1"></span> * <script> * var bookmarkNode = document.getElementById("_baidu_bookmark_1"); * //output: true * console.log( UE.dom.domUtils.isBookmarkNode( bookmarkNode ) ); * </script> * ``` */ 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 * <body> * <div></div> * <span id="test"></span> * <i></i> * <b></b> * <em>xxx</em> * <span></span> * </body> * <script> * * UE.dom.domUtils.clearEmptySibling( document.getElementById( "test" ) ); * * //output: <div></div><span id="test"></span><em>xxx</em><span></span> * console.log( document.body.innerHTML ); * * </script> * ``` */ /** * 清除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 * <div id="test">abcdef</div> * <script> * var newNode = UE.dom.domUtils.split( document.getElementById( "test" ).firstChild, 3 ); * //output: def * console.log( newNode.nodeValue ); * </script> * ``` */ 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 * <div id="test"> * * </div> * <script> * //output: true * console.log( UE.dom.domUtils.isWhitespace( document.getElementById("test").firstChild ) ); * </script> * ``` */ 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 * <span style="font-size:12px">ssss</span> * <span style="font-size:12px">bbbbb</span> * <span style="font-size:13px">ssss</span> * <span style="font-size:14px">bbbbb</span> * * <script> * * var nodes = document.getElementsByTagName( "span" ); * * //output: true * console.log( UE.dom.domUtils.isSameElement( nodes[0], nodes[1] ) ); * * //output: false * console.log( UE.dom.domUtils.isSameElement( nodes[2], nodes[3] ) ); * * </script> * ``` */ 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 * <span style="font-size:12px">ssss</span> * <span style="font-size:12px">bbbbb</span> * <span style="font-size:13px">ssss</span> * <span style="font-size:14px">bbbbb</span> * * <script> * * var nodes = document.getElementsByTagName( "span" ); * * //output: true * console.log( UE.dom.domUtils.isSameStyle( nodes[0], nodes[1] ) ); * * //output: false * console.log( UE.dom.domUtils.isSameStyle( nodes[2], nodes[3] ) ); * * </script> * ``` */ 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 * <span id="test1" style="display: block"></span> * <span id="test2"></span> * <div id="test3" style="display: inline"></div> * * <script> * * //output: true * console.log( UE.dom.domUtils.isBlockElm( document.getElementById("test1") ) ); * * //output: false * console.log( UE.dom.domUtils.isBlockElm( document.getElementById("test2") ) ); * * //output: true * console.log( UE.dom.domUtils.isBlockElm( document.getElementById("test3") ) ); * * </script> * ``` */ 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: <p><span></span></p> * console.log( wrapNode.innerHTML ); * * * UE.dom.domUtils.breakParent( node, parent ); * //拆分後 * //output: <p></p><span></span><p></p> * 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 * <b><i></i></b> => 1 * <b><i></i><u></u></b> => 1 * <b></b> => 1 * <b>xx<i></i></b> => 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 * <p><span style="font-size:12px;">xx<span style="font-size:12px;">aa</span>xx</span></p> * ==> UE.dom.domUtils.mergeChild(node,'span') * <p><span style="font-size:12px;">xxaaxx</span></p> */ 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 * <div id="parent"> * <div id="sub"> * <span id="child"></span> * </div> * </div> * * <script> * * var child = document.getElementById( "child" ); * * //output: sub * console.log( child.parentNode.id ); * * UE.dom.domUtils.mergeToParent( child ); * * //output: parent * console.log( child.parentNode.id ); * * </script> * ``` */ 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須要特殊處理 不處理這樣的狀況 <span stlye="color:#fff">xxx<span style="color:#ccc">xxx</span>xxx</span> 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 * <b>xxxx</b><b id="test">ooo</b><b>xxxx</b> * * <script> * var demoNode = document.getElementById("test"); * UE.dom.domUtils.mergeSibling( demoNode ); * //output: xxxxoooxxxx * console.log( demoNode.innerHTML ); * </script> * ``` */ /** * 合併節點node的左右兄弟節點, 能夠根據給定的條件選擇是否忽略合併左節點。 * @method mergeSibling * @param { Element } node 須要合併的目標節點 * @param { Boolean } ignorePre 是否忽略合併左節點 * @example * ```html * <b>xxxx</b><b id="test">ooo</b><b>xxxx</b> * * <script> * var demoNode = document.getElementById("test"); * UE.dom.domUtils.mergeSibling( demoNode, true ); * //output: oooxxxx * console.log( demoNode.innerHTML ); * </script> * ``` */ /** * 合併節點node的左右兄弟節點,能夠根據給定的條件選擇是否忽略合併左右節點。 * @method mergeSibling * @param { Element } node 須要合併的目標節點 * @param { Boolean } ignorePre 是否忽略合併左節點 * @param { Boolean } ignoreNext 是否忽略合併右節點 * @remind 若是同時忽略左右節點, 則該操做什麼也不會作 * @example * ```html * <b>xxxx</b><b id="test">ooo</b><b>xxxx</b> * * <script> * var demoNode = document.getElementById("test"); * UE.dom.domUtils.mergeSibling( demoNode, false, true ); * //output: xxxxooo * console.log( demoNode.innerHTML ); * </script> * ``` */ 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 * <div id="wrap"> * <span style="font-size:14px;" id="test" name="followMe">xxxxx</span> * </div> * * <script> * * UE.dom.domUtils.removeAttributes( document.getElementById( "test" ), "id name" ); * * //output: <span style="font-size:14px;">xxxxx</span> * console.log( document.getElementById("wrap").innerHTML ); * * </script> * ``` */ /** * 刪除節點node上的指定屬性名稱的屬性 * @method removeAttributes * @param { Node } node 須要刪除屬性的節點對象 * @param { Array } attrNames 須要刪除的屬性名數組 * @example * ```html * <div id="wrap"> * <span style="font-size:14px;" id="test" name="followMe">xxxxx</span> * </div> * * <script> * * UE.dom.domUtils.removeAttributes( document.getElementById( "test" ), ["id", "name"] ); * * //output: <span style="font-size:14px;">xxxxx</span> * console.log( document.getElementById("wrap").innerHTML ); * * </script> * ``` */ 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 * <span id="test"></span> * * <script> * * var testNode = UE.dom.domUtils.setAttributes( document.getElementById( "test" ), { * id: 'demo' * } ); * * //output: demo * console.log( testNode.id ); * * </script> * */ 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 * <style type="text/css"> * #test { * font-size: 15px; * } * </style> * * <span id="test"></span> * * <script> * //output: 15px * console.log( UE.dom.domUtils.getComputedStyle( document.getElementById( "test" ), 'font-size' ) ); * </script> * ``` */ 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 * <span id="test" class="test1 test2 test3">xxx</span> * * <script> * * var testNode = document.getElementById( "test" ); * UE.dom.domUtils.removeClasses( testNode, "test1 test2" ); * * //output: test3 * console.log( testNode.className ); * * </script> * ``` */ /** * 刪除元素element指定的className * @method removeClasses * @param { Element } ele 須要刪除class的元素節點 * @param { Array } classNames 須要刪除的className數組 * @example * ```html * <span id="test" class="test1 test2 test3">xxx</span> * * <script> * * var testNode = document.getElementById( "test" ); * UE.dom.domUtils.removeClasses( testNode, ["test1", "test2"] ); * * //output: test3 * console.log( testNode.className ); * * </script> * ``` */ 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 * <span id="test" class="cls1 cls2"></span> * * <script> * var testNode = document.getElementById("test"); * * UE.dom.domUtils.addClass( testNode, "cls2 cls3 cls4" ); * * //output: cl1 cls2 cls3 cls4 * console.log( testNode.className ); * * <script> * ``` */ /** * 給元素element添加className * @method addClass * @param { Node } ele 須要增長className的元素 * @param { Array } classNames 須要添加的className的數組 * @remind 相同的類名不會被重複添加 * @example * ```html * <span id="test" class="cls1 cls2"></span> * * <script> * var testNode = document.getElementById("test"); * * UE.dom.domUtils.addClass( testNode, ["cls2", "cls3", "cls4"] ); * * //output: cl1 cls2 cls3 cls4 * console.log( testNode.className ); * * <script> * ``` */ addClass: function (elm, classNames) { if (!elm) return; classNames = utils.trim(classNames).replace(/[ ]{2,}/g, ' ').split(' '); for (var i = 0, ci, cls = elm.className; ci = classNames[i++];) { if (!new RegExp('\\b' + ci + '\\b').test(cls)) { cls += ' ' + ci; } } elm.className = utils.trim(cls); }, /** * 判斷元素element是否包含給定的樣式類名className * @method hasClass * @param { Node } ele 須要檢測的元素 * @param { String } classNames 須要檢測的className, 多個className之間用空格分割 * @return { Boolean } 元素是否包含全部給定的className * @example * ```html * <span id="test1" class="cls1 cls2"></span> * * <script> * var test1 = document.getElementById("test1"); * * //output: false * console.log( UE.dom.domUtils.hasClass( test1, "cls2 cls1 cls3" ) ); * * //output: true * console.log( UE.dom.domUtils.hasClass( test1, "cls2 cls1" ) ); * </script> * ``` */ /** * 判斷元素element是否包含給定的樣式類名className * @method hasClass * @param { Node } ele 須要檢測的元素 * @param { Array } classNames 須要檢測的className數組 * @return { Boolean } 元素是否包含全部給定的className * @example * ```html * <span id="test1" class="cls1 cls2"></span> * * <script> * var test1 = document.getElementById("test1"); * * //output: false * console.log( UE.dom.domUtils.hasClass( test1, [ "cls2", "cls1", "cls3" ] ) ); * * //output: true * console.log( UE.dom.domUtils.hasClass( test1, [ "cls2", "cls1" ]) ); * </script> * ``` */ 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 * <span id="test" style="color: red; background: blue;"></span> * * <script> * * var testNode = document.getElementById("test"); * * UE.dom.domUtils.removeStyle( testNode, 'color' ); * * //output: background: blue; * console.log( testNode.style.cssText ); * * </script> * ``` */ 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 * <div id="test" style="color: red;"></div> * * <script> * * var testNode = document.getElementById( "test" ); * * //output: red * console.log( UE.dom.domUtils.getStyle( testNode, "color" ) ); * * //output: "" * console.log( UE.dom.domUtils.getStyle( testNode, "background" ) ); * * </script> * ``` */ 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 * <div id="test"></div> * * <script> * * var testNode = document.getElementById( "test" ); * * //output: "" * console.log( testNode.style.color ); * * UE.dom.domUtils.setStyle( testNode, 'color', 'red' ); * //output: "red" * console.log( testNode.style.color ); * * </script> * ``` */ 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 * <div id="test"></div> * * <script> * * var testNode = document.getElementById( "test" ); * * //output: "" * console.log( testNode.style.color ); * * UE.dom.domUtils.setStyles( testNode, { * 'color': 'red' * } ); * //output: "red" * console.log( testNode.style.color ); * * </script> * ``` */ 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 * <div id="test"> * <span></span> * </div> * * <script> * * //output: 3 * console.log( UE.dom.domUtils.getChildCount( document.getElementById("test") ) ); * * </script> * ``` */ /** * 根據給定的過濾規則, 獲取符合條件的子節點的數量 * @method getChildCount * @param { Element } node 須要檢測的元素 * @param { Function } fn 過濾器, 要求對符合條件的子節點返回true, 反之則要求返回false * @return { Number } 符合過濾條件的node元素的子節點數量 * @example * ```html * <div id="test"> * <span></span> * </div> * * <script> * * //output: 1 * console.log( UE.dom.domUtils.getChildCount( document.getElementById("test"), function ( node ) { * * return node.nodeType === 1; * * } ) ); * * </script> * ``` */ 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 * <div id="test"></div> * * <script> * //output: true * console.log( UE.dom.domUtils.isEmptyBlock( document.getElementById("test") ) ); * </script> * ``` */ /** * 根據指定的判斷規則判斷給定的元素是不是一個空元素 * @method isEmptyBlock * @param { Element } node 須要判斷的元素 * @param { RegExp } reg 對內容執行判斷的正則表達式對象 * @return { Boolean } 是不是空元素 */ isEmptyBlock: function (node, reg) { 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 * <div id="test" style="top: 100px; left: 50px; position: absolute;"></div> * * <script> * * var testNode = document.getElementById("test"); * * UE.dom.domUtils.setViewportOffset( testNode, { * left: 200, * top: 50 * } ); * * //output: top: 300px; left: 100px; position: absolute; * console.log( testNode.style.cssText ); * * </script> * ``` */ 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 * <div id="test"></div> * * <script> * var testNode = document.getElementById("test"); * * //output: 0 * console.log( testNode.childNodes.length ); * * UE.dom.domUtils.fillNode( document, testNode ); * * //output: 1 * console.log( testNode.childNodes.length ); * * </script> * ``` */ 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 * <div id="test1"> * <span></span> * </div> * <div id="test2"> * <div></div> * </div> * * <script> * * var test1 = document.getElementById("test1"), * test2 = document.getElementById("test2"); * * UE.dom.domUtils.moveChild( test1, test2 ); * * //output: ""(空字符串) * console.log( test1.innerHTML ); * * //output: "<div></div><span></span>" * console.log( test2.innerHTML ); * * </script> * ``` */ /** * 把節點src的全部子節點移動到另外一個節點tag上去, 能夠經過dir參數控制附加的行爲是「追加」仍是「插入頂部」 * @method moveChild * @param { Node } src 源節點, 該節點下的全部子節點將被移除 * @param { Node } tag 目標節點, 從源節點移除的子節點將被附加到該節點下 * @param { Boolean } dir 附加方式, 若是爲true, 則附加進去的節點將被放到目標節點的頂部, 反之,則放到末尾 * @example * ```html * <div id="test1"> * <span></span> * </div> * <div id="test2"> * <div></div> * </div> * * <script> * * var test1 = document.getElementById("test1"), * test2 = document.getElementById("test2"); * * UE.dom.domUtils.moveChild( test1, test2, true ); * * //output: ""(空字符串) * console.log( test1.innerHTML ); * * //output: "<span></span><div></div>" * console.log( test2.innerHTML ); * * </script> * ``` */ 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 * <div id="test"><span>xxxx</span></div> * * <script> * * //output: false * console.log( UE.dom.domUtils.hasNoAttributes( document.getElementById("test") ) ); * * //output: true * console.log( UE.dom.domUtils.hasNoAttributes( document.getElementById("test").firstChild ) ); * * </script> * ``` */ 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 * <div id="test"></div> * * <script> * * //output: true * console.log( UE.dom.domUtils.isTagNode( document.getElementById("test"), "div" ) ); * * </script> * ``` */ 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 ? ' ' : '<br/>' }; 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 * <body> * <!-- 中括號表示選區 --> * <b>x<i>x[x</i>xx]x</b> * * <script> * //range是已選中的選區 * var fragment = range.cloneContents(), * node = document.createElement("div"); * * node.appendChild( fragment ); * * //output: <i>x</i>xx * console.log( node.innerHTML ); * * </script> * </body> * ``` */ cloneContents: function () { return this.collapsed ? null : execContentsAction(this, 0); }, /** * 刪除當前選區範圍中的全部內容 * @method deleteContents * @remind 執行完該操做後, 當前Range對象變成了閉合狀態 * @return { UE.dom.Range } 當前操做的Range對象 * @example * ```html * <body> * <!-- 中括號表示選區 --> * <b>x<i>x[x</i>xx]x</b> * * <script> * //range是已選中的選區 * range.deleteContents(); * * //豎線表示閉合後的選區位置 * //output: <b>x<i>x</i>|x</b> * console.log( document.body.innerHTML ); * * //此時, range的各項屬性爲 * //output: B * console.log( range.startContainer.tagName ); * //output: 2 * console.log( range.startOffset ); * //output: B * console.log( range.endContainer.tagName ); * //output: 2 * console.log( range.endOffset ); * //output: true * console.log( range.collapsed ); * * </script> * </body> * ``` */ 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 * <body> * <!-- 中括號表示選區 --> * <b>x<i>x[x</i>xx]x</b> * * <script> * //range是已選中的選區 * var fragment = range.extractContents(), * node = document.createElement( "div" ); * * node.appendChild( fragment ); * * //豎線表示閉合後的選區位置 * * //output: <b>x<i>x</i>|x</b> * console.log( document.body.innerHTML ); * //output: <i>x</i>xx * console.log( node.innerHTML ); * * //此時, range的各項屬性爲 * //output: B * console.log( range.startContainer.tagName ); * //output: 2 * console.log( range.startOffset ); * //output: B * console.log( range.endContainer.tagName ); * //output: 2 * console.log( range.endOffset ); * //output: true * console.log( range.collapsed ); * * </script> * </body> */ 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 * <!-- 選區 --> * <b>xxx<i>x<span>xx</span>xx<em>xx</em>xxx</i>[xxx]</b> * * <script> * * //執行操做 * range.setStart( document.getElementsByTagName("i")[0], 1 ); * * //此時, 選區變成了 * //<b>xxx<i>x[<span>xx</span>xx<em>xx</em>xxx</i>xxx]</b> * * </script> * ``` * @example * ```html * <!-- 選區 --> * <b>xxx<img>[xx]x</b> * * <script> * * //執行操做 * range.setStart( document.getElementsByTagName("img")[0], 3 ); * * //此時, 選區變成了 * //<b>xxx[<img>xx]x</b> * * </script> * ``` */ 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 * <!-- 選區示例 --> * <b>xx<i>xxx</i><span>xx[x</span>xxx]</b> * * <script> * * //執行操做 * range.setStartAfter( document.getElementsByTagName("i")[0] ); * * //結果選區 * //<b>xx<i>xxx</i>[<span>xxx</span>xxx]</b> * * </script> * ``` */ 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 * <!-- 選區示例 --> * <b>[xx<i>xxx</i><span>xx]x</span>xxx</b> * * <script> * * //執行操做 * range.setStartAfter( document.getElementsByTagName("span")[0] ); * * //結果選區 * //<b>[xx<i>xxx</i><span>xxx</span>]xxx</b> * * </script> * ``` */ 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 * <!-- 選區示例 --> * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b> * * <script> * * //執行操做 * range.setStartAtFirst( document.getElementsByTagName("i")[0] ); * * //結果選區 * //<b>xx<i>[xxx</i><span>xx]x</span>xxx</b> * * </script> * ``` */ 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 * <!-- 選區示例 --> * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b> * * <script> * * //執行操做 * range.selectNode( document.getElementsByTagName("i")[0] ); * * //結果選區 * //<b>xx[<i>xxx</i>]<span>xxx</span>xxx</b> * * </script> * ``` */ 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 * <!-- 選區示例 --> * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b> * * <script> * * //執行操做 * range.selectNode( document.getElementsByTagName("b")[0] ); * * //結果選區 * //<b>[xx<i>xxx</i><span>xxx</span>xxx]</b> * * </script> * ``` */ 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 * <!-- 選區示例 --> * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b> * * <script> * * //執行操做 * range.collapse(); * * //結果選區 * //「|」表示選區已閉合 * //<b>xx<i>xxx</i><span>xx|x</span>xxx</b> * * </script> * ``` */ /** * 閉合當前選區,根據給定的toStart參數項決定是向當前選區開始處閉合仍是向結束處閉合, * 若是toStart的值爲true,則向開始位置閉合, 反之,向結束位置閉合。 * @method collapse * @param { Boolean } toStart 是否向選區開始處閉合 * @return { UE.dom.Range } 當前range對象,此時range對象處於閉合狀態 * @see UE.dom.Range:collapse() * @example * ```html * <!-- 選區示例 --> * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b> * * <script> * * //執行操做 * range.collapse( true ); * * //結果選區 * //「|」表示選區已閉合 * //<b>xx<i>xxx</i><span>|xxx</span>xxx</b> * * </script> * ``` */ 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 * <span>xx<b>xx[</b>xxxxx]</span> => <span>xx<b>xx</b>[xxxxx]</span> * ``` * * @example * ```html * <!-- 選區示例 --> * <b>x[xx</b><i>]xxx</i> * * <script> * * //執行收縮 * range.shrinkBoundary(); * * //結果選區 * //<b>x[xx]</b><i>xxx</i> * </script> * ``` * * @example * ```html * [<b><i>xxxx</i>xxxxxxx</b>] => <b><i>[xxxx</i>xxxxxxx]</b> * ``` */ /** * 調整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 * //選區示例 * <span>xxx<b>x[x<em>xx]x</em>xxx</b>xx</span> * <script> * * var node = range.getCommonAncestor(); * * //公共祖先節點是: b節點 * //輸出: B * console.log(node.tagName); * * </script> * ``` */ /** * 獲取當前選區所包含的全部節點的公共祖先節點, 能夠根據給定的參數 includeSelf 決定獲取到 * 的公共祖先節點是否能夠是當前選區的startContainer或endContainer節點, 若是 includeSelf * 的取值爲true, 則返回的節點能夠是自身的容器節點, 不然, 則不能是容器節點 * @method getCommonAncestor * @param { Boolean } includeSelf 是否容許獲取到的公共祖先節點是當前range對象的容器節點 * @return { Node } 當前range對象內全部節點的公共祖先節點 * @see UE.dom.Range:getCommonAncestor() * @example * ```html * <body> * * <!-- 選區示例 --> * <b>xxx<i>xxxx<span>xx[x</span>xx]x</i>xxxxxxx</b> * * <script> * * var node = range.getCommonAncestor( false ); * * //這裏的公共祖先節點是B而不是I, 是由於參數限制了獲取到的節點不能是容器節點 * //output: B * console.log( node.tagName ); * * </script> * * </body> * ``` */ /** * 獲取當前選區所包含的全部節點的公共祖先節點, 能夠根據給定的參數 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 * <body> * * <!-- 選區示例 --> * <b>xxx<i>xxxx<span>x[x]x</span>xxx</i>xxxxxxx</b> * * <script> * * var node = range.getCommonAncestor( true, false ); * * //output: SPAN * console.log( node.tagName ); * * </script> * * </body> * ``` */ 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 * * //選區示例 * <b>xxx<i>[xxxxx]</i>xxx</b> * * <script> * //未調整前, 選區的開始容器和結束都是文本節點 * //執行調整 * range.trimBoundary(); * * //調整以後, 容器節點變成了i節點 * //<b>xxx[<i>xxxxx</i>]xxx</b> * </script> * ``` */ /** * 調整當前Range的開始和結束邊界容器,若是是容器節點是文本節點,就調整到包含該文本節點的父節點上, * 能夠根據 ignoreEnd 參數的值決定是否調整對結束邊界的調整 * @method trimBoundary * @param { Boolean } ignoreEnd 是否忽略對結束邊界的調整 * @return { UE.dom.Range } 當前range對象 * @example * ```html * * //選區示例 * <b>xxx<i>[xxxxx]</i>xxx</b> * * <script> * //未調整前, 選區的開始容器和結束都是文本節點 * //執行調整 * range.trimBoundary( true ); * * //調整以後, 開始容器節點變成了i節點 * //可是, 結束容器沒有發生變化 * //<b>xxx[<i>xxxxx]</i>xxx</b> * </script> * ``` */ 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 * <p>xxxx[xxxx]x</p> ==> range.applyInlineStyle("strong") ==> <p>xxxx[<strong>xxxx</strong>]x</p> * ``` */ /** * 給range選區中的內容添加給定的inline標籤, 而且爲標籤附加上一些初始化屬性。 * @method applyInlineStyle * @param { String } tagName 須要添加的標籤名 * @param { Object } attrs 跟隨新添加的標籤的屬性 * @return { UE.dom.Range } 當前選區 * @example * ```html * <p>xxxx[xxxx]x</p> * * ==> * * <!-- 執行操做 --> * range.applyInlineStyle("strong",{"style":"font-size:12px"}) * * ==> * * <p>xxxx[<strong style="font-size:12px">xxxx</strong>]x</p> * ``` */ 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[x<span>xxx<em>yyy</em>zz]z</span> => range.removeInlineStyle(["em"]) => xx[x<span>xxxyyyzz]z</span> * ``` */ /** * 移除當前選區內指定的一組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) { //使用<span>|x<span>固定住光標 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); // } // //處理光標落在文本節點的狀況 //處理如下的狀況 //<b>|xxxx</b> //<b>xxxx</b>|xxxx //xxxx<b>|</b> 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 * <body> * <p> * aaaa * <em> * <!-- 選區開始 --> * bbbb * <!-- 選區結束 --> * </em> * </p> * * <script> * //output: {startAddress:[0,1,0,0],endAddress:[0,1,0,4]} * console.log( range.createAddress() ); * </script> * </body> * ``` */ 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 * <body> * <p> * aaaa * <em> * <!-- 選區開始 --> * bbbb * <!-- 選區結束 --> * </em> * </p> * * <script> * var range = editor.selection.getRange(); * range.moveToAddress({startAddress:[0,1,0,0],endAddress:[0,1,0,4]}); * range.select(); * //output: 'bbbb' * console.log(editor.selection.getText()); * </script> * </body> * ``` */ 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 * * <body> * * <!-- 選區開始 --> * <span></span> * <a></a> * <!-- 選區結束 --> * </body> * * <script> * * //output: <span></span><a></a> * console.log( range.cloneContents() ); * * range.traversal( function ( node ) { * * if ( node.nodeType === 1 ) { * node.className = "test"; * } * * } ); * * //output: <span class="test"></span><a class="test"></a> * console.log( range.cloneContents() ); * * </script> * ``` */ /** * 遍歷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 * * <body> * * <!-- 選區開始 --> * <span></span> * <a></a> * <!-- 選區結束 --> * </body> * * <script> * * //output: <span></span><a></a> * console.log( range.cloneContents() ); * * range.traversal( function ( node ) { * * node.className = "test"; * * }, function ( node ) { * return node.nodeType === 1; * } ); * * //output: <span class="test"></span><a class="test"></a> * console.log( range.cloneContents() ); * * </script> * ``` */ 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下<b>x</b>[xx] 閉合後 <b>x|</b>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 ? '' : '<!DOCTYPE html>') + '<html xmlns=\'http://www.w3.org/1999/xhtml\' class=\'view\' ><head>' + '<style type=\'text/css\'>' + //設置四周的留邊 '.view{padding:0;word-wrap:break-word;cursor:text;height:90%;}\n' + //設置默認字體和字號 //font-family不能呢隨便改,在safari下fillchar會有解析問題 'body{margin:8px;font-family:sans-serif;font-size:16px;}' + //設置段落間距 'p{margin:5px 0;}</style>' + (options.iframeCssUrl ? '<link rel=\'stylesheet\' type=\'text/css\' href=\'' + utils.unhtml(options.iframeCssUrl) + '\'/>' : '') + (options.initialStyle ? '<style>' + options.initialStyle + '</style>' : '') + '</head><body class=\'view\' ></body>' + '<script type=\'text/javascript\' ' + (ie ? 'defer=\'defer\'' : '') + ' id=\'_initialScript\'>' + 'setTimeout(function(){editor = window.parent.UE.instants[\'ueditorInstant' + me.uid + '\'];editor._setup(document);},0);' + 'var _tmpScript = document.getElementById(\'_initialScript\');_tmpScript.parentNode.removeChild(_tmpScript);</script></html>'; 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 = '<p>' + (browser.ie ? '' : '<br/>') + '</p>'; me.body.innerHTML = '<p>' + (browser.ie ? '' : '<br/>') + '</p>'; } //若是要求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內容:<p>1<strong>2<em>34</em>5</strong>6</p> * var content = editor.getContent(); //返回值:<p>1<strong>2<em>34</em>5</strong>6</p> * ``` */ /** * 獲取編輯器的內容。 能夠經過參數定義編輯器內置的判空規則 * @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(); //返回格式大體是: <html><head>...</head><body>...</body></html> * ``` */ 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 ? '<link rel="stylesheet" type="text/css" href="' + si.href + '" />' : '<style>' + si.cssText + '</style>'); }); utils.each(me.document.getElementsByTagName('script'), function (si) { headHtmlForIE9 += si.outerHTML; }); } return '<html><head>' + (me.options.charset ? '<meta http-equiv="Content-Type" content="text/html; charset=' + me.options.charset + '"/>' : '') + (headHtmlForIE9 || me.document.getElementsByTagName('head')[0].innerHTML) + headHtml.join('\n') + '</head>' + '<body ' + (ie && browser.version < 9 ? 'class="view"' : '') + '>' + me.getContent(null, null, true) + '</body></html>'; }, /** * 獲得編輯器的純文本內容,但會保留段落格式 * @method getPlainTxt * @return { String } 編輯器帶段落格式的純文本內容字符串 * @example * ```javascript * //編輯器html內容:<p><strong>1</strong></p><p><strong>2</strong></p> * 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)[^>]*>(<br\/?>| )<\/\1>/gi, '\n') .replace(/<br\/?>/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內容:<p><strong>1</strong></p><p><strong>2</strong></p> * 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('<p>test</p>'); * ``` */ /** * 設置編輯器的內容,可修改編輯器當前的html內容 * @method setContent * @warning 經過該方法插入的內容,是通過編輯器內置的過濾規則進行過濾後獲得的內容 * @warning 該方法會觸發selectionchange事件 * @param { String } html 要插入的html內容 * @param { Boolean } isAppendTo 若傳入true,不清空原來的內容,在最後插入內容,不然,清空內容再插入 * @example * ```javascript * //假設設置前的編輯器內容是 <p>old text</p> * editor.setContent('<p>new text</p>', true); //插入的結果是<p>old text</p><p>new text</p> * ``` */ 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 = '<p>' + (browser.ie ? ' ' : '<br/>') + '</p>' + this.body.innerHTML; //this.body.innerHTML = '' + 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 具體命令的使用請參考<a href="#COMMAND.LIST">命令列表</a> * @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 具體命令的使用請參考<a href="#COMMAND.LIST">命令列表</a> * @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 具體命令的使用請參考<a href="#COMMAND.LIST">命令列表</a> * @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 = '<p>' + (ie ? '' : '<br/>') + '</p>'; me.removeListener('firstBeforeExecCommand focus', clear); setTimeout(function () { me.focus(); me._selectionChange(); }, 0) } } return function (cont) { var me = this; me.body.innerHTML = '<p id="initContent">' + cont + '</p>'; 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) { 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內容<p><strong>132</strong></p> * editor.getContentLength() //返回27 * ``` */ /** * 計算編輯器當前純文本內容的長度 * @method getContentLength * @param { Boolean } ingoneHtml 傳入true時,只按照純文原本計算 * @return { Number } 返回計算的長度,內容中有hr/img/iframe標籤,長度加1 * @example * ```javascript * //編輯器html內容<p><strong>132</strong></p> * 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=)/ig.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(/<!--[\s\S]*?-->/ig, "") //轉換圖片 .replace(/<v:shape [^>]*>[\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 '<img width="' + transUnit(width) + '" height="' + transUnit(height) + '" src="' + src + '" />'; } 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(/<p [^>]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi, "<p><strong>$1</strong></p>") //去掉多餘的屬性 .replace(/\s+(class|lang|align)\s*=\s*(['"]?)([\w-]+)\2/ig, 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 (!/<table/.test(tag)) n[i] = name.replace(/^mso-|-alt$/g, "") + ":" + transUnit(value); continue; case "horiz-align": n[i] = "text-align:" + value; continue; case "vert-align": n[i] = "vertical-align:" + value; continue; case "font-color": case "mso-foreground": n[i] = "color:" + value; continue; case "mso-background": case "mso-highlight": n[i] = "background:" + value; continue; case "mso-default-height": n[i] = "min-height:" + transUnit(value); continue; case "mso-default-width": n[i] = "min-width:" + transUnit(value); continue; case "mso-padding-between-alt": n[i] = "border-collapse:separate;border-spacing:" + transUnit(value); continue; case "text-line-through": if ((value == "single") || (value == "double")) { n[i] = "text-decoration:line-through"; } continue; case "mso-zero-height": if (value == "yes") { n[i] = "display:none"; } continue; // case 'background': // break; case 'margin': if (!/[1-9]/.test(value)) { continue; } } if (/^(mso|column|font-emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|table-border|text-(?:decor|trans)|top-bar|version|vnd|word-break)/.test(name) || /text\-indent|padding|margin/.test(name) && /\-[\d.]+/.test(value) ) { continue; } n[i] = name + ":" + parts[1]; } } return tag + (n.length ? ' style="' + n.join(';').replace(/;{2,}/g, ';') + '"' : ''); }) } return function (html) { return (isWordDocument(html) ? filterPasteWord(html) : html); }; }(); // core/node.js /** * 編輯器模擬的節點類 * @file * @module UE * @class uNode * @since 1.2.6.1 */ /** * UEditor公用空間,UEditor全部的功能都掛載在該空間下 * @unfile * @module UE */ (function () { /** * 編輯器模擬的節點類 * @unfile * @module UE * @class uNode */ /** * 經過一個鍵值對,建立一個uNode對象 * @constructor * @param { Object } attr 傳入要建立的uNode的初始屬性 * @example * ```javascript * var node = new uNode({ * type:'element', * tagName:'span', * attrs:{style:'font-size:14px;'} * } * ``` */ var uNode = UE.uNode = function (obj) { this.type = obj.type; this.data = obj.data; this.tagName = obj.tagName; this.parentNode = obj.parentNode; this.attrs = obj.attrs || {}; this.children = obj.children; }; var notTransAttrs = { 'href': 1, 'src': 1, '_src': 1, '_href': 1, 'cdata_data': 1 }; var notTransTagName = { style: 1, script: 1 }; var indentChar = ' ', breakChar = '\n'; function insertLine(arr, current, begin) { arr.push(breakChar); return current + (begin ? 1 : -1); } function insertIndent(arr, current) { //插入縮進 for (var i = 0; i < current; i++) { arr.push(indentChar); } } //建立uNode的靜態方法 //支持標籤和html uNode.createElement = function (html) { 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) { //這裏就針對 //<p>'<img src='http://nsclick.baidu.com/u.gif?&asdf=\"sdf&asdfasdfs;asdf'></p> //這裏邊的\"作轉換,要不用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('<\/' + node.tagName + '>'); } } function isComment(node, arr) { arr.push('<!--' + node.data + '-->'); } 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('<span>text</span>'); * ``` */ 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('<span>text</span>'); * ``` */ 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('<p><b>htmlparser</b></p>', true); * ``` */ var htmlparser = UE.htmlparser = function (htmlstr, ignoreBlank) { //todo 原來的方式 [^"'<>\/] 有\/就不能配對上 <TD vAlign=top background=../AAA.JPG> 這樣的標籤了 //先去掉了,加上的緣由忘了,這裏先記錄 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 ? '' : ' ') + ']*<\/?(\\w+)\\s*(?:[^>]*)>[\\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; } //若是結束是文本,就有可能丟掉,因此這裏手動判斷一下 //例如 <li>sdfsdfsdf<li>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 ? ' ' : '<br/>') } 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這兩個文本 * //執行命令,插入<b>CC</b> * //插入後的效果 xxx<b>CC</b>xxx * //<p>xx|xxx</p> 當前選區爲閉合狀態 * //插入<p>CC</p> * //結果 <p>xx</p><p>CC</p><p>xxx</p> * //<p>xxxx</p>|</p>xxx</p> 當前選區在兩個p標籤之間 * //插入 xxxx * //結果 <p>xxxx</p><p>xxxx</p></p>xxx</p> * ``` */ 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]<br/> 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 = '<p>' + (browser.ie ? '' : '<br/>') + '</p>'; 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>|<[p> ==> <p></p><div></div><p>|</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 : '<br/>'; } 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(/"|\(|\)/ig, ""); } else { url = su != "none" ? su.replace(/url\("?|"?\)/ig, "") : ""; } var html = '<style type="text/css">body{'; var bgObj = { "background-color": domUtils.getComputedStyle(body, "background-color") || "#ffffff", 'background-image': url ? 'url(' + url + ')' : '', 'background-repeat': domUtils.getComputedStyle(body, "background-repeat") || "", 'background-position': browser.ie ? (domUtils.getComputedStyle(body, "background-position-x") + " " + domUtils.getComputedStyle(body, "background-position-y")) : domUtils.getComputedStyle(body, "background-position"), 'height': domUtils.getComputedStyle(body, "height") }; for (var name in bgObj) { if (bgObj.hasOwnProperty(name)) { html += name + ":" + bgObj[name] + "; "; } } html += '}</style> '; 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('<p style="display:none;" data-background="' + utils.trim(styles[1].replace(/"/g, '').replace(/[\s]+/g, ' ')) + '"><br/></p>')); } }, 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', '<p id="_img_parent_tmp" style="text-align:center">' + pN.innerHTML + '</p>'); 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 = '<img src="' + ci.src + '" ' + (ci._src ? ' _src="' + ci._src + '" ' : '') + (ci.width ? 'width="' + ci.width + '" ' : '') + (ci.height ? ' height="' + ci.height + '" ' : '') + (ci['floatStyle'] == 'left' || ci['floatStyle'] == 'right' ? ' style="float:' + ci['floatStyle'] + ';"' : '') + (ci.title && ci.title != "" ? ' title="' + ci.title + '"' : '') + (ci.border && ci.border != "0" ? ' border="' + ci.border + '"' : '') + (ci.alt && ci.alt != "" ? ' alt="' + ci.alt + '"' : '') + (ci.hspace && ci.hspace != "0" ? ' hspace = "' + ci.hspace + '"' : '') + (ci.vspace && ci.vspace != "0" ? ' vspace = "' + ci.vspace + '"' : '') + '/>'; if (ci['floatStyle'] == 'center') { str = '<p style="text-align: center">' + str + '</p>'; } html.push(str); } else { for (var i = 0; ci = opt[i++];) { unhtmlData(ci); str = '<p ' + (ci['floatStyle'] == 'center' ? 'style="text-align: center" ' : '') + '><img src="' + ci.src + '" ' + (ci.width ? 'width="' + ci.width + '" ' : '') + (ci._src ? ' _src="' + ci._src + '" ' : '') + (ci.height ? ' height="' + ci.height + '" ' : '') + ' style="' + (ci['floatStyle'] && ci['floatStyle'] != 'center' ? 'float:' + ci['floatStyle'] + ';' : '') + (ci.border || '') + '" ' + (ci.title ? ' title="' + ci.title + '"' : '') + ' /></p>'; 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 若是是<p><a>xx</a></p> 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); //清除冗餘的代碼 <b><bookmark></b> 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('<!DOCTYPE html><html><head><meta charset="utf-8"/><script src="' + this.options.UEDITOR_HOME_URL + 'ueditor.parse.js"></script><script>' + "setTimeout(function(){uParse('div',{rootPath: '" + this.options.UEDITOR_HOME_URL + "'})},300)" + '</script></head><body><div>' + this.getContent(null, null, true) + '</div></body></html>'); 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', '<hr>'); 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/ig, hh).replace(/ii/ig, ii).replace(/ss/ig, 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/ig, yyyy).replace(/yy/ig, yy).replace(/mm/ig, mm).replace(/dd/ig, 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.]*/ig, ""); } } }; }; // 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 ? ' ' : '') : '<br/>'; } 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 += '<br>' } else if (!dtd.$empty[node.tagName]) { utils.each(node.children, function (cn) { if (cn.type == 'element') { if (cn.tagName == 'br') { code += '<br>' } else if (!dtd.$empty[node.tagName]) { code += cn.innerText(); } } else { code += cn.data } }); if (!/br>$/.test(code)) { code += '<br>'; } } } else { code += node.data + '<br>' } if (!node.nextSibling() && /<br>$/.test(code)) { code = code.replace(/<br>$/, ''); } } else { code += (node.type == 'element' ? (dtd.$empty[node.tagName] ? '' : node.innerText()) : node.data); if (!/br\/?\s*>$/.test(code)) { if (!node.nextSibling()) return; code += '<br>' } } } }); } me.execCommand('inserthtml', '<pre id="coder"class="brush:' + lang + ';toolbar:false">' + code + '</pre>', 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 = "<br/>"; range.setStart(me.body, 0).setCursor(); } else { me.body.innerHTML = "<p>" + (ie ? "" : "<br/>") + "</p>"; 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('<hr class="pagebreak" noshade="noshade" size="5" style="-webkit-user-select: none;">'); 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 + '|<\/hr>', 'gi'); // ie會產生多餘的</hr> 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(/> </g, '><').replace(/\s*</g, '<').replace(/>\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 = '<p><br/></p>'; 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| )*?)</g,'><'); //過濾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 ? ' ' : '<br/>') } //去掉末尾的空白 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 = '<p></p>'; 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><br/></p>類型的空節點,幹掉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() || '<p>' + (browser.ie ? '' : '<br/>') + '</p>'; }; } else { me.iframe.style.cssText = bakCssText; var cont = sourceEditor.getContent() || '<p>' + (browser.ie ? '' : '<br/>') + '</p>'; //處理掉block節點先後的空格,有可能會誤命中,暫時不考慮 cont = cont.replace(new RegExp('[\\r\\t\\n ]*<\/?(\\w+)\\s*(?:[^>]*)>', '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 = '<p>' + (browser.ie ? '' : '<br/>') + '</p>'; 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, '<p>'); 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, '<p>'); 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]<br/> 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 = '<p>' + (browser.ie ? '' : '<br/>') + '</p>'; 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 : '<br/>' }); 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('<span class="edui-editor-imagescale-hand' + i + '"></span>'); } 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?:\/\/)/ig.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); width = parseInt(width, 10) || 0; height = parseInt(height, 10) || 0; var str; switch (type) { case 'image': str = '<img ' + (id ? 'id="' + id + '"' : '') + ' width="' + width + '" height="' + height + '" _url="' + url + '" class="' + classname.replace(/\bvideo-js\b/, '') + '"' + ' src="' + me.options.UEDITOR_HOME_URL + 'themes/default/images/spacer.gif" style="background:url(' + me.options.UEDITOR_HOME_URL + 'themes/default/images/videologo.gif) no-repeat center center; border:1px solid gray;' + (align ? 'float:' + align + ';' : '') + '" />' break; case 'embed': str = '<embed type="application/x-shockwave-flash" class="' + classname + '" pluginspage="http://www.macromedia.com/go/getflashplayer"' + ' src="' + utils.html(url) + '" width="' + width + '" height="' + height + '"' + (align ? ' style="float:' + align + '"' : '') + ' wmode="transparent" play="true" loop="false" menu="false" allowscriptaccess="never" allowfullscreen="true" >'; break; case 'video': var ext = url.substr(url.lastIndexOf('.') + 1); if (ext == 'ogv') ext = 'ogg'; str = '<video' + (id ? ' id="' + id + '"' : '') + ' class="' + classname + ' video-js" ' + (align ? ' style="float:' + align + '"' : '') + ' controls preload="none" width="' + width + '" height="' + height + '" src="' + url + '" data-setup="{}">' + '<source src="' + url + '" type="video/' + ext + '" /></video>'; 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, 'video')); } 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 : "<br />"); }, 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('<tr' + (r == 0 ? ' class="firstRow"' : '') + '>'); for (var c = 0; c < colsNum; c++) { html.push('<td width="' + tdWidth + '" vAlign="' + opt.tdvalign + '" >' + (browser.ie && browser.version < 11 ? domUtils.fillChar : '<br/>') + '</td>') } html.push('</tr>') } //禁止指定table-width return '<table><tbody>' + html.join('') + '</tbody></table>' } 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 ? ' ' : '<br />'; 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 : '<br/>' }); 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 : '<br/>'; 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/ig.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/ig.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 ' + (isFullCol && isFullRow ? 'width="' + width + '"' : '') + '>' + table.innerHTML.replace(/>\s*</g, '><').replace(/\bth\b/gi, "td") + '</table>') } 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\))/ig.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(/[&]+/ig, '&'); } 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 百度應用主頁: <a href="http://app.baidu.com/" target="_blank">http://app.baidu.com/</a> * @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 ? '<p>' : '') + '<img title="'+obj.title+'" width="' + obj.width + '" height="' + obj.height + '"' + // ' src="' + me.options.UEDITOR_HOME_URL + 'themes/default/images/spacer.gif" style="background:url(' + obj.logo+') no-repeat center center; border:1px solid gray;" class="edui-faked-webapp" _url="' + obj.url + '" />' + // (addParagraph ? '</p>' : '') // : // '<iframe class="edui-faked-webapp" title="'+obj.title+'" width="' + obj.width + '" height="' + obj.height + '" scrolling="no" frameborder="0" src="' + obj.url + '" logo_url = '+obj.logo+'></iframe>'; // } // // 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 ? '<img title="' + obj.title + '" width="' + obj.width + '" height="' + obj.height + '"' + ' src="' + me.options.UEDITOR_HOME_URL + 'themes/default/images/spacer.gif" _logo_url="' + obj.logo + '" style="background:url(' + obj.logo + ') no-repeat center center; border:1px solid gray;" class="edui-faked-webapp" _url="' + obj.url + '" ' + (obj.align && !obj.cssfloat ? 'align="' + obj.align + '"' : '') + (obj.cssfloat ? 'style="float:' + obj.cssfloat + '"' : '') + '/>' : '<iframe class="edui-faked-webapp" title="' + obj.title + '" ' + (obj.align && !obj.cssfloat ? 'align="' + obj.align + '"' : '') + (obj.cssfloat ? 'style="float:' + obj.cssfloat + '"' : '') + 'width="' + obj.width + '" height="' + obj.height + '" scrolling="no" frameborder="0" src="' + obj.url + '" logo_url = "' + obj.logo + '"></iframe>' } 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 百度應用主頁: <a href="http://app.baidu.com/" target="_blank">http://app.baidu.com/</a> * @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 ? '<img ' + (align && !cssfloat ? 'align="' + align + '"' : '') + (cssfloat ? 'style="float:' + cssfloat + '"' : '') + ' width="' + width + '" height="' + height + '" _url="' + url + '" class="edui-faked-music"' + ' src="' + me.options.langPath + me.options.lang + '/images/music.png" />' : '<embed type="application/x-shockwave-flash" class="edui-faked-music" pluginspage="http://www.macromedia.com/go/getflashplayer"' + ' src="' + url + '" width="' + width + '" height="' + height + '" ' + (align && !cssfloat ? 'align="' + align + '"' : '') + (cssfloat ? 'style="float:' + cssfloat + '"' : '') + ' wmode="transparent" play="true" loop="false" menu="false" allowscriptaccess="never" allowfullscreen="true" >'; } 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 = '<img class="loadingclass" id="' + loadingId + '" src="' + me.options.themePath + me.options.theme + '/images/spacer.gif" title="' + (me.getLang('autoupload.loading') || '') + '" >'; 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 = '<p>' + '<img class="loadingclass" id="' + loadingId + '" src="' + me.options.themePath + me.options.theme + '/images/spacer.gif" title="' + (me.getLang('autoupload.loading') || '') + '" >' + '</p>'; 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,