From 33ea8e631743704d8751408a5f176f5c211c6ca0 Mon Sep 17 00:00:00 2001
From: thinkgem
- * console.log( wrapNode.innerHTML );
- *
- *
- * UE.dom.domUtils.breakParent( node, parent );
- * //拆分后
- * //output:
- * console.log( wrapNode.innerHTML );
- *
- * ```
- */
- breakParent:function (node, parent) {
- var tmpNode,
- parentClone = node,
- clone = node,
- leftNodes,
- rightNodes;
- do {
- parentClone = parentClone.parentNode;
- if (leftNodes) {
- tmpNode = parentClone.cloneNode(false);
- tmpNode.appendChild(leftNodes);
- leftNodes = tmpNode;
- tmpNode = parentClone.cloneNode(false);
- tmpNode.appendChild(rightNodes);
- rightNodes = tmpNode;
- } else {
- leftNodes = parentClone.cloneNode(false);
- rightNodes = leftNodes.cloneNode(false);
- }
- while (tmpNode = clone.previousSibling) {
- leftNodes.insertBefore(tmpNode, leftNodes.firstChild);
- }
- while (tmpNode = clone.nextSibling) {
- rightNodes.appendChild(tmpNode);
- }
- clone = parentClone;
- } while (parent !== parentClone);
- tmpNode = parent.parentNode;
- tmpNode.insertBefore(leftNodes, parent);
- tmpNode.insertBefore(rightNodes, parent);
- tmpNode.insertBefore(node, rightNodes);
- domUtils.remove(parent);
- return node;
- },
- /**
- * 检查节点node是否是空inline节点
- * @method isEmptyInlineElement
- * @param { Node } node 需要检测的节点对象
- * @return { Number } 如果给定的节点是空的inline节点, 则返回1, 否则返回0。
- * @example
- * ```html
- * => 1
- * => 1
- * => 1
- * xx => 0
- * ```
- */
- isEmptyInlineElement:function (node) {
- if (node.nodeType != 1 || !dtd.$removeEmpty[ node.tagName ]) {
- return 0;
- }
- node = node.firstChild;
- while (node) {
- //如果是创建的bookmark就跳过
- if (domUtils.isBookmarkNode(node)) {
- return 0;
- }
- if (node.nodeType == 1 && !domUtils.isEmptyInlineElement(node) ||
- node.nodeType == 3 && !domUtils.isWhitespace(node)
- ) {
- return 0;
- }
- node = node.nextSibling;
- }
- return 1;
-
- },
-
- /**
- * 删除node节点下首尾两端的空白文本子节点
- * @method trimWhiteTextNode
- * @param { Element } node 需要执行删除操作的元素对象
- * @example
- * ```javascript
- * var node = document.createElement("div");
- *
- * node.appendChild( document.createTextNode( "" ) );
- *
- * node.appendChild( document.createElement("div") );
- *
- * node.appendChild( document.createTextNode( "" ) );
- *
- * //3
- * console.log( node.childNodes.length );
- *
- * UE.dom.domUtils.trimWhiteTextNode( node );
- *
- * //1
- * console.log( node.childNodes.length );
- * ```
- */
- trimWhiteTextNode:function (node) {
- function remove(dir) {
- var child;
- while ((child = node[dir]) && child.nodeType == 3 && domUtils.isWhitespace(child)) {
- node.removeChild(child);
- }
- }
- remove('firstChild');
- remove('lastChild');
- },
-
- /**
- * 合并node节点下相同的子节点
- * @name mergeChild
- * @desc
- * UE.dom.domUtils.mergeChild(node,tagName) //tagName要合并的子节点的标签
- * @example
- * xxaaxx xxaaxx xxxx[xxxx]x xxxx[xxxx]x xxxx[xxxx]x xxxx[xxxx]x
- * aaaa
- *
- *
- * bbbb
- *
- *
- *
- * aaaa
- *
- *
- * bbbb
- *
- *
- *
- *
- * @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, '');
- },
-
- /**
- * 滚动到指定元素,修正选中元素后和搜索后不滚动到选中的位置问题 ThinkGem Add 2015-12-7
- */
- scrollElementPosition: function (me,node) {
- var curtop = 0,
- obj = node;
- if (obj.offsetParent) {
- curtop = obj.offsetTop;
- while (obj = obj.offsetParent) {
- curtop += obj.offsetTop;
- }
- }
- var scrollPos = curtop;
- scrollPos = scrollPos - me.document.documentElement.scrollTop;
- if (me.options.autoHeightEnabled == true){
- me.body.scrollTop = scrollPos;
- }else{
- document.body.scrollTop = $(me.iframe).offset().top + scrollPos - 200;
- }
- },
-
- /**
- * 将字符串str以','分隔成数组后,将该数组转换成哈希对象, 其生成的hash对象的key为数组中的元素, value为1
- * @method listToMap
- * @warning 该方法在生成的hash对象中,会为每一个key同时生成一个另一个全大写的key。
- * @param { String } str 该字符串将被以','分割为数组, 然后进行转化
- * @return { Object } 转化之后的hash对象
- * @example
- * ```javascript
- *
- * //output: Object {UEdtior: 1, UEDTIOR: 1, Hello: 1, HELLO: 1}
- * console.log( UE.utils.listToMap( 'UEdtior,Hello' ) );
- *
- * ```
- */
-
- /**
- * 将字符串数组转换成哈希对象, 其生成的hash对象的key为数组中的元素, value为1
- * @method listToMap
- * @warning 该方法在生成的hash对象中,会为每一个key同时生成一个另一个全大写的key。
- * @param { Array } arr 字符串数组
- * @return { Object } 转化之后的hash对象
- * @example
- * ```javascript
- *
- * //output: Object {UEdtior: 1, UEDTIOR: 1, Hello: 1, HELLO: 1}
- * console.log( UE.utils.listToMap( [ 'UEdtior', 'Hello' ] ) );
- *
- * ```
- */
- listToMap:function (list) {
- if (!list)return {};
- list = utils.isArray(list) ? list : list.split(',');
- for (var i = 0, ci, obj = {}; ci = list[i++];) {
- obj[ci.toUpperCase()] = obj[ci] = 1;
- }
- return obj;
- },
-
- /**
- * 将str中的html符号转义,将转义“',&,<,",>”五个字符
- * @method unhtml
- * @param { String } str 需要转义的字符串
- * @return { String } 转义后的字符串
- * @example
- * ```javascript
- * var html = '&';
- *
- * //output: <body>&</body>
- * console.log( UE.utils.unhtml( html ) );
- *
- * ```
- */
- unhtml:function (str, reg) {
- return str ? str.replace(reg || /[&<">'](?:(amp|lt|quot|gt|#39|nbsp|#\d+);)?/g, function (a, b) {
- if (b) {
- return a;
- } else {
- return {
- '<':'<',
- '&':'&',
- '"':'"',
- '>':'>',
- "'":'''
- }[a]
- }
-
- }) : '';
- },
-
- /**
- * 将str中的转义字符还原成html字符
- * @see UE.utils.unhtml(String);
- * @method html
- * @param { String } str 需要逆转义的字符串
- * @return { String } 逆转义后的字符串
- * @example
- * ```javascript
- *
- * var str = '<body>&</body>';
- *
- * //output: &
- * console.log( UE.utils.html( str ) );
- *
- * ```
- */
- html:function (str) {
- return str ? str.replace(/&((g|l|quo)t|amp|#39|nbsp);/g, function (m) {
- return {
- '<':'<',
- '&':'&',
- '"':'"',
- '>':'>',
- ''':"'",
- ' ':' '
- }[m]
- }) : '';
- },
-
- /**
- * 将css样式转换为驼峰的形式
- * @method cssStyleToDomStyle
- * @param { String } cssName 需要转换的css样式名
- * @return { String } 转换成驼峰形式后的css样式名
- * @example
- * ```javascript
- *
- * var str = 'border-top';
- *
- * //output: borderTop
- * console.log( UE.utils.cssStyleToDomStyle( str ) );
- *
- * ```
- */
- cssStyleToDomStyle:function () {
- var test = document.createElement('div').style,
- cache = {
- 'float':test.cssFloat != undefined ? 'cssFloat' : test.styleFloat != undefined ? 'styleFloat' : 'float'
- };
-
- return function (cssName) {
- return cache[cssName] || (cache[cssName] = cssName.toLowerCase().replace(/-./g, function (match) {
- return match.charAt(1).toUpperCase();
- }));
- };
- }(),
-
- /**
- * 动态加载文件到doc中
- * @method loadFile
- * @param { DomDocument } document 需要加载资源文件的文档对象
- * @param { Object } options 加载资源文件的属性集合, 取值请参考代码示例
- * @example
- * ```javascript
- *
- * UE.utils.loadFile( document, {
- * src:"test.js",
- * tag:"script",
- * type:"text/javascript",
- * defer:"defer"
- * } );
- *
- * ```
- */
-
- /**
- * 动态加载文件到doc中,加载成功后执行的回调函数fn
- * @method loadFile
- * @param { DomDocument } document 需要加载资源文件的文档对象
- * @param { Object } options 加载资源文件的属性集合, 该集合支持的值是script标签和style标签支持的所有属性。
- * @param { Function } fn 资源文件加载成功之后执行的回调
- * @warning 对于在同一个文档中多次加载同一URL的文件, 该方法会在第一次加载之后缓存该请求,
- * 在此之后的所有同一URL的请求, 将会直接触发回调。
- * @example
- * ```javascript
- *
- * UE.utils.loadFile( document, {
- * src:"test.js",
- * tag:"script",
- * type:"text/javascript",
- * defer:"defer"
- * }, function () {
- * console.log('加载成功');
- * } );
- *
- * ```
- */
- loadFile:function () {
- var tmpList = [];
-
- function getItem(doc, obj) {
- try {
- for (var i = 0, ci; ci = tmpList[i++];) {
- if (ci.doc === doc && ci.url == (obj.src || obj.href)) {
- return ci;
- }
- }
- } catch (e) {
- return null;
- }
-
- }
-
- return function (doc, obj, fn) {
- var item = getItem(doc, obj);
- if (item) {
- if (item.ready) {
- fn && fn();
- } else {
- item.funs.push(fn)
- }
- return;
- }
- tmpList.push({
- doc:doc,
- url:obj.src || obj.href,
- funs:[fn]
- });
- if (!doc.body) {
- var html = [];
- for (var p in obj) {
- if (p == 'tag')continue;
- html.push(p + '="' + obj[p] + '"')
- }
- doc.write('<' + obj.tag + ' ' + html.join(' ') + ' >' + 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
'
-};
-var fillCharReg = new RegExp(domUtils.fillChar, 'g');
-
-// core/Range.js
-/**
- * Range封装
- * @file
- * @module UE.dom
- * @class Range
- * @since 1.2.6.1
- */
-
-/**
- * dom操作封装
- * @unfile
- * @module UE.dom
- */
-
-/**
- * Range实现类,本类是UEditor底层核心类,封装不同浏览器之间的Range操作。
- * @unfile
- * @module UE.dom
- * @class Range
- */
-
-
-(function () {
- var guid = 0,
- fillChar = domUtils.fillChar,
- fillData;
-
- /**
- * 更新range的collapse状态
- * @param {Range} range range对象
- */
- function updateCollapse(range) {
- range.collapsed =
- range.startContainer && range.endContainer &&
- range.startContainer === range.endContainer &&
- range.startOffset == range.endOffset;
- }
-
- function selectOneNode(rng){
- return !rng.collapsed && rng.startContainer.nodeType == 1 && rng.startContainer === rng.endContainer && rng.endOffset - rng.startOffset == 1
- }
- function setEndPoint(toStart, node, offset, range) {
- //如果node是自闭合标签要处理
- if (node.nodeType == 1 && (dtd.$empty[node.tagName] || dtd.$nonChild[node.tagName])) {
- offset = domUtils.getNodeIndex(node) + (toStart ? 0 : 1);
- node = node.parentNode;
- }
- if (toStart) {
- range.startContainer = node;
- range.startOffset = offset;
- if (!range.endContainer) {
- range.collapse(true);
- }
- } else {
- range.endContainer = node;
- range.endOffset = offset;
- if (!range.startContainer) {
- range.collapse(false);
- }
- }
- updateCollapse(range);
- return range;
- }
-
- function execContentsAction(range, action) {
- //调整边界
- //range.includeBookmark();
- var start = range.startContainer,
- end = range.endContainer,
- startOffset = range.startOffset,
- endOffset = range.endOffset,
- doc = range.document,
- frag = doc.createDocumentFragment(),
- tmpStart, tmpEnd;
- if (start.nodeType == 1) {
- start = start.childNodes[startOffset] || (tmpStart = start.appendChild(doc.createTextNode('')));
- }
- if (end.nodeType == 1) {
- end = end.childNodes[endOffset] || (tmpEnd = end.appendChild(doc.createTextNode('')));
- }
- if (start === end && start.nodeType == 3) {
- frag.appendChild(doc.createTextNode(start.substringData(startOffset, endOffset - startOffset)));
- //is not clone
- if (action) {
- start.deleteData(startOffset, endOffset - startOffset);
- range.collapse(true);
- }
- return frag;
- }
- var current, currentLevel, clone = frag,
- startParents = domUtils.findParents(start, true), endParents = domUtils.findParents(end, true);
- for (var i = 0; startParents[i] == endParents[i];) {
- i++;
- }
- for (var j = i, si; si = startParents[j]; j++) {
- current = si.nextSibling;
- if (si == start) {
- if (!tmpStart) {
- if (range.startContainer.nodeType == 3) {
- clone.appendChild(doc.createTextNode(start.nodeValue.slice(startOffset)));
- //is not clone
- if (action) {
- start.deleteData(startOffset, start.nodeValue.length - startOffset);
- }
- } else {
- clone.appendChild(!action ? start.cloneNode(true) : start);
- }
- }
- } else {
- currentLevel = si.cloneNode(false);
- clone.appendChild(currentLevel);
- }
- while (current) {
- if (current === end || current === endParents[j]) {
- break;
- }
- si = current.nextSibling;
- clone.appendChild(!action ? current.cloneNode(true) : current);
- current = si;
- }
- clone = currentLevel;
- }
- clone = frag;
- if (!startParents[i]) {
- clone.appendChild(startParents[i - 1].cloneNode(false));
- clone = clone.firstChild;
- }
- for (var j = i, ei; ei = endParents[j]; j++) {
- current = ei.previousSibling;
- if (ei == end) {
- if (!tmpEnd && range.endContainer.nodeType == 3) {
- clone.appendChild(doc.createTextNode(end.substringData(0, endOffset)));
- //is not clone
- if (action) {
- end.deleteData(0, endOffset);
- }
- }
- } else {
- currentLevel = ei.cloneNode(false);
- clone.appendChild(currentLevel);
- }
- //如果两端同级,右边第一次已经被开始做了
- if (j != i || !startParents[i]) {
- while (current) {
- if (current === start) {
- break;
- }
- ei = current.previousSibling;
- clone.insertBefore(!action ? current.cloneNode(true) : current, clone.firstChild);
- current = ei;
- }
- }
- clone = currentLevel;
- }
- if (action) {
- range.setStartBefore(!endParents[i] ? endParents[i - 1] : !startParents[i] ? startParents[i - 1] : endParents[i]).collapse(true);
- }
- tmpStart && domUtils.remove(tmpStart);
- tmpEnd && domUtils.remove(tmpEnd);
- return frag;
- }
-
- /**
- * 创建一个跟document绑定的空的Range实例
- * @constructor
- * @param { Document } document 新建的选区所属的文档对象
- */
-
- /**
- * @property { Node } startContainer 当前Range的开始边界的容器节点, 可以是一个元素节点或者是文本节点
- */
-
- /**
- * @property { Node } startOffset 当前Range的开始边界容器节点的偏移量, 如果是元素节点,
- * 该值就是childNodes中的第几个节点, 如果是文本节点就是文本内容的第几个字符
- */
-
- /**
- * @property { Node } endContainer 当前Range的结束边界的容器节点, 可以是一个元素节点或者是文本节点
- */
-
- /**
- * @property { Node } endOffset 当前Range的结束边界容器节点的偏移量, 如果是元素节点,
- * 该值就是childNodes中的第几个节点, 如果是文本节点就是文本内容的第几个字符
- */
-
- /**
- * @property { Boolean } collapsed 当前Range是否闭合
- * @default true
- * @remind Range是闭合的时候, startContainer === endContainer && startOffset === endOffset
- */
-
- /**
- * @property { Document } document 当前Range所属的Document对象
- * @remind 不同range的的document属性可以是不同的
- */
- var Range = dom.Range = function (document) {
- var me = this;
- me.startContainer =
- me.startOffset =
- me.endContainer =
- me.endOffset = null;
- me.document = document;
- me.collapsed = true;
- };
-
- /**
- * 删除fillData
- * @param doc
- * @param excludeNode
- */
- function removeFillData(doc, excludeNode) {
- try {
- if (fillData && domUtils.inDoc(fillData, doc)) {
- if (!fillData.nodeValue.replace(fillCharReg, '').length) {
- var tmpNode = fillData.parentNode;
- domUtils.remove(fillData);
- while (tmpNode && domUtils.isEmptyInlineElement(tmpNode) &&
- //safari的contains有bug
- (browser.safari ? !(domUtils.getPosition(tmpNode,excludeNode) & domUtils.POSITION_CONTAINS) : !tmpNode.contains(excludeNode))
- ) {
- fillData = tmpNode.parentNode;
- domUtils.remove(tmpNode);
- tmpNode = fillData;
- }
- } else {
- fillData.nodeValue = fillData.nodeValue.replace(fillCharReg, '');
- }
- }
- } catch (e) {
- }
- }
-
- /**
- * @param node
- * @param dir
- */
- function mergeSibling(node, dir) {
- var tmpNode;
- node = node[dir];
- while (node && domUtils.isFillChar(node)) {
- tmpNode = node[dir];
- domUtils.remove(node);
- node = tmpNode;
- }
- }
-
- Range.prototype = {
-
- /**
- * 克隆选区的内容到一个DocumentFragment里
- * @method cloneContents
- * @return { DocumentFragment | NULL } 如果选区是闭合的将返回null, 否则, 返回包含所clone内容的DocumentFragment元素
- * @example
- * ```html
- *
- *
- * xx[xxx]x
- *
- *
- *
- * ```
- */
- cloneContents:function () {
- return this.collapsed ? null : execContentsAction(this, 0);
- },
-
- /**
- * 删除当前选区范围中的所有内容
- * @method deleteContents
- * @remind 执行完该操作后, 当前Range对象变成了闭合状态
- * @return { UE.dom.Range } 当前操作的Range对象
- * @example
- * ```html
- *
- *
- * xx[xxx]x
- *
- *
- *
- * ```
- */
- deleteContents:function () {
- var txt;
- if (!this.collapsed) {
- execContentsAction(this, 1);
- }
- if (browser.webkit) {
- txt = this.startContainer;
- if (txt.nodeType == 3 && !txt.nodeValue.length) {
- this.setStartBefore(txt).collapse(true);
- domUtils.remove(txt);
- }
- }
- return this;
- },
-
- /**
- * 将当前选区的内容提取到一个DocumentFragment里
- * @method extractContents
- * @remind 执行该操作后, 选区将变成闭合状态
- * @warning 执行该操作后, 原来选区所选中的内容将从dom树上剥离出来
- * @return { DocumentFragment } 返回包含所提取内容的DocumentFragment对象
- * @example
- * ```html
- *
- *
- * xx[xxx]x
- *
- *
- *
- */
- extractContents:function () {
- return this.collapsed ? null : execContentsAction(this, 2);
- },
-
- /**
- * 设置Range的开始容器节点和偏移量
- * @method setStart
- * @remind 如果给定的节点是元素节点,那么offset指的是其子元素中索引为offset的元素,
- * 如果是文本节点,那么offset指的是其文本内容的第offset个字符
- * @remind 如果提供的容器节点是一个不能包含子元素的节点, 则该选区的开始容器将被设置
- * 为该节点的父节点, 此时, 其距离开始容器的偏移量也变成了该节点在其父节点
- * 中的索引
- * @param { Node } node 将被设为当前选区开始边界容器的节点对象
- * @param { int } offset 选区的开始位置偏移量
- * @return { UE.dom.Range } 当前range对象
- * @example
- * ```html
- *
- * xxxxxxxxxxxxx[xxx]
- *
- *
- * ```
- * @example
- * ```html
- *
- * xxx[xx]x
- *
- *
- * ```
- */
- setStart:function (node, offset) {
- return setEndPoint(true, node, offset, this);
- },
-
- /**
- * 设置Range的结束容器和偏移量
- * @method setEnd
- * @param { Node } node 作为当前选区结束边界容器的节点对象
- * @param { int } offset 结束边界的偏移量
- * @see UE.dom.Range:setStart(Node,int)
- * @return { UE.dom.Range } 当前range对象
- */
- setEnd:function (node, offset) {
- return setEndPoint(false, node, offset, this);
- },
-
- /**
- * 将Range开始位置设置到node节点之后
- * @method setStartAfter
- * @remind 该操作将会把给定节点的父节点作为range的开始容器, 且偏移量是该节点在其父节点中的位置索引+1
- * @param { Node } node 选区的开始边界将紧接着该节点之后
- * @return { UE.dom.Range } 当前range对象
- * @example
- * ```html
- *
- * xxxxxxx[xxxx]
- *
- *
- * ```
- */
- setStartAfter:function (node) {
- return this.setStart(node.parentNode, domUtils.getNodeIndex(node) + 1);
- },
-
- /**
- * 将Range开始位置设置到node节点之前
- * @method setStartBefore
- * @remind 该操作将会把给定节点的父节点作为range的开始容器, 且偏移量是该节点在其父节点中的位置索引
- * @param { Node } node 新的选区开始位置在该节点之前
- * @see UE.dom.Range:setStartAfter(Node)
- * @return { UE.dom.Range } 当前range对象
- */
- setStartBefore:function (node) {
- return this.setStart(node.parentNode, domUtils.getNodeIndex(node));
- },
-
- /**
- * 将Range结束位置设置到node节点之后
- * @method setEndAfter
- * @remind 该操作将会把给定节点的父节点作为range的结束容器, 且偏移量是该节点在其父节点中的位置索引+1
- * @param { Node } node 目标节点
- * @see UE.dom.Range:setStartAfter(Node)
- * @return { UE.dom.Range } 当前range对象
- * @example
- * ```html
- *
- * [xxxxxxx]xxxx
- *
- *
- * ```
- */
- setEndAfter:function (node) {
- return this.setEnd(node.parentNode, domUtils.getNodeIndex(node) + 1);
- },
-
- /**
- * 将Range结束位置设置到node节点之前
- * @method setEndBefore
- * @remind 该操作将会把给定节点的父节点作为range的结束容器, 且偏移量是该节点在其父节点中的位置索引
- * @param { Node } node 目标节点
- * @see UE.dom.Range:setEndAfter(Node)
- * @return { UE.dom.Range } 当前range对象
- */
- setEndBefore:function (node) {
- return this.setEnd(node.parentNode, domUtils.getNodeIndex(node));
- },
-
- /**
- * 设置Range的开始位置到node节点内的第一个子节点之前
- * @method setStartAtFirst
- * @remind 选区的开始容器将变成给定的节点, 且偏移量为0
- * @remind 如果给定的节点是元素节点, 则该节点必须是允许包含子节点的元素。
- * @param { Node } node 目标节点
- * @see UE.dom.Range:setStartBefore(Node)
- * @return { UE.dom.Range } 当前range对象
- * @example
- * ```html
- *
- * xxxxx[xx]xxxx
- *
- *
- * ```
- */
- setStartAtFirst:function (node) {
- return this.setStart(node, 0);
- },
-
- /**
- * 设置Range的开始位置到node节点内的最后一个节点之后
- * @method setStartAtLast
- * @remind 选区的开始容器将变成给定的节点, 且偏移量为该节点的子节点数
- * @remind 如果给定的节点是元素节点, 则该节点必须是允许包含子节点的元素。
- * @param { Node } node 目标节点
- * @see UE.dom.Range:setStartAtFirst(Node)
- * @return { UE.dom.Range } 当前range对象
- */
- setStartAtLast:function (node) {
- return this.setStart(node, node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length);
- },
-
- /**
- * 设置Range的结束位置到node节点内的第一个节点之前
- * @method setEndAtFirst
- * @param { Node } node 目标节点
- * @remind 选区的结束容器将变成给定的节点, 且偏移量为0
- * @remind node必须是一个元素节点, 且必须是允许包含子节点的元素。
- * @see UE.dom.Range:setStartAtFirst(Node)
- * @return { UE.dom.Range } 当前range对象
- */
- setEndAtFirst:function (node) {
- return this.setEnd(node, 0);
- },
-
- /**
- * 设置Range的结束位置到node节点内的最后一个节点之后
- * @method setEndAtLast
- * @param { Node } node 目标节点
- * @remind 选区的结束容器将变成给定的节点, 且偏移量为该节点的子节点数量
- * @remind node必须是一个元素节点, 且必须是允许包含子节点的元素。
- * @see UE.dom.Range:setStartAtFirst(Node)
- * @return { UE.dom.Range } 当前range对象
- */
- setEndAtLast:function (node) {
- return this.setEnd(node, node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length);
- },
-
- /**
- * 选中给定节点
- * @method selectNode
- * @remind 此时, 选区的开始容器和结束容器都是该节点的父节点, 其startOffset是该节点在父节点中的位置索引,
- * 而endOffset为startOffset+1
- * @param { Node } node 需要选中的节点
- * @return { UE.dom.Range } 当前range对象,此时的range仅包含当前给定的节点对象
- * @example
- * ```html
- *
- * xxxxx[xx]xxxx
- *
- *
- * ```
- */
- selectNode:function (node) {
- return this.setStartBefore(node).setEndAfter(node);
- },
-
- /**
- * 选中给定节点内部的所有节点
- * @method selectNodeContents
- * @remind 此时, 选区的开始容器和结束容器都是该节点, 其startOffset为0,
- * 而endOffset是该节点的子节点数。
- * @param { Node } node 目标节点, 当前range将包含该节点内的所有节点
- * @return { UE.dom.Range } 当前range对象, 此时range仅包含给定节点的所有子节点
- * @example
- * ```html
- *
- * xxxxx[xx]xxxx
- *
- *
- * ```
- */
- selectNodeContents:function (node) {
- return this.setStart(node, 0).setEndAtLast(node);
- },
-
- /**
- * clone当前Range对象
- * @method cloneRange
- * @remind 返回的range是一个全新的range对象, 其内部所有属性与当前被clone的range相同。
- * @return { UE.dom.Range } 当前range对象的一个副本
- */
- cloneRange:function () {
- var me = this;
- return new Range(me.document).setStart(me.startContainer, me.startOffset).setEnd(me.endContainer, me.endOffset);
-
- },
-
- /**
- * 向当前选区的结束处闭合选区
- * @method collapse
- * @return { UE.dom.Range } 当前range对象
- * @example
- * ```html
- *
- * xxxxx[xx]xxxx
- *
- *
- * ```
- */
-
- /**
- * 闭合当前选区,根据给定的toStart参数项决定是向当前选区开始处闭合还是向结束处闭合,
- * 如果toStart的值为true,则向开始位置闭合, 反之,向结束位置闭合。
- * @method collapse
- * @param { Boolean } toStart 是否向选区开始处闭合
- * @return { UE.dom.Range } 当前range对象,此时range对象处于闭合状态
- * @see UE.dom.Range:collapse()
- * @example
- * ```html
- *
- * xxxxx[xx]xxxx
- *
- *
- * ```
- */
- collapse:function (toStart) {
- var me = this;
- if (toStart) {
- me.endContainer = me.startContainer;
- me.endOffset = me.startOffset;
- } else {
- me.startContainer = me.endContainer;
- me.startOffset = me.endOffset;
- }
- me.collapsed = true;
- return me;
- },
-
- /**
- * 调整range的开始位置和结束位置,使其"收缩"到最小的位置
- * @method shrinkBoundary
- * @return { UE.dom.Range } 当前range对象
- * @example
- * ```html
- * xxxx[xxxxx] => xxxx[xxxxx]
- * ```
- *
- * @example
- * ```html
- *
- * x[xx]xxx
- *
- *
- * ```
- *
- * @example
- * ```html
- * [xxxxxxxxxxx] => [xxxxxxxxxxx]
- * ```
- */
-
- /**
- * 调整range的开始位置和结束位置,使其"收缩"到最小的位置,
- * 如果ignoreEnd的值为true,则忽略对结束位置的调整
- * @method shrinkBoundary
- * @param { Boolean } ignoreEnd 是否忽略对结束位置的调整
- * @return { UE.dom.Range } 当前range对象
- * @see UE.dom.domUtils.Range:shrinkBoundary()
- */
- shrinkBoundary:function (ignoreEnd) {
- var me = this, child,
- collapsed = me.collapsed;
- function check(node){
- return node.nodeType == 1 && !domUtils.isBookmarkNode(node) && !dtd.$empty[node.tagName] && !dtd.$nonChild[node.tagName]
- }
- while (me.startContainer.nodeType == 1 //是element
- && (child = me.startContainer.childNodes[me.startOffset]) //子节点也是element
- && check(child)) {
- me.setStart(child, 0);
- }
- if (collapsed) {
- return me.collapse(true);
- }
- if (!ignoreEnd) {
- while (me.endContainer.nodeType == 1//是element
- && me.endOffset > 0 //如果是空元素就退出 endOffset=0那么endOffst-1为负值,childNodes[endOffset]报错
- && (child = me.endContainer.childNodes[me.endOffset - 1]) //子节点也是element
- && check(child)) {
- me.setEnd(child, child.childNodes.length);
- }
- }
- return me;
- },
-
- /**
- * 获取离当前选区内包含的所有节点最近的公共祖先节点,
- * @method getCommonAncestor
- * @remind 返回的公共祖先节点一定不是range自身的容器节点, 但有可能是一个文本节点
- * @return { Node } 当前range对象内所有节点的公共祖先节点
- * @example
- * ```html
- * //选区示例
- * xxxx[xxx]xxxxxx
- *
- * ```
- */
-
- /**
- * 获取当前选区所包含的所有节点的公共祖先节点, 可以根据给定的参数 includeSelf 决定获取到
- * 的公共祖先节点是否可以是当前选区的startContainer或endContainer节点, 如果 includeSelf
- * 的取值为true, 则返回的节点可以是自身的容器节点, 否则, 则不能是容器节点
- * @method getCommonAncestor
- * @param { Boolean } includeSelf 是否允许获取到的公共祖先节点是当前range对象的容器节点
- * @return { Node } 当前range对象内所有节点的公共祖先节点
- * @see UE.dom.Range:getCommonAncestor()
- * @example
- * ```html
- *
- *
- *
- * xxxxxxxxx[xxx]xxxxxxxx
- *
- *
- *
- *
- * ```
- */
-
- /**
- * 获取当前选区所包含的所有节点的公共祖先节点, 可以根据给定的参数 includeSelf 决定获取到
- * 的公共祖先节点是否可以是当前选区的startContainer或endContainer节点, 如果 includeSelf
- * 的取值为true, 则返回的节点可以是自身的容器节点, 否则, 则不能是容器节点; 同时可以根据
- * ignoreTextNode 参数的取值决定是否忽略类型为文本节点的祖先节点。
- * @method getCommonAncestor
- * @param { Boolean } includeSelf 是否允许获取到的公共祖先节点是当前range对象的容器节点
- * @param { Boolean } ignoreTextNode 获取祖先节点的过程中是否忽略类型为文本节点的祖先节点
- * @return { Node } 当前range对象内所有节点的公共祖先节点
- * @see UE.dom.Range:getCommonAncestor()
- * @see UE.dom.Range:getCommonAncestor(Boolean)
- * @example
- * ```html
- *
- *
- *
- * xxxxxxxx[x]xxxxxxxxxxx
- *
- *
- *
- *
- * ```
- */
- getCommonAncestor:function (includeSelf, ignoreTextNode) {
- var me = this,
- start = me.startContainer,
- end = me.endContainer;
- if (start === end) {
- if (includeSelf && selectOneNode(this)) {
- start = start.childNodes[me.startOffset];
- if(start.nodeType == 1)
- return start;
- }
- //只有在上来就相等的情况下才会出现是文本的情况
- return ignoreTextNode && start.nodeType == 3 ? start.parentNode : start;
- }
- return domUtils.getCommonAncestor(start, end);
- },
-
- /**
- * 调整当前Range的开始和结束边界容器,如果是容器节点是文本节点,就调整到包含该文本节点的父节点上
- * @method trimBoundary
- * @remind 该操作有可能会引起文本节点被切开
- * @return { UE.dom.Range } 当前range对象
- * @example
- * ```html
- *
- * //选区示例
- * xxx[xxxxx]xxx
- *
- *
- * ```
- */
-
- /**
- * 调整当前Range的开始和结束边界容器,如果是容器节点是文本节点,就调整到包含该文本节点的父节点上,
- * 可以根据 ignoreEnd 参数的值决定是否调整对结束边界的调整
- * @method trimBoundary
- * @param { Boolean } ignoreEnd 是否忽略对结束边界的调整
- * @return { UE.dom.Range } 当前range对象
- * @example
- * ```html
- *
- * //选区示例
- * xxx[xxxxx]xxx
- *
- *
- * ```
- */
- trimBoundary:function (ignoreEnd) {
- this.txtToElmBoundary();
- var start = this.startContainer,
- offset = this.startOffset,
- collapsed = this.collapsed,
- end = this.endContainer;
- if (start.nodeType == 3) {
- if (offset == 0) {
- this.setStartBefore(start);
- } else {
- if (offset >= start.nodeValue.length) {
- this.setStartAfter(start);
- } else {
- var textNode = domUtils.split(start, offset);
- //跟新结束边界
- if (start === end) {
- this.setEnd(textNode, this.endOffset - offset);
- } else if (start.parentNode === end) {
- this.endOffset += 1;
- }
- this.setStartBefore(textNode);
- }
- }
- if (collapsed) {
- return this.collapse(true);
- }
- }
- if (!ignoreEnd) {
- offset = this.endOffset;
- end = this.endContainer;
- if (end.nodeType == 3) {
- if (offset == 0) {
- this.setEndBefore(end);
- } else {
- offset < end.nodeValue.length && domUtils.split(end, offset);
- this.setEndAfter(end);
- }
- }
- }
- return this;
- },
-
- /**
- * 如果选区在文本的边界上,就扩展选区到文本的父节点上, 如果当前选区是闭合的, 则什么也不做
- * @method txtToElmBoundary
- * @remind 该操作不会修改dom节点
- * @return { UE.dom.Range } 当前range对象
- */
-
- /**
- * 如果选区在文本的边界上,就扩展选区到文本的父节点上, 如果当前选区是闭合的, 则根据参数项
- * ignoreCollapsed 的值决定是否执行该调整
- * @method txtToElmBoundary
- * @param { Boolean } ignoreCollapsed 是否忽略选区的闭合状态, 如果该参数取值为true, 则
- * 不论选区是否闭合, 都会执行该操作, 反之, 则不会对闭合的选区执行该操作
- * @return { UE.dom.Range } 当前range对象
- */
- txtToElmBoundary:function (ignoreCollapsed) {
- function adjust(r, c) {
- var container = r[c + 'Container'],
- offset = r[c + 'Offset'];
- if (container.nodeType == 3) {
- if (!offset) {
- r['set' + c.replace(/(\w)/, function (a) {
- return a.toUpperCase();
- }) + 'Before'](container);
- } else if (offset >= container.nodeValue.length) {
- r['set' + c.replace(/(\w)/, function (a) {
- return a.toUpperCase();
- }) + 'After' ](container);
- }
- }
- }
-
- if (ignoreCollapsed || !this.collapsed) {
- adjust(this, 'start');
- adjust(this, 'end');
- }
- return this;
- },
-
- /**
- * 在当前选区的开始位置前插入节点,新插入的节点会被该range包含
- * @method insertNode
- * @param { Node } node 需要插入的节点
- * @remind 插入的节点可以是一个DocumentFragment依次插入多个节点
- * @return { UE.dom.Range } 当前range对象
- */
- insertNode:function (node) {
- var first = node, length = 1;
- if (node.nodeType == 11) {
- first = node.firstChild;
- length = node.childNodes.length;
- }
- this.trimBoundary(true);
- var start = this.startContainer,
- offset = this.startOffset;
- var nextNode = start.childNodes[ offset ];
- if (nextNode) {
- start.insertBefore(node, nextNode);
- } else {
- start.appendChild(node);
- }
- if (first.parentNode === this.endContainer) {
- this.endOffset = this.endOffset + length;
- }
- return this.setStartBefore(first);
- },
-
- /**
- * 闭合选区到当前选区的开始位置, 并且定位光标到闭合后的位置
- * @method setCursor
- * @return { UE.dom.Range } 当前range对象
- * @see UE.dom.Range:collapse()
- */
-
- /**
- * 闭合选区,可以根据参数toEnd的值控制选区是向前闭合还是向后闭合, 并且定位光标到闭合后的位置。
- * @method setCursor
- * @param { Boolean } toEnd 是否向后闭合, 如果为true, 则闭合选区时, 将向结束容器方向闭合,
- * 反之,则向开始容器方向闭合
- * @return { UE.dom.Range } 当前range对象
- * @see UE.dom.Range:collapse(Boolean)
- */
- setCursor:function (toEnd, noFillData) {
- return this.collapse(!toEnd).select(noFillData);
- },
-
- /**
- * 创建当前range的一个书签,记录下当前range的位置,方便当dom树改变时,还能找回原来的选区位置
- * @method createBookmark
- * @param { Boolean } serialize 控制返回的标记位置是对当前位置的引用还是ID,如果该值为true,则
- * 返回标记位置的ID, 反之则返回标记位置节点的引用
- * @return { Object } 返回一个书签记录键值对, 其包含的key有: start => 开始标记的ID或者引用,
- * end => 结束标记的ID或引用, id => 当前标记的类型, 如果为true,则表示
- * 返回的记录的类型为ID, 反之则为引用
- */
- createBookmark:function (serialize, same) {
- var endNode,
- startNode = this.document.createElement('span');
- startNode.style.cssText = 'display:none;line-height:0px;';
- startNode.appendChild(this.document.createTextNode('\u200D'));
- startNode.id = '_baidu_bookmark_start_' + (same ? '' : guid++);
-
- if (!this.collapsed) {
- endNode = startNode.cloneNode(true);
- endNode.id = '_baidu_bookmark_end_' + (same ? '' : guid++);
- }
- this.insertNode(startNode);
- if (endNode) {
- this.collapse().insertNode(endNode).setEndBefore(endNode);
- }
- this.setStartAfter(startNode);
- return {
- start:serialize ? startNode.id : startNode,
- end:endNode ? serialize ? endNode.id : endNode : null,
- id:serialize
- }
- },
-
- /**
- * 调整当前range的边界到书签位置,并删除该书签对象所标记的位置内的节点
- * @method moveToBookmark
- * @param { BookMark } bookmark createBookmark所创建的标签对象
- * @return { UE.dom.Range } 当前range对象
- * @see UE.dom.Range:createBookmark(Boolean)
- */
- moveToBookmark:function (bookmark) {
- var start = bookmark.id ? this.document.getElementById(bookmark.start) : bookmark.start,
- end = bookmark.end && bookmark.id ? this.document.getElementById(bookmark.end) : bookmark.end;
- this.setStartBefore(start);
- domUtils.remove(start);
- if (end) {
- this.setEndBefore(end);
- domUtils.remove(end);
- } else {
- this.collapse(true);
- }
- return this;
- },
-
- /**
- * 调整range的边界,使其"放大"到最近的父节点
- * @method enlarge
- * @remind 会引起选区的变化
- * @return { UE.dom.Range } 当前range对象
- */
-
- /**
- * 调整range的边界,使其"放大"到最近的父节点,根据参数 toBlock 的取值, 可以
- * 要求扩大之后的父节点是block节点
- * @method enlarge
- * @param { Boolean } toBlock 是否要求扩大之后的父节点必须是block节点
- * @return { UE.dom.Range } 当前range对象
- */
- enlarge:function (toBlock, stopFn) {
- var isBody = domUtils.isBody,
- pre, node, tmp = this.document.createTextNode('');
- if (toBlock) {
- node = this.startContainer;
- if (node.nodeType == 1) {
- if (node.childNodes[this.startOffset]) {
- pre = node = node.childNodes[this.startOffset]
- } else {
- node.appendChild(tmp);
- pre = node = tmp;
- }
- } else {
- pre = node;
- }
- while (1) {
- if (domUtils.isBlockElm(node)) {
- node = pre;
- while ((pre = node.previousSibling) && !domUtils.isBlockElm(pre)) {
- node = pre;
- }
- this.setStartBefore(node);
- break;
- }
- pre = node;
- node = node.parentNode;
- }
- node = this.endContainer;
- if (node.nodeType == 1) {
- if (pre = node.childNodes[this.endOffset]) {
- node.insertBefore(tmp, pre);
- } else {
- node.appendChild(tmp);
- }
- pre = node = tmp;
- } else {
- pre = node;
- }
- while (1) {
- if (domUtils.isBlockElm(node)) {
- node = pre;
- while ((pre = node.nextSibling) && !domUtils.isBlockElm(pre)) {
- node = pre;
- }
- this.setEndAfter(node);
- break;
- }
- pre = node;
- node = node.parentNode;
- }
- if (tmp.parentNode === this.endContainer) {
- this.endOffset--;
- }
- domUtils.remove(tmp);
- }
-
- // 扩展边界到最大
- if (!this.collapsed) {
- while (this.startOffset == 0) {
- if (stopFn && stopFn(this.startContainer)) {
- break;
- }
- if (isBody(this.startContainer)) {
- break;
- }
- this.setStartBefore(this.startContainer);
- }
- while (this.endOffset == (this.endContainer.nodeType == 1 ? this.endContainer.childNodes.length : this.endContainer.nodeValue.length)) {
- if (stopFn && stopFn(this.endContainer)) {
- break;
- }
- if (isBody(this.endContainer)) {
- break;
- }
- this.setEndAfter(this.endContainer);
- }
- }
- return this;
- },
- enlargeToBlockElm:function(ignoreEnd){
- while(!domUtils.isBlockElm(this.startContainer)){
- this.setStartBefore(this.startContainer);
- }
- if(!ignoreEnd){
- while(!domUtils.isBlockElm(this.endContainer)){
- this.setEndAfter(this.endContainer);
- }
- }
- return this;
- },
- /**
- * 调整Range的边界,使其"缩小"到最合适的位置
- * @method adjustmentBoundary
- * @return { UE.dom.Range } 当前range对象
- * @see UE.dom.Range:shrinkBoundary()
- */
- adjustmentBoundary:function () {
- if (!this.collapsed) {
- while (!domUtils.isBody(this.startContainer) &&
- this.startOffset == this.startContainer[this.startContainer.nodeType == 3 ? 'nodeValue' : 'childNodes'].length &&
- this.startContainer[this.startContainer.nodeType == 3 ? 'nodeValue' : 'childNodes'].length
- ) {
-
- this.setStartAfter(this.startContainer);
- }
- while (!domUtils.isBody(this.endContainer) && !this.endOffset &&
- this.endContainer[this.endContainer.nodeType == 3 ? 'nodeValue' : 'childNodes'].length
- ) {
- this.setEndBefore(this.endContainer);
- }
- }
- return this;
- },
-
- /**
- * 给range选区中的内容添加给定的inline标签
- * @method applyInlineStyle
- * @param { String } tagName 需要添加的标签名
- * @example
- * ```html
- *
- * - * - * ``` - */ - - /** - * 遍历range内的节点。 - * 每当遍历一个节点时, 都会执行参数项 doFn 指定的函数, 该函数的接受当前遍历的节点 - * 作为其参数。 - * 可以通过参数项 filterFn 来指定一个过滤器, 只有符合该过滤器过滤规则的节点才会触 - * 发doFn函数的执行 - * @method traversal - * @param { Function } doFn 对每个遍历的节点要执行的方法, 该方法接受当前遍历的节点作为其参数 - * @param { Function } filterFn 过滤器, 该函数接受当前遍历的节点作为参数, 如果该节点满足过滤 - * 规则, 请返回true, 该节点会触发doFn, 否则, 请返回false, 则该节点不 - * 会触发doFn。 - * @return { UE.dom.Range } 当前range对象 - * @see UE.dom.Range:traversal(Function) - * @example - * ```html - * - *
- * - * - * ``` - */ - traversal:function(doFn,filterFn){ - if (this.collapsed) - return this; - var bookmark = this.createBookmark(), - end = bookmark.end, - current = domUtils.getNextDomNode(bookmark.start, false, filterFn); - while (current && current !== end && (domUtils.getPosition(current, end) & domUtils.POSITION_PRECEDING)) { - var tmpNode = domUtils.getNextDomNode(current,false,filterFn); - doFn(current); - current = tmpNode; - } - return this.moveToBookmark(bookmark); - } - }; -})(); - -// core/Selection.js -/** - * 选集 - * @file - * @module UE.dom - * @class Selection - * @since 1.2.6.1 - */ - -/** - * 选区集合 - * @unfile - * @module UE.dom - * @class Selection - */ -(function () { - - function getBoundaryInformation( range, start ) { - var getIndex = domUtils.getNodeIndex; - range = range.duplicate(); - range.collapse( start ); - var parent = range.parentElement(); - //如果节点里没有子节点,直接退出 - if ( !parent.hasChildNodes() ) { - return {container:parent, offset:0}; - } - var siblings = parent.children, - child, - testRange = range.duplicate(), - startIndex = 0, endIndex = siblings.length - 1, index = -1, - distance; - while ( startIndex <= endIndex ) { - index = Math.floor( (startIndex + endIndex) / 2 ); - child = siblings[index]; - testRange.moveToElementText( child ); - var position = testRange.compareEndPoints( 'StartToStart', range ); - if ( position > 0 ) { - endIndex = index - 1; - } else if ( position < 0 ) { - startIndex = index + 1; - } else { - //trace:1043 - return {container:parent, offset:getIndex( child )}; - } - } - if ( index == -1 ) { - testRange.moveToElementText( parent ); - testRange.setEndPoint( 'StartToStart', range ); - distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length; - siblings = parent.childNodes; - if ( !distance ) { - child = siblings[siblings.length - 1]; - return {container:child, offset:child.nodeValue.length}; - } - - var i = siblings.length; - while ( distance > 0 ){ - distance -= siblings[ --i ].nodeValue.length; - } - return {container:siblings[i], offset:-distance}; - } - testRange.collapse( position > 0 ); - testRange.setEndPoint( position > 0 ? 'StartToStart' : 'EndToStart', range ); - distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length; - if ( !distance ) { - return dtd.$empty[child.tagName] || dtd.$nonChild[child.tagName] ? - {container:parent, offset:getIndex( child ) + (position > 0 ? 0 : 1)} : - {container:child, offset:position > 0 ? 0 : child.childNodes.length} - } - while ( distance > 0 ) { - try { - var pre = child; - child = child[position > 0 ? 'previousSibling' : 'nextSibling']; - distance -= child.nodeValue.length; - } catch ( e ) { - return {container:parent, offset:getIndex( pre )}; - } - } - return {container:child, offset:position > 0 ? -distance : child.nodeValue.length + distance} - } - - /** - * 将ieRange转换为Range对象 - * @param {Range} ieRange ieRange对象 - * @param {Range} range Range对象 - * @return {Range} range 返回转换后的Range对象 - */ - function transformIERangeToRange( ieRange, range ) { - if ( ieRange.item ) { - range.selectNode( ieRange.item( 0 ) ); - } else { - var bi = getBoundaryInformation( ieRange, true ); - range.setStart( bi.container, bi.offset ); - if ( ieRange.compareEndPoints( 'StartToEnd', ieRange ) != 0 ) { - bi = getBoundaryInformation( ieRange, false ); - range.setEnd( bi.container, bi.offset ); - } - } - return range; - } - - /** - * 获得ieRange - * @param {Selection} sel Selection对象 - * @return {ieRange} 得到ieRange - */ - function _getIERange( sel ) { - var ieRange; - //ie下有可能报错 - try { - ieRange = sel.getNative().createRange(); - } catch ( e ) { - return null; - } - var el = ieRange.item ? ieRange.item( 0 ) : ieRange.parentElement(); - if ( ( el.ownerDocument || el ) === sel.document ) { - return ieRange; - } - return null; - } - - var Selection = dom.Selection = function ( doc ) { - var me = this, iframe; - me.document = doc; - if ( browser.ie9below ) { - iframe = domUtils.getWindow( doc ).frameElement; - domUtils.on( iframe, 'beforedeactivate', function () { - me._bakIERange = me.getIERange(); - } ); - domUtils.on( iframe, 'activate', function () { - try { - if ( !_getIERange( me ) && me._bakIERange ) { - me._bakIERange.select(); - } - } catch ( ex ) { - } - me._bakIERange = null; - } ); - } - iframe = doc = null; - }; - - Selection.prototype = { - - rangeInBody : function(rng,txtRange){ - var node = browser.ie9below || txtRange ? rng.item ? rng.item() : rng.parentElement() : rng.startContainer; - - return node === this.document.body || domUtils.inDoc(node,this.document); - }, - - /** - * 获取原生seleciton对象 - * @method getNative - * @return { Object } 获得selection对象 - * @example - * ```javascript - * editor.selection.getNative(); - * ``` - */ - getNative:function () { - var doc = this.document; - try { - return !doc ? null : browser.ie9below ? doc.selection : domUtils.getWindow( doc ).getSelection(); - } catch ( e ) { - return null; - } - }, - - /** - * 获得ieRange - * @method getIERange - * @return { Object } 返回ie原生的Range - * @example - * ```javascript - * editor.selection.getIERange(); - * ``` - */ - getIERange:function () { - var ieRange = _getIERange( this ); - if ( !ieRange ) { - if ( this._bakIERange ) { - return this._bakIERange; - } - } - return ieRange; - }, - - /** - * 缓存当前选区的range和选区的开始节点 - * @method cache - */ - cache:function () { - this.clear(); - this._cachedRange = this.getRange(); - this._cachedStartElement = this.getStart(); - this._cachedStartElementPath = this.getStartElementPath(); - }, - - /** - * 获取选区开始位置的父节点到body - * @method getStartElementPath - * @return { Array } 返回父节点集合 - * @example - * ```javascript - * editor.selection.getStartElementPath(); - * ``` - */ - getStartElementPath:function () { - if ( this._cachedStartElementPath ) { - return this._cachedStartElementPath; - } - var start = this.getStart(); - if ( start ) { - return domUtils.findParents( start, true, null, true ) - } - return []; - }, - - /** - * 清空缓存 - * @method clear - */ - clear:function () { - this._cachedStartElementPath = this._cachedRange = this._cachedStartElement = null; - }, - - /** - * 编辑器是否得到了选区 - * @method isFocus - */ - isFocus:function () { - try { - if(browser.ie9below){ - - var nativeRange = _getIERange(this); - return !!(nativeRange && this.rangeInBody(nativeRange)); - }else{ - return !!this.getNative().rangeCount; - } - } catch ( e ) { - return false; - } - - }, - - /** - * 获取选区对应的Range - * @method getRange - * @return { Object } 得到Range对象 - * @example - * ```javascript - * editor.selection.getRange(); - * ``` - */ - getRange:function () { - var me = this; - function optimze( range ) { - var child = me.document.body.firstChild, - collapsed = range.collapsed; - while ( child && child.firstChild ) { - range.setStart( child, 0 ); - child = child.firstChild; - } - if ( !range.startContainer ) { - range.setStart( me.document.body, 0 ) - } - if ( collapsed ) { - range.collapse( true ); - } - } - - if ( me._cachedRange != null ) { - return this._cachedRange; - } - var range = new baidu.editor.dom.Range( me.document ); - - if ( browser.ie9below ) { - var nativeRange = me.getIERange(); - if ( nativeRange ) { - //备份的_bakIERange可能已经实效了,dom树发生了变化比如从源码模式切回来,所以try一下,实效就放到body开始位置 - try{ - transformIERangeToRange( nativeRange, range ); - }catch(e){ - optimze( range ); - } - - } else { - optimze( range ); - } - } else { - var sel = me.getNative(); - if ( sel && sel.rangeCount ) { - var firstRange = sel.getRangeAt( 0 ); - var lastRange = sel.getRangeAt( sel.rangeCount - 1 ); - range.setStart( firstRange.startContainer, firstRange.startOffset ).setEnd( lastRange.endContainer, lastRange.endOffset ); - if ( range.collapsed && domUtils.isBody( range.startContainer ) && !range.startOffset ) { - optimze( range ); - } - } else { - //trace:1734 有可能已经不在dom树上了,标识的节点 - if ( this._bakRange && domUtils.inDoc( this._bakRange.startContainer, this.document ) ){ - return this._bakRange; - } - optimze( range ); - } - } - return this._bakRange = range; - }, - - /** - * 获取开始元素,用于状态反射 - * @method getStart - * @return { Element } 获得开始元素 - * @example - * ```javascript - * editor.selection.getStart(); - * ``` - */ - getStart:function () { - if ( this._cachedStartElement ) { - return this._cachedStartElement; - } - var range = browser.ie9below ? this.getIERange() : this.getRange(), - tmpRange, - start, tmp, parent; - if ( browser.ie9below ) { - if ( !range ) { - //todo 给第一个值可能会有问题 - return this.document.body.firstChild; - } - //control元素 - if ( range.item ){ - return range.item( 0 ); - } - tmpRange = range.duplicate(); - //修正ie下x[xx] 闭合后 x|xx - tmpRange.text.length > 0 && tmpRange.moveStart( 'character', 1 ); - tmpRange.collapse( 1 ); - start = tmpRange.parentElement(); - parent = tmp = range.parentElement(); - while ( tmp = tmp.parentNode ) { - if ( tmp == start ) { - start = parent; - break; - } - } - } else { - range.shrinkBoundary(); - start = range.startContainer; - if ( start.nodeType == 1 && start.hasChildNodes() ){ - start = start.childNodes[Math.min( start.childNodes.length - 1, range.startOffset )]; - } - if ( start.nodeType == 3 ){ - return start.parentNode; - } - } - return start; - }, - - /** - * 得到选区中的文本 - * @method getText - * @return { String } 选区中包含的文本 - * @example - * ```javascript - * editor.selection.getText(); - * ``` - */ - getText:function () { - var nativeSel, nativeRange; - if ( this.isFocus() && (nativeSel = this.getNative()) ) { - nativeRange = browser.ie9below ? nativeSel.createRange() : nativeSel.getRangeAt( 0 ); - return browser.ie9below ? nativeRange.text : nativeRange.toString(); - } - return ''; - }, - - /** - * 清除选区 - * @method clearRange - * @example - * ```javascript - * editor.selection.clearRange(); - * ``` - */ - clearRange : function(){ - this.getNative()[browser.ie9below ? 'empty' : 'removeAllRanges'](); - } - }; -})(); - -// core/Editor.js -/** - * 编辑器主类,包含编辑器提供的大部分公用接口 - * @file - * @module UE - * @class Editor - * @since 1.2.6.1 - */ - -/** - * UEditor公用空间,UEditor所有的功能都挂载在该空间下 - * @unfile - * @module UE - */ - -/** - * UEditor的核心类,为用户提供与编辑器交互的接口。 - * @unfile - * @module UE - * @class Editor - */ - -(function () { - var uid = 0, _selectionChangeTimer; - - /** - * 获取编辑器的html内容,赋值到编辑器所在表单的textarea文本域里面 - * @private - * @method setValue - * @param { UE.Editor } editor 编辑器事例 - */ - function setValue(form, editor) { - var textarea; - if (editor.textarea) { - if (utils.isString(editor.textarea)) { - for (var i = 0, ti, tis = domUtils.getElementsByTagName(form, 'textarea'); ti = tis[i++];) { - if (ti.id == 'ueditor_textarea_' + editor.options.textarea) { - textarea = ti; - break; - } - } - } else { - textarea = editor.textarea; - } - } - if (!textarea) { - form.appendChild(textarea = domUtils.createElement(document, 'textarea', { - 'name': editor.options.textarea, - 'id': 'ueditor_textarea_' + editor.options.textarea, - 'style': "display:none" - })); - //不要产生多个textarea - editor.textarea = textarea; - } - !textarea.getAttribute('name') && textarea.setAttribute('name', editor.options.textarea ); - textarea.value = editor.hasContents() ? - (editor.options.allHtmlEnabled ? editor.getAllHtml() : editor.getContent(null, null, true)) : - '' - } - function loadPlugins(me){ - //初始化插件 - for (var pi in UE.plugins) { - UE.plugins[pi].call(me); - } - - } - function checkCurLang(I18N){ - for(var lang in I18N){ - return lang - } - } - - function langReadied(me){ - me.langIsReady = true; - - me.fireEvent("langReady"); - } - - /** - * 编辑器准备就绪后会触发该事件 - * @module UE - * @class Editor - * @event ready - * @remind render方法执行完成之后,会触发该事件 - * @remind - * @example - * ```javascript - * editor.addListener( 'ready', function( editor ) { - * editor.execCommand( 'focus' ); //编辑器家在完成后,让编辑器拿到焦点 - * } ); - * ``` - */ - /** - * 执行destroy方法,会触发该事件 - * @module UE - * @class Editor - * @event destroy - * @see UE.Editor:destroy() - */ - /** - * 执行reset方法,会触发该事件 - * @module UE - * @class Editor - * @event reset - * @see UE.Editor:reset() - */ - /** - * 执行focus方法,会触发该事件 - * @module UE - * @class Editor - * @event focus - * @see UE.Editor:focus(Boolean) - */ - /** - * 语言加载完成会触发该事件 - * @module UE - * @class Editor - * @event langReady - */ - /** - * 运行命令之后会触发该命令 - * @module UE - * @class Editor - * @event beforeExecCommand - */ - /** - * 运行命令之后会触发该命令 - * @module UE - * @class Editor - * @event afterExecCommand - */ - /** - * 运行命令之前会触发该命令 - * @module UE - * @class Editor - * @event firstBeforeExecCommand - */ - /** - * 在getContent方法执行之前会触发该事件 - * @module UE - * @class Editor - * @event beforeGetContent - * @see UE.Editor:getContent() - */ - /** - * 在getContent方法执行之后会触发该事件 - * @module UE - * @class Editor - * @event afterGetContent - * @see UE.Editor:getContent() - */ - /** - * 在getAllHtml方法执行时会触发该事件 - * @module UE - * @class Editor - * @event getAllHtml - * @see UE.Editor:getAllHtml() - */ - /** - * 在setContent方法执行之前会触发该事件 - * @module UE - * @class Editor - * @event beforeSetContent - * @see UE.Editor:setContent(String) - */ - /** - * 在setContent方法执行之后会触发该事件 - * @module UE - * @class Editor - * @event afterSetContent - * @see UE.Editor:setContent(String) - */ - /** - * 每当编辑器内部选区发生改变时,将触发该事件 - * @event selectionchange - * @warning 该事件的触发非常频繁,不建议在该事件的处理过程中做重量级的处理 - * @example - * ```javascript - * editor.addListener( 'selectionchange', function( editor ) { - * console.log('选区发生改变'); - * } - */ - /** - * 在所有selectionchange的监听函数执行之前,会触发该事件 - * @module UE - * @class Editor - * @event beforeSelectionChange - * @see UE.Editor:selectionchange - */ - /** - * 在所有selectionchange的监听函数执行完之后,会触发该事件 - * @module UE - * @class Editor - * @event afterSelectionChange - * @see UE.Editor:selectionchange - */ - /** - * 编辑器内容发生改变时会触发该事件 - * @module UE - * @class Editor - * @event contentChange - */ - - - /** - * 以默认参数构建一个编辑器实例 - * @constructor - * @remind 通过 改构造方法实例化的编辑器,不带ui层.需要render到一个容器,编辑器实例才能正常渲染到页面 - * @example - * ```javascript - * var editor = new UE.Editor(); - * editor.execCommand('blod'); - * ``` - * @see UE.Config - */ - - /** - * 以给定的参数集合创建一个编辑器实例,对于未指定的参数,将应用默认参数。 - * @constructor - * @remind 通过 改构造方法实例化的编辑器,不带ui层.需要render到一个容器,编辑器实例才能正常渲染到页面 - * @param { Object } setting 创建编辑器的参数 - * @example - * ```javascript - * var editor = new UE.Editor(); - * editor.execCommand('blod'); - * ``` - * @see UE.Config - */ - var Editor = UE.Editor = function (options) { - var me = this; - me.uid = uid++; - EventBase.call(me); - me.commands = {}; - me.options = utils.extend(utils.clone(options || {}), UEDITOR_CONFIG, true); - me.shortcutkeys = {}; - me.inputRules = []; - me.outputRules = []; - //设置默认的常用属性 - me.setOpt(Editor.defaultOptions(me)); - - /* 尝试异步加载后台配置 */ - me.loadServerConfig(); - - if(!utils.isEmptyObject(UE.I18N)){ - //修改默认的语言类型 - me.options.lang = checkCurLang(UE.I18N); - UE.plugin.load(me); - langReadied(me); - - }else{ - utils.loadFile(document, { - src: me.options.langPath + me.options.lang + "/" + me.options.lang + ".js", - tag: "script", - type: "text/javascript", - defer: "defer" - }, function () { - UE.plugin.load(me); - langReadied(me); - }); - } - - UE.instants['ueditorInstant' + me.uid] = me; - }; - Editor.prototype = { - registerCommand : function(name,obj){ - this.commands[name] = obj; - }, - /** - * 编辑器对外提供的监听ready事件的接口, 通过调用该方法,达到的效果与监听ready事件是一致的 - * @method ready - * @param { Function } fn 编辑器ready之后所执行的回调, 如果在注册事件之前编辑器已经ready,将会 - * 立即触发该回调。 - * @remind 需要等待编辑器加载完成后才能执行的代码,可以使用该方法传入 - * @example - * ```javascript - * editor.ready( function( editor ) { - * editor.setContent('初始化完毕'); - * } ); - * ``` - * @see UE.Editor.event:ready - */ - ready: function (fn) { - var me = this; - if (fn) { - me.isReady ? fn.apply(me) : me.addListener('ready', fn); - } - }, - - /** - * 该方法是提供给插件里面使用,设置配置项默认值 - * @method setOpt - * @warning 三处设置配置项的优先级: 实例化时传入参数 > setOpt()设置 > config文件里设置 - * @warning 该方法仅供编辑器插件内部和编辑器初始化时调用,其他地方不能调用。 - * @param { String } key 编辑器的可接受的选项名称 - * @param { * } val 该选项可接受的值 - * @example - * ```javascript - * editor.setOpt( 'initContent', '欢迎使用编辑器' ); - * ``` - */ - - /** - * 该方法是提供给插件里面使用,以{key:value}集合的方式设置插件内用到的配置项默认值 - * @method setOpt - * @warning 三处设置配置项的优先级: 实例化时传入参数 > setOpt()设置 > config文件里设置 - * @warning 该方法仅供编辑器插件内部和编辑器初始化时调用,其他地方不能调用。 - * @param { Object } options 将要设置的选项的键值对对象 - * @example - * ```javascript - * editor.setOpt( { - * 'initContent': '欢迎使用编辑器' - * } ); - * ``` - */ - setOpt: function (key, val) { - var obj = {}; - if (utils.isString(key)) { - obj[key] = val - } else { - obj = key; - } - utils.extend(this.options, obj, true); - }, - getOpt:function(key){ - return this.options[key] - }, - /** - * 销毁编辑器实例,使用textarea代替 - * @method destroy - * @example - * ```javascript - * editor.destroy(); - * ``` - */ - destroy: function () { - - var me = this; - me.fireEvent('destroy'); - var container = me.container.parentNode; - var textarea = me.textarea; - if (!textarea) { - textarea = document.createElement('textarea'); - container.parentNode.insertBefore(textarea, container); - } else { - textarea.style.display = '' - } - - textarea.style.width = me.iframe.offsetWidth + 'px'; - textarea.style.height = me.iframe.offsetHeight + 'px'; - textarea.value = me.getContent(); - textarea.id = me.key; - container.innerHTML = ''; - domUtils.remove(container); - var key = me.key; - //trace:2004 - for (var p in me) { - if (me.hasOwnProperty(p)) { - delete this[p]; - } - } - UE.delEditor(key); - }, - - /** - * 渲染编辑器的DOM到指定容器 - * @method render - * @param { String } containerId 指定一个容器ID - * @remind 执行该方法,会触发ready事件 - * @warning 必须且只能调用一次 - */ - - /** - * 渲染编辑器的DOM到指定容器 - * @method render - * @param { Element } containerDom 直接指定容器对象 - * @remind 执行该方法,会触发ready事件 - * @warning 必须且只能调用一次 - */ - render: function (container) { - var me = this, - options = me.options, - getStyleValue=function(attr){ - return parseInt(domUtils.getComputedStyle(container,attr)); - }; - if (utils.isString(container)) { - container = document.getElementById(container); - } - if (container) { - if(options.initialFrameWidth){ - options.minFrameWidth = options.initialFrameWidth - }else{ - options.minFrameWidth = options.initialFrameWidth = container.offsetWidth; - } - if(options.initialFrameHeight){ - options.minFrameHeight = options.initialFrameHeight - }else{ - options.initialFrameHeight = options.minFrameHeight = container.offsetHeight; - } - - container.style.width = /%$/.test(options.initialFrameWidth) ? '100%' : options.initialFrameWidth- - getStyleValue("padding-left")- getStyleValue("padding-right") +'px'; - container.style.height = /%$/.test(options.initialFrameHeight) ? '100%' : options.initialFrameHeight - - getStyleValue("padding-top")- getStyleValue("padding-bottom") +'px'; - - container.style.zIndex = options.zIndex; - - var html = ( ie && browser.version < 9 ? '' : '') + - '
' + - '' + - ( options.iframeCssUrl ? '' : '' ) + - (options.initialStyle ? '' : '') + - '
' + - '