0){
+ var t = array[i];
+ array[i] = array[j];
+ array[j] = t;
}
- for (var i = 0, ci; ci = this.children[i]; i++) {
- if (ci === node) {
- this.children.splice(i, 1);
- break;
- }
+ }
+ }
+ 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]) );
}
- this.children.push(node);
- node.parentNode = this;
- return node;
}
+ }
+ 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(){
- /**
- * 在传入节点的前面插入一个节点
- * @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;
- }
- }
+ if (window.JSON) {
- }
- },
+ return JSON.stringify;
- /**
- * 在传入节点的后面插入一个节点
- * @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;
- }
+ } 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 + '"';
}
- },
- /**
- * 从当前节点的子节点列表中,移除节点
- * @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;
+ 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(',');
}
- }
- return ci;
+ result.push(utils.json2str(item));
+ preComma = 1;
}
}
+ result.push("]");
+ return result.join("");
}
- },
- /**
- * 获取当前节点所代表的元素属性,即获取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 = {};
+ function pad(source) {
+ return source < 10 ? '0' + source : source;
}
- 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;
- }
+ function encodeDate(source){
+ return '"' + source.getFullYear() + "-"
+ + pad(source.getMonth() + 1) + "-"
+ + pad(source.getDate()) + "T"
+ + pad(source.getHours()) + ":"
+ + pad(source.getMinutes()) + ":"
+ + pad(source.getSeconds()) + '"';
}
- },
- /**
- * 获取当前节点在父节点下的位置索引
- * @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;
- },
+ return function (value) {
+ switch (typeof value) {
+ case 'undefined':
+ return 'undefined';
- /**
- * 在当前节点下,根据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;
- }
- }
- }
- },
+ case 'number':
+ return isFinite(value) ? String(value) : "null";
- /**
- * 在当前节点下,根据元素名称查找节点列表
- * @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;
- },
+ case 'string':
+ return encodeString(value);
- /**
- * 根据样式名称,获取节点的样式值
- * @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 '';
- },
+ case 'boolean':
+ return String(value);
- /**
- * 给节点设置样式
- * @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
+ 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('');
+ }
}
+ };
+ }
- }
+ })()
- 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 isString
+ * @param { * } object 需要判断的对象
+ * @return { Boolean } 给定的对象是否是字符串
+ */
- /**
- * 传入一个函数,递归遍历当前节点下的所有节点
- * @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;
- }
+/**
+ * 判断给定的对象是否是数组
+ * @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/htmlparser.js
+// core/EventBase.js
/**
- * html字符串转换成uNode节点
+ * UE采用的事件基类
* @file
* @module UE
+ * @class EventBase
* @since 1.2.6.1
*/
@@ -9367,18066 +1518,25915 @@ var filterWord = UE.filterWord = function () {
*/
/**
- * html字符串转换成uNode节点的静态方法
- * @method htmlparser
- * @param { String } htmlstr 要转换的html代码
- * @param { Boolean } ignoreBlank 若设置为true,转换的时候忽略\n\r\t等空白字符
- * @return { uNode } 给定的html片段转换形成的uNode对象
+ * UE采用的事件基类,继承此类的对应类将获取addListener,removeListener,fireEvent方法。
+ * 在UE中,Editor以及所有ui实例都继承了该类,故可以在对应的ui对象以及editor对象上使用上述方法。
+ * @unfile
+ * @module UE
+ * @class EventBase
+ */
+
+/**
+ * 通过此构造器,子类可以继承EventBase获取事件监听的方法
+ * @constructor
* @example
* ```javascript
- * var root = UE.htmlparser('htmlparser
', true);
+ * UE.EventBase.call(editor);
* ```
*/
+var EventBase = UE.EventBase = function () {};
-var htmlparser = UE.htmlparser = function (htmlstr,ignoreBlank) {
- //todo 原来的方式 [^"'<>\/] 有\/就不能配对上 这样的标签了
- //先去掉了,加上的原因忘了,这里先记录
- var re_tag = /<(?:(?:\/([^>]+)>)|(?:!--([\S|\s]*?)-->)|(?:([^\s\/<>]+)\s*((?:(?:"[^"]*")|(?:'[^']*')|[^"'<>])*)\/?>))/g,
- re_attr = /([\w\-:.]+)(?:(?:\s*=\s*(?:(?:"([^"]*)")|(?:'([^']*)')|([^\s>]+)))|(?=\s|$))/g;
+EventBase.prototype = {
- //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?'':' ')+']+$'),'');
- });
- }
+ /**
+ * 注册事件监听器
+ * @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);
+ }
+ },
- var notTransAttrs = {
- 'href':1,
- 'src':1
- };
+ 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);
+ }
+ },
- 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'
- };
+ /**
+ * 触发事件
+ * @method fireEvent
+ * @param { String } types 触发的事件名称,同时触发多个事件使用空格分隔
+ * @remind 该方法会触发addListener
+ * @return { * } 返回触发事件的队列中,最后执行的回调函数的返回值
+ * @example
+ * ```javascript
+ * editor.fireEvent("selectionchange");
+ * ```
+ */
- function text(parent, data) {
+ /**
+ * 触发事件
+ * @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] = [] ) ) );
+}
- 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));
+
+// 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
+ });
+})();
- 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;
+
+// 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;
}
- if(!hasParent){
- parent = element(parent, utils.isArray(needParentTag) ? needParentTag[0] : needParentTag)
+ 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);
}
- //按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])
+ 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
}
- 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;
+ // domUtils.POSITION_FOLLOWING
+ return 2;
+ },
- while (match = re_tag.exec(htmlstr)) {
- currentIndex = match.index;
- try{
- if (currentIndex > nextIndex) {
- //text node
- text(currentParent, htmlstr.slice(nextIndex, currentIndex));
- }
- if (match[3]) {
+ /**
+ * 检测节点node在父节点中的索引位置
+ * @method getNodeIndex
+ * @param { Node } node 需要检测的节点对象
+ * @return { Number } 该节点在父节点中的位置
+ * @see UE.dom.domUtils.getNodeIndex(Node,Boolean)
+ */
- if(dtd.$cdata[currentParent.tagName]){
- text(currentParent, match[0]);
- }else{
- //start tag
- currentParent = element(currentParent, match[3].toLowerCase(), match[4]);
+ /**
+ * 检测节点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 );
+ * ```
+ */
- } 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;
- }
-
+ /**
+ * 根据给定的过滤规则filterFn, 查找符合该过滤规则的node节点的第一个祖先节点,
+ * 如果includeSelf的值为true,则查找的起点是给定的节点node, 否则, 起点是node的父节点
+ * @method findParent
+ * @param { Node } node 需要查找的节点
+ * @param { Function } filterFn 自定义的过滤方法。
+ * @param { Boolean } includeSelf 查找过程是否包含自身
+ * @warning 查找的终点是到body节点为止
+ * @remind 自定义的过滤方法filterFn接受一个Node对象作为参数, 该对象代表当前执行检测的祖先节点。 如果该
+ * 节点满足过滤条件, 则要求返回true, 这时将直接返回该节点作为findParent()的结果, 否则, 请返回false。
+ * @remind 如果includeSelf为true, 则过滤器第一次执行时的参数会是节点本身。
+ * 反之, 过滤器第一次执行时的参数将是该节点的父节点。
+ * @return { Node | Null } 如果找到符合过滤条件的节点, 就返回该节点, 否则返回NULL
+ * @example
+ * ```html
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * ```
+ */
+ findParent:function (node, filterFn, includeSelf) {
+ if (node && !domUtils.isBody(node)) {
+ node = includeSelf ? node : node.parentNode;
+ while (node) {
+ if (!filterFn || filterFn(node) || domUtils.isBody(node)) {
+ return filterFn && !filterFn(node) && domUtils.isBody(node) ? null : node;
}
-
- } else if (match[2]) {
- //comment
- comment(currentParent, match[2])
+ node = node.parentNode;
}
- }catch(e){}
-
- nextIndex = re_tag.lastIndex;
+ }
+ 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 );
+ * ```
+ */
- }
- //如果结束是文本,就有可能丢掉,所以这里手动判断一下
- //例如 sdfsdfsdfsdfsdfsdfsdf
- if (nextIndex < htmlstr.length) {
- text(currentParent, htmlstr.slice(nextIndex));
- }
- return root;
-};
+ /**
+ * 查找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();
+ },
-// 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;
- }
-}();
+ /**
+ * 在节点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);
+ },
-// 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)
- }
- })
- }
+ /**
+ * 删除节点node及其下属的所有节点
+ * @method remove
+ * @param { Node } node 需要删除的节点对象
+ * @return { Node } 返回刚删除的节点对象
+ * @example
+ * ```html
+ *
+ *
+ * ```
+ */
- }else if(plugin.afterDisabled){
- plugin.afterDisabled.call(editor)
+ /**
+ * 删除节点node,并根据keepChildren的值决定是否保留子节点
+ * @method remove
+ * @param { Node } node 需要删除的节点对象
+ * @param { Boolean } keepChildren 是否需要保留子节点
+ * @return { Node } 返回刚删除的节点对象
+ * @example
+ * ```html
+ *
+ *
+ * ```
+ */
+ remove:function (node, keepChildren) {
+ var parent = node.parentNode,
+ child;
+ if (parent) {
+ if (keepChildren && node.hasChildNodes()) {
+ while (child = node.firstChild) {
+ parent.insertBefore(child, node);
}
-
- });
- //向下兼容
- utils.each(UE.plugins,function(plugin){
- plugin.call(editor);
- });
- },
- run : function(pluginName,editor){
- var plugin = _plugins[pluginName];
- if(plugin){
- plugin.exeFn.call(editor)
}
+ parent.removeChild(node);
}
- }
-}();
-
-// 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 {
+ return node;
+ },
- saveLocalData: function (key, data) {
+ /**
+ * 取得node节点的下一个兄弟节点, 如果该节点其后没有兄弟节点, 则递归查找其父节点之后的第一个兄弟节点,
+ * 直到找到满足条件的节点或者递归到BODY节点之后才会结束。
+ * @method getNextDomNode
+ * @param { Node } node 需要获取其后的兄弟节点的节点对象
+ * @return { Node | NULL } 如果找满足条件的节点, 则返回该节点, 否则返回NULL
+ * @example
+ * ```html
+ *
+ *
+ *
+ *
+ * xxx
+ *
+ *
+ * ```
+ * @example
+ * ```html
+ *
+ *
+ *
+ * xxx
+ *
+ * xxx
+ *
+ *
+ * ```
+ */
- if (storage && data) {
- storage.setItem(key, data);
- return true;
+ /**
+ * 取得node节点的下一个兄弟节点, 如果startFromChild的值为ture,则先获取其子节点,
+ * 如果有子节点则直接返回第一个子节点;如果没有子节点或者startFromChild的值为false,
+ * 则执行getNextDomNode(Node node)的查找过程。
+ * @method getNextDomNode
+ * @param { Node } node 需要获取其后的兄弟节点的节点对象
+ * @param { Boolean } startFromChild 查找过程是否从其子节点开始
+ * @return { Node | NULL } 如果找满足条件的节点, 则返回该节点, 否则返回NULL
+ * @see UE.dom.domUtils.getNextDomNode(Node)
+ */
+ getNextDomNode:function (node, startFromChild, filterFn, guard) {
+ return getDomNode(node, 'firstChild', 'nextSibling', startFromChild, filterFn, guard);
+ },
+ getPreDomNode:function (node, startFromChild, filterFn, guard) {
+ return getDomNode(node, 'lastChild', 'previousSibling', startFromChild, filterFn, guard);
+ },
+ /**
+ * 检测节点node是否属是UEditor定义的bookmark节点
+ * @method isBookmarkNode
+ * @private
+ * @param { Node } node 需要检测的节点对象
+ * @return { Boolean } 是否是bookmark节点
+ * @example
+ * ```html
+ *
+ *
+ * ```
+ */
+ isBookmarkNode:function (node) {
+ return node.nodeType == 1 && node.id && /^_baidu_bookmark_/i.test(node.id);
+ },
+ /**
+ * 获取节点node所属的window对象
+ * @method getWindow
+ * @param { Node } node 节点对象
+ * @return { Window } 当前节点所属的window对象
+ * @example
+ * ```javascript
+ * //output: true
+ * console.log( UE.dom.domUtils.getWindow( document.body ) === window );
+ * ```
+ */
+ getWindow:function (node) {
+ var doc = node.ownerDocument || node;
+ return doc.defaultView || doc.parentWindow;
+ },
+ /**
+ * 获取离nodeA与nodeB最近的公共的祖先节点
+ * @method getCommonAncestor
+ * @param { Node } nodeA 第一个节点
+ * @param { Node } nodeB 第二个节点
+ * @remind 如果给定的两个节点是同一个节点, 将直接返回该节点。
+ * @return { Node | NULL } 如果未找到公共节点, 返回NULL, 否则返回最近的公共祖先节点。
+ * @example
+ * ```javascript
+ * var commonAncestor = UE.dom.domUtils.getCommonAncestor( document.body, document.body.firstChild );
+ * //output: true
+ * console.log( commonAncestor.tagName.toLowerCase() === 'body' );
+ * ```
+ */
+ getCommonAncestor:function (nodeA, nodeB) {
+ if (nodeA === nodeB)
+ return nodeA;
+ var parentsA = [nodeA] , parentsB = [nodeB], parent = nodeA, i = -1;
+ while (parent = parent.parentNode) {
+ if (parent === nodeB) {
+ return parent;
}
+ parentsA.push(parent);
+ }
+ parent = nodeB;
+ while (parent = parent.parentNode) {
+ if (parent === nodeA)
+ return parent;
+ parentsB.push(parent);
+ }
+ parentsA.reverse();
+ parentsB.reverse();
+ while (i++, parentsA[i] === parentsB[i]) {
+ }
+ return i == 0 ? null : parentsA[i - 1];
- return false;
-
- },
+ },
+ /**
+ * 清除node节点左右连续为空的兄弟inline节点
+ * @method clearEmptySibling
+ * @param { Node } node 执行的节点对象, 如果该节点的左右连续的兄弟节点是空的inline节点,
+ * 则这些兄弟节点将被删除
+ * @grammar UE.dom.domUtils.clearEmptySibling(node,ignoreNext) //ignoreNext指定是否忽略右边空节点
+ * @grammar UE.dom.domUtils.clearEmptySibling(node,ignoreNext,ignorePre) //ignorePre指定是否忽略左边空节点
+ * @example
+ * ```html
+ *
+ *
+ *
+ *
+ *
+ * xxx
+ *
+ *
+ *
+ * ```
+ */
- getLocalData: function (key) {
+ /**
+ * 清除node节点左右连续为空的兄弟inline节点, 如果ignoreNext的值为true,
+ * 则忽略对右边兄弟节点的操作。
+ * @method clearEmptySibling
+ * @param { Node } node 执行的节点对象, 如果该节点的左右连续的兄弟节点是空的inline节点,
+ * @param { Boolean } ignoreNext 是否忽略忽略对右边的兄弟节点的操作
+ * 则这些兄弟节点将被删除
+ * @see UE.dom.domUtils.clearEmptySibling(Node)
+ */
- if (storage) {
- return storage.getItem(key);
+ /**
+ * 清除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;
}
-
- return null;
-
- },
-
- removeItem: function (key) {
-
- storage && storage.removeItem(key);
-
}
+ !ignoreNext && clear(node.nextSibling, 'nextSibling');
+ !ignorePre && clear(node.previousSibling, 'previousSibling');
+ },
+ /**
+ * 将一个文本节点textNode拆分成两个文本节点,offset指定拆分位置
+ * @method split
+ * @param { Node } textNode 需要拆分的文本节点对象
+ * @param { int } offset 需要拆分的位置, 位置计算从0开始
+ * @return { Node } 拆分后形成的新节点
+ * @example
+ * ```html
+ * abcdef
+ *
+ * ```
+ */
+ split:function (node, offset) {
+ var doc = node.ownerDocument;
+ if (browser.ie && offset == node.nodeValue.length) {
+ var next = doc.createTextNode('');
+ return domUtils.insertAfter(node, next);
+ }
+ var retval = node.splitText(offset);
+ //ie8下splitText不会跟新childNodes,我们手动触发他的更新
+ if (browser.ie8) {
+ var tmpNode = doc.createTextNode('');
+ domUtils.insertAfter(retval, tmpNode);
+ domUtils.remove(tmpNode);
+ }
+ return retval;
+ },
- };
+ /**
+ * 检测文本节点textNode是否为空节点(包括空格、换行、占位符等字符)
+ * @method isWhitespace
+ * @param { Node } node 需要检测的节点对象
+ * @return { Boolean } 检测的节点是否为空
+ * @example
+ * ```html
+ *
+ *
+ *
+ *
+ * ```
+ */
+ isWhitespace:function (node) {
+ return !new RegExp('[^ \t\n\r' + domUtils.fillChar + ']').test(node.nodeValue);
+ },
+ /**
+ * 获取元素element相对于viewport的位置坐标
+ * @method getXY
+ * @param { Node } element 需要计算位置的节点对象
+ * @return { Object } 返回形如{x:left,y:top}的一个key-value映射对象, 其中键x代表水平偏移距离,
+ * y代表垂直偏移距离。
+ *
+ * @example
+ * ```javascript
+ * var location = UE.dom.domUtils.getXY( document.getElementById("test") );
+ * //output: test的坐标为: 12, 24
+ * console.log( 'test的坐标为: ', location.x, ',', location.y );
+ * ```
+ */
+ getXY:function (element) {
+ var x = 0, y = 0;
+ while (element.offsetParent) {
+ y += element.offsetTop;
+ x += element.offsetLeft;
+ element = element.offsetParent;
+ }
+ return { 'x':x, 'y':y};
+ },
+ /**
+ * 为元素element绑定原生DOM事件,type为事件类型,handler为处理函数
+ * @method on
+ * @param { Node } element 需要绑定事件的节点对象
+ * @param { String } type 绑定的事件类型
+ * @param { Function } handler 事件处理器
+ * @example
+ * ```javascript
+ * UE.dom.domUtils.on(document.body,"click",function(e){
+ * //e为事件对象,this为被点击元素对戏那个
+ * });
+ * ```
+ */
- function getUserData() {
+ /**
+ * 为元素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);
+ };
+ }
- var container = document.createElement("div");
- container.style.display = "none";
- if (!container.addBehavior) {
- return null;
+ element.attachEvent('on' + type, handler._d[key]);
+ }
+ }
}
-
+ element = null;
+ },
+ /**
+ * 解除DOM事件绑定
+ * @method un
+ * @param { Node } element 需要解除事件绑定的节点对象
+ * @param { String } type 需要接触绑定的事件类型
+ * @param { Function } handler 对应的事件处理器
+ * @example
+ * ```javascript
+ * UE.dom.domUtils.un(document.body,"click",function(evt){
+ * //evt为事件对象,this为被点击元素对象
+ * });
+ * ```
+ */
+
+ /**
+ * 解除DOM事件绑定
+ * @method un
+ * @param { Node } element 需要解除事件绑定的节点对象
+ * @param { Array } type 需要接触绑定的事件类型数组
+ * @param { Function } handler 对应的事件处理器
+ * @example
+ * ```javascript
+ * UE.dom.domUtils.un(document.body, ["click","mousedown"],function(evt){
+ * //evt为事件对象,this为被点击元素对象
+ * });
+ * ```
+ */
+ un:function (element, type, handler) {
+ var types = utils.isArray(type) ? type : utils.trim(type).split(/\s+/),
+ k = types.length;
+ if (k) while (k--) {
+ type = types[k];
+ if (element.removeEventListener) {
+ element.removeEventListener(type, handler, false);
+ } else {
+ var key = type + handler.toString();
+ try{
+ element.detachEvent('on' + type, handler._d ? handler._d[key] : handler);
+ }catch(e){}
+ if (handler._d && handler._d[key]) {
+ var index = utils.indexOf(handler._d.els,element);
+ if(index!=-1){
+ handler._d.els.splice(index,1);
+ }
+ handler._d.els.length == 0 && delete handler._d[key];
+ }
+ }
+ }
+ },
+
+ /**
+ * 比较节点nodeA与节点nodeB是否具有相同的标签名、属性名以及属性值
+ * @method isSameElement
+ * @param { Node } nodeA 需要比较的节点
+ * @param { Node } nodeB 需要比较的节点
+ * @return { Boolean } 两个节点是否具有相同的标签名、属性名以及属性值
+ * @example
+ * ```html
+ * ssss
+ * bbbbb
+ * ssss
+ * bbbbb
+ *
+ *
+ * ```
+ */
+ isSameElement:function (nodeA, nodeB) {
+ if (nodeA.tagName != nodeB.tagName) {
+ return false;
+ }
+ var thisAttrs = nodeA.attributes,
+ otherAttrs = nodeB.attributes;
+ if (!ie && thisAttrs.length != otherAttrs.length) {
+ return false;
+ }
+ var attrA, attrB, al = 0, bl = 0;
+ for (var i = 0; attrA = thisAttrs[i++];) {
+ if (attrA.nodeName == 'style') {
+ if (attrA.specified) {
+ al++;
+ }
+ if (domUtils.isSameStyle(nodeA, nodeB)) {
+ continue;
+ } else {
+ return false;
+ }
+ }
+ if (ie) {
+ if (attrA.specified) {
+ al++;
+ attrB = otherAttrs.getNamedItem(attrA.nodeName);
+ } else {
+ continue;
+ }
+ } else {
+ attrB = nodeB.attributes[attrA.nodeName];
+ }
+ if (!attrB.specified || attrA.nodeValue != attrB.nodeValue) {
+ return false;
+ }
+ }
+ // 有可能attrB的属性包含了attrA的属性之外还有自己的属性
+ if (ie) {
+ for (i = 0; attrB = otherAttrs[i++];) {
+ if (attrB.specified) {
+ bl++;
+ }
+ }
+ if (al != bl) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ /**
+ * 判断节点nodeA与节点nodeB的元素的style属性是否一致
+ * @method isSameStyle
+ * @param { Node } nodeA 需要比较的节点
+ * @param { Node } nodeB 需要比较的节点
+ * @return { Boolean } 两个节点是否具有相同的style属性值
+ * @example
+ * ```html
+ * ssss
+ * bbbbb
+ * ssss
+ * bbbbb
+ *
+ *
+ * ```
+ */
+ isSameStyle:function (nodeA, nodeB) {
+ var styleA = nodeA.style.cssText.replace(/( ?; ?)/g, ';').replace(/( ?: ?)/g, ':'),
+ styleB = nodeB.style.cssText.replace(/( ?; ?)/g, ';').replace(/( ?: ?)/g, ':');
+ if (browser.opera) {
+ styleA = nodeA.style;
+ styleB = nodeB.style;
+ if (styleA.length != styleB.length)
+ return false;
+ for (var p in styleA) {
+ if (/^(\d+|csstext)$/i.test(p)) {
+ continue;
+ }
+ if (styleA[p] != styleB[p]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ if (!styleA || !styleB) {
+ return styleA == styleB;
+ }
+ styleA = styleA.split(';');
+ styleB = styleB.split(';');
+ if (styleA.length != styleB.length) {
+ return false;
+ }
+ for (var i = 0, ci; ci = styleA[i++];) {
+ if (utils.indexOf(styleB, ci) == -1) {
+ return false;
+ }
+ }
+ return true;
+ },
+ /**
+ * 检查节点node是否为block元素
+ * @method isBlockElm
+ * @param { Node } node 需要检测的节点对象
+ * @return { Boolean } 是否是block元素节点
+ * @warning 该方法的判断规则如下: 如果该元素原本是block元素, 则不论该元素当前的css样式是什么都会返回true;
+ * 否则,检测该元素的css样式, 如果该元素当前是block元素, 则返回true。 其余情况下都返回false。
+ * @example
+ * ```html
+ *
+ *
+ *
+ *
+ *
+ * ```
+ */
+ isBlockElm:function (node) {
+ return node.nodeType == 1 && (dtd.$block[node.tagName] || styleBlock[domUtils.getComputedStyle(node, 'display')]) && !dtd.$nonChild[node.tagName];
+ },
+ /**
+ * 检测node节点是否为body节点
+ * @method isBody
+ * @param { Element } node 需要检测的dom元素
+ * @return { Boolean } 给定的元素是否是body元素
+ * @example
+ * ```javascript
+ * //output: true
+ * console.log( UE.dom.domUtils.isBody( document.body ) );
+ * ```
+ */
+ isBody:function (node) {
+ return node && node.nodeType == 1 && node.tagName.toLowerCase() == 'body';
+ },
+ /**
+ * 以node节点为分界,将该节点的指定祖先节点parent拆分成两个独立的节点,
+ * 拆分形成的两个节点之间是node节点
+ * @method breakParent
+ * @param { Node } node 作为分界的节点对象
+ * @param { Node } parent 该节点必须是node节点的祖先节点, 且是block节点。
+ * @return { Node } 给定的node分界节点
+ * @example
+ * ```javascript
+ *
+ * var node = document.createElement("span"),
+ * wrapNode = document.createElement( "div" ),
+ * parent = document.createElement("p");
+ *
+ * parent.appendChild( node );
+ * wrapNode.appendChild( parent );
+ *
+ * //拆分前
+ * //output:
+ * console.log( wrapNode.innerHTML );
+ *
+ *
+ * UE.dom.domUtils.breakParent( node, parent );
+ * //拆分后
+ * //output:
+ * console.log( wrapNode.innerHTML );
+ *
+ * ```
+ */
+ breakParent:function (node, parent) {
+ var tmpNode,
+ parentClone = node,
+ clone = node,
+ leftNodes,
+ rightNodes;
+ do {
+ parentClone = parentClone.parentNode;
+ if (leftNodes) {
+ tmpNode = parentClone.cloneNode(false);
+ tmpNode.appendChild(leftNodes);
+ leftNodes = tmpNode;
+ tmpNode = parentClone.cloneNode(false);
+ tmpNode.appendChild(rightNodes);
+ rightNodes = tmpNode;
+ } else {
+ leftNodes = parentClone.cloneNode(false);
+ rightNodes = leftNodes.cloneNode(false);
+ }
+ while (tmpNode = clone.previousSibling) {
+ leftNodes.insertBefore(tmpNode, leftNodes.firstChild);
+ }
+ while (tmpNode = clone.nextSibling) {
+ rightNodes.appendChild(tmpNode);
+ }
+ clone = parentClone;
+ } while (parent !== parentClone);
+ tmpNode = parent.parentNode;
+ tmpNode.insertBefore(leftNodes, parent);
+ tmpNode.insertBefore(rightNodes, parent);
+ tmpNode.insertBefore(node, rightNodes);
+ domUtils.remove(parent);
+ return node;
+ },
+ /**
+ * 检查节点node是否是空inline节点
+ * @method isEmptyInlineElement
+ * @param { Node } node 需要检测的节点对象
+ * @return { Number } 如果给定的节点是空的inline节点, 则返回1, 否则返回0。
+ * @example
+ * ```html
+ * => 1
+ * => 1
+ * => 1
+ * xx => 0
+ * ```
+ */
+ isEmptyInlineElement:function (node) {
+ if (node.nodeType != 1 || !dtd.$removeEmpty[ node.tagName ]) {
+ return 0;
+ }
+ node = node.firstChild;
+ while (node) {
+ //如果是创建的bookmark就跳过
+ if (domUtils.isBookmarkNode(node)) {
+ return 0;
+ }
+ if (node.nodeType == 1 && !domUtils.isEmptyInlineElement(node) ||
+ node.nodeType == 3 && !domUtils.isWhitespace(node)
+ ) {
+ return 0;
+ }
+ node = node.nextSibling;
+ }
+ return 1;
+
+ },
+
+ /**
+ * 删除node节点下首尾两端的空白文本子节点
+ * @method trimWhiteTextNode
+ * @param { Element } node 需要执行删除操作的元素对象
+ * @example
+ * ```javascript
+ * var node = document.createElement("div");
+ *
+ * node.appendChild( document.createTextNode( "" ) );
+ *
+ * node.appendChild( document.createElement("div") );
+ *
+ * node.appendChild( document.createTextNode( "" ) );
+ *
+ * //3
+ * console.log( node.childNodes.length );
+ *
+ * UE.dom.domUtils.trimWhiteTextNode( node );
+ *
+ * //1
+ * console.log( node.childNodes.length );
+ * ```
+ */
+ trimWhiteTextNode:function (node) {
+ function remove(dir) {
+ var child;
+ while ((child = node[dir]) && child.nodeType == 3 && domUtils.isWhitespace(child)) {
+ node.removeChild(child);
+ }
+ }
+ remove('firstChild');
+ remove('lastChild');
+ },
+
+ /**
+ * 合并node节点下相同的子节点
+ * @name mergeChild
+ * @desc
+ * UE.dom.domUtils.mergeChild(node,tagName) //tagName要合并的子节点的标签
+ * @example
+ * xxaaxx
+ * ==> UE.dom.domUtils.mergeChild(node,'span')
+ * xxaaxx
+ */
+ mergeChild:function (node, tagName, attrs) {
+ var list = domUtils.getElementsByTagName(node, node.tagName.toLowerCase());
+ for (var i = 0, ci; ci = list[i++];) {
+ if (!ci.parentNode || domUtils.isBookmarkNode(ci)) {
+ continue;
+ }
+ //span单独处理
+ if (ci.tagName.toLowerCase() == 'span') {
+ if (node === ci.parentNode) {
+ domUtils.trimWhiteTextNode(node);
+ if (node.childNodes.length == 1) {
+ node.style.cssText = ci.style.cssText + ";" + node.style.cssText;
+ domUtils.remove(ci, true);
+ continue;
+ }
+ }
+ ci.style.cssText = node.style.cssText + ';' + ci.style.cssText;
+ if (attrs) {
+ var style = attrs.style;
+ if (style) {
+ style = style.split(';');
+ for (var j = 0, s; s = style[j++];) {
+ ci.style[utils.cssStyleToDomStyle(s.split(':')[0])] = s.split(':')[1];
+ }
+ }
+ }
+ if (domUtils.isSameStyle(ci, node)) {
+ domUtils.remove(ci, true);
+ }
+ continue;
+ }
+ if (domUtils.isSameElement(node, ci)) {
+ domUtils.remove(ci, true);
+ }
+ }
+ },
+
+ /**
+ * 原生方法getElementsByTagName的封装
+ * @method getElementsByTagName
+ * @param { Node } node 目标节点对象
+ * @param { String } tagName 需要查找的节点的tagName, 多个tagName以空格分割
+ * @return { Array } 符合条件的节点集合
+ */
+ getElementsByTagName:function (node, name,filter) {
+ if(filter && utils.isString(filter)){
+ var className = filter;
+ filter = function(node){return domUtils.hasClass(node,className)}
+ }
+ name = utils.trim(name).replace(/[ ]{2,}/g,' ').split(' ');
+ var arr = [];
+ for(var n = 0,ni;ni=name[n++];){
+ var list = node.getElementsByTagName(ni);
+ for (var i = 0, ci; ci = list[i++];) {
+ if(!filter || filter(ci))
+ arr.push(ci);
+ }
+ }
+
+ return arr;
+ },
+ /**
+ * 将节点node提取到父节点上
+ * @method mergeToParent
+ * @param { Element } node 需要提取的元素对象
+ * @example
+ * ```html
+ *
+ *
+ *
+ * ```
+ */
+ mergeToParent:function (node) {
+ var parent = node.parentNode;
+ while (parent && dtd.$removeEmpty[parent.tagName]) {
+ if (parent.tagName == node.tagName || parent.tagName == 'A') {//针对a标签单独处理
+ domUtils.trimWhiteTextNode(parent);
+ //span需要特殊处理 不处理这样的情况 xxxxxxxxx
+ if (parent.tagName == 'SPAN' && !domUtils.isSameStyle(parent, node)
+ || (parent.tagName == 'A' && node.tagName == 'SPAN')) {
+ if (parent.childNodes.length > 1 || parent !== node.parentNode) {
+ node.style.cssText = parent.style.cssText + ";" + node.style.cssText;
+ parent = parent.parentNode;
+ continue;
+ } else {
+ parent.style.cssText += ";" + node.style.cssText;
+ //trace:952 a标签要保持下划线
+ if (parent.tagName == 'A') {
+ parent.style.textDecoration = 'underline';
+ }
+ }
+ }
+ if (parent.tagName != 'A') {
+ parent === node.parentNode && domUtils.remove(node, true);
+ break;
+ }
+ }
+ parent = parent.parentNode;
+ }
+ },
+ /**
+ * 合并节点node的左右兄弟节点
+ * @method mergeSibling
+ * @param { Element } node 需要合并的目标节点
+ * @example
+ * ```html
+ * xxxxoooxxxx
+ *
+ *
+ * ```
+ */
+
+ /**
+ * 合并节点node的左右兄弟节点, 可以根据给定的条件选择是否忽略合并左节点。
+ * @method mergeSibling
+ * @param { Element } node 需要合并的目标节点
+ * @param { Boolean } ignorePre 是否忽略合并左节点
+ * @example
+ * ```html
+ * xxxxoooxxxx
+ *
+ *
+ * ```
+ */
+
+ /**
+ * 合并节点node的左右兄弟节点,可以根据给定的条件选择是否忽略合并左右节点。
+ * @method mergeSibling
+ * @param { Element } node 需要合并的目标节点
+ * @param { Boolean } ignorePre 是否忽略合并左节点
+ * @param { Boolean } ignoreNext 是否忽略合并右节点
+ * @remind 如果同时忽略左右节点, 则该操作什么也不会做
+ * @example
+ * ```html
+ * xxxxoooxxxx
+ *
+ *
+ * ```
+ */
+ mergeSibling:function (node, ignorePre, ignoreNext) {
+ function merge(rtl, start, node) {
+ var next;
+ if ((next = node[rtl]) && !domUtils.isBookmarkNode(next) && next.nodeType == 1 && domUtils.isSameElement(node, next)) {
+ while (next.firstChild) {
+ if (start == 'firstChild') {
+ node.insertBefore(next.lastChild, node.firstChild);
+ } else {
+ node.appendChild(next.firstChild);
+ }
+ }
+ domUtils.remove(next);
+ }
+ }
+ !ignorePre && merge('previousSibling', 'firstChild', node);
+ !ignoreNext && merge('nextSibling', 'lastChild', node);
+ },
+
+ /**
+ * 设置节点node及其子节点不会被选中
+ * @method unSelectable
+ * @param { Element } node 需要执行操作的dom元素
+ * @remind 执行该操作后的节点, 将不能被鼠标选中
+ * @example
+ * ```javascript
+ * UE.dom.domUtils.unSelectable( document.body );
+ * ```
+ */
+ unSelectable:ie && browser.ie9below || browser.opera ? function (node) {
+ //for ie9
+ node.onselectstart = function () {
+ return false;
+ };
+ node.onclick = node.onkeyup = node.onkeydown = function () {
+ return false;
+ };
+ node.unselectable = 'on';
+ node.setAttribute("unselectable", "on");
+ for (var i = 0, ci; ci = node.all[i++];) {
+ switch (ci.tagName.toLowerCase()) {
+ case 'iframe' :
+ case 'textarea' :
+ case 'input' :
+ case 'select' :
+ break;
+ default :
+ ci.unselectable = 'on';
+ node.setAttribute("unselectable", "on");
+ }
+ }
+ } : function (node) {
+ node.style.MozUserSelect =
+ node.style.webkitUserSelect =
+ node.style.msUserSelect =
+ node.style.KhtmlUserSelect = 'none';
+ },
+ /**
+ * 删除节点node上的指定属性名称的属性
+ * @method removeAttributes
+ * @param { Node } node 需要删除属性的节点对象
+ * @param { String } attrNames 可以是空格隔开的多个属性名称,该操作将会依次删除相应的属性
+ * @example
+ * ```html
+ *
+ * xxxxx
+ *
+ *
+ *
+ * ```
+ */
+
+ /**
+ * 删除节点node上的指定属性名称的属性
+ * @method removeAttributes
+ * @param { Node } node 需要删除属性的节点对象
+ * @param { Array } attrNames 需要删除的属性名数组
+ * @example
+ * ```html
+ *
+ * xxxxx
+ *
+ *
+ *
+ * ```
+ */
+ removeAttributes:function (node, attrNames) {
+ attrNames = utils.isArray(attrNames) ? attrNames : utils.trim(attrNames).replace(/[ ]{2,}/g,' ').split(' ');
+ for (var i = 0, ci; ci = attrNames[i++];) {
+ ci = attrFix[ci] || ci;
+ switch (ci) {
+ case 'className':
+ node[ci] = '';
+ break;
+ case 'style':
+ node.style.cssText = '';
+ var val = node.getAttributeNode('style');
+ !browser.ie && val && node.removeAttributeNode(val);
+ }
+ node.removeAttribute(ci);
+ }
+ },
+ /**
+ * 在doc下创建一个标签名为tag,属性为attrs的元素
+ * @method createElement
+ * @param { DomDocument } doc 新创建的元素属于该document节点创建
+ * @param { String } tagName 需要创建的元素的标签名
+ * @param { Object } attrs 新创建的元素的属性key-value集合
+ * @return { Element } 新创建的元素对象
+ * @example
+ * ```javascript
+ * var ele = UE.dom.domUtils.createElement( document, 'div', {
+ * id: 'test'
+ * } );
+ *
+ * //output: DIV
+ * console.log( ele.tagName );
+ *
+ * //output: test
+ * console.log( ele.id );
+ *
+ * ```
+ */
+ createElement:function (doc, tag, attrs) {
+ return domUtils.setAttributes(doc.createElement(tag), attrs)
+ },
+ /**
+ * 为节点node添加属性attrs,attrs为属性键值对
+ * @method setAttributes
+ * @param { Element } node 需要设置属性的元素对象
+ * @param { Object } attrs 需要设置的属性名-值对
+ * @return { Element } 设置属性的元素对象
+ * @example
+ * ```html
+ *
+ *
+ *
+ *
+ */
+ setAttributes:function (node, attrs) {
+ for (var attr in attrs) {
+ if(attrs.hasOwnProperty(attr)){
+ var value = attrs[attr];
+ switch (attr) {
+ case 'class':
+ //ie下要这样赋值,setAttribute不起作用
+ node.className = value;
+ break;
+ case 'style' :
+ node.style.cssText = node.style.cssText + ";" + value;
+ break;
+ case 'innerHTML':
+ node[attr] = value;
+ break;
+ case 'value':
+ node.value = value;
+ break;
+ default:
+ node.setAttribute(attrFix[attr] || attr, value);
+ }
+ }
+ }
+ return node;
+ },
+
+ /**
+ * 获取元素element经过计算后的样式值
+ * @method getComputedStyle
+ * @param { Element } element 需要获取样式的元素对象
+ * @param { String } styleName 需要获取的样式名
+ * @return { String } 获取到的样式值
+ * @example
+ * ```html
+ *
+ *
+ *
+ *
+ *
+ * ```
+ */
+ getComputedStyle:function (element, styleName) {
+ //一下的属性单独处理
+ var pros = 'width height top left';
+
+ if(pros.indexOf(styleName) > -1){
+ return element['offset' + styleName.replace(/^\w/,function(s){return s.toUpperCase()})] + 'px';
+ }
+ //忽略文本节点
+ if (element.nodeType == 3) {
+ element = element.parentNode;
+ }
+ //ie下font-size若body下定义了font-size,则从currentStyle里会取到这个font-size. 取不到实际值,故此修改.
+ if (browser.ie && browser.version < 9 && styleName == 'font-size' && !element.style.fontSize &&
+ !dtd.$empty[element.tagName] && !dtd.$nonChild[element.tagName]) {
+ var span = element.ownerDocument.createElement('span');
+ span.style.cssText = 'padding:0;border:0;font-family:simsun;';
+ span.innerHTML = '.';
+ element.appendChild(span);
+ var result = span.offsetHeight;
+ element.removeChild(span);
+ span = null;
+ return result + 'px';
+ }
+ try {
+ var value = domUtils.getStyle(element, styleName) ||
+ (window.getComputedStyle ? domUtils.getWindow(element).getComputedStyle(element, '').getPropertyValue(styleName) :
+ ( element.currentStyle || element.style )[utils.cssStyleToDomStyle(styleName)]);
+
+ } catch (e) {
+ return "";
+ }
+ return utils.transUnitToPx(utils.fixColor(styleName, value));
+ },
+ /**
+ * 删除元素element指定的className
+ * @method removeClasses
+ * @param { Element } ele 需要删除class的元素节点
+ * @param { String } classNames 需要删除的className, 多个className之间以空格分开
+ * @example
+ * ```html
+ * xxx
+ *
+ *
+ * ```
+ */
+
+ /**
+ * 删除元素element指定的className
+ * @method removeClasses
+ * @param { Element } ele 需要删除class的元素节点
+ * @param { Array } classNames 需要删除的className数组
+ * @example
+ * ```html
+ * xxx
+ *
+ *
+ * ```
+ */
+ removeClasses:function (elm, classNames) {
+ classNames = utils.isArray(classNames) ? classNames :
+ utils.trim(classNames).replace(/[ ]{2,}/g,' ').split(' ');
+ for(var i = 0,ci,cls = elm.className;ci=classNames[i++];){
+ cls = cls.replace(new RegExp('\\b' + ci + '\\b'),'')
+ }
+ cls = utils.trim(cls).replace(/[ ]{2,}/g,' ');
+ if(cls){
+ elm.className = cls;
+ }else{
+ domUtils.removeAttributes(elm,['class']);
+ }
+ },
+ /**
+ * 给元素element添加className
+ * @method addClass
+ * @param { Node } ele 需要增加className的元素
+ * @param { String } classNames 需要添加的className, 多个className之间以空格分割
+ * @remind 相同的类名不会被重复添加
+ * @example
+ * ```html
+ *
+ *
+ *
+ * ```
+ */
+
+ /**
+ * 判断元素element是否包含给定的样式类名className
+ * @method hasClass
+ * @param { Node } ele 需要检测的元素
+ * @param { Array } classNames 需要检测的className数组
+ * @return { Boolean } 元素是否包含所有给定的className
+ * @example
+ * ```html
+ *
+ *
+ *
+ * ```
+ */
+ hasClass:function (element, className) {
+ if(utils.isRegExp(className)){
+ return className.test(element.className)
+ }
+ className = utils.trim(className).replace(/[ ]{2,}/g,' ').split(' ');
+ for(var i = 0,ci,cls = element.className;ci=className[i++];){
+ if(!new RegExp('\\b' + ci + '\\b','i').test(cls)){
+ return false;
+ }
+ }
+ return i - 1 == className.length;
+ },
+
+ /**
+ * 阻止事件默认行为
+ * @method preventDefault
+ * @param { Event } evt 需要阻止默认行为的事件对象
+ * @example
+ * ```javascript
+ * UE.dom.domUtils.preventDefault( evt );
+ * ```
+ */
+ preventDefault:function (evt) {
+ evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);
+ },
+ /**
+ * 删除元素element指定的样式
+ * @method removeStyle
+ * @param { Element } element 需要删除样式的元素
+ * @param { String } styleName 需要删除的样式名
+ * @example
+ * ```html
+ *
+ *
+ *
+ * ```
+ */
+ removeStyle:function (element, name) {
+ if(browser.ie ){
+ //针对color先单独处理一下
+ if(name == 'color'){
+ name = '(^|;)' + name;
+ }
+ element.style.cssText = element.style.cssText.replace(new RegExp(name + '[^:]*:[^;]+;?','ig'),'')
+ }else{
+ if (element.style.removeProperty) {
+ element.style.removeProperty (name);
+ }else {
+ element.style.removeAttribute (utils.cssStyleToDomStyle(name));
+ }
+ }
+
+
+ if (!element.style.cssText) {
+ domUtils.removeAttributes(element, ['style']);
+ }
+ },
+ /**
+ * 获取元素element的style属性的指定值
+ * @method getStyle
+ * @param { Element } element 需要获取属性值的元素
+ * @param { String } styleName 需要获取的style的名称
+ * @warning 该方法仅获取元素style属性中所标明的值
+ * @return { String } 该元素包含指定的style属性值
+ * @example
+ * ```html
+ *
+ *
+ *
+ * ```
+ */
+ getStyle:function (element, name) {
+ var value = element.style[ utils.cssStyleToDomStyle(name) ];
+ return utils.fixColor(name, value);
+ },
+ /**
+ * 为元素element设置样式属性值
+ * @method setStyle
+ * @param { Element } element 需要设置样式的元素
+ * @param { String } styleName 样式名
+ * @param { String } styleValue 样式值
+ * @example
+ * ```html
+ *
+ *
+ *
+ * ```
+ */
+ setStyle:function (element, name, value) {
+ element.style[utils.cssStyleToDomStyle(name)] = value;
+ if(!utils.trim(element.style.cssText)){
+ this.removeAttributes(element,'style')
+ }
+ },
+ /**
+ * 为元素element设置多个样式属性值
+ * @method setStyles
+ * @param { Element } element 需要设置样式的元素
+ * @param { Object } styles 样式名值对
+ * @example
+ * ```html
+ *
+ *
+ *
+ * ```
+ */
+ setStyles:function (element, styles) {
+ for (var name in styles) {
+ if (styles.hasOwnProperty(name)) {
+ domUtils.setStyle(element, name, styles[name]);
+ }
+ }
+ },
+ /**
+ * 删除_moz_dirty属性
+ * @private
+ * @method removeDirtyAttr
+ */
+ removeDirtyAttr:function (node) {
+ for (var i = 0, ci, nodes = node.getElementsByTagName('*'); ci = nodes[i++];) {
+ ci.removeAttribute('_moz_dirty');
+ }
+ node.removeAttribute('_moz_dirty');
+ },
+ /**
+ * 获取子节点的数量
+ * @method getChildCount
+ * @param { Element } node 需要检测的元素
+ * @return { Number } 给定的node元素的子节点数量
+ * @example
+ * ```html
+ *
+ *
+ *
+ *
+ *
+ * ```
+ */
+
+ /**
+ * 根据给定的过滤规则, 获取符合条件的子节点的数量
+ * @method getChildCount
+ * @param { Element } node 需要检测的元素
+ * @param { Function } fn 过滤器, 要求对符合条件的子节点返回true, 反之则要求返回false
+ * @return { Number } 符合过滤条件的node元素的子节点数量
+ * @example
+ * ```html
+ *
+ *
+ *
+ *
+ *
+ * ```
+ */
+ getChildCount:function (node, fn) {
+ var count = 0, first = node.firstChild;
+ fn = fn || function () {
+ return 1;
+ };
+ while (first) {
+ if (fn(first)) {
+ count++;
+ }
+ first = first.nextSibling;
+ }
+ return count;
+ },
+
+ /**
+ * 判断给定节点是否为空节点
+ * @method isEmptyNode
+ * @param { Node } node 需要检测的节点对象
+ * @return { Boolean } 节点是否为空
+ * @example
+ * ```javascript
+ * UE.dom.domUtils.isEmptyNode( document.body );
+ * ```
+ */
+ isEmptyNode:function (node) {
+ return !node.firstChild || domUtils.getChildCount(node, function (node) {
+ return !domUtils.isBr(node) && !domUtils.isBookmarkNode(node) && !domUtils.isWhitespace(node)
+ }) == 0
+ },
+ clearSelectedArr:function (nodes) {
+ var node;
+ while (node = nodes.pop()) {
+ domUtils.removeAttributes(node, ['class']);
+ }
+ },
+ /**
+ * 将显示区域滚动到指定节点的位置
+ * @method scrollToView
+ * @param {Node} node 节点
+ * @param {window} win window对象
+ * @param {Number} offsetTop 距离上方的偏移量
+ */
+ scrollToView:function (node, win, offsetTop) {
+ var getViewPaneSize = function () {
+ var doc = win.document,
+ mode = doc.compatMode == 'CSS1Compat';
+ return {
+ width:( mode ? doc.documentElement.clientWidth : doc.body.clientWidth ) || 0,
+ height:( mode ? doc.documentElement.clientHeight : doc.body.clientHeight ) || 0
+ };
+ },
+ getScrollPosition = function (win) {
+ if ('pageXOffset' in win) {
+ return {
+ x:win.pageXOffset || 0,
+ y:win.pageYOffset || 0
+ };
+ }
+ else {
+ var doc = win.document;
+ return {
+ x:doc.documentElement.scrollLeft || doc.body.scrollLeft || 0,
+ y:doc.documentElement.scrollTop || doc.body.scrollTop || 0
+ };
+ }
+ };
+ var winHeight = getViewPaneSize().height, offset = winHeight * -1 + offsetTop;
+ offset += (node.offsetHeight || 0);
+ var elementPosition = domUtils.getXY(node);
+ offset += elementPosition.y;
+ var currentScroll = getScrollPosition(win).y;
+ // offset += 50;
+ if (offset > currentScroll || offset < currentScroll - winHeight) {
+ win.scrollTo(0, offset + (offset < 0 ? -20 : 20));
+ }
+ },
+ /**
+ * 判断给定节点是否为br
+ * @method isBr
+ * @param { Node } node 需要判断的节点对象
+ * @return { Boolean } 给定的节点是否是br节点
+ */
+ isBr:function (node) {
+ return node.nodeType == 1 && node.tagName == 'BR';
+ },
+ /**
+ * 判断给定的节点是否是一个“填充”节点
+ * @private
+ * @method isFillChar
+ * @param { Node } node 需要判断的节点
+ * @param { Boolean } isInStart 是否从节点内容的开始位置匹配
+ * @returns { Boolean } 节点是否是填充节点
+ */
+ isFillChar:function (node,isInStart) {
+ if(node.nodeType != 3)
+ return false;
+ var text = node.nodeValue;
+ if(isInStart){
+ return new RegExp('^' + domUtils.fillChar).test(text)
+ }
+ return !text.replace(new RegExp(domUtils.fillChar,'g'), '').length
+ },
+ isStartInblock:function (range) {
+ var tmpRange = range.cloneRange(),
+ flag = 0,
+ start = tmpRange.startContainer,
+ tmp;
+ if(start.nodeType == 1 && start.childNodes[tmpRange.startOffset]){
+ start = start.childNodes[tmpRange.startOffset];
+ var pre = start.previousSibling;
+ while(pre && domUtils.isFillChar(pre)){
+ start = pre;
+ pre = pre.previousSibling;
+ }
+ }
+ if(this.isFillChar(start,true) && tmpRange.startOffset == 1){
+ tmpRange.setStartBefore(start);
+ start = tmpRange.startContainer;
+ }
+
+ while (start && domUtils.isFillChar(start)) {
+ tmp = start;
+ start = start.previousSibling
+ }
+ if (tmp) {
+ tmpRange.setStartBefore(tmp);
+ start = tmpRange.startContainer;
+ }
+ if (start.nodeType == 1 && domUtils.isEmptyNode(start) && tmpRange.startOffset == 1) {
+ tmpRange.setStart(start, 0).collapse(true);
+ }
+ while (!tmpRange.startOffset) {
+ start = tmpRange.startContainer;
+ if (domUtils.isBlockElm(start) || domUtils.isBody(start)) {
+ flag = 1;
+ break;
+ }
+ var pre = tmpRange.startContainer.previousSibling,
+ tmpNode;
+ if (!pre) {
+ tmpRange.setStartBefore(tmpRange.startContainer);
+ } else {
+ while (pre && domUtils.isFillChar(pre)) {
+ tmpNode = pre;
+ pre = pre.previousSibling;
+ }
+ if (tmpNode) {
+ tmpRange.setStartBefore(tmpNode);
+ } else {
+ tmpRange.setStartBefore(tmpRange.startContainer);
+ }
+ }
+ }
+ return flag && !domUtils.isBody(tmpRange.startContainer) ? 1 : 0;
+ },
+
+ /**
+ * 判断给定的元素是否是一个空元素
+ * @method isEmptyBlock
+ * @param { Element } node 需要判断的元素
+ * @return { Boolean } 是否是空元素
+ * @example
+ * ```html
+ *
+ *
+ *
+ * ```
+ */
+
+ /**
+ * 根据指定的判断规则判断给定的元素是否是一个空元素
+ * @method isEmptyBlock
+ * @param { Element } node 需要判断的元素
+ * @param { RegExp } reg 对内容执行判断的正则表达式对象
+ * @return { Boolean } 是否是空元素
+ */
+ isEmptyBlock:function (node,reg) {
+ if(node.nodeType != 1)
+ return 0;
+ reg = reg || new RegExp('[ \xa0\t\r\n' + domUtils.fillChar + ']', 'g');
+
+ if (node[browser.ie ? 'innerText' : 'textContent'].replace(reg, '').length > 0) {
+ return 0;
+ }
+ for (var n in dtd.$isNotEmpty) {
+ if (node.getElementsByTagName(n).length) {
+ return 0;
+ }
+ }
+ return 1;
+ },
+
+ /**
+ * 移动元素使得该元素的位置移动指定的偏移量的距离
+ * @method setViewportOffset
+ * @param { Element } element 需要设置偏移量的元素
+ * @param { Object } offset 偏移量, 形如{ left: 100, top: 50 }的一个键值对, 表示该元素将在
+ * 现有的位置上向水平方向偏移offset.left的距离, 在竖直方向上偏移
+ * offset.top的距离
+ * @example
+ * ```html
+ *
+ *
+ *
+ * ```
+ */
+ setViewportOffset:function (element, offset) {
+ var left = parseInt(element.style.left) | 0;
+ var top = parseInt(element.style.top) | 0;
+ var rect = element.getBoundingClientRect();
+ var offsetLeft = offset.left - rect.left;
+ var offsetTop = offset.top - rect.top;
+ if (offsetLeft) {
+ element.style.left = left + offsetLeft + 'px';
+ }
+ if (offsetTop) {
+ element.style.top = top + offsetTop + 'px';
+ }
+ },
+
+ /**
+ * 用“填充字符”填充节点
+ * @method fillNode
+ * @private
+ * @param { DomDocument } doc 填充的节点所在的docment对象
+ * @param { Node } node 需要填充的节点对象
+ * @example
+ * ```html
+ *
+ *
+ *
+ * ```
+ */
+ fillNode:function (doc, node) {
+ var tmpNode = browser.ie ? doc.createTextNode(domUtils.fillChar) : doc.createElement('br');
+ node.innerHTML = '';
+ node.appendChild(tmpNode);
+ },
+
+ /**
+ * 把节点src的所有子节点追加到另一个节点tag上去
+ * @method moveChild
+ * @param { Node } src 源节点, 该节点下的所有子节点将被移除
+ * @param { Node } tag 目标节点, 从源节点移除的子节点将被追加到该节点下
+ * @example
+ * ```html
+ *
+ *
+ *
+ *
+ *
+ *
+ * ```
+ */
+
+ /**
+ * 把节点src的所有子节点移动到另一个节点tag上去, 可以通过dir参数控制附加的行为是“追加”还是“插入顶部”
+ * @method moveChild
+ * @param { Node } src 源节点, 该节点下的所有子节点将被移除
+ * @param { Node } tag 目标节点, 从源节点移除的子节点将被附加到该节点下
+ * @param { Boolean } dir 附加方式, 如果为true, 则附加进去的节点将被放到目标节点的顶部, 反之,则放到末尾
+ * @example
+ * ```html
+ *
+ *
+ *
+ *
+ *
+ *
+ * ```
+ */
+ moveChild:function (src, tag, dir) {
+ while (src.firstChild) {
+ if (dir && tag.firstChild) {
+ tag.insertBefore(src.lastChild, tag.firstChild);
+ } else {
+ tag.appendChild(src.firstChild);
+ }
+ }
+ },
+
+ /**
+ * 判断节点的标签上是否不存在任何属性
+ * @method hasNoAttributes
+ * @private
+ * @param { Node } node 需要检测的节点对象
+ * @return { Boolean } 节点是否不包含任何属性
+ * @example
+ * ```html
+ * xxxx
+ *
+ *
+ * ```
+ */
+ hasNoAttributes:function (node) {
+ return browser.ie ? /^<\w+\s*?>/.test(node.outerHTML) : node.attributes.length == 0;
+ },
+
+ /**
+ * 检测节点是否是UEditor所使用的辅助节点
+ * @method isCustomeNode
+ * @private
+ * @param { Node } node 需要检测的节点
+ * @remind 辅助节点是指编辑器要完成工作临时添加的节点, 在输出的时候将会从编辑器内移除, 不会影响最终的结果。
+ * @return { Boolean } 给定的节点是否是一个辅助节点
+ */
+ isCustomeNode:function (node) {
+ return node.nodeType == 1 && node.getAttribute('_ue_custom_node_');
+ },
+
+ /**
+ * 检测节点的标签是否是给定的标签
+ * @method isTagNode
+ * @param { Node } node 需要检测的节点对象
+ * @param { String } tagName 标签
+ * @return { Boolean } 节点的标签是否是给定的标签
+ * @example
+ * ```html
+ *
+ *
+ *
+ * ```
+ */
+ isTagNode:function (node, tagNames) {
+ return node.nodeType == 1 && new RegExp('\\b' + node.tagName + '\\b','i').test(tagNames)
+ },
+
+ /**
+ * 给定一个节点数组,在通过指定的过滤器过滤后, 获取其中满足过滤条件的第一个节点
+ * @method filterNodeList
+ * @param { Array } nodeList 需要过滤的节点数组
+ * @param { Function } fn 过滤器, 对符合条件的节点, 执行结果返回true, 反之则返回false
+ * @return { Node | NULL } 如果找到符合过滤条件的节点, 则返回该节点, 否则返回NULL
+ * @example
+ * ```javascript
+ * var divNodes = document.getElementsByTagName("div");
+ * divNodes = [].slice.call( divNodes, 0 );
+ *
+ * //output: null
+ * console.log( UE.dom.domUtils.filterNodeList( divNodes, function ( node ) {
+ * return node.tagName.toLowerCase() !== 'div';
+ * } ) );
+ * ```
+ */
+
+ /**
+ * 给定一个节点数组nodeList和一组标签名tagNames, 获取其中能够匹配标签名的节点集合中的第一个节点
+ * @method filterNodeList
+ * @param { Array } nodeList 需要过滤的节点数组
+ * @param { String } tagNames 需要匹配的标签名, 多个标签名之间用空格分割
+ * @return { Node | NULL } 如果找到标签名匹配的节点, 则返回该节点, 否则返回NULL
+ * @example
+ * ```javascript
+ * var divNodes = document.getElementsByTagName("div");
+ * divNodes = [].slice.call( divNodes, 0 );
+ *
+ * //output: null
+ * console.log( UE.dom.domUtils.filterNodeList( divNodes, 'a span' ) );
+ * ```
+ */
+
+ /**
+ * 给定一个节点数组,在通过指定的过滤器过滤后, 如果参数forAll为true, 则会返回所有满足过滤
+ * 条件的节点集合, 否则, 返回满足条件的节点集合中的第一个节点
+ * @method filterNodeList
+ * @param { Array } nodeList 需要过滤的节点数组
+ * @param { Function } fn 过滤器, 对符合条件的节点, 执行结果返回true, 反之则返回false
+ * @param { Boolean } forAll 是否返回整个节点数组, 如果该参数为false, 则返回节点集合中的第一个节点
+ * @return { Array | Node | NULL } 如果找到符合过滤条件的节点, 则根据参数forAll的值决定返回满足
+ * 过滤条件的节点数组或第一个节点, 否则返回NULL
+ * @example
+ * ```javascript
+ * var divNodes = document.getElementsByTagName("div");
+ * divNodes = [].slice.call( divNodes, 0 );
+ *
+ * //output: 3(假定有3个div)
+ * console.log( divNodes.length );
+ *
+ * var nodes = UE.dom.domUtils.filterNodeList( divNodes, function ( node ) {
+ * return node.tagName.toLowerCase() === 'div';
+ * }, true );
+ *
+ * //output: 3
+ * console.log( nodes.length );
+ *
+ * var node = UE.dom.domUtils.filterNodeList( divNodes, function ( node ) {
+ * return node.tagName.toLowerCase() === 'div';
+ * }, false );
+ *
+ * //output: div
+ * console.log( node.nodeName );
+ * ```
+ */
+ filterNodeList : function(nodelist,filter,forAll){
+ var results = [];
+ if(!utils .isFunction(filter)){
+ var str = filter;
+ filter = function(n){
+ return utils.indexOf(utils.isArray(str) ? str:str.split(' '), n.tagName.toLowerCase()) != -1
+ };
+ }
+ utils.each(nodelist,function(n){
+ filter(n) && results.push(n)
+ });
+ return results.length == 0 ? null : results.length == 1 || !forAll ? results[0] : results
+ },
+
+ /**
+ * 查询给定的range选区是否在给定的node节点内,且在该节点的最末尾
+ * @method isInNodeEndBoundary
+ * @param { UE.dom.Range } rng 需要判断的range对象, 该对象的startContainer不能为NULL
+ * @param node 需要检测的节点对象
+ * @return { Number } 如果给定的选取range对象是在node内部的最末端, 则返回1, 否则返回0
+ */
+ isInNodeEndBoundary : function (rng,node){
+ var start = rng.startContainer;
+ if(start.nodeType == 3 && rng.startOffset != start.nodeValue.length){
+ return 0;
+ }
+ if(start.nodeType == 1 && rng.startOffset != start.childNodes.length){
+ return 0;
+ }
+ while(start !== node){
+ if(start.nextSibling){
+ return 0
+ };
+ start = start.parentNode;
+ }
+ return 1;
+ },
+ isBoundaryNode : function (node,dir){
+ var tmp;
+ while(!domUtils.isBody(node)){
+ tmp = node;
+ node = node.parentNode;
+ if(tmp !== node[dir]){
+ return false;
+ }
+ }
+ return true;
+ },
+ fillHtml : browser.ie11below ? ' ' : ' '
+};
+var fillCharReg = new RegExp(domUtils.fillChar, 'g');
+
+// core/Range.js
+/**
+ * Range封装
+ * @file
+ * @module UE.dom
+ * @class Range
+ * @since 1.2.6.1
+ */
+
+/**
+ * dom操作封装
+ * @unfile
+ * @module UE.dom
+ */
+
+/**
+ * Range实现类,本类是UEditor底层核心类,封装不同浏览器之间的Range操作。
+ * @unfile
+ * @module UE.dom
+ * @class Range
+ */
+
+
+(function () {
+ var guid = 0,
+ fillChar = domUtils.fillChar,
+ fillData;
+
+ /**
+ * 更新range的collapse状态
+ * @param {Range} range range对象
+ */
+ function updateCollapse(range) {
+ range.collapsed =
+ range.startContainer && range.endContainer &&
+ range.startContainer === range.endContainer &&
+ range.startOffset == range.endOffset;
+ }
+
+ function selectOneNode(rng){
+ return !rng.collapsed && rng.startContainer.nodeType == 1 && rng.startContainer === rng.endContainer && rng.endOffset - rng.startOffset == 1
+ }
+ function setEndPoint(toStart, node, offset, range) {
+ //如果node是自闭合标签要处理
+ if (node.nodeType == 1 && (dtd.$empty[node.tagName] || dtd.$nonChild[node.tagName])) {
+ offset = domUtils.getNodeIndex(node) + (toStart ? 0 : 1);
+ node = node.parentNode;
+ }
+ if (toStart) {
+ range.startContainer = node;
+ range.startOffset = offset;
+ if (!range.endContainer) {
+ range.collapse(true);
+ }
+ } else {
+ range.endContainer = node;
+ range.endOffset = offset;
+ if (!range.startContainer) {
+ range.collapse(false);
+ }
+ }
+ updateCollapse(range);
+ return range;
+ }
+
+ function execContentsAction(range, action) {
+ //调整边界
+ //range.includeBookmark();
+ var start = range.startContainer,
+ end = range.endContainer,
+ startOffset = range.startOffset,
+ endOffset = range.endOffset,
+ doc = range.document,
+ frag = doc.createDocumentFragment(),
+ tmpStart, tmpEnd;
+ if (start.nodeType == 1) {
+ start = start.childNodes[startOffset] || (tmpStart = start.appendChild(doc.createTextNode('')));
+ }
+ if (end.nodeType == 1) {
+ end = end.childNodes[endOffset] || (tmpEnd = end.appendChild(doc.createTextNode('')));
+ }
+ if (start === end && start.nodeType == 3) {
+ frag.appendChild(doc.createTextNode(start.substringData(startOffset, endOffset - startOffset)));
+ //is not clone
+ if (action) {
+ start.deleteData(startOffset, endOffset - startOffset);
+ range.collapse(true);
+ }
+ return frag;
+ }
+ var current, currentLevel, clone = frag,
+ startParents = domUtils.findParents(start, true), endParents = domUtils.findParents(end, true);
+ for (var i = 0; startParents[i] == endParents[i];) {
+ i++;
+ }
+ for (var j = i, si; si = startParents[j]; j++) {
+ current = si.nextSibling;
+ if (si == start) {
+ if (!tmpStart) {
+ if (range.startContainer.nodeType == 3) {
+ clone.appendChild(doc.createTextNode(start.nodeValue.slice(startOffset)));
+ //is not clone
+ if (action) {
+ start.deleteData(startOffset, start.nodeValue.length - startOffset);
+ }
+ } else {
+ clone.appendChild(!action ? start.cloneNode(true) : start);
+ }
+ }
+ } else {
+ currentLevel = si.cloneNode(false);
+ clone.appendChild(currentLevel);
+ }
+ while (current) {
+ if (current === end || current === endParents[j]) {
+ break;
+ }
+ si = current.nextSibling;
+ clone.appendChild(!action ? current.cloneNode(true) : current);
+ current = si;
+ }
+ clone = currentLevel;
+ }
+ clone = frag;
+ if (!startParents[i]) {
+ clone.appendChild(startParents[i - 1].cloneNode(false));
+ clone = clone.firstChild;
+ }
+ for (var j = i, ei; ei = endParents[j]; j++) {
+ current = ei.previousSibling;
+ if (ei == end) {
+ if (!tmpEnd && range.endContainer.nodeType == 3) {
+ clone.appendChild(doc.createTextNode(end.substringData(0, endOffset)));
+ //is not clone
+ if (action) {
+ end.deleteData(0, endOffset);
+ }
+ }
+ } else {
+ currentLevel = ei.cloneNode(false);
+ clone.appendChild(currentLevel);
+ }
+ //如果两端同级,右边第一次已经被开始做了
+ if (j != i || !startParents[i]) {
+ while (current) {
+ if (current === start) {
+ break;
+ }
+ ei = current.previousSibling;
+ clone.insertBefore(!action ? current.cloneNode(true) : current, clone.firstChild);
+ current = ei;
+ }
+ }
+ clone = currentLevel;
+ }
+ if (action) {
+ range.setStartBefore(!endParents[i] ? endParents[i - 1] : !startParents[i] ? startParents[i - 1] : endParents[i]).collapse(true);
+ }
+ tmpStart && domUtils.remove(tmpStart);
+ tmpEnd && domUtils.remove(tmpEnd);
+ return frag;
+ }
+
+ /**
+ * 创建一个跟document绑定的空的Range实例
+ * @constructor
+ * @param { Document } document 新建的选区所属的文档对象
+ */
+
+ /**
+ * @property { Node } startContainer 当前Range的开始边界的容器节点, 可以是一个元素节点或者是文本节点
+ */
+
+ /**
+ * @property { Node } startOffset 当前Range的开始边界容器节点的偏移量, 如果是元素节点,
+ * 该值就是childNodes中的第几个节点, 如果是文本节点就是文本内容的第几个字符
+ */
+
+ /**
+ * @property { Node } endContainer 当前Range的结束边界的容器节点, 可以是一个元素节点或者是文本节点
+ */
+
+ /**
+ * @property { Node } endOffset 当前Range的结束边界容器节点的偏移量, 如果是元素节点,
+ * 该值就是childNodes中的第几个节点, 如果是文本节点就是文本内容的第几个字符
+ */
+
+ /**
+ * @property { Boolean } collapsed 当前Range是否闭合
+ * @default true
+ * @remind Range是闭合的时候, startContainer === endContainer && startOffset === endOffset
+ */
+
+ /**
+ * @property { Document } document 当前Range所属的Document对象
+ * @remind 不同range的的document属性可以是不同的
+ */
+ var Range = dom.Range = function (document) {
+ var me = this;
+ me.startContainer =
+ me.startOffset =
+ me.endContainer =
+ me.endOffset = null;
+ me.document = document;
+ me.collapsed = true;
+ };
+
+ /**
+ * 删除fillData
+ * @param doc
+ * @param excludeNode
+ */
+ function removeFillData(doc, excludeNode) {
+ try {
+ if (fillData && domUtils.inDoc(fillData, doc)) {
+ if (!fillData.nodeValue.replace(fillCharReg, '').length) {
+ var tmpNode = fillData.parentNode;
+ domUtils.remove(fillData);
+ while (tmpNode && domUtils.isEmptyInlineElement(tmpNode) &&
+ //safari的contains有bug
+ (browser.safari ? !(domUtils.getPosition(tmpNode,excludeNode) & domUtils.POSITION_CONTAINS) : !tmpNode.contains(excludeNode))
+ ) {
+ fillData = tmpNode.parentNode;
+ domUtils.remove(tmpNode);
+ tmpNode = fillData;
+ }
+ } else {
+ fillData.nodeValue = fillData.nodeValue.replace(fillCharReg, '');
+ }
+ }
+ } catch (e) {
+ }
+ }
+
+ /**
+ * @param node
+ * @param dir
+ */
+ function mergeSibling(node, dir) {
+ var tmpNode;
+ node = node[dir];
+ while (node && domUtils.isFillChar(node)) {
+ tmpNode = node[dir];
+ domUtils.remove(node);
+ node = tmpNode;
+ }
+ }
+
+ Range.prototype = {
+
+ /**
+ * 克隆选区的内容到一个DocumentFragment里
+ * @method cloneContents
+ * @return { DocumentFragment | NULL } 如果选区是闭合的将返回null, 否则, 返回包含所clone内容的DocumentFragment元素
+ * @example
+ * ```html
+ *
+ *
+ * xx[xxx]x
+ *
+ *
+ *
+ * ```
+ */
+ cloneContents:function () {
+ return this.collapsed ? null : execContentsAction(this, 0);
+ },
+
+ /**
+ * 删除当前选区范围中的所有内容
+ * @method deleteContents
+ * @remind 执行完该操作后, 当前Range对象变成了闭合状态
+ * @return { UE.dom.Range } 当前操作的Range对象
+ * @example
+ * ```html
+ *
+ *
+ * xx[xxx]x
+ *
+ *
+ *
+ * ```
+ */
+ deleteContents:function () {
+ var txt;
+ if (!this.collapsed) {
+ execContentsAction(this, 1);
+ }
+ if (browser.webkit) {
+ txt = this.startContainer;
+ if (txt.nodeType == 3 && !txt.nodeValue.length) {
+ this.setStartBefore(txt).collapse(true);
+ domUtils.remove(txt);
+ }
+ }
+ return this;
+ },
+
+ /**
+ * 将当前选区的内容提取到一个DocumentFragment里
+ * @method extractContents
+ * @remind 执行该操作后, 选区将变成闭合状态
+ * @warning 执行该操作后, 原来选区所选中的内容将从dom树上剥离出来
+ * @return { DocumentFragment } 返回包含所提取内容的DocumentFragment对象
+ * @example
+ * ```html
+ *
+ *
+ * xx[xxx]x
+ *
+ *
+ *
+ */
+ extractContents:function () {
+ return this.collapsed ? null : execContentsAction(this, 2);
+ },
+
+ /**
+ * 设置Range的开始容器节点和偏移量
+ * @method setStart
+ * @remind 如果给定的节点是元素节点,那么offset指的是其子元素中索引为offset的元素,
+ * 如果是文本节点,那么offset指的是其文本内容的第offset个字符
+ * @remind 如果提供的容器节点是一个不能包含子元素的节点, 则该选区的开始容器将被设置
+ * 为该节点的父节点, 此时, 其距离开始容器的偏移量也变成了该节点在其父节点
+ * 中的索引
+ * @param { Node } node 将被设为当前选区开始边界容器的节点对象
+ * @param { int } offset 选区的开始位置偏移量
+ * @return { UE.dom.Range } 当前range对象
+ * @example
+ * ```html
+ *
+ * xxxxxxxxxxxxx[xxx]
+ *
+ *
+ * ```
+ * @example
+ * ```html
+ *
+ * xxx[xx]x
+ *
+ *
+ * ```
+ */
+ setStart:function (node, offset) {
+ return setEndPoint(true, node, offset, this);
+ },
+
+ /**
+ * 设置Range的结束容器和偏移量
+ * @method setEnd
+ * @param { Node } node 作为当前选区结束边界容器的节点对象
+ * @param { int } offset 结束边界的偏移量
+ * @see UE.dom.Range:setStart(Node,int)
+ * @return { UE.dom.Range } 当前range对象
+ */
+ setEnd:function (node, offset) {
+ return setEndPoint(false, node, offset, this);
+ },
+
+ /**
+ * 将Range开始位置设置到node节点之后
+ * @method setStartAfter
+ * @remind 该操作将会把给定节点的父节点作为range的开始容器, 且偏移量是该节点在其父节点中的位置索引+1
+ * @param { Node } node 选区的开始边界将紧接着该节点之后
+ * @return { UE.dom.Range } 当前range对象
+ * @example
+ * ```html
+ *
+ * xxxxxxx[xxxx]
+ *
+ *
+ * ```
+ */
+ setStartAfter:function (node) {
+ return this.setStart(node.parentNode, domUtils.getNodeIndex(node) + 1);
+ },
+
+ /**
+ * 将Range开始位置设置到node节点之前
+ * @method setStartBefore
+ * @remind 该操作将会把给定节点的父节点作为range的开始容器, 且偏移量是该节点在其父节点中的位置索引
+ * @param { Node } node 新的选区开始位置在该节点之前
+ * @see UE.dom.Range:setStartAfter(Node)
+ * @return { UE.dom.Range } 当前range对象
+ */
+ setStartBefore:function (node) {
+ return this.setStart(node.parentNode, domUtils.getNodeIndex(node));
+ },
+
+ /**
+ * 将Range结束位置设置到node节点之后
+ * @method setEndAfter
+ * @remind 该操作将会把给定节点的父节点作为range的结束容器, 且偏移量是该节点在其父节点中的位置索引+1
+ * @param { Node } node 目标节点
+ * @see UE.dom.Range:setStartAfter(Node)
+ * @return { UE.dom.Range } 当前range对象
+ * @example
+ * ```html
+ *
+ * [xxxxxxx]xxxx
+ *
+ *
+ * ```
+ */
+ setEndAfter:function (node) {
+ return this.setEnd(node.parentNode, domUtils.getNodeIndex(node) + 1);
+ },
+
+ /**
+ * 将Range结束位置设置到node节点之前
+ * @method setEndBefore
+ * @remind 该操作将会把给定节点的父节点作为range的结束容器, 且偏移量是该节点在其父节点中的位置索引
+ * @param { Node } node 目标节点
+ * @see UE.dom.Range:setEndAfter(Node)
+ * @return { UE.dom.Range } 当前range对象
+ */
+ setEndBefore:function (node) {
+ return this.setEnd(node.parentNode, domUtils.getNodeIndex(node));
+ },
+
+ /**
+ * 设置Range的开始位置到node节点内的第一个子节点之前
+ * @method setStartAtFirst
+ * @remind 选区的开始容器将变成给定的节点, 且偏移量为0
+ * @remind 如果给定的节点是元素节点, 则该节点必须是允许包含子节点的元素。
+ * @param { Node } node 目标节点
+ * @see UE.dom.Range:setStartBefore(Node)
+ * @return { UE.dom.Range } 当前range对象
+ * @example
+ * ```html
+ *
+ * xxxxx[xx]xxxx
+ *
+ *
+ * ```
+ */
+ setStartAtFirst:function (node) {
+ return this.setStart(node, 0);
+ },
+
+ /**
+ * 设置Range的开始位置到node节点内的最后一个节点之后
+ * @method setStartAtLast
+ * @remind 选区的开始容器将变成给定的节点, 且偏移量为该节点的子节点数
+ * @remind 如果给定的节点是元素节点, 则该节点必须是允许包含子节点的元素。
+ * @param { Node } node 目标节点
+ * @see UE.dom.Range:setStartAtFirst(Node)
+ * @return { UE.dom.Range } 当前range对象
+ */
+ setStartAtLast:function (node) {
+ return this.setStart(node, node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length);
+ },
+
+ /**
+ * 设置Range的结束位置到node节点内的第一个节点之前
+ * @method setEndAtFirst
+ * @param { Node } node 目标节点
+ * @remind 选区的结束容器将变成给定的节点, 且偏移量为0
+ * @remind node必须是一个元素节点, 且必须是允许包含子节点的元素。
+ * @see UE.dom.Range:setStartAtFirst(Node)
+ * @return { UE.dom.Range } 当前range对象
+ */
+ setEndAtFirst:function (node) {
+ return this.setEnd(node, 0);
+ },
+
+ /**
+ * 设置Range的结束位置到node节点内的最后一个节点之后
+ * @method setEndAtLast
+ * @param { Node } node 目标节点
+ * @remind 选区的结束容器将变成给定的节点, 且偏移量为该节点的子节点数量
+ * @remind node必须是一个元素节点, 且必须是允许包含子节点的元素。
+ * @see UE.dom.Range:setStartAtFirst(Node)
+ * @return { UE.dom.Range } 当前range对象
+ */
+ setEndAtLast:function (node) {
+ return this.setEnd(node, node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length);
+ },
+
+ /**
+ * 选中给定节点
+ * @method selectNode
+ * @remind 此时, 选区的开始容器和结束容器都是该节点的父节点, 其startOffset是该节点在父节点中的位置索引,
+ * 而endOffset为startOffset+1
+ * @param { Node } node 需要选中的节点
+ * @return { UE.dom.Range } 当前range对象,此时的range仅包含当前给定的节点对象
+ * @example
+ * ```html
+ *
+ * xxxxx[xx]xxxx
+ *
+ *
+ * ```
+ */
+ selectNode:function (node) {
+ return this.setStartBefore(node).setEndAfter(node);
+ },
+
+ /**
+ * 选中给定节点内部的所有节点
+ * @method selectNodeContents
+ * @remind 此时, 选区的开始容器和结束容器都是该节点, 其startOffset为0,
+ * 而endOffset是该节点的子节点数。
+ * @param { Node } node 目标节点, 当前range将包含该节点内的所有节点
+ * @return { UE.dom.Range } 当前range对象, 此时range仅包含给定节点的所有子节点
+ * @example
+ * ```html
+ *
+ * xxxxx[xx]xxxx
+ *
+ *
+ * ```
+ */
+ selectNodeContents:function (node) {
+ return this.setStart(node, 0).setEndAtLast(node);
+ },
+
+ /**
+ * clone当前Range对象
+ * @method cloneRange
+ * @remind 返回的range是一个全新的range对象, 其内部所有属性与当前被clone的range相同。
+ * @return { UE.dom.Range } 当前range对象的一个副本
+ */
+ cloneRange:function () {
+ var me = this;
+ return new Range(me.document).setStart(me.startContainer, me.startOffset).setEnd(me.endContainer, me.endOffset);
+
+ },
+
+ /**
+ * 向当前选区的结束处闭合选区
+ * @method collapse
+ * @return { UE.dom.Range } 当前range对象
+ * @example
+ * ```html
+ *
+ * xxxxx[xx]xxxx
+ *
+ *
+ * ```
+ */
+
+ /**
+ * 闭合当前选区,根据给定的toStart参数项决定是向当前选区开始处闭合还是向结束处闭合,
+ * 如果toStart的值为true,则向开始位置闭合, 反之,向结束位置闭合。
+ * @method collapse
+ * @param { Boolean } toStart 是否向选区开始处闭合
+ * @return { UE.dom.Range } 当前range对象,此时range对象处于闭合状态
+ * @see UE.dom.Range:collapse()
+ * @example
+ * ```html
+ *
+ * xxxxx[xx]xxxx
+ *
+ *
+ * ```
+ */
+ collapse:function (toStart) {
+ var me = this;
+ if (toStart) {
+ me.endContainer = me.startContainer;
+ me.endOffset = me.startOffset;
+ } else {
+ me.startContainer = me.endContainer;
+ me.startOffset = me.endOffset;
+ }
+ me.collapsed = true;
+ return me;
+ },
+
+ /**
+ * 调整range的开始位置和结束位置,使其"收缩"到最小的位置
+ * @method shrinkBoundary
+ * @return { UE.dom.Range } 当前range对象
+ * @example
+ * ```html
+ * xxxx[xxxxx] => xxxx[xxxxx]
+ * ```
+ *
+ * @example
+ * ```html
+ *
+ * x[xx]xxx
+ *
+ *
+ * ```
+ *
+ * @example
+ * ```html
+ * [xxxxxxxxxxx] => [xxxxxxxxxxx]
+ * ```
+ */
+
+ /**
+ * 调整range的开始位置和结束位置,使其"收缩"到最小的位置,
+ * 如果ignoreEnd的值为true,则忽略对结束位置的调整
+ * @method shrinkBoundary
+ * @param { Boolean } ignoreEnd 是否忽略对结束位置的调整
+ * @return { UE.dom.Range } 当前range对象
+ * @see UE.dom.domUtils.Range:shrinkBoundary()
+ */
+ shrinkBoundary:function (ignoreEnd) {
+ var me = this, child,
+ collapsed = me.collapsed;
+ function check(node){
+ return node.nodeType == 1 && !domUtils.isBookmarkNode(node) && !dtd.$empty[node.tagName] && !dtd.$nonChild[node.tagName]
+ }
+ while (me.startContainer.nodeType == 1 //是element
+ && (child = me.startContainer.childNodes[me.startOffset]) //子节点也是element
+ && check(child)) {
+ me.setStart(child, 0);
+ }
+ if (collapsed) {
+ return me.collapse(true);
+ }
+ if (!ignoreEnd) {
+ while (me.endContainer.nodeType == 1//是element
+ && me.endOffset > 0 //如果是空元素就退出 endOffset=0那么endOffst-1为负值,childNodes[endOffset]报错
+ && (child = me.endContainer.childNodes[me.endOffset - 1]) //子节点也是element
+ && check(child)) {
+ me.setEnd(child, child.childNodes.length);
+ }
+ }
+ return me;
+ },
+
+ /**
+ * 获取离当前选区内包含的所有节点最近的公共祖先节点,
+ * @method getCommonAncestor
+ * @remind 返回的公共祖先节点一定不是range自身的容器节点, 但有可能是一个文本节点
+ * @return { Node } 当前range对象内所有节点的公共祖先节点
+ * @example
+ * ```html
+ * //选区示例
+ * xxxx[xxx]xxxxxx
+ *
+ * ```
+ */
+
+ /**
+ * 获取当前选区所包含的所有节点的公共祖先节点, 可以根据给定的参数 includeSelf 决定获取到
+ * 的公共祖先节点是否可以是当前选区的startContainer或endContainer节点, 如果 includeSelf
+ * 的取值为true, 则返回的节点可以是自身的容器节点, 否则, 则不能是容器节点
+ * @method getCommonAncestor
+ * @param { Boolean } includeSelf 是否允许获取到的公共祖先节点是当前range对象的容器节点
+ * @return { Node } 当前range对象内所有节点的公共祖先节点
+ * @see UE.dom.Range:getCommonAncestor()
+ * @example
+ * ```html
+ *
+ *
+ *
+ * xxxxxxxxx[xxx]xxxxxxxx
+ *
+ *
+ *
+ *
+ * ```
+ */
+
+ /**
+ * 获取当前选区所包含的所有节点的公共祖先节点, 可以根据给定的参数 includeSelf 决定获取到
+ * 的公共祖先节点是否可以是当前选区的startContainer或endContainer节点, 如果 includeSelf
+ * 的取值为true, 则返回的节点可以是自身的容器节点, 否则, 则不能是容器节点; 同时可以根据
+ * ignoreTextNode 参数的取值决定是否忽略类型为文本节点的祖先节点。
+ * @method getCommonAncestor
+ * @param { Boolean } includeSelf 是否允许获取到的公共祖先节点是当前range对象的容器节点
+ * @param { Boolean } ignoreTextNode 获取祖先节点的过程中是否忽略类型为文本节点的祖先节点
+ * @return { Node } 当前range对象内所有节点的公共祖先节点
+ * @see UE.dom.Range:getCommonAncestor()
+ * @see UE.dom.Range:getCommonAncestor(Boolean)
+ * @example
+ * ```html
+ *
+ *
+ *
+ * xxxxxxxx[x]xxxxxxxxxxx
+ *
+ *
+ *
+ *
+ * ```
+ */
+ getCommonAncestor:function (includeSelf, ignoreTextNode) {
+ var me = this,
+ start = me.startContainer,
+ end = me.endContainer;
+ if (start === end) {
+ if (includeSelf && selectOneNode(this)) {
+ start = start.childNodes[me.startOffset];
+ if(start.nodeType == 1)
+ return start;
+ }
+ //只有在上来就相等的情况下才会出现是文本的情况
+ return ignoreTextNode && start.nodeType == 3 ? start.parentNode : start;
+ }
+ return domUtils.getCommonAncestor(start, end);
+ },
+
+ /**
+ * 调整当前Range的开始和结束边界容器,如果是容器节点是文本节点,就调整到包含该文本节点的父节点上
+ * @method trimBoundary
+ * @remind 该操作有可能会引起文本节点被切开
+ * @return { UE.dom.Range } 当前range对象
+ * @example
+ * ```html
+ *
+ * //选区示例
+ * xxx[xxxxx]xxx
+ *
+ *
+ * ```
+ */
+
+ /**
+ * 调整当前Range的开始和结束边界容器,如果是容器节点是文本节点,就调整到包含该文本节点的父节点上,
+ * 可以根据 ignoreEnd 参数的值决定是否调整对结束边界的调整
+ * @method trimBoundary
+ * @param { Boolean } ignoreEnd 是否忽略对结束边界的调整
+ * @return { UE.dom.Range } 当前range对象
+ * @example
+ * ```html
+ *
+ * //选区示例
+ * xxx[xxxxx]xxx
+ *
+ *
+ * ```
+ */
+ trimBoundary:function (ignoreEnd) {
+ this.txtToElmBoundary();
+ var start = this.startContainer,
+ offset = this.startOffset,
+ collapsed = this.collapsed,
+ end = this.endContainer;
+ if (start.nodeType == 3) {
+ if (offset == 0) {
+ this.setStartBefore(start);
+ } else {
+ if (offset >= start.nodeValue.length) {
+ this.setStartAfter(start);
+ } else {
+ var textNode = domUtils.split(start, offset);
+ //跟新结束边界
+ if (start === end) {
+ this.setEnd(textNode, this.endOffset - offset);
+ } else if (start.parentNode === end) {
+ this.endOffset += 1;
+ }
+ this.setStartBefore(textNode);
+ }
+ }
+ if (collapsed) {
+ return this.collapse(true);
+ }
+ }
+ if (!ignoreEnd) {
+ offset = this.endOffset;
+ end = this.endContainer;
+ if (end.nodeType == 3) {
+ if (offset == 0) {
+ this.setEndBefore(end);
+ } else {
+ offset < end.nodeValue.length && domUtils.split(end, offset);
+ this.setEndAfter(end);
+ }
+ }
+ }
+ return this;
+ },
+
+ /**
+ * 如果选区在文本的边界上,就扩展选区到文本的父节点上, 如果当前选区是闭合的, 则什么也不做
+ * @method txtToElmBoundary
+ * @remind 该操作不会修改dom节点
+ * @return { UE.dom.Range } 当前range对象
+ */
+
+ /**
+ * 如果选区在文本的边界上,就扩展选区到文本的父节点上, 如果当前选区是闭合的, 则根据参数项
+ * ignoreCollapsed 的值决定是否执行该调整
+ * @method txtToElmBoundary
+ * @param { Boolean } ignoreCollapsed 是否忽略选区的闭合状态, 如果该参数取值为true, 则
+ * 不论选区是否闭合, 都会执行该操作, 反之, 则不会对闭合的选区执行该操作
+ * @return { UE.dom.Range } 当前range对象
+ */
+ txtToElmBoundary:function (ignoreCollapsed) {
+ function adjust(r, c) {
+ var container = r[c + 'Container'],
+ offset = r[c + 'Offset'];
+ if (container.nodeType == 3) {
+ if (!offset) {
+ r['set' + c.replace(/(\w)/, function (a) {
+ return a.toUpperCase();
+ }) + 'Before'](container);
+ } else if (offset >= container.nodeValue.length) {
+ r['set' + c.replace(/(\w)/, function (a) {
+ return a.toUpperCase();
+ }) + 'After' ](container);
+ }
+ }
+ }
+
+ if (ignoreCollapsed || !this.collapsed) {
+ adjust(this, 'start');
+ adjust(this, 'end');
+ }
+ return this;
+ },
+
+ /**
+ * 在当前选区的开始位置前插入节点,新插入的节点会被该range包含
+ * @method insertNode
+ * @param { Node } node 需要插入的节点
+ * @remind 插入的节点可以是一个DocumentFragment依次插入多个节点
+ * @return { UE.dom.Range } 当前range对象
+ */
+ insertNode:function (node) {
+ var first = node, length = 1;
+ if (node.nodeType == 11) {
+ first = node.firstChild;
+ length = node.childNodes.length;
+ }
+ this.trimBoundary(true);
+ var start = this.startContainer,
+ offset = this.startOffset;
+ var nextNode = start.childNodes[ offset ];
+ if (nextNode) {
+ start.insertBefore(node, nextNode);
+ } else {
+ start.appendChild(node);
+ }
+ if (first.parentNode === this.endContainer) {
+ this.endOffset = this.endOffset + length;
+ }
+ return this.setStartBefore(first);
+ },
+
+ /**
+ * 闭合选区到当前选区的开始位置, 并且定位光标到闭合后的位置
+ * @method setCursor
+ * @return { UE.dom.Range } 当前range对象
+ * @see UE.dom.Range:collapse()
+ */
+
+ /**
+ * 闭合选区,可以根据参数toEnd的值控制选区是向前闭合还是向后闭合, 并且定位光标到闭合后的位置。
+ * @method setCursor
+ * @param { Boolean } toEnd 是否向后闭合, 如果为true, 则闭合选区时, 将向结束容器方向闭合,
+ * 反之,则向开始容器方向闭合
+ * @return { UE.dom.Range } 当前range对象
+ * @see UE.dom.Range:collapse(Boolean)
+ */
+ setCursor:function (toEnd, noFillData) {
+ return this.collapse(!toEnd).select(noFillData);
+ },
+
+ /**
+ * 创建当前range的一个书签,记录下当前range的位置,方便当dom树改变时,还能找回原来的选区位置
+ * @method createBookmark
+ * @param { Boolean } serialize 控制返回的标记位置是对当前位置的引用还是ID,如果该值为true,则
+ * 返回标记位置的ID, 反之则返回标记位置节点的引用
+ * @return { Object } 返回一个书签记录键值对, 其包含的key有: start => 开始标记的ID或者引用,
+ * end => 结束标记的ID或引用, id => 当前标记的类型, 如果为true,则表示
+ * 返回的记录的类型为ID, 反之则为引用
+ */
+ createBookmark:function (serialize, same) {
+ var endNode,
+ startNode = this.document.createElement('span');
+ startNode.style.cssText = 'display:none;line-height:0px;';
+ startNode.appendChild(this.document.createTextNode('\u200D'));
+ startNode.id = '_baidu_bookmark_start_' + (same ? '' : guid++);
+
+ if (!this.collapsed) {
+ endNode = startNode.cloneNode(true);
+ endNode.id = '_baidu_bookmark_end_' + (same ? '' : guid++);
+ }
+ this.insertNode(startNode);
+ if (endNode) {
+ this.collapse().insertNode(endNode).setEndBefore(endNode);
+ }
+ this.setStartAfter(startNode);
+ return {
+ start:serialize ? startNode.id : startNode,
+ end:endNode ? serialize ? endNode.id : endNode : null,
+ id:serialize
+ }
+ },
+
+ /**
+ * 调整当前range的边界到书签位置,并删除该书签对象所标记的位置内的节点
+ * @method moveToBookmark
+ * @param { BookMark } bookmark createBookmark所创建的标签对象
+ * @return { UE.dom.Range } 当前range对象
+ * @see UE.dom.Range:createBookmark(Boolean)
+ */
+ moveToBookmark:function (bookmark) {
+ var start = bookmark.id ? this.document.getElementById(bookmark.start) : bookmark.start,
+ end = bookmark.end && bookmark.id ? this.document.getElementById(bookmark.end) : bookmark.end;
+ this.setStartBefore(start);
+ domUtils.remove(start);
+ if (end) {
+ this.setEndBefore(end);
+ domUtils.remove(end);
+ } else {
+ this.collapse(true);
+ }
+ return this;
+ },
+
+ /**
+ * 调整range的边界,使其"放大"到最近的父节点
+ * @method enlarge
+ * @remind 会引起选区的变化
+ * @return { UE.dom.Range } 当前range对象
+ */
+
+ /**
+ * 调整range的边界,使其"放大"到最近的父节点,根据参数 toBlock 的取值, 可以
+ * 要求扩大之后的父节点是block节点
+ * @method enlarge
+ * @param { Boolean } toBlock 是否要求扩大之后的父节点必须是block节点
+ * @return { UE.dom.Range } 当前range对象
+ */
+ enlarge:function (toBlock, stopFn) {
+ var isBody = domUtils.isBody,
+ pre, node, tmp = this.document.createTextNode('');
+ if (toBlock) {
+ node = this.startContainer;
+ if (node.nodeType == 1) {
+ if (node.childNodes[this.startOffset]) {
+ pre = node = node.childNodes[this.startOffset]
+ } else {
+ node.appendChild(tmp);
+ pre = node = tmp;
+ }
+ } else {
+ pre = node;
+ }
+ while (1) {
+ if (domUtils.isBlockElm(node)) {
+ node = pre;
+ while ((pre = node.previousSibling) && !domUtils.isBlockElm(pre)) {
+ node = pre;
+ }
+ this.setStartBefore(node);
+ break;
+ }
+ pre = node;
+ node = node.parentNode;
+ }
+ node = this.endContainer;
+ if (node.nodeType == 1) {
+ if (pre = node.childNodes[this.endOffset]) {
+ node.insertBefore(tmp, pre);
+ } else {
+ node.appendChild(tmp);
+ }
+ pre = node = tmp;
+ } else {
+ pre = node;
+ }
+ while (1) {
+ if (domUtils.isBlockElm(node)) {
+ node = pre;
+ while ((pre = node.nextSibling) && !domUtils.isBlockElm(pre)) {
+ node = pre;
+ }
+ this.setEndAfter(node);
+ break;
+ }
+ pre = node;
+ node = node.parentNode;
+ }
+ if (tmp.parentNode === this.endContainer) {
+ this.endOffset--;
+ }
+ domUtils.remove(tmp);
+ }
+
+ // 扩展边界到最大
+ if (!this.collapsed) {
+ while (this.startOffset == 0) {
+ if (stopFn && stopFn(this.startContainer)) {
+ break;
+ }
+ if (isBody(this.startContainer)) {
+ break;
+ }
+ this.setStartBefore(this.startContainer);
+ }
+ while (this.endOffset == (this.endContainer.nodeType == 1 ? this.endContainer.childNodes.length : this.endContainer.nodeValue.length)) {
+ if (stopFn && stopFn(this.endContainer)) {
+ break;
+ }
+ if (isBody(this.endContainer)) {
+ break;
+ }
+ this.setEndAfter(this.endContainer);
+ }
+ }
+ return this;
+ },
+ enlargeToBlockElm:function(ignoreEnd){
+ while(!domUtils.isBlockElm(this.startContainer)){
+ this.setStartBefore(this.startContainer);
+ }
+ if(!ignoreEnd){
+ while(!domUtils.isBlockElm(this.endContainer)){
+ this.setEndAfter(this.endContainer);
+ }
+ }
+ return this;
+ },
+ /**
+ * 调整Range的边界,使其"缩小"到最合适的位置
+ * @method adjustmentBoundary
+ * @return { UE.dom.Range } 当前range对象
+ * @see UE.dom.Range:shrinkBoundary()
+ */
+ adjustmentBoundary:function () {
+ if (!this.collapsed) {
+ while (!domUtils.isBody(this.startContainer) &&
+ this.startOffset == this.startContainer[this.startContainer.nodeType == 3 ? 'nodeValue' : 'childNodes'].length &&
+ this.startContainer[this.startContainer.nodeType == 3 ? 'nodeValue' : 'childNodes'].length
+ ) {
+
+ this.setStartAfter(this.startContainer);
+ }
+ while (!domUtils.isBody(this.endContainer) && !this.endOffset &&
+ this.endContainer[this.endContainer.nodeType == 3 ? 'nodeValue' : 'childNodes'].length
+ ) {
+ this.setEndBefore(this.endContainer);
+ }
+ }
+ return this;
+ },
+
+ /**
+ * 给range选区中的内容添加给定的inline标签
+ * @method applyInlineStyle
+ * @param { String } tagName 需要添加的标签名
+ * @example
+ * ```html
+ * xxxx[xxxx]x ==> range.applyInlineStyle("strong") ==> xxxx[xxxx]x
+ * ```
+ */
+
+ /**
+ * 给range选区中的内容添加给定的inline标签, 并且为标签附加上一些初始化属性。
+ * @method applyInlineStyle
+ * @param { String } tagName 需要添加的标签名
+ * @param { Object } attrs 跟随新添加的标签的属性
+ * @return { UE.dom.Range } 当前选区
+ * @example
+ * ```html
+ * xxxx[xxxx]x
+ *
+ * ==>
+ *
+ *
+ * range.applyInlineStyle("strong",{"style":"font-size:12px"})
+ *
+ * ==>
+ *
+ * xxxx[xxxx]x
+ * ```
+ */
+ applyInlineStyle:function (tagName, attrs, list) {
+ if (this.collapsed)return this;
+ this.trimBoundary().enlarge(false,
+ function (node) {
+ return node.nodeType == 1 && domUtils.isBlockElm(node)
+ }).adjustmentBoundary();
+ var bookmark = this.createBookmark(),
+ end = bookmark.end,
+ filterFn = function (node) {
+ return node.nodeType == 1 ? node.tagName.toLowerCase() != 'br' : !domUtils.isWhitespace(node);
+ },
+ current = domUtils.getNextDomNode(bookmark.start, false, filterFn),
+ node,
+ pre,
+ range = this.cloneRange();
+ while (current && (domUtils.getPosition(current, end) & domUtils.POSITION_PRECEDING)) {
+ if (current.nodeType == 3 || dtd[tagName][current.tagName]) {
+ range.setStartBefore(current);
+ node = current;
+ while (node && (node.nodeType == 3 || dtd[tagName][node.tagName]) && node !== end) {
+ pre = node;
+ node = domUtils.getNextDomNode(node, node.nodeType == 1, null, function (parent) {
+ return dtd[tagName][parent.tagName];
+ });
+ }
+ var frag = range.setEndAfter(pre).extractContents(), elm;
+ if (list && list.length > 0) {
+ var level, top;
+ top = level = list[0].cloneNode(false);
+ for (var i = 1, ci; ci = list[i++];) {
+ level.appendChild(ci.cloneNode(false));
+ level = level.firstChild;
+ }
+ elm = level;
+ } else {
+ elm = range.document.createElement(tagName);
+ }
+ if (attrs) {
+ domUtils.setAttributes(elm, attrs);
+ }
+ elm.appendChild(frag);
+ range.insertNode(list ? top : elm);
+ //处理下滑线在a上的情况
+ var aNode;
+ if (tagName == 'span' && attrs.style && /text\-decoration/.test(attrs.style) && (aNode = domUtils.findParentByTagName(elm, 'a', true))) {
+ domUtils.setAttributes(aNode, attrs);
+ domUtils.remove(elm, true);
+ elm = aNode;
+ } else {
+ domUtils.mergeSibling(elm);
+ domUtils.clearEmptySibling(elm);
+ }
+ //去除子节点相同的
+ domUtils.mergeChild(elm, attrs);
+ current = domUtils.getNextDomNode(elm, false, filterFn);
+ domUtils.mergeToParent(elm);
+ if (node === end) {
+ break;
+ }
+ } else {
+ current = domUtils.getNextDomNode(current, true, filterFn);
+ }
+ }
+ return this.moveToBookmark(bookmark);
+ },
+
+ /**
+ * 移除当前选区内指定的inline标签,但保留其中的内容
+ * @method removeInlineStyle
+ * @param { String } tagName 需要移除的标签名
+ * @return { UE.dom.Range } 当前的range对象
+ * @example
+ * ```html
+ * xx[xxxxyyyzz]z => range.removeInlineStyle(["em"]) => xx[xxxxyyyzz]z
+ * ```
+ */
+
+ /**
+ * 移除当前选区内指定的一组inline标签,但保留其中的内容
+ * @method removeInlineStyle
+ * @param { Array } tagNameArr 需要移除的标签名的数组
+ * @return { UE.dom.Range } 当前的range对象
+ * @see UE.dom.Range:removeInlineStyle(String)
+ */
+ removeInlineStyle:function (tagNames) {
+ if (this.collapsed)return this;
+ tagNames = utils.isArray(tagNames) ? tagNames : [tagNames];
+ this.shrinkBoundary().adjustmentBoundary();
+ var start = this.startContainer, end = this.endContainer;
+ while (1) {
+ if (start.nodeType == 1) {
+ if (utils.indexOf(tagNames, start.tagName.toLowerCase()) > -1) {
+ break;
+ }
+ if (start.tagName.toLowerCase() == 'body') {
+ start = null;
+ break;
+ }
+ }
+ start = start.parentNode;
+ }
+ while (1) {
+ if (end.nodeType == 1) {
+ if (utils.indexOf(tagNames, end.tagName.toLowerCase()) > -1) {
+ break;
+ }
+ if (end.tagName.toLowerCase() == 'body') {
+ end = null;
+ break;
+ }
+ }
+ end = end.parentNode;
+ }
+ var bookmark = this.createBookmark(),
+ frag,
+ tmpRange;
+ if (start) {
+ tmpRange = this.cloneRange().setEndBefore(bookmark.start).setStartBefore(start);
+ frag = tmpRange.extractContents();
+ tmpRange.insertNode(frag);
+ domUtils.clearEmptySibling(start, true);
+ start.parentNode.insertBefore(bookmark.start, start);
+ }
+ if (end) {
+ tmpRange = this.cloneRange().setStartAfter(bookmark.end).setEndAfter(end);
+ frag = tmpRange.extractContents();
+ tmpRange.insertNode(frag);
+ domUtils.clearEmptySibling(end, false, true);
+ end.parentNode.insertBefore(bookmark.end, end.nextSibling);
+ }
+ var current = domUtils.getNextDomNode(bookmark.start, false, function (node) {
+ return node.nodeType == 1;
+ }), next;
+ while (current && current !== bookmark.end) {
+ next = domUtils.getNextDomNode(current, true, function (node) {
+ return node.nodeType == 1;
+ });
+ if (utils.indexOf(tagNames, current.tagName.toLowerCase()) > -1) {
+ domUtils.remove(current, true);
+ }
+ current = next;
+ }
+ return this.moveToBookmark(bookmark);
+ },
+
+ /**
+ * 获取当前选中的自闭合的节点
+ * @method getClosedNode
+ * @return { Node | NULL } 如果当前选中的是自闭合节点, 则返回该节点, 否则返回NULL
+ */
+ getClosedNode:function () {
+ var node;
+ if (!this.collapsed) {
+ var range = this.cloneRange().adjustmentBoundary().shrinkBoundary();
+ if (selectOneNode(range)) {
+ var child = range.startContainer.childNodes[range.startOffset];
+ if (child && child.nodeType == 1 && (dtd.$empty[child.tagName] || dtd.$nonChild[child.tagName])) {
+ node = child;
+ }
+ }
+ }
+ return node;
+ },
+
+ /**
+ * 在页面上高亮range所表示的选区
+ * @method select
+ * @return { UE.dom.Range } 返回当前Range对象
+ */
+ //这里不区分ie9以上,trace:3824
+ select:browser.ie ? function (noFillData, textRange) {
+ var nativeRange;
+ if (!this.collapsed)
+ this.shrinkBoundary();
+ var node = this.getClosedNode();
+ if (node && !textRange) {
+ try {
+ nativeRange = this.document.body.createControlRange();
+ nativeRange.addElement(node);
+ nativeRange.select();
+ } catch (e) {}
+ return this;
+ }
+ var bookmark = this.createBookmark(),
+ start = bookmark.start,
+ end;
+ nativeRange = this.document.body.createTextRange();
+ nativeRange.moveToElementText(start);
+ nativeRange.moveStart('character', 1);
+ if (!this.collapsed) {
+ var nativeRangeEnd = this.document.body.createTextRange();
+ end = bookmark.end;
+ nativeRangeEnd.moveToElementText(end);
+ nativeRange.setEndPoint('EndToEnd', nativeRangeEnd);
+ } else {
+ if (!noFillData && this.startContainer.nodeType != 3) {
+ //使用|x固定住光标
+ var tmpText = this.document.createTextNode(fillChar),
+ tmp = this.document.createElement('span');
+ tmp.appendChild(this.document.createTextNode(fillChar));
+ start.parentNode.insertBefore(tmp, start);
+ start.parentNode.insertBefore(tmpText, start);
+ //当点b,i,u时,不能清除i上边的b
+ removeFillData(this.document, tmpText);
+ fillData = tmpText;
+ mergeSibling(tmp, 'previousSibling');
+ mergeSibling(start, 'nextSibling');
+ nativeRange.moveStart('character', -1);
+ nativeRange.collapse(true);
+ }
+ }
+ this.moveToBookmark(bookmark);
+ tmp && domUtils.remove(tmp);
+ //IE在隐藏状态下不支持range操作,catch一下
+ try {
+ nativeRange.select();
+ } catch (e) {
+ }
+ return this;
+ } : function (notInsertFillData) {
+ function checkOffset(rng){
+
+ function check(node,offset,dir){
+ if(node.nodeType == 3 && node.nodeValue.length < offset){
+ rng[dir + 'Offset'] = node.nodeValue.length
+ }
+ }
+ check(rng.startContainer,rng.startOffset,'start');
+ check(rng.endContainer,rng.endOffset,'end');
+ }
+ var win = domUtils.getWindow(this.document),
+ sel = win.getSelection(),
+ txtNode;
+ //FF下关闭自动长高时滚动条在关闭dialog时会跳
+ //ff下如果不body.focus将不能定位闭合光标到编辑器内
+ browser.gecko ? this.document.body.focus() : win.focus();
+ if (sel) {
+ sel.removeAllRanges();
+ // trace:870 chrome/safari后边是br对于闭合得range不能定位 所以去掉了判断
+ // this.startContainer.nodeType != 3 &&! ((child = this.startContainer.childNodes[this.startOffset]) && child.nodeType == 1 && child.tagName == 'BR'
+ if (this.collapsed && !notInsertFillData) {
+// //opear如果没有节点接着,原生的不能够定位,不能在body的第一级插入空白节点
+// if (notInsertFillData && browser.opera && !domUtils.isBody(this.startContainer) && this.startContainer.nodeType == 1) {
+// var tmp = this.document.createTextNode('');
+// this.insertNode(tmp).setStart(tmp, 0).collapse(true);
+// }
+//
+ //处理光标落在文本节点的情况
+ //处理以下的情况
+ //|xxxx
+ //xxxx|xxxx
+ //xxxx|
+ var start = this.startContainer,child = start;
+ if(start.nodeType == 1){
+ child = start.childNodes[this.startOffset];
+
+ }
+ if( !(start.nodeType == 3 && this.startOffset) &&
+ (child ?
+ (!child.previousSibling || child.previousSibling.nodeType != 3)
+ :
+ (!start.lastChild || start.lastChild.nodeType != 3)
+ )
+ ){
+ txtNode = this.document.createTextNode(fillChar);
+ //跟着前边走
+ this.insertNode(txtNode);
+ removeFillData(this.document, txtNode);
+ mergeSibling(txtNode, 'previousSibling');
+ mergeSibling(txtNode, 'nextSibling');
+ fillData = txtNode;
+ this.setStart(txtNode, browser.webkit ? 1 : 0).collapse(true);
+ }
+ }
+ var nativeRange = this.document.createRange();
+ if(this.collapsed && browser.opera && this.startContainer.nodeType == 1){
+ var child = this.startContainer.childNodes[this.startOffset];
+ if(!child){
+ //往前靠拢
+ child = this.startContainer.lastChild;
+ if( child && domUtils.isBr(child)){
+ this.setStartBefore(child).collapse(true);
+ }
+ }else{
+ //向后靠拢
+ while(child && domUtils.isBlockElm(child)){
+ if(child.nodeType == 1 && child.childNodes[0]){
+ child = child.childNodes[0]
+ }else{
+ break;
+ }
+ }
+ child && this.setStartBefore(child).collapse(true)
+ }
+
+ }
+ //是createAddress最后一位算的不准,现在这里进行微调
+ checkOffset(this);
+ nativeRange.setStart(this.startContainer, this.startOffset);
+ nativeRange.setEnd(this.endContainer, this.endOffset);
+ sel.addRange(nativeRange);
+ }
+ return this;
+ },
+
+ /**
+ * 滚动到当前range开始的位置
+ * @method scrollToView
+ * @param { Window } win 当前range对象所属的window对象
+ * @return { UE.dom.Range } 当前Range对象
+ */
+
+ /**
+ * 滚动到距离当前range开始位置 offset 的位置处
+ * @method scrollToView
+ * @param { Window } win 当前range对象所属的window对象
+ * @param { Number } offset 距离range开始位置处的偏移量, 如果为正数, 则向下偏移, 反之, 则向上偏移
+ * @return { UE.dom.Range } 当前Range对象
+ */
+ scrollToView:function (win, offset) {
+ win = win ? window : domUtils.getWindow(this.document);
+ var me = this,
+ span = me.document.createElement('span');
+ //trace:717
+ span.innerHTML = ' ';
+ me.cloneRange().insertNode(span);
+ domUtils.scrollToView(span, win, offset);
+ domUtils.remove(span);
+ return me;
+ },
+
+ /**
+ * 判断当前选区内容是否占位符
+ * @private
+ * @method inFillChar
+ * @return { Boolean } 如果是占位符返回true,否则返回false
+ */
+ inFillChar : function(){
+ var start = this.startContainer;
+ if(this.collapsed && start.nodeType == 3
+ && start.nodeValue.replace(new RegExp('^' + domUtils.fillChar),'').length + 1 == start.nodeValue.length
+ ){
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * 保存
+ * @method createAddress
+ * @private
+ * @return { Boolean } 返回开始和结束的位置
+ * @example
+ * ```html
+ *
+ *
+ * aaaa
+ *
+ *
+ * bbbb
+ *
+ *
+ *
+ *
+ *
+ *
+ * ```
+ */
+ createAddress : function(ignoreEnd,ignoreTxt){
+ var addr = {},me = this;
+
+ function getAddress(isStart){
+ var node = isStart ? me.startContainer : me.endContainer;
+ var parents = domUtils.findParents(node,true,function(node){return !domUtils.isBody(node)}),
+ addrs = [];
+ for(var i = 0,ci;ci = parents[i++];){
+ addrs.push(domUtils.getNodeIndex(ci,ignoreTxt));
+ }
+ var firstIndex = 0;
+
+ if(ignoreTxt){
+ if(node.nodeType == 3){
+ var tmpNode = node.previousSibling;
+ while(tmpNode && tmpNode.nodeType == 3){
+ firstIndex += tmpNode.nodeValue.replace(fillCharReg,'').length;
+ tmpNode = tmpNode.previousSibling;
+ }
+ firstIndex += (isStart ? me.startOffset : me.endOffset)// - (fillCharReg.test(node.nodeValue) ? 1 : 0 )
+ }else{
+ node = node.childNodes[ isStart ? me.startOffset : me.endOffset];
+ if(node){
+ firstIndex = domUtils.getNodeIndex(node,ignoreTxt);
+ }else{
+ node = isStart ? me.startContainer : me.endContainer;
+ var first = node.firstChild;
+ while(first){
+ if(domUtils.isFillChar(first)){
+ first = first.nextSibling;
+ continue;
+ }
+ firstIndex++;
+ if(first.nodeType == 3){
+ while( first && first.nodeType == 3){
+ first = first.nextSibling;
+ }
+ }else{
+ first = first.nextSibling;
+ }
+ }
+ }
+ }
+
+ }else{
+ firstIndex = isStart ? domUtils.isFillChar(node) ? 0 : me.startOffset : me.endOffset
+ }
+ if(firstIndex < 0){
+ firstIndex = 0;
+ }
+ addrs.push(firstIndex);
+ return addrs;
+ }
+ addr.startAddress = getAddress(true);
+ if(!ignoreEnd){
+ addr.endAddress = me.collapsed ? [].concat(addr.startAddress) : getAddress();
+ }
+ return addr;
+ },
+
+ /**
+ * 保存
+ * @method createAddress
+ * @private
+ * @return { Boolean } 返回开始和结束的位置
+ * @example
+ * ```html
+ *
+ *
+ * aaaa
+ *
+ *
+ * bbbb
+ *
+ *
+ *
+ *
+ *
+ *
+ * ```
+ */
+ moveToAddress : function(addr,ignoreEnd){
+ var me = this;
+ function getNode(address,isStart){
+ var tmpNode = me.document.body,
+ parentNode,offset;
+ for(var i= 0,ci,l=address.length;i
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * ```
+ */
+
+ /**
+ * 遍历range内的节点。
+ * 每当遍历一个节点时, 都会执行参数项 doFn 指定的函数, 该函数的接受当前遍历的节点
+ * 作为其参数。
+ * 可以通过参数项 filterFn 来指定一个过滤器, 只有符合该过滤器过滤规则的节点才会触
+ * 发doFn函数的执行
+ * @method traversal
+ * @param { Function } doFn 对每个遍历的节点要执行的方法, 该方法接受当前遍历的节点作为其参数
+ * @param { Function } filterFn 过滤器, 该函数接受当前遍历的节点作为参数, 如果该节点满足过滤
+ * 规则, 请返回true, 该节点会触发doFn, 否则, 请返回false, 则该节点不
+ * 会触发doFn。
+ * @return { UE.dom.Range } 当前range对象
+ * @see UE.dom.Range:traversal(Function)
+ * @example
+ * ```html
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * ```
+ */
+ traversal:function(doFn,filterFn){
+ if (this.collapsed)
+ return this;
+ var bookmark = this.createBookmark(),
+ end = bookmark.end,
+ current = domUtils.getNextDomNode(bookmark.start, false, filterFn);
+ while (current && current !== end && (domUtils.getPosition(current, end) & domUtils.POSITION_PRECEDING)) {
+ var tmpNode = domUtils.getNextDomNode(current,false,filterFn);
+ doFn(current);
+ current = tmpNode;
+ }
+ return this.moveToBookmark(bookmark);
+ }
+ };
+})();
+
+// core/Selection.js
+/**
+ * 选集
+ * @file
+ * @module UE.dom
+ * @class Selection
+ * @since 1.2.6.1
+ */
+
+/**
+ * 选区集合
+ * @unfile
+ * @module UE.dom
+ * @class Selection
+ */
+(function () {
+
+ function getBoundaryInformation( range, start ) {
+ var getIndex = domUtils.getNodeIndex;
+ range = range.duplicate();
+ range.collapse( start );
+ var parent = range.parentElement();
+ //如果节点里没有子节点,直接退出
+ if ( !parent.hasChildNodes() ) {
+ return {container:parent, offset:0};
+ }
+ var siblings = parent.children,
+ child,
+ testRange = range.duplicate(),
+ startIndex = 0, endIndex = siblings.length - 1, index = -1,
+ distance;
+ while ( startIndex <= endIndex ) {
+ index = Math.floor( (startIndex + endIndex) / 2 );
+ child = siblings[index];
+ testRange.moveToElementText( child );
+ var position = testRange.compareEndPoints( 'StartToStart', range );
+ if ( position > 0 ) {
+ endIndex = index - 1;
+ } else if ( position < 0 ) {
+ startIndex = index + 1;
+ } else {
+ //trace:1043
+ return {container:parent, offset:getIndex( child )};
+ }
+ }
+ if ( index == -1 ) {
+ testRange.moveToElementText( parent );
+ testRange.setEndPoint( 'StartToStart', range );
+ distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length;
+ siblings = parent.childNodes;
+ if ( !distance ) {
+ child = siblings[siblings.length - 1];
+ return {container:child, offset:child.nodeValue.length};
+ }
+
+ var i = siblings.length;
+ while ( distance > 0 ){
+ distance -= siblings[ --i ].nodeValue.length;
+ }
+ return {container:siblings[i], offset:-distance};
+ }
+ testRange.collapse( position > 0 );
+ testRange.setEndPoint( position > 0 ? 'StartToStart' : 'EndToStart', range );
+ distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length;
+ if ( !distance ) {
+ return dtd.$empty[child.tagName] || dtd.$nonChild[child.tagName] ?
+ {container:parent, offset:getIndex( child ) + (position > 0 ? 0 : 1)} :
+ {container:child, offset:position > 0 ? 0 : child.childNodes.length}
+ }
+ while ( distance > 0 ) {
+ try {
+ var pre = child;
+ child = child[position > 0 ? 'previousSibling' : 'nextSibling'];
+ distance -= child.nodeValue.length;
+ } catch ( e ) {
+ return {container:parent, offset:getIndex( pre )};
+ }
+ }
+ return {container:child, offset:position > 0 ? -distance : child.nodeValue.length + distance}
+ }
+
+ /**
+ * 将ieRange转换为Range对象
+ * @param {Range} ieRange ieRange对象
+ * @param {Range} range Range对象
+ * @return {Range} range 返回转换后的Range对象
+ */
+ function transformIERangeToRange( ieRange, range ) {
+ if ( ieRange.item ) {
+ range.selectNode( ieRange.item( 0 ) );
+ } else {
+ var bi = getBoundaryInformation( ieRange, true );
+ range.setStart( bi.container, bi.offset );
+ if ( ieRange.compareEndPoints( 'StartToEnd', ieRange ) != 0 ) {
+ bi = getBoundaryInformation( ieRange, false );
+ range.setEnd( bi.container, bi.offset );
+ }
+ }
+ return range;
+ }
+
+ /**
+ * 获得ieRange
+ * @param {Selection} sel Selection对象
+ * @return {ieRange} 得到ieRange
+ */
+ function _getIERange( sel ) {
+ var ieRange;
+ //ie下有可能报错
+ try {
+ ieRange = sel.getNative().createRange();
+ } catch ( e ) {
+ return null;
+ }
+ var el = ieRange.item ? ieRange.item( 0 ) : ieRange.parentElement();
+ if ( ( el.ownerDocument || el ) === sel.document ) {
+ return ieRange;
+ }
+ return null;
+ }
+
+ var Selection = dom.Selection = function ( doc ) {
+ var me = this, iframe;
+ me.document = doc;
+ if ( browser.ie9below ) {
+ iframe = domUtils.getWindow( doc ).frameElement;
+ domUtils.on( iframe, 'beforedeactivate', function () {
+ me._bakIERange = me.getIERange();
+ } );
+ domUtils.on( iframe, 'activate', function () {
+ try {
+ if ( !_getIERange( me ) && me._bakIERange ) {
+ me._bakIERange.select();
+ }
+ } catch ( ex ) {
+ }
+ me._bakIERange = null;
+ } );
+ }
+ iframe = doc = null;
+ };
+
+ Selection.prototype = {
+
+ rangeInBody : function(rng,txtRange){
+ var node = browser.ie9below || txtRange ? rng.item ? rng.item() : rng.parentElement() : rng.startContainer;
+
+ return node === this.document.body || domUtils.inDoc(node,this.document);
+ },
+
+ /**
+ * 获取原生seleciton对象
+ * @method getNative
+ * @return { Object } 获得selection对象
+ * @example
+ * ```javascript
+ * editor.selection.getNative();
+ * ```
+ */
+ getNative:function () {
+ var doc = this.document;
+ try {
+ return !doc ? null : browser.ie9below ? doc.selection : domUtils.getWindow( doc ).getSelection();
+ } catch ( e ) {
+ return null;
+ }
+ },
+
+ /**
+ * 获得ieRange
+ * @method getIERange
+ * @return { Object } 返回ie原生的Range
+ * @example
+ * ```javascript
+ * editor.selection.getIERange();
+ * ```
+ */
+ getIERange:function () {
+ var ieRange = _getIERange( this );
+ if ( !ieRange ) {
+ if ( this._bakIERange ) {
+ return this._bakIERange;
+ }
+ }
+ return ieRange;
+ },
+
+ /**
+ * 缓存当前选区的range和选区的开始节点
+ * @method cache
+ */
+ cache:function () {
+ this.clear();
+ this._cachedRange = this.getRange();
+ this._cachedStartElement = this.getStart();
+ this._cachedStartElementPath = this.getStartElementPath();
+ },
+
+ /**
+ * 获取选区开始位置的父节点到body
+ * @method getStartElementPath
+ * @return { Array } 返回父节点集合
+ * @example
+ * ```javascript
+ * editor.selection.getStartElementPath();
+ * ```
+ */
+ getStartElementPath:function () {
+ if ( this._cachedStartElementPath ) {
+ return this._cachedStartElementPath;
+ }
+ var start = this.getStart();
+ if ( start ) {
+ return domUtils.findParents( start, true, null, true )
+ }
+ return [];
+ },
+
+ /**
+ * 清空缓存
+ * @method clear
+ */
+ clear:function () {
+ this._cachedStartElementPath = this._cachedRange = this._cachedStartElement = null;
+ },
+
+ /**
+ * 编辑器是否得到了选区
+ * @method isFocus
+ */
+ isFocus:function () {
+ try {
+ if(browser.ie9below){
+
+ var nativeRange = _getIERange(this);
+ return !!(nativeRange && this.rangeInBody(nativeRange));
+ }else{
+ return !!this.getNative().rangeCount;
+ }
+ } catch ( e ) {
+ return false;
+ }
+
+ },
+
+ /**
+ * 获取选区对应的Range
+ * @method getRange
+ * @return { Object } 得到Range对象
+ * @example
+ * ```javascript
+ * editor.selection.getRange();
+ * ```
+ */
+ getRange:function () {
+ var me = this;
+ function optimze( range ) {
+ var child = me.document.body.firstChild,
+ collapsed = range.collapsed;
+ while ( child && child.firstChild ) {
+ range.setStart( child, 0 );
+ child = child.firstChild;
+ }
+ if ( !range.startContainer ) {
+ range.setStart( me.document.body, 0 )
+ }
+ if ( collapsed ) {
+ range.collapse( true );
+ }
+ }
+
+ if ( me._cachedRange != null ) {
+ return this._cachedRange;
+ }
+ var range = new baidu.editor.dom.Range( me.document );
+
+ if ( browser.ie9below ) {
+ var nativeRange = me.getIERange();
+ if ( nativeRange ) {
+ //备份的_bakIERange可能已经实效了,dom树发生了变化比如从源码模式切回来,所以try一下,实效就放到body开始位置
+ try{
+ transformIERangeToRange( nativeRange, range );
+ }catch(e){
+ optimze( range );
+ }
+
+ } else {
+ optimze( range );
+ }
+ } else {
+ var sel = me.getNative();
+ if ( sel && sel.rangeCount ) {
+ var firstRange = sel.getRangeAt( 0 );
+ var lastRange = sel.getRangeAt( sel.rangeCount - 1 );
+ range.setStart( firstRange.startContainer, firstRange.startOffset ).setEnd( lastRange.endContainer, lastRange.endOffset );
+ if ( range.collapsed && domUtils.isBody( range.startContainer ) && !range.startOffset ) {
+ optimze( range );
+ }
+ } else {
+ //trace:1734 有可能已经不在dom树上了,标识的节点
+ if ( this._bakRange && domUtils.inDoc( this._bakRange.startContainer, this.document ) ){
+ return this._bakRange;
+ }
+ optimze( range );
+ }
+ }
+ return this._bakRange = range;
+ },
+
+ /**
+ * 获取开始元素,用于状态反射
+ * @method getStart
+ * @return { Element } 获得开始元素
+ * @example
+ * ```javascript
+ * editor.selection.getStart();
+ * ```
+ */
+ getStart:function () {
+ if ( this._cachedStartElement ) {
+ return this._cachedStartElement;
+ }
+ var range = browser.ie9below ? this.getIERange() : this.getRange(),
+ tmpRange,
+ start, tmp, parent;
+ if ( browser.ie9below ) {
+ if ( !range ) {
+ //todo 给第一个值可能会有问题
+ return this.document.body.firstChild;
+ }
+ //control元素
+ if ( range.item ){
+ return range.item( 0 );
+ }
+ tmpRange = range.duplicate();
+ //修正ie下x[xx] 闭合后 x|xx
+ tmpRange.text.length > 0 && tmpRange.moveStart( 'character', 1 );
+ tmpRange.collapse( 1 );
+ start = tmpRange.parentElement();
+ parent = tmp = range.parentElement();
+ while ( tmp = tmp.parentNode ) {
+ if ( tmp == start ) {
+ start = parent;
+ break;
+ }
+ }
+ } else {
+ range.shrinkBoundary();
+ start = range.startContainer;
+ if ( start.nodeType == 1 && start.hasChildNodes() ){
+ start = start.childNodes[Math.min( start.childNodes.length - 1, range.startOffset )];
+ }
+ if ( start.nodeType == 3 ){
+ return start.parentNode;
+ }
+ }
+ return start;
+ },
+
+ /**
+ * 得到选区中的文本
+ * @method getText
+ * @return { String } 选区中包含的文本
+ * @example
+ * ```javascript
+ * editor.selection.getText();
+ * ```
+ */
+ getText:function () {
+ var nativeSel, nativeRange;
+ if ( this.isFocus() && (nativeSel = this.getNative()) ) {
+ nativeRange = browser.ie9below ? nativeSel.createRange() : nativeSel.getRangeAt( 0 );
+ return browser.ie9below ? nativeRange.text : nativeRange.toString();
+ }
+ return '';
+ },
+
+ /**
+ * 清除选区
+ * @method clearRange
+ * @example
+ * ```javascript
+ * editor.selection.clearRange();
+ * ```
+ */
+ clearRange : function(){
+ this.getNative()[browser.ie9below ? 'empty' : 'removeAllRanges']();
+ }
+ };
+})();
+
+// core/Editor.js
+/**
+ * 编辑器主类,包含编辑器提供的大部分公用接口
+ * @file
+ * @module UE
+ * @class Editor
+ * @since 1.2.6.1
+ */
+
+/**
+ * UEditor公用空间,UEditor所有的功能都挂载在该空间下
+ * @unfile
+ * @module UE
+ */
+
+/**
+ * UEditor的核心类,为用户提供与编辑器交互的接口。
+ * @unfile
+ * @module UE
+ * @class Editor
+ */
+
+(function () {
+ var uid = 0, _selectionChangeTimer;
+
+ /**
+ * 获取编辑器的html内容,赋值到编辑器所在表单的textarea文本域里面
+ * @private
+ * @method setValue
+ * @param { UE.Editor } editor 编辑器事例
+ */
+ function setValue(form, editor) {
+ var textarea;
+ if (editor.textarea) {
+ if (utils.isString(editor.textarea)) {
+ for (var i = 0, ti, tis = domUtils.getElementsByTagName(form, 'textarea'); ti = tis[i++];) {
+ if (ti.id == 'ueditor_textarea_' + editor.options.textarea) {
+ textarea = ti;
+ break;
+ }
+ }
+ } else {
+ textarea = editor.textarea;
+ }
+ }
+ if (!textarea) {
+ form.appendChild(textarea = domUtils.createElement(document, 'textarea', {
+ 'name': editor.options.textarea,
+ 'id': 'ueditor_textarea_' + editor.options.textarea,
+ 'style': "display:none"
+ }));
+ //不要产生多个textarea
+ editor.textarea = textarea;
+ }
+ !textarea.getAttribute('name') && textarea.setAttribute('name', editor.options.textarea );
+ textarea.value = editor.hasContents() ?
+ (editor.options.allHtmlEnabled ? editor.getAllHtml() : editor.getContent(null, null, true)) :
+ ''
+ }
+ function loadPlugins(me){
+ //初始化插件
+ for (var pi in UE.plugins) {
+ UE.plugins[pi].call(me);
+ }
+
+ }
+ function checkCurLang(I18N){
+ for(var lang in I18N){
+ return lang
+ }
+ }
+
+ function langReadied(me){
+ me.langIsReady = true;
+
+ me.fireEvent("langReady");
+ }
+
+ /**
+ * 编辑器准备就绪后会触发该事件
+ * @module UE
+ * @class Editor
+ * @event ready
+ * @remind render方法执行完成之后,会触发该事件
+ * @remind
+ * @example
+ * ```javascript
+ * editor.addListener( 'ready', function( editor ) {
+ * editor.execCommand( 'focus' ); //编辑器家在完成后,让编辑器拿到焦点
+ * } );
+ * ```
+ */
+ /**
+ * 执行destroy方法,会触发该事件
+ * @module UE
+ * @class Editor
+ * @event destroy
+ * @see UE.Editor:destroy()
+ */
+ /**
+ * 执行reset方法,会触发该事件
+ * @module UE
+ * @class Editor
+ * @event reset
+ * @see UE.Editor:reset()
+ */
+ /**
+ * 执行focus方法,会触发该事件
+ * @module UE
+ * @class Editor
+ * @event focus
+ * @see UE.Editor:focus(Boolean)
+ */
+ /**
+ * 语言加载完成会触发该事件
+ * @module UE
+ * @class Editor
+ * @event langReady
+ */
+ /**
+ * 运行命令之后会触发该命令
+ * @module UE
+ * @class Editor
+ * @event beforeExecCommand
+ */
+ /**
+ * 运行命令之后会触发该命令
+ * @module UE
+ * @class Editor
+ * @event afterExecCommand
+ */
+ /**
+ * 运行命令之前会触发该命令
+ * @module UE
+ * @class Editor
+ * @event firstBeforeExecCommand
+ */
+ /**
+ * 在getContent方法执行之前会触发该事件
+ * @module UE
+ * @class Editor
+ * @event beforeGetContent
+ * @see UE.Editor:getContent()
+ */
+ /**
+ * 在getContent方法执行之后会触发该事件
+ * @module UE
+ * @class Editor
+ * @event afterGetContent
+ * @see UE.Editor:getContent()
+ */
+ /**
+ * 在getAllHtml方法执行时会触发该事件
+ * @module UE
+ * @class Editor
+ * @event getAllHtml
+ * @see UE.Editor:getAllHtml()
+ */
+ /**
+ * 在setContent方法执行之前会触发该事件
+ * @module UE
+ * @class Editor
+ * @event beforeSetContent
+ * @see UE.Editor:setContent(String)
+ */
+ /**
+ * 在setContent方法执行之后会触发该事件
+ * @module UE
+ * @class Editor
+ * @event afterSetContent
+ * @see UE.Editor:setContent(String)
+ */
+ /**
+ * 每当编辑器内部选区发生改变时,将触发该事件
+ * @event selectionchange
+ * @warning 该事件的触发非常频繁,不建议在该事件的处理过程中做重量级的处理
+ * @example
+ * ```javascript
+ * editor.addListener( 'selectionchange', function( editor ) {
+ * console.log('选区发生改变');
+ * }
+ */
+ /**
+ * 在所有selectionchange的监听函数执行之前,会触发该事件
+ * @module UE
+ * @class Editor
+ * @event beforeSelectionChange
+ * @see UE.Editor:selectionchange
+ */
+ /**
+ * 在所有selectionchange的监听函数执行完之后,会触发该事件
+ * @module UE
+ * @class Editor
+ * @event afterSelectionChange
+ * @see UE.Editor:selectionchange
+ */
+ /**
+ * 编辑器内容发生改变时会触发该事件
+ * @module UE
+ * @class Editor
+ * @event contentChange
+ */
+
+
+ /**
+ * 以默认参数构建一个编辑器实例
+ * @constructor
+ * @remind 通过 改构造方法实例化的编辑器,不带ui层.需要render到一个容器,编辑器实例才能正常渲染到页面
+ * @example
+ * ```javascript
+ * var editor = new UE.Editor();
+ * editor.execCommand('blod');
+ * ```
+ * @see UE.Config
+ */
+
+ /**
+ * 以给定的参数集合创建一个编辑器实例,对于未指定的参数,将应用默认参数。
+ * @constructor
+ * @remind 通过 改构造方法实例化的编辑器,不带ui层.需要render到一个容器,编辑器实例才能正常渲染到页面
+ * @param { Object } setting 创建编辑器的参数
+ * @example
+ * ```javascript
+ * var editor = new UE.Editor();
+ * editor.execCommand('blod');
+ * ```
+ * @see UE.Config
+ */
+ var Editor = UE.Editor = function (options) {
+ var me = this;
+ me.uid = uid++;
+ EventBase.call(me);
+ me.commands = {};
+ me.options = utils.extend(utils.clone(options || {}), UEDITOR_CONFIG, true);
+ me.shortcutkeys = {};
+ me.inputRules = [];
+ me.outputRules = [];
+ //设置默认的常用属性
+ me.setOpt(Editor.defaultOptions(me));
+
+ /* 尝试异步加载后台配置 */
+ me.loadServerConfig();
+
+ if(!utils.isEmptyObject(UE.I18N)){
+ //修改默认的语言类型
+ me.options.lang = checkCurLang(UE.I18N);
+ UE.plugin.load(me);
+ langReadied(me);
+
+ }else{
+ utils.loadFile(document, {
+ src: me.options.langPath + me.options.lang + "/" + me.options.lang + ".js",
+ tag: "script",
+ type: "text/javascript",
+ defer: "defer"
+ }, function () {
+ UE.plugin.load(me);
+ langReadied(me);
+ });
+ }
+
+ UE.instants['ueditorInstant' + me.uid] = me;
+ };
+ Editor.prototype = {
+ registerCommand : function(name,obj){
+ this.commands[name] = obj;
+ },
+ /**
+ * 编辑器对外提供的监听ready事件的接口, 通过调用该方法,达到的效果与监听ready事件是一致的
+ * @method ready
+ * @param { Function } fn 编辑器ready之后所执行的回调, 如果在注册事件之前编辑器已经ready,将会
+ * 立即触发该回调。
+ * @remind 需要等待编辑器加载完成后才能执行的代码,可以使用该方法传入
+ * @example
+ * ```javascript
+ * editor.ready( function( editor ) {
+ * editor.setContent('初始化完毕');
+ * } );
+ * ```
+ * @see UE.Editor.event:ready
+ */
+ ready: function (fn) {
+ var me = this;
+ if (fn) {
+ me.isReady ? fn.apply(me) : me.addListener('ready', fn);
+ }
+ },
+
+ /**
+ * 该方法是提供给插件里面使用,设置配置项默认值
+ * @method setOpt
+ * @warning 三处设置配置项的优先级: 实例化时传入参数 > setOpt()设置 > config文件里设置
+ * @warning 该方法仅供编辑器插件内部和编辑器初始化时调用,其他地方不能调用。
+ * @param { String } key 编辑器的可接受的选项名称
+ * @param { * } val 该选项可接受的值
+ * @example
+ * ```javascript
+ * editor.setOpt( 'initContent', '欢迎使用编辑器' );
+ * ```
+ */
+
+ /**
+ * 该方法是提供给插件里面使用,以{key:value}集合的方式设置插件内用到的配置项默认值
+ * @method setOpt
+ * @warning 三处设置配置项的优先级: 实例化时传入参数 > setOpt()设置 > config文件里设置
+ * @warning 该方法仅供编辑器插件内部和编辑器初始化时调用,其他地方不能调用。
+ * @param { Object } options 将要设置的选项的键值对对象
+ * @example
+ * ```javascript
+ * editor.setOpt( {
+ * 'initContent': '欢迎使用编辑器'
+ * } );
+ * ```
+ */
+ setOpt: function (key, val) {
+ var obj = {};
+ if (utils.isString(key)) {
+ obj[key] = val
+ } else {
+ obj = key;
+ }
+ utils.extend(this.options, obj, true);
+ },
+ getOpt:function(key){
+ return this.options[key]
+ },
+ /**
+ * 销毁编辑器实例,使用textarea代替
+ * @method destroy
+ * @example
+ * ```javascript
+ * editor.destroy();
+ * ```
+ */
+ destroy: function () {
+
+ var me = this;
+ me.fireEvent('destroy');
+ var container = me.container.parentNode;
+ var textarea = me.textarea;
+ if (!textarea) {
+ textarea = document.createElement('textarea');
+ container.parentNode.insertBefore(textarea, container);
+ } else {
+ textarea.style.display = ''
+ }
+
+ textarea.style.width = me.iframe.offsetWidth + 'px';
+ textarea.style.height = me.iframe.offsetHeight + 'px';
+ textarea.value = me.getContent();
+ textarea.id = me.key;
+ container.innerHTML = '';
+ domUtils.remove(container);
+ var key = me.key;
+ //trace:2004
+ for (var p in me) {
+ if (me.hasOwnProperty(p)) {
+ delete this[p];
+ }
+ }
+ UE.delEditor(key);
+ },
+
+ /**
+ * 渲染编辑器的DOM到指定容器
+ * @method render
+ * @param { String } containerId 指定一个容器ID
+ * @remind 执行该方法,会触发ready事件
+ * @warning 必须且只能调用一次
+ */
+
+ /**
+ * 渲染编辑器的DOM到指定容器
+ * @method render
+ * @param { Element } containerDom 直接指定容器对象
+ * @remind 执行该方法,会触发ready事件
+ * @warning 必须且只能调用一次
+ */
+ render: function (container) {
+ var me = this,
+ options = me.options,
+ getStyleValue=function(attr){
+ return parseInt(domUtils.getComputedStyle(container,attr));
+ };
+ if (utils.isString(container)) {
+ container = document.getElementById(container);
+ }
+ if (container) {
+ if(options.initialFrameWidth){
+ options.minFrameWidth = options.initialFrameWidth
+ }else{
+ options.minFrameWidth = options.initialFrameWidth = container.offsetWidth;
+ }
+ if(options.initialFrameHeight){
+ options.minFrameHeight = options.initialFrameHeight
+ }else{
+ options.initialFrameHeight = options.minFrameHeight = container.offsetHeight;
+ }
+
+ container.style.width = /%$/.test(options.initialFrameWidth) ? '100%' : options.initialFrameWidth-
+ getStyleValue("padding-left")- getStyleValue("padding-right") +'px';
+ container.style.height = /%$/.test(options.initialFrameHeight) ? '100%' : options.initialFrameHeight -
+ getStyleValue("padding-top")- getStyleValue("padding-bottom") +'px';
+
+ container.style.zIndex = options.zIndex;
+
+ var html = ( ie && browser.version < 9 ? '' : '') +
+ '' +
+ '' +
+ ( options.iframeCssUrl ? '' : '' ) +
+ (options.initialStyle ? '' : '') +
+ '' +
+ '';
+ container.appendChild(domUtils.createElement(document, 'iframe', {
+ id: 'ueditor_' + me.uid,
+ width: "100%",
+ height: "100%",
+ frameborder: "0",
+ //先注释掉了,加的原因忘记了,但开启会直接导致全屏模式下内容多时不会出现滚动条
+// scrolling :'no',
+ src: 'javascript:void(function(){document.open();' + (options.customDomain && document.domain != location.hostname ? 'document.domain="' + document.domain + '";' : '') +
+ 'document.write("' + html + '");document.close();}())'
+ }));
+ container.style.overflow = 'hidden';
+ //解决如果是给定的百分比,会导致高度算不对的问题
+ setTimeout(function(){
+ if( /%$/.test(options.initialFrameWidth)){
+ options.minFrameWidth = options.initialFrameWidth = container.offsetWidth;
+ //如果这里给定宽度,会导致ie在拖动窗口大小时,编辑区域不随着变化
+// container.style.width = options.initialFrameWidth + 'px';
+ }
+ if(/%$/.test(options.initialFrameHeight)){
+ options.minFrameHeight = options.initialFrameHeight = container.offsetHeight;
+ container.style.height = options.initialFrameHeight + 'px';
+ }
+ })
+ }
+ },
+
+ /**
+ * 编辑器初始化
+ * @method _setup
+ * @private
+ * @param { Element } doc 编辑器Iframe中的文档对象
+ */
+ _setup: function (doc) {
+
+ var me = this,
+ options = me.options;
+ if (ie) {
+ doc.body.disabled = true;
+ doc.body.contentEditable = true;
+ doc.body.disabled = false;
+ } else {
+ doc.body.contentEditable = true;
+ }
+ doc.body.spellcheck = false;
+ me.document = doc;
+ me.window = doc.defaultView || doc.parentWindow;
+ me.iframe = me.window.frameElement;
+ me.body = doc.body;
+ me.selection = new dom.Selection(doc);
+ //gecko初始化就能得到range,无法判断isFocus了
+ var geckoSel;
+ if (browser.gecko && (geckoSel = this.selection.getNative())) {
+ geckoSel.removeAllRanges();
+ }
+ this._initEvents();
+ //为form提交提供一个隐藏的textarea
+ for (var form = this.iframe.parentNode; !domUtils.isBody(form); form = form.parentNode) {
+ if (form.tagName == 'FORM') {
+ me.form = form;
+ if(me.options.autoSyncData){
+ domUtils.on(me.window,'blur',function(){
+ setValue(form,me);
+ });
+ }else{
+ domUtils.on(form, 'submit', function () {
+ setValue(this, me);
+ });
+ }
+ break;
+ }
+ }
+ if (options.initialContent) {
+ if (options.autoClearinitialContent) {
+ var oldExecCommand = me.execCommand;
+ me.execCommand = function () {
+ me.fireEvent('firstBeforeExecCommand');
+ return oldExecCommand.apply(me, arguments);
+ };
+ this._setDefaultContent(options.initialContent);
+ } else
+ this.setContent(options.initialContent, false, true);
+ }
+
+ //编辑器不能为空内容
+
+ if (domUtils.isEmptyNode(me.body)) {
+ me.body.innerHTML = '' + (browser.ie ? '' : ' ') + ' ';
+ }
+ //如果要求focus, 就把光标定位到内容开始
+ if (options.focus) {
+ setTimeout(function () {
+ me.focus(me.options.focusInEnd);
+ //如果自动清除开着,就不需要做selectionchange;
+ !me.options.autoClearinitialContent && me._selectionChange();
+ }, 0);
+ }
+ if (!me.container) {
+ me.container = this.iframe.parentNode;
+ }
+ if (options.fullscreen && me.ui) {
+ me.ui.setFullScreen(true);
+ }
+
+ try {
+ me.document.execCommand('2D-position', false, false);
+ } catch (e) {
+ }
+ try {
+ me.document.execCommand('enableInlineTableEditing', false, false);
+ } catch (e) {
+ }
+ try {
+ me.document.execCommand('enableObjectResizing', false, false);
+ } catch (e) {
+ }
+
+ //挂接快捷键
+ me._bindshortcutKeys();
+ me.isReady = 1;
+ me.fireEvent('ready');
+ options.onready && options.onready.call(me);
+ if (!browser.ie9below) {
+ domUtils.on(me.window, ['blur', 'focus'], function (e) {
+ //chrome下会出现alt+tab切换时,导致选区位置不对
+ if (e.type == 'blur') {
+ me._bakRange = me.selection.getRange();
+ try {
+ me._bakNativeRange = me.selection.getNative().getRangeAt(0);
+ me.selection.getNative().removeAllRanges();
+ } catch (e) {
+ me._bakNativeRange = null;
+ }
+
+ } else {
+ try {
+ me._bakRange && me._bakRange.select();
+ } catch (e) {
+ }
+ }
+ });
+ }
+ //trace:1518 ff3.6body不够寛,会导致点击空白处无法获得焦点
+ if (browser.gecko && browser.version <= 10902) {
+ //修复ff3.6初始化进来,不能点击获得焦点
+ me.body.contentEditable = false;
+ setTimeout(function () {
+ me.body.contentEditable = true;
+ }, 100);
+ setInterval(function () {
+ me.body.style.height = me.iframe.offsetHeight - 20 + 'px'
+ }, 100)
+ }
+
+ !options.isShow && me.setHide();
+ options.readonly && me.setDisabled();
+ },
+
+ /**
+ * 同步数据到编辑器所在的form
+ * 从编辑器的容器节点向上查找form元素,若找到,就同步编辑内容到找到的form里,为提交数据做准备,主要用于是手动提交的情况
+ * 后台取得数据的键值,使用你容器上的name属性,如果没有就使用参数里的textarea项
+ * @method sync
+ * @example
+ * ```javascript
+ * editor.sync();
+ * form.sumbit(); //form变量已经指向了form元素
+ * ```
+ */
+
+ /**
+ * 根据传入的formId,在页面上查找要同步数据的表单,若找到,就同步编辑内容到找到的form里,为提交数据做准备
+ * 后台取得数据的键值,该键值默认使用给定的编辑器容器的name属性,如果没有name属性则使用参数项里给定的“textarea”项
+ * @method sync
+ * @param { String } formID 指定一个要同步数据的form的id,编辑器的数据会同步到你指定form下
+ */
+ sync: function (formId) {
+ var me = this,
+ form = formId ? document.getElementById(formId) :
+ domUtils.findParent(me.iframe.parentNode, function (node) {
+ return node.tagName == 'FORM'
+ }, true);
+ form && setValue(form, me);
+ },
+
+ /**
+ * 设置编辑器高度
+ * @method setHeight
+ * @remind 当配置项autoHeightEnabled为真时,该方法无效
+ * @param { Number } number 设置的高度值,纯数值,不带单位
+ * @example
+ * ```javascript
+ * editor.setHeight(number);
+ * ```
+ */
+ setHeight: function (height,notSetHeight) {
+ if (height !== parseInt(this.iframe.parentNode.style.height)) {
+ this.iframe.parentNode.style.height = height + 'px';
+ }
+ !notSetHeight && (this.options.minFrameHeight = this.options.initialFrameHeight = height);
+ this.body.style.height = height + 'px';
+ !notSetHeight && this.trigger('setHeight')
+ },
+
+ /**
+ * 为编辑器的编辑命令提供快捷键
+ * 这个接口是为插件扩展提供的接口,主要是为新添加的插件,如果需要添加快捷键,所提供的接口
+ * @method addshortcutkey
+ * @param { Object } keyset 命令名和快捷键键值对对象,多个按钮的快捷键用“+”分隔
+ * @example
+ * ```javascript
+ * editor.addshortcutkey({
+ * "Bold" : "ctrl+66",//^B
+ * "Italic" : "ctrl+73", //^I
+ * });
+ * ```
+ */
+ /**
+ * 这个接口是为插件扩展提供的接口,主要是为新添加的插件,如果需要添加快捷键,所提供的接口
+ * @method addshortcutkey
+ * @param { String } cmd 触发快捷键时,响应的命令
+ * @param { String } keys 快捷键的字符串,多个按钮用“+”分隔
+ * @example
+ * ```javascript
+ * editor.addshortcutkey("Underline", "ctrl+85"); //^U
+ * ```
+ */
+ addshortcutkey: function (cmd, keys) {
+ var obj = {};
+ if (keys) {
+ obj[cmd] = keys
+ } else {
+ obj = cmd;
+ }
+ utils.extend(this.shortcutkeys, obj)
+ },
+
+ /**
+ * 对编辑器设置keydown事件监听,绑定快捷键和命令,当快捷键组合触发成功,会响应对应的命令
+ * @method _bindshortcutKeys
+ * @private
+ */
+ _bindshortcutKeys: function () {
+ var me = this, shortcutkeys = this.shortcutkeys;
+ me.addListener('keydown', function (type, e) {
+ var keyCode = e.keyCode || e.which;
+ for (var i in shortcutkeys) {
+ var tmp = shortcutkeys[i].split(',');
+ for (var t = 0, ti; ti = tmp[t++];) {
+ ti = ti.split(':');
+ var key = ti[0], param = ti[1];
+ if (/^(ctrl)(\+shift)?\+(\d+)$/.test(key.toLowerCase()) || /^(\d+)$/.test(key)) {
+ if (( (RegExp.$1 == 'ctrl' ? (e.ctrlKey || e.metaKey) : 0)
+ && (RegExp.$2 != "" ? e[RegExp.$2.slice(1) + "Key"] : 1)
+ && keyCode == RegExp.$3
+ ) ||
+ keyCode == RegExp.$1
+ ) {
+ if (me.queryCommandState(i,param) != -1)
+ me.execCommand(i, param);
+ domUtils.preventDefault(e);
+ }
+ }
+ }
+
+ }
+ });
+ },
+
+ /**
+ * 获取编辑器的内容
+ * @method getContent
+ * @warning 该方法获取到的是经过编辑器内置的过滤规则进行过滤后得到的内容
+ * @return { String } 编辑器的内容字符串, 如果编辑器的内容为空,或者是空的标签内容(如:”<p><br/></p>“), 则返回空字符串
+ * @example
+ * ```javascript
+ * //编辑器html内容:123456
+ * var content = editor.getContent(); //返回值:123456
+ * ```
+ */
+
+ /**
+ * 获取编辑器的内容。 可以通过参数定义编辑器内置的判空规则
+ * @method getContent
+ * @param { Function } fn 自定的判空规则, 要求该方法返回一个boolean类型的值,
+ * 代表当前编辑器的内容是否空,
+ * 如果返回true, 则该方法将直接返回空字符串;如果返回false,则编辑器将返回
+ * 经过内置过滤规则处理后的内容。
+ * @remind 该方法在处理包含有初始化内容的时候能起到很好的作用。
+ * @warning 该方法获取到的是经过编辑器内置的过滤规则进行过滤后得到的内容
+ * @return { String } 编辑器的内容字符串
+ * @example
+ * ```javascript
+ * // editor 是一个编辑器的实例
+ * var content = editor.getContent( function ( editor ) {
+ * return editor.body.innerHTML === '欢迎使用UEditor'; //返回空字符串
+ * } );
+ * ```
+ */
+ getContent: function (cmd, fn,notSetCursor,ignoreBlank,formatter) {
+ var me = this;
+ if (cmd && utils.isFunction(cmd)) {
+ fn = cmd;
+ cmd = '';
+ }
+ if (fn ? !fn() : !this.hasContents()) {
+ return '';
+ }
+ me.fireEvent('beforegetcontent');
+ var root = UE.htmlparser(me.body.innerHTML,ignoreBlank);
+ me.filterOutputRule(root);
+ me.fireEvent('aftergetcontent', cmd,root);
+ return root.toHtml(formatter);
+ },
+
+ /**
+ * 取得完整的html代码,可以直接显示成完整的html文档
+ * @method getAllHtml
+ * @return { String } 编辑器的内容html文档字符串
+ * @eaxmple
+ * ```javascript
+ * editor.getAllHtml(); //返回格式大致是: ......
+ * ```
+ */
+ getAllHtml: function () {
+ var me = this,
+ headHtml = [],
+ html = '';
+ me.fireEvent('getAllHtml', headHtml);
+ if (browser.ie && browser.version > 8) {
+ var headHtmlForIE9 = '';
+ utils.each(me.document.styleSheets, function (si) {
+ headHtmlForIE9 += ( si.href ? '' : '');
+ });
+ utils.each(me.document.getElementsByTagName('script'), function (si) {
+ headHtmlForIE9 += si.outerHTML;
+ });
+
+ }
+ return '' + (me.options.charset ? '' : '')
+ + (headHtmlForIE9 || me.document.getElementsByTagName('head')[0].innerHTML) + headHtml.join('\n') + ''
+ + '' + me.getContent(null, null, true) + '';
+ },
+
+ /**
+ * 得到编辑器的纯文本内容,但会保留段落格式
+ * @method getPlainTxt
+ * @return { String } 编辑器带段落格式的纯文本内容字符串
+ * @example
+ * ```javascript
+ * //编辑器html内容:1 2
+ * console.log(editor.getPlainTxt()); //输出:"1\n2\n
+ * ```
+ */
+ getPlainTxt: function () {
+ var reg = new RegExp(domUtils.fillChar, 'g'),
+ html = this.body.innerHTML.replace(/[\n\r]/g, '');//ie要先去了\n在处理
+ html = html.replace(/<(p|div)[^>]*>( | )<\/\1>/gi, '\n')
+ .replace(/ /gi, '\n')
+ .replace(/<[^>/]+>/g, '')
+ .replace(/(\n)?<\/([^>]+)>/g, function (a, b, c) {
+ return dtd.$block[c] ? '\n' : b ? b : '';
+ });
+ //取出来的空格会有c2a0会变成乱码,处理这种情况\u00a0
+ return html.replace(reg, '').replace(/\u00a0/g, ' ').replace(/ /g, ' ');
+ },
+
+ /**
+ * 获取编辑器中的纯文本内容,没有段落格式
+ * @method getContentTxt
+ * @return { String } 编辑器不带段落格式的纯文本内容字符串
+ * @example
+ * ```javascript
+ * //编辑器html内容:1 2
+ * console.log(editor.getPlainTxt()); //输出:"12
+ * ```
+ */
+ getContentTxt: function () {
+ var reg = new RegExp(domUtils.fillChar, 'g');
+ //取出来的空格会有c2a0会变成乱码,处理这种情况\u00a0
+ return this.body[browser.ie ? 'innerText' : 'textContent'].replace(reg, '').replace(/\u00a0/g, ' ');
+ },
+
+ /**
+ * 设置编辑器的内容,可修改编辑器当前的html内容
+ * @method setContent
+ * @warning 通过该方法插入的内容,是经过编辑器内置的过滤规则进行过滤后得到的内容
+ * @warning 该方法会触发selectionchange事件
+ * @param { String } html 要插入的html内容
+ * @example
+ * ```javascript
+ * editor.getContent('test ');
+ * ```
+ */
+
+ /**
+ * 设置编辑器的内容,可修改编辑器当前的html内容
+ * @method setContent
+ * @warning 通过该方法插入的内容,是经过编辑器内置的过滤规则进行过滤后得到的内容
+ * @warning 该方法会触发selectionchange事件
+ * @param { String } html 要插入的html内容
+ * @param { Boolean } isAppendTo 若传入true,不清空原来的内容,在最后插入内容,否则,清空内容再插入
+ * @example
+ * ```javascript
+ * //假设设置前的编辑器内容是 old text
+ * editor.setContent('new text ', true); //插入的结果是old text new text
+ * ```
+ */
+ setContent: function (html, isAppendTo, notFireSelectionchange) {
+ var me = this;
+
+ me.fireEvent('beforesetcontent', html);
+ var root = UE.htmlparser(html);
+ me.filterInputRule(root);
+ html = root.toHtml();
+
+ me.body.innerHTML = (isAppendTo ? me.body.innerHTML : '') + html;
+
+
+ function isCdataDiv(node){
+ return node.tagName == 'DIV' && node.getAttribute('cdata_tag');
+ }
+ //给文本或者inline节点套p标签
+ if (me.options.enterTag == 'p') {
+
+ var child = this.body.firstChild, tmpNode;
+ if (!child || child.nodeType == 1 &&
+ (dtd.$cdata[child.tagName] || isCdataDiv(child) ||
+ domUtils.isCustomeNode(child)
+ )
+ && child === this.body.lastChild) {
+ this.body.innerHTML = '' + (browser.ie ? ' ' : ' ') + ' ' + this.body.innerHTML;
+
+ } else {
+ var p = me.document.createElement('p');
+ while (child) {
+ while (child && (child.nodeType == 3 || child.nodeType == 1 && dtd.p[child.tagName] && !dtd.$cdata[child.tagName])) {
+ tmpNode = child.nextSibling;
+ p.appendChild(child);
+ child = tmpNode;
+ }
+ if (p.firstChild) {
+ if (!child) {
+ me.body.appendChild(p);
+ break;
+ } else {
+ child.parentNode.insertBefore(p, child);
+ p = me.document.createElement('p');
+ }
+ }
+ child = child.nextSibling;
+ }
+ }
+ }
+ me.fireEvent('aftersetcontent');
+ me.fireEvent('contentchange');
+
+ !notFireSelectionchange && me._selectionChange();
+ //清除保存的选区
+ me._bakRange = me._bakIERange = me._bakNativeRange = null;
+ //trace:1742 setContent后gecko能得到焦点问题
+ var geckoSel;
+ if (browser.gecko && (geckoSel = this.selection.getNative())) {
+ geckoSel.removeAllRanges();
+ }
+ if(me.options.autoSyncData){
+ me.form && setValue(me.form,me);
+ }
+ },
+
+ /**
+ * 让编辑器获得焦点,默认focus到编辑器头部
+ * @method focus
+ * @example
+ * ```javascript
+ * editor.focus()
+ * ```
+ */
+
+ /**
+ * 让编辑器获得焦点,toEnd确定focus位置
+ * @method focus
+ * @param { Boolean } toEnd 默认focus到编辑器头部,toEnd为true时focus到内容尾部
+ * @example
+ * ```javascript
+ * editor.focus(true)
+ * ```
+ */
+ focus: function (toEnd) {
+ try {
+ var me = this,
+ rng = me.selection.getRange();
+ if (toEnd) {
+ var node = me.body.lastChild;
+ if(node && node.nodeType == 1 && !dtd.$empty[node.tagName]){
+ if(domUtils.isEmptyBlock(node)){
+ rng.setStartAtFirst(node)
+ }else{
+ rng.setStartAtLast(node)
+ }
+ rng.collapse(true);
+ }
+ rng.setCursor(true);
+ } else {
+ if(!rng.collapsed && domUtils.isBody(rng.startContainer) && rng.startOffset == 0){
+
+ var node = me.body.firstChild;
+ if(node && node.nodeType == 1 && !dtd.$empty[node.tagName]){
+ rng.setStartAtFirst(node).collapse(true);
+ }
+ }
+
+ rng.select(true);
+
+ }
+ this.fireEvent('focus selectionchange');
+ } catch (e) {
+ }
+
+ },
+ isFocus:function(){
+ return this.selection.isFocus();
+ },
+ blur:function(){
+ var sel = this.selection.getNative();
+ if(sel.empty && browser.ie){
+ var nativeRng = document.body.createTextRange();
+ nativeRng.moveToElementText(document.body);
+ nativeRng.collapse(true);
+ nativeRng.select();
+ sel.empty()
+ }else{
+ sel.removeAllRanges()
+ }
+
+ //this.fireEvent('blur selectionchange');
+ },
+ /**
+ * 初始化UE事件及部分事件代理
+ * @method _initEvents
+ * @private
+ */
+ _initEvents: function () {
+ var me = this,
+ doc = me.document,
+ win = me.window;
+ me._proxyDomEvent = utils.bind(me._proxyDomEvent, me);
+ domUtils.on(doc, ['click', 'contextmenu', 'mousedown', 'keydown', 'keyup', 'keypress', 'mouseup', 'mouseover', 'mouseout', 'selectstart'], me._proxyDomEvent);
+ domUtils.on(win, ['focus', 'blur'], me._proxyDomEvent);
+ domUtils.on(me.body,'drop',function(e){
+ //阻止ff下默认的弹出新页面打开图片
+ if(browser.gecko && e.stopPropagation) { e.stopPropagation(); }
+ me.fireEvent('contentchange')
+ });
+ domUtils.on(doc, ['mouseup', 'keydown'], function (evt) {
+ //特殊键不触发selectionchange
+ if (evt.type == 'keydown' && (evt.ctrlKey || evt.metaKey || evt.shiftKey || evt.altKey)) {
+ return;
+ }
+ if (evt.button == 2)return;
+ me._selectionChange(250, evt);
+ });
+ },
+ /**
+ * 触发事件代理
+ * @method _proxyDomEvent
+ * @private
+ * @return { * } fireEvent的返回值
+ * @see UE.EventBase:fireEvent(String)
+ */
+ _proxyDomEvent: function (evt) {
+ if(this.fireEvent('before' + evt.type.replace(/^on/, '').toLowerCase()) === false){
+ return false;
+ }
+ if(this.fireEvent(evt.type.replace(/^on/, ''), evt) === false){
+ return false;
+ }
+ return this.fireEvent('after' + evt.type.replace(/^on/, '').toLowerCase())
+ },
+ /**
+ * 变化选区
+ * @method _selectionChange
+ * @private
+ */
+ _selectionChange: function (delay, evt) {
+ var me = this;
+ //有光标才做selectionchange 为了解决未focus时点击source不能触发更改工具栏状态的问题(source命令notNeedUndo=1)
+// if ( !me.selection.isFocus() ){
+// return;
+// }
+
+
+ var hackForMouseUp = false;
+ var mouseX, mouseY;
+ if (browser.ie && browser.version < 9 && evt && evt.type == 'mouseup') {
+ var range = this.selection.getRange();
+ if (!range.collapsed) {
+ hackForMouseUp = true;
+ mouseX = evt.clientX;
+ mouseY = evt.clientY;
+ }
+ }
+ clearTimeout(_selectionChangeTimer);
+ _selectionChangeTimer = setTimeout(function () {
+ if (!me.selection || !me.selection.getNative()) {
+ return;
+ }
+ //修复一个IE下的bug: 鼠标点击一段已选择的文本中间时,可能在mouseup后的一段时间内取到的range是在selection的type为None下的错误值.
+ //IE下如果用户是拖拽一段已选择文本,则不会触发mouseup事件,所以这里的特殊处理不会对其有影响
+ var ieRange;
+ if (hackForMouseUp && me.selection.getNative().type == 'None') {
+ ieRange = me.document.body.createTextRange();
+ try {
+ ieRange.moveToPoint(mouseX, mouseY);
+ } catch (ex) {
+ ieRange = null;
+ }
+ }
+ var bakGetIERange;
+ if (ieRange) {
+ bakGetIERange = me.selection.getIERange;
+ me.selection.getIERange = function () {
+ return ieRange;
+ };
+ }
+ me.selection.cache();
+ if (bakGetIERange) {
+ me.selection.getIERange = bakGetIERange;
+ }
+ if (me.selection._cachedRange && me.selection._cachedStartElement) {
+ me.fireEvent('beforeselectionchange');
+ // 第二个参数causeByUi为true代表由用户交互造成的selectionchange.
+ me.fireEvent('selectionchange', !!evt);
+ me.fireEvent('afterselectionchange');
+ me.selection.clear();
+ }
+ }, delay || 50);
+ },
+
+ /**
+ * 执行编辑命令
+ * @method _callCmdFn
+ * @private
+ * @param { String } fnName 函数名称
+ * @param { * } args 传给命令函数的参数
+ * @return { * } 返回命令函数运行的返回值
+ */
+ _callCmdFn: function (fnName, args) {
+ var cmdName = args[0].toLowerCase(),
+ cmd, cmdFn;
+ cmd = this.commands[cmdName] || UE.commands[cmdName];
+ cmdFn = cmd && cmd[fnName];
+ //没有querycommandstate或者没有command的都默认返回0
+ if ((!cmd || !cmdFn) && fnName == 'queryCommandState') {
+ return 0;
+ } else if (cmdFn) {
+ return cmdFn.apply(this, args);
+ }
+ },
+
+ /**
+ * 执行编辑命令cmdName,完成富文本编辑效果
+ * @method execCommand
+ * @param { String } cmdName 需要执行的命令
+ * @remind 具体命令的使用请参考命令列表
+ * @return { * } 返回命令函数运行的返回值
+ * @example
+ * ```javascript
+ * editor.execCommand(cmdName);
+ * ```
+ */
+ execCommand: function (cmdName) {
+ cmdName = cmdName.toLowerCase();
+ var me = this,
+ result,
+ cmd = me.commands[cmdName] || UE.commands[cmdName];
+ if (!cmd || !cmd.execCommand) {
+ return null;
+ }
+ if (!cmd.notNeedUndo && !me.__hasEnterExecCommand) {
+ me.__hasEnterExecCommand = true;
+ if (me.queryCommandState.apply(me,arguments) != -1) {
+ me.fireEvent('saveScene');
+ me.fireEvent.apply(me, ['beforeexeccommand', cmdName].concat(arguments));
+ result = this._callCmdFn('execCommand', arguments);
+ //保存场景时,做了内容对比,再看是否进行contentchange触发,这里多触发了一次,去掉
+// (!cmd.ignoreContentChange && !me._ignoreContentChange) && me.fireEvent('contentchange');
+ me.fireEvent.apply(me, ['afterexeccommand', cmdName].concat(arguments));
+ me.fireEvent('saveScene');
+ }
+ me.__hasEnterExecCommand = false;
+ } else {
+ result = this._callCmdFn('execCommand', arguments);
+ (!me.__hasEnterExecCommand && !cmd.ignoreContentChange && !me._ignoreContentChange) && me.fireEvent('contentchange')
+ }
+ (!me.__hasEnterExecCommand && !cmd.ignoreContentChange && !me._ignoreContentChange) && me._selectionChange();
+ return result;
+ },
+
+ /**
+ * 根据传入的command命令,查选编辑器当前的选区,返回命令的状态
+ * @method queryCommandState
+ * @param { String } cmdName 需要查询的命令名称
+ * @remind 具体命令的使用请参考命令列表
+ * @return { Number } number 返回放前命令的状态,返回值三种情况:(-1|0|1)
+ * @example
+ * ```javascript
+ * editor.queryCommandState(cmdName) => (-1|0|1)
+ * ```
+ * @see COMMAND.LIST
+ */
+ queryCommandState: function (cmdName) {
+ return this._callCmdFn('queryCommandState', arguments);
+ },
+
+ /**
+ * 根据传入的command命令,查选编辑器当前的选区,根据命令返回相关的值
+ * @method queryCommandValue
+ * @param { String } cmdName 需要查询的命令名称
+ * @remind 具体命令的使用请参考命令列表
+ * @remind 只有部分插件有此方法
+ * @return { * } 返回每个命令特定的当前状态值
+ * @grammar editor.queryCommandValue(cmdName) => {*}
+ * @see COMMAND.LIST
+ */
+ queryCommandValue: function (cmdName) {
+ return this._callCmdFn('queryCommandValue', arguments);
+ },
+
+ /**
+ * 检查编辑区域中是否有内容
+ * @method hasContents
+ * @remind 默认有文本内容,或者有以下节点都不认为是空
+ * table,ul,ol,dl,iframe,area,base,col,hr,img,embed,input,link,meta,param
+ * @return { Boolean } 检查有内容返回true,否则返回false
+ * @example
+ * ```javascript
+ * editor.hasContents()
+ * ```
+ */
+
+ /**
+ * 检查编辑区域中是否有内容,若包含参数tags中的节点类型,直接返回true
+ * @method hasContents
+ * @param { Array } tags 传入数组判断时用到的节点类型
+ * @return { Boolean } 若文档中包含tags数组里对应的tag,返回true,否则返回false
+ * @example
+ * ```javascript
+ * editor.hasContents(['span']);
+ * ```
+ */
+ hasContents: function (tags) {
+ if (tags) {
+ for (var i = 0, ci; ci = tags[i++];) {
+ if (this.document.getElementsByTagName(ci).length > 0) {
+ return true;
+ }
+ }
+ }
+ if (!domUtils.isEmptyBlock(this.body)) {
+ return true
+ }
+ //随时添加,定义的特殊标签如果存在,不能认为是空
+ tags = ['div'];
+ for (i = 0; ci = tags[i++];) {
+ var nodes = domUtils.getElementsByTagName(this.document, ci);
+ for (var n = 0, cn; cn = nodes[n++];) {
+ if (domUtils.isCustomeNode(cn)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+
+ /**
+ * 重置编辑器,可用来做多个tab使用同一个编辑器实例
+ * @method reset
+ * @remind 此方法会清空编辑器内容,清空回退列表,会触发reset事件
+ * @example
+ * ```javascript
+ * editor.reset()
+ * ```
+ */
+ reset: function () {
+ this.fireEvent('reset');
+ },
+
+ /**
+ * 设置当前编辑区域可以编辑
+ * @method setEnabled
+ * @example
+ * ```javascript
+ * editor.setEnabled()
+ * ```
+ */
+ setEnabled: function () {
+ var me = this, range;
+ if (me.body.contentEditable == 'false') {
+ me.body.contentEditable = true;
+ range = me.selection.getRange();
+ //有可能内容丢失了
+ try {
+ range.moveToBookmark(me.lastBk);
+ delete me.lastBk
+ } catch (e) {
+ range.setStartAtFirst(me.body).collapse(true)
+ }
+ range.select(true);
+ if (me.bkqueryCommandState) {
+ me.queryCommandState = me.bkqueryCommandState;
+ delete me.bkqueryCommandState;
+ }
+ if (me.bkqueryCommandValue) {
+ me.queryCommandValue = me.bkqueryCommandValue;
+ delete me.bkqueryCommandValue;
+ }
+ me.fireEvent('selectionchange');
+ }
+ },
+ enable: function () {
+ return this.setEnabled();
+ },
+
+ /** 设置当前编辑区域不可编辑
+ * @method setDisabled
+ */
+
+ /** 设置当前编辑区域不可编辑,except中的命令除外
+ * @method setDisabled
+ * @param { String } except 例外命令的字符串
+ * @remind 即使设置了disable,此处配置的例外命令仍然可以执行
+ * @example
+ * ```javascript
+ * editor.setDisabled('bold'); //禁用工具栏中除加粗之外的所有功能
+ * ```
+ */
+
+ /** 设置当前编辑区域不可编辑,except中的命令除外
+ * @method setDisabled
+ * @param { Array } except 例外命令的字符串数组,数组中的命令仍然可以执行
+ * @remind 即使设置了disable,此处配置的例外命令仍然可以执行
+ * @example
+ * ```javascript
+ * editor.setDisabled(['bold','insertimage']); //禁用工具栏中除加粗和插入图片之外的所有功能
+ * ```
+ */
+ setDisabled: function (except) {
+ var me = this;
+ except = except ? utils.isArray(except) ? except : [except] : [];
+ if (me.body.contentEditable == 'true') {
+ if (!me.lastBk) {
+ me.lastBk = me.selection.getRange().createBookmark(true);
+ }
+ me.body.contentEditable = false;
+ me.bkqueryCommandState = me.queryCommandState;
+ me.bkqueryCommandValue = me.queryCommandValue;
+ me.queryCommandState = function (type) {
+ if (utils.indexOf(except, type) != -1) {
+ return me.bkqueryCommandState.apply(me, arguments);
+ }
+ return -1;
+ };
+ me.queryCommandValue = function (type) {
+ if (utils.indexOf(except, type) != -1) {
+ return me.bkqueryCommandValue.apply(me, arguments);
+ }
+ return null;
+ };
+ me.fireEvent('selectionchange');
+ }
+ },
+ disable: function (except) {
+ return this.setDisabled(except);
+ },
+
+ /**
+ * 设置默认内容
+ * @method _setDefaultContent
+ * @private
+ * @param { String } cont 要存入的内容
+ */
+ _setDefaultContent: function () {
+ function clear() {
+ var me = this;
+ if (me.document.getElementById('initContent')) {
+ me.body.innerHTML = '' + (ie ? '' : ' ') + ' ';
+ me.removeListener('firstBeforeExecCommand focus', clear);
+ setTimeout(function () {
+ me.focus();
+ me._selectionChange();
+ }, 0)
+ }
+ }
+
+ return function (cont) {
+ var me = this;
+ me.body.innerHTML = '' + cont + ' ';
+
+ me.addListener('firstBeforeExecCommand focus', clear);
+ }
+ }(),
+
+ /**
+ * 显示编辑器
+ * @method setShow
+ * @example
+ * ```javascript
+ * editor.setShow()
+ * ```
+ */
+ setShow: function () {
+ var me = this, range = me.selection.getRange();
+ if (me.container.style.display == 'none') {
+ //有可能内容丢失了
+ try {
+ range.moveToBookmark(me.lastBk);
+ delete me.lastBk
+ } catch (e) {
+ range.setStartAtFirst(me.body).collapse(true)
+ }
+ //ie下focus实效,所以做了个延迟
+ setTimeout(function () {
+ range.select(true);
+ }, 100);
+ me.container.style.display = '';
+ }
+
+ },
+ show: function () {
+ return this.setShow();
+ },
+ /**
+ * 隐藏编辑器
+ * @method setHide
+ * @example
+ * ```javascript
+ * editor.setHide()
+ * ```
+ */
+ setHide: function () {
+ var me = this;
+ if (!me.lastBk) {
+ me.lastBk = me.selection.getRange().createBookmark(true);
+ }
+ me.container.style.display = 'none'
+ },
+ hide: function () {
+ return this.setHide();
+ },
+
+ /**
+ * 根据指定的路径,获取对应的语言资源
+ * @method getLang
+ * @param { String } path 路径根据的是lang目录下的语言文件的路径结构
+ * @return { Object | String } 根据路径返回语言资源的Json格式对象或者语言字符串
+ * @example
+ * ```javascript
+ * editor.getLang('contextMenu.delete'); //如果当前是中文,那返回是的是'删除'
+ * ```
+ */
+ getLang: function (path) {
+ var lang = UE.I18N[this.options.lang];
+ if (!lang) {
+ throw Error("not import language file");
+ }
+ path = (path || "").split(".");
+ for (var i = 0, ci; ci = path[i++];) {
+ lang = lang[ci];
+ if (!lang)break;
+ }
+ return lang;
+ },
+
+ /**
+ * 计算编辑器html内容字符串的长度
+ * @method getContentLength
+ * @return { Number } 返回计算的长度
+ * @example
+ * ```javascript
+ * //编辑器html内容132
+ * editor.getContentLength() //返回27
+ * ```
+ */
+ /**
+ * 计算编辑器当前纯文本内容的长度
+ * @method getContentLength
+ * @param { Boolean } ingoneHtml 传入true时,只按照纯文本来计算
+ * @return { Number } 返回计算的长度,内容中有hr/img/iframe标签,长度加1
+ * @example
+ * ```javascript
+ * //编辑器html内容132
+ * editor.getContentLength() //返回3
+ * ```
+ */
+ getContentLength: function (ingoneHtml, tagNames) {
+ var count = this.getContent(false,false,true).length;
+ if (ingoneHtml) {
+ tagNames = (tagNames || []).concat([ 'hr', 'img', 'iframe']);
+ count = this.getContentTxt().replace(/[\t\r\n]+/g, '').length;
+ for (var i = 0, ci; ci = tagNames[i++];) {
+ count += this.document.getElementsByTagName(ci).length;
+ }
+ }
+ return count;
+ },
+
+ /**
+ * 注册输入过滤规则
+ * @method addInputRule
+ * @param { Function } rule 要添加的过滤规则
+ * @example
+ * ```javascript
+ * editor.addInputRule(function(root){
+ * $.each(root.getNodesByTagName('div'),function(i,node){
+ * node.tagName="p";
+ * });
+ * });
+ * ```
+ */
+ addInputRule: function (rule) {
+ this.inputRules.push(rule);
+ },
+
+ /**
+ * 执行注册的过滤规则
+ * @method filterInputRule
+ * @param { UE.uNode } root 要过滤的uNode节点
+ * @remind 执行editor.setContent方法和执行'inserthtml'命令后,会运行该过滤函数
+ * @example
+ * ```javascript
+ * editor.filterInputRule(editor.body);
+ * ```
+ * @see UE.Editor:addInputRule
+ */
+ filterInputRule: function (root) {
+ for (var i = 0, ci; ci = this.inputRules[i++];) {
+ ci.call(this, root)
+ }
+ },
+
+ /**
+ * 注册输出过滤规则
+ * @method addOutputRule
+ * @param { Function } rule 要添加的过滤规则
+ * @example
+ * ```javascript
+ * editor.addOutputRule(function(root){
+ * $.each(root.getNodesByTagName('p'),function(i,node){
+ * node.tagName="div";
+ * });
+ * });
+ * ```
+ */
+ addOutputRule: function (rule) {
+ this.outputRules.push(rule)
+ },
+
+ /**
+ * 根据输出过滤规则,过滤编辑器内容
+ * @method filterOutputRule
+ * @remind 执行editor.getContent方法的时候,会先运行该过滤函数
+ * @param { UE.uNode } root 要过滤的uNode节点
+ * @example
+ * ```javascript
+ * editor.filterOutputRule(editor.body);
+ * ```
+ * @see UE.Editor:addOutputRule
+ */
+ filterOutputRule: function (root) {
+ for (var i = 0, ci; ci = this.outputRules[i++];) {
+ ci.call(this, root)
+ }
+ },
+
+ /**
+ * 根据action名称获取请求的路径
+ * @method getActionUrl
+ * @remind 假如没有设置serverUrl,会根据imageUrl设置默认的controller路径
+ * @param { String } action action名称
+ * @example
+ * ```javascript
+ * editor.getActionUrl('config'); //返回 "/ueditor/php/controller.php?action=config"
+ * editor.getActionUrl('image'); //返回 "/ueditor/php/controller.php?action=uplaodimage"
+ * editor.getActionUrl('scrawl'); //返回 "/ueditor/php/controller.php?action=uplaodscrawl"
+ * editor.getActionUrl('imageManager'); //返回 "/ueditor/php/controller.php?action=listimage"
+ * ```
+ */
+ getActionUrl: function(action){
+ var actionName = this.getOpt(action) || action,
+ imageUrl = this.getOpt('imageUrl'),
+ serverUrl = this.getOpt('serverUrl');
+
+ if(!serverUrl && imageUrl) {
+ serverUrl = imageUrl.replace(/^(.*[\/]).+([\.].+)$/, '$1controller$2');
+ }
+
+ if(serverUrl) {
+ serverUrl = serverUrl + (serverUrl.indexOf('?') == -1 ? '?':'&') + 'action=' + (actionName || '');
+ return utils.formatUrl(serverUrl);
+ } else {
+ return '';
+ }
+ }
+ };
+ utils.inherits(Editor, EventBase);
+})();
+
+
+// core/Editor.defaultoptions.js
+//维护编辑器一下默认的不在插件中的配置项
+UE.Editor.defaultOptions = function(editor){
+
+ var _url = editor.options.UEDITOR_HOME_URL;
+ return {
+ isShow: true,
+ initialContent: '',
+ initialStyle:'',
+ autoClearinitialContent: false,
+ iframeCssUrl: _url + 'themes/iframe.css',
+ textarea: 'editorValue',
+ focus: false,
+ focusInEnd: true,
+ autoClearEmptyNode: true,
+ fullscreen: false,
+ readonly: false,
+ zIndex: 999,
+ imagePopup: true,
+ enterTag: 'p',
+ customDomain: false,
+ lang: 'zh-cn',
+ langPath: _url + 'lang/',
+ theme: 'default',
+ themePath: _url + 'themes/',
+ allHtmlEnabled: false,
+ scaleEnabled: false,
+ tableNativeEditInFF: false,
+ autoSyncData : true,
+ fileNameFormat: '{time}{rand:6}'
+ }
+};
+
+// core/loadconfig.js
+(function(){
+
+ UE.Editor.prototype.loadServerConfig = function(){
+ var me = this;
+ setTimeout(function(){
+ try{
+ me.options.imageUrl && me.setOpt('serverUrl', me.options.imageUrl.replace(/^(.*[\/]).+([\.].+)$/, '$1controller$2'));
+
+ var configUrl = me.getActionUrl('config'),
+ isJsonp = utils.isCrossDomainUrl(configUrl);
+
+ /* 发出ajax请求 */
+ me._serverConfigLoaded = false;
+
+ configUrl && UE.ajax.request(configUrl,{
+ 'method': 'GET',
+ 'dataType': isJsonp ? 'jsonp':'',
+ 'onsuccess':function(r){
+ try {
+ var config = isJsonp ? r:eval("("+r.responseText+")");
+ utils.extend(me.options, config);
+ me.fireEvent('serverConfigLoaded');
+ me._serverConfigLoaded = true;
+ } catch (e) {
+ showErrorMsg(me.getLang('loadconfigFormatError'));
+ }
+ },
+ 'onerror':function(){
+ showErrorMsg(me.getLang('loadconfigHttpError'));
+ }
+ });
+ } catch(e){
+ showErrorMsg(me.getLang('loadconfigError'));
+ }
+ });
+
+ function showErrorMsg(msg) {
+ console && console.error(msg);
+ //me.fireEvent('showMessage', {
+ // 'title': msg,
+ // 'type': 'error'
+ //});
+ }
+ };
+
+ UE.Editor.prototype.isServerConfigLoaded = function(){
+ var me = this;
+ return me._serverConfigLoaded || false;
+ };
+
+ UE.Editor.prototype.afterConfigReady = function(handler){
+ if (!handler || !utils.isFunction(handler)) return;
+ var me = this;
+ var readyHandler = function(){
+ handler.apply(me, arguments);
+ me.removeListener('serverConfigLoaded', readyHandler);
+ };
+
+ if (me.isServerConfigLoaded()) {
+ handler.call(me, 'serverConfigLoaded');
+ } else {
+ me.addListener('serverConfigLoaded', readyHandler);
+ }
+ };
+
+})();
+
+
+// core/ajax.js
+/**
+ * @file
+ * @module UE.ajax
+ * @since 1.2.6.1
+ */
+
+/**
+ * 提供对ajax请求的支持
+ * @module UE.ajax
+ */
+UE.ajax = function() {
+
+ //创建一个ajaxRequest对象
+ var fnStr = 'XMLHttpRequest()';
+ try {
+ new ActiveXObject("Msxml2.XMLHTTP");
+ fnStr = 'ActiveXObject(\'Msxml2.XMLHTTP\')';
+ } catch (e) {
+ try {
+ new ActiveXObject("Microsoft.XMLHTTP");
+ fnStr = 'ActiveXObject(\'Microsoft.XMLHTTP\')'
+ } catch (e) {
+ }
+ }
+ var creatAjaxRequest = new Function('return new ' + fnStr);
+
+
+ /**
+ * 将json参数转化成适合ajax提交的参数列表
+ * @param json
+ */
+ function json2str(json) {
+ var strArr = [];
+ for (var i in json) {
+ //忽略默认的几个参数
+ if(i=="method" || i=="timeout" || i=="async" || i=="dataType" || i=="callback") continue;
+ //忽略控制
+ if(json[i] == undefined || json[i] == null) continue;
+ //传递过来的对象和函数不在提交之列
+ if (!((typeof json[i]).toLowerCase() == "function" || (typeof json[i]).toLowerCase() == "object")) {
+ strArr.push( encodeURIComponent(i) + "="+encodeURIComponent(json[i]) );
+ } else if (utils.isArray(json[i])) {
+ //支持传数组内容
+ for(var j = 0; j < json[i].length; j++) {
+ strArr.push( encodeURIComponent(i) + "[]="+encodeURIComponent(json[i][j]) );
+ }
+ }
+ }
+ return strArr.join("&");
+ }
+
+ function doAjax(url, ajaxOptions) {
+ var xhr = creatAjaxRequest(),
+ //是否超时
+ timeIsOut = false,
+ //默认参数
+ defaultAjaxOptions = {
+ method:"POST",
+ timeout:5000,
+ async:true,
+ data:{},//需要传递对象的话只能覆盖
+ onsuccess:function() {
+ },
+ onerror:function() {
+ }
+ };
+
+ if (typeof url === "object") {
+ ajaxOptions = url;
+ url = ajaxOptions.url;
+ }
+ if (!xhr || !url) return;
+ var ajaxOpts = ajaxOptions ? utils.extend(defaultAjaxOptions,ajaxOptions) : defaultAjaxOptions;
+
+ var submitStr = json2str(ajaxOpts); // { name:"Jim",city:"Beijing" } --> "name=Jim&city=Beijing"
+ //如果用户直接通过data参数传递json对象过来,则也要将此json对象转化为字符串
+ if (!utils.isEmptyObject(ajaxOpts.data)){
+ submitStr += (submitStr? "&":"") + json2str(ajaxOpts.data);
+ }
+ //超时检测
+ var timerID = setTimeout(function() {
+ if (xhr.readyState != 4) {
+ timeIsOut = true;
+ xhr.abort();
+ clearTimeout(timerID);
+ }
+ }, ajaxOpts.timeout);
+
+ var method = ajaxOpts.method.toUpperCase();
+ var str = url + (url.indexOf("?")==-1?"?":"&") + (method=="POST"?"":submitStr+ "&noCache=" + +new Date);
+ xhr.open(method, str, ajaxOpts.async);
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ if (!timeIsOut && xhr.status == 200) {
+ ajaxOpts.onsuccess(xhr);
+ } else {
+ ajaxOpts.onerror(xhr);
+ }
+ }
+ };
+ if (method == "POST") {
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+ xhr.send(submitStr);
+ } else {
+ xhr.send(null);
+ }
+ }
+
+ function doJsonp(url, opts) {
+
+ var successhandler = opts.onsuccess || function(){},
+ scr = document.createElement('SCRIPT'),
+ options = opts || {},
+ charset = options['charset'],
+ callbackField = options['jsonp'] || 'callback',
+ callbackFnName,
+ timeOut = options['timeOut'] || 0,
+ timer,
+ reg = new RegExp('(\\?|&)' + callbackField + '=([^&]*)'),
+ matches;
+
+ if (utils.isFunction(successhandler)) {
+ callbackFnName = 'bd__editor__' + Math.floor(Math.random() * 2147483648).toString(36);
+ window[callbackFnName] = getCallBack(0);
+ } else if(utils.isString(successhandler)){
+ callbackFnName = successhandler;
+ } else {
+ if (matches = reg.exec(url)) {
+ callbackFnName = matches[2];
+ }
+ }
+
+ url = url.replace(reg, '\x241' + callbackField + '=' + callbackFnName);
+
+ if (url.search(reg) < 0) {
+ url += (url.indexOf('?') < 0 ? '?' : '&') + callbackField + '=' + callbackFnName;
+ }
+
+ var queryStr = json2str(opts); // { name:"Jim",city:"Beijing" } --> "name=Jim&city=Beijing"
+ //如果用户直接通过data参数传递json对象过来,则也要将此json对象转化为字符串
+ if (!utils.isEmptyObject(opts.data)){
+ queryStr += (queryStr? "&":"") + json2str(opts.data);
+ }
+ if (queryStr) {
+ url = url.replace(/\?/, '?' + queryStr + '&');
+ }
+
+ scr.onerror = getCallBack(1);
+ if( timeOut ){
+ timer = setTimeout(getCallBack(1), timeOut);
+ }
+ createScriptTag(scr, url, charset);
+
+ function createScriptTag(scr, url, charset) {
+ scr.setAttribute('type', 'text/javascript');
+ scr.setAttribute('defer', 'defer');
+ charset && scr.setAttribute('charset', charset);
+ scr.setAttribute('src', url);
+ document.getElementsByTagName('head')[0].appendChild(scr);
+ }
+
+ function getCallBack(onTimeOut){
+ return function(){
+ try {
+ if(onTimeOut){
+ options.onerror && options.onerror();
+ }else{
+ try{
+ clearTimeout(timer);
+ successhandler.apply(window, arguments);
+ } catch (e){}
+ }
+ } catch (exception) {
+ options.onerror && options.onerror.call(window, exception);
+ } finally {
+ options.oncomplete && options.oncomplete.apply(window, arguments);
+ scr.parentNode && scr.parentNode.removeChild(scr);
+ window[callbackFnName] = null;
+ try {
+ delete window[callbackFnName];
+ }catch(e){}
+ }
+ }
+ }
+ }
+
+ return {
+ /**
+ * 根据给定的参数项,向指定的url发起一个ajax请求。 ajax请求完成后,会根据请求结果调用相应回调: 如果请求
+ * 成功, 则调用onsuccess回调, 失败则调用 onerror 回调
+ * @method request
+ * @param { URLString } url ajax请求的url地址
+ * @param { Object } ajaxOptions ajax请求选项的键值对,支持的选项如下:
+ * @example
+ * ```javascript
+ * //向sayhello.php发起一个异步的Ajax GET请求, 请求超时时间为10s, 请求完成后执行相应的回调。
+ * UE.ajax.requeset( 'sayhello.php', {
+ *
+ * //请求方法。可选值: 'GET', 'POST',默认值是'POST'
+ * method: 'GET',
+ *
+ * //超时时间。 默认为5000, 单位是ms
+ * timeout: 10000,
+ *
+ * //是否是异步请求。 true为异步请求, false为同步请求
+ * async: true,
+ *
+ * //请求携带的数据。如果请求为GET请求, data会经过stringify后附加到请求url之后。
+ * data: {
+ * name: 'ueditor'
+ * },
+ *
+ * //请求成功后的回调, 该回调接受当前的XMLHttpRequest对象作为参数。
+ * onsuccess: function ( xhr ) {
+ * console.log( xhr.responseText );
+ * },
+ *
+ * //请求失败或者超时后的回调。
+ * onerror: function ( xhr ) {
+ * alert( 'Ajax请求失败' );
+ * }
+ *
+ * } );
+ * ```
+ */
+
+ /**
+ * 根据给定的参数项发起一个ajax请求, 参数项里必须包含一个url地址。 ajax请求完成后,会根据请求结果调用相应回调: 如果请求
+ * 成功, 则调用onsuccess回调, 失败则调用 onerror 回调。
+ * @method request
+ * @warning 如果在参数项里未提供一个key为“url”的地址值,则该请求将直接退出。
+ * @param { Object } ajaxOptions ajax请求选项的键值对,支持的选项如下:
+ * @example
+ * ```javascript
+ *
+ * //向sayhello.php发起一个异步的Ajax POST请求, 请求超时时间为5s, 请求完成后不执行任何回调。
+ * UE.ajax.requeset( 'sayhello.php', {
+ *
+ * //请求的地址, 该项是必须的。
+ * url: 'sayhello.php'
+ *
+ * } );
+ * ```
+ */
+ request:function(url, opts) {
+ if (opts && opts.dataType == 'jsonp') {
+ doJsonp(url, opts);
+ } else {
+ doAjax(url, opts);
+ }
+ },
+ getJSONP:function(url, data, fn) {
+ var opts = {
+ 'data': data,
+ 'oncomplete': fn
+ };
+ doJsonp(url, opts);
+ }
+ };
+
+
+}();
+
+
+// core/filterword.js
+/**
+ * UE过滤word的静态方法
+ * @file
+ */
+
+/**
+ * UEditor公用空间,UEditor所有的功能都挂载在该空间下
+ * @module UE
+ */
+
+
+/**
+ * 根据传入html字符串过滤word
+ * @module UE
+ * @since 1.2.6.1
+ * @method filterWord
+ * @param { String } html html字符串
+ * @return { String } 已过滤后的结果字符串
+ * @example
+ * ```javascript
+ * UE.filterWord(html);
+ * ```
+ */
+var filterWord = UE.filterWord = function () {
+
+ //是否是word过来的内容
+ function isWordDocument( str ) {
+ return /(class="?Mso|style="[^"]*\bmso\-|w:WordDocument|<(v|o):|lang=)/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( //ig, "" )
+ //转换图片
+ .replace(/]*>[\s\S]*?.<\/v:shape>/gi,function(str){
+ //opera能自己解析出image所这里直接返回空
+ if(browser.opera){
+ return '';
+ }
+ try{
+ //有可能是bitmap占为图,无用,直接过滤掉,主要体现在粘贴excel表格中
+ if(/Bitmap/i.test(str)){
+ return '';
+ }
+ var width = str.match(/width:([ \d.]*p[tx])/i)[1],
+ height = str.match(/height:([ \d.]*p[tx])/i)[1],
+ src = str.match(/src=\s*"([^"]*)"/i)[1];
+ return '';
+ } catch(e){
+ return '';
+ }
+ })
+ //针对wps添加的多余标签处理
+ .replace(/<\/?div[^>]*>/g,'')
+ //去掉多余的属性
+ .replace( /v:\w+=(["']?)[^'"]+\1/g, '' )
+ .replace( /<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|xml|meta|link|style|\w+:\w+)(?=[\s\/>]))[^>]*>/gi, "" )
+ .replace( /]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi, " $1 " )
+ //去掉多余的属性
+ .replace( /\s+(class|lang|align)\s*=\s*(['"]?)([\w-]+)\2/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(!/]/.test(html)) {
+ return UE.htmlparser(html).children[0]
+ } else {
+ return new uNode({
+ type:'element',
+ children:[],
+ tagName:html
+ })
+ }
+ };
+ uNode.createText = function (data,noTrans) {
+ return new UE.uNode({
+ type:'text',
+ 'data':noTrans ? data : utils.unhtml(data || '')
+ })
+ };
+ function nodeToHtml(node, arr, formatter, current) {
+ switch (node.type) {
+ case 'root':
+ for (var i = 0, ci; ci = node.children[i++];) {
+ //插入新行
+ if (formatter && ci.type == 'element' && !dtd.$inlineWithA[ci.tagName] && i > 1) {
+ insertLine(arr, current, true);
+ insertIndent(arr, current)
+ }
+ nodeToHtml(ci, arr, formatter, current)
+ }
+ break;
+ case 'text':
+ isText(node, arr);
+ break;
+ case 'element':
+ isElement(node, arr, formatter, current);
+ break;
+ case 'comment':
+ isComment(node, arr, formatter);
+ }
+ return arr;
+ }
+
+ function isText(node, arr) {
+ if(node.parentNode.tagName == 'pre'){
+ //源码模式下输入html标签,不能做转换处理,直接输出
+ arr.push(node.data)
+ }else{
+ arr.push(notTransTagName[node.parentNode.tagName] ? utils.html(node.data) : node.data.replace(/[ ]{2}/g,' '))
+ }
+
+ }
+
+ function isElement(node, arr, formatter, current) {
+ var attrhtml = '';
+ if (node.attrs) {
+ attrhtml = [];
+ var attrs = node.attrs;
+ for (var a in attrs) {
+ //这里就针对
+ //'
+ //这里边的\"做转换,要不用innerHTML直接被截断了,属性src
+ //有可能做的不够
+ attrhtml.push(a + (attrs[a] !== undefined ? '="' + (notTransAttrs[a] ? utils.html(attrs[a]).replace(/["]/g, function (a) {
+ return '"'
+ }) : utils.unhtml(attrs[a])) + '"' : ''))
+ }
+ attrhtml = attrhtml.join(' ');
+ }
+ arr.push('<' + node.tagName +
+ (attrhtml ? ' ' + attrhtml : '') +
+ (dtd.$empty[node.tagName] ? '\/' : '' ) + '>'
+ );
+ //插入新行
+ if (formatter && !dtd.$inlineWithA[node.tagName] && node.tagName != 'pre') {
+ if(node.children && node.children.length){
+ current = insertLine(arr, current, true);
+ insertIndent(arr, current)
+ }
+
+ }
+ if (node.children && node.children.length) {
+ for (var i = 0, ci; ci = node.children[i++];) {
+ if (formatter && ci.type == 'element' && !dtd.$inlineWithA[ci.tagName] && i > 1) {
+ insertLine(arr, current);
+ insertIndent(arr, current)
+ }
+ nodeToHtml(ci, arr, formatter, current)
+ }
+ }
+ if (!dtd.$empty[node.tagName]) {
+ if (formatter && !dtd.$inlineWithA[node.tagName] && node.tagName != 'pre') {
+
+ if(node.children && node.children.length){
+ current = insertLine(arr, current);
+ insertIndent(arr, current)
+ }
+ }
+ arr.push('<\/' + node.tagName + '>');
+ }
+
+ }
+
+ function isComment(node, arr) {
+ arr.push('');
+ }
+
+ function getNodeById(root, id) {
+ var node;
+ if (root.type == 'element' && root.getAttr('id') == id) {
+ return root;
+ }
+ if (root.children && root.children.length) {
+ for (var i = 0, ci; ci = root.children[i++];) {
+ if (node = getNodeById(ci, id)) {
+ return node;
+ }
+ }
+ }
+ }
+
+ function getNodesByTagName(node, tagName, arr) {
+ if (node.type == 'element' && node.tagName == tagName) {
+ arr.push(node);
+ }
+ if (node.children && node.children.length) {
+ for (var i = 0, ci; ci = node.children[i++];) {
+ getNodesByTagName(ci, tagName, arr)
+ }
+ }
+ }
+ function nodeTraversal(root,fn){
+ if(root.children && root.children.length){
+ for(var i= 0,ci;ci=root.children[i];){
+ nodeTraversal(ci,fn);
+ //ci被替换的情况,这里就不再走 fn了
+ if(ci.parentNode ){
+ if(ci.children && ci.children.length){
+ fn(ci)
+ }
+ if(ci.parentNode) i++
+ }
+ }
+ }else{
+ fn(root)
+ }
+
+ }
+ uNode.prototype = {
+
+ /**
+ * 当前节点对象,转换成html文本
+ * @method toHtml
+ * @return { String } 返回转换后的html字符串
+ * @example
+ * ```javascript
+ * node.toHtml();
+ * ```
+ */
+
+ /**
+ * 当前节点对象,转换成html文本
+ * @method toHtml
+ * @param { Boolean } formatter 是否格式化返回值
+ * @return { String } 返回转换后的html字符串
+ * @example
+ * ```javascript
+ * node.toHtml( true );
+ * ```
+ */
+ toHtml:function (formatter) {
+ var arr = [];
+ nodeToHtml(this, arr, formatter, 0);
+ return arr.join('')
+ },
+
+ /**
+ * 获取节点的html内容
+ * @method innerHTML
+ * @warning 假如节点的type不是'element',或节点的标签名称不在dtd列表里,直接返回当前节点
+ * @return { String } 返回节点的html内容
+ * @example
+ * ```javascript
+ * var htmlstr = node.innerHTML();
+ * ```
+ */
+
+ /**
+ * 设置节点的html内容
+ * @method innerHTML
+ * @warning 假如节点的type不是'element',或节点的标签名称不在dtd列表里,直接返回当前节点
+ * @param { String } htmlstr 传入要设置的html内容
+ * @return { UE.uNode } 返回节点本身
+ * @example
+ * ```javascript
+ * node.innerHTML('text');
+ * ```
+ */
+ innerHTML:function (htmlstr) {
+ if (this.type != 'element' || dtd.$empty[this.tagName]) {
+ return this;
+ }
+ if (utils.isString(htmlstr)) {
+ if(this.children){
+ for (var i = 0, ci; ci = this.children[i++];) {
+ ci.parentNode = null;
+ }
+ }
+ this.children = [];
+ var tmpRoot = UE.htmlparser(htmlstr);
+ for (var i = 0, ci; ci = tmpRoot.children[i++];) {
+ this.children.push(ci);
+ ci.parentNode = this;
+ }
+ return this;
+ } else {
+ var tmpRoot = new UE.uNode({
+ type:'root',
+ children:this.children
+ });
+ return tmpRoot.toHtml();
+ }
+ },
+
+ /**
+ * 获取节点的纯文本内容
+ * @method innerText
+ * @warning 假如节点的type不是'element',或节点的标签名称不在dtd列表里,直接返回当前节点
+ * @return { String } 返回节点的存文本内容
+ * @example
+ * ```javascript
+ * var textStr = node.innerText();
+ * ```
+ */
+
+ /**
+ * 设置节点的纯文本内容
+ * @method innerText
+ * @warning 假如节点的type不是'element',或节点的标签名称不在dtd列表里,直接返回当前节点
+ * @param { String } textStr 传入要设置的文本内容
+ * @return { UE.uNode } 返回节点本身
+ * @example
+ * ```javascript
+ * node.innerText('text');
+ * ```
+ */
+ innerText:function (textStr,noTrans) {
+ if (this.type != 'element' || dtd.$empty[this.tagName]) {
+ return this;
+ }
+ if (textStr) {
+ if(this.children){
+ for (var i = 0, ci; ci = this.children[i++];) {
+ ci.parentNode = null;
+ }
+ }
+ this.children = [];
+ this.appendChild(uNode.createText(textStr,noTrans));
+ return this;
+ } else {
+ return this.toHtml().replace(/<[^>]+>/g, '');
+ }
+ },
+
+ /**
+ * 获取当前对象的data属性
+ * @method getData
+ * @return { Object } 若节点的type值是elemenet,返回空字符串,否则返回节点的data属性
+ * @example
+ * ```javascript
+ * node.getData();
+ * ```
+ */
+ getData:function () {
+ if (this.type == 'element')
+ return '';
+ return this.data
+ },
+
+ /**
+ * 获取当前节点下的第一个子节点
+ * @method firstChild
+ * @return { UE.uNode } 返回第一个子节点
+ * @example
+ * ```javascript
+ * node.firstChild(); //返回第一个子节点
+ * ```
+ */
+ firstChild:function () {
+// if (this.type != 'element' || dtd.$empty[this.tagName]) {
+// return this;
+// }
+ return this.children ? this.children[0] : null;
+ },
+
+ /**
+ * 获取当前节点下的最后一个子节点
+ * @method lastChild
+ * @return { UE.uNode } 返回最后一个子节点
+ * @example
+ * ```javascript
+ * node.lastChild(); //返回最后一个子节点
+ * ```
+ */
+ lastChild:function () {
+// if (this.type != 'element' || dtd.$empty[this.tagName] ) {
+// return this;
+// }
+ return this.children ? this.children[this.children.length - 1] : null;
+ },
+
+ /**
+ * 获取和当前节点有相同父亲节点的前一个节点
+ * @method previousSibling
+ * @return { UE.uNode } 返回前一个节点
+ * @example
+ * ```javascript
+ * node.children[2].previousSibling(); //返回子节点node.children[1]
+ * ```
+ */
+ previousSibling : function(){
+ var parent = this.parentNode;
+ for (var i = 0, ci; ci = parent.children[i]; i++) {
+ if (ci === this) {
+ return i == 0 ? null : parent.children[i-1];
+ }
+ }
+
+ },
+
+ /**
+ * 获取和当前节点有相同父亲节点的后一个节点
+ * @method nextSibling
+ * @return { UE.uNode } 返回后一个节点,找不到返回null
+ * @example
+ * ```javascript
+ * node.children[2].nextSibling(); //如果有,返回子节点node.children[3]
+ * ```
+ */
+ nextSibling : function(){
+ var parent = this.parentNode;
+ for (var i = 0, ci; ci = parent.children[i++];) {
+ if (ci === this) {
+ return parent.children[i];
+ }
+ }
+ },
+
+ /**
+ * 用新的节点替换当前节点
+ * @method replaceChild
+ * @param { UE.uNode } target 要替换成该节点参数
+ * @param { UE.uNode } source 要被替换掉的节点
+ * @return { UE.uNode } 返回替换之后的节点对象
+ * @example
+ * ```javascript
+ * node.replaceChild(newNode, childNode); //用newNode替换childNode,childNode是node的子节点
+ * ```
+ */
+ replaceChild:function (target, source) {
+ if (this.children) {
+ if(target.parentNode){
+ target.parentNode.removeChild(target);
+ }
+ for (var i = 0, ci; ci = this.children[i]; i++) {
+ if (ci === source) {
+ this.children.splice(i, 1, target);
+ source.parentNode = null;
+ target.parentNode = this;
+ return target;
+ }
+ }
+ }
+ },
+
+ /**
+ * 在节点的子节点列表最后位置插入一个节点
+ * @method appendChild
+ * @param { UE.uNode } node 要插入的节点
+ * @return { UE.uNode } 返回刚插入的子节点
+ * @example
+ * ```javascript
+ * node.appendChild( newNode ); //在node内插入子节点newNode
+ * ```
+ */
+ appendChild:function (node) {
+ if (this.type == 'root' || (this.type == 'element' && !dtd.$empty[this.tagName])) {
+ if (!this.children) {
+ this.children = []
+ }
+ if(node.parentNode){
+ node.parentNode.removeChild(node);
+ }
+ for (var i = 0, ci; ci = this.children[i]; i++) {
+ if (ci === node) {
+ this.children.splice(i, 1);
+ break;
+ }
+ }
+ this.children.push(node);
+ node.parentNode = this;
+ return node;
+ }
+
+
+ },
+
+ /**
+ * 在传入节点的前面插入一个节点
+ * @method insertBefore
+ * @param { UE.uNode } target 要插入的节点
+ * @param { UE.uNode } source 在该参数节点前面插入
+ * @return { UE.uNode } 返回刚插入的子节点
+ * @example
+ * ```javascript
+ * node.parentNode.insertBefore(newNode, node); //在node节点后面插入newNode
+ * ```
+ */
+ insertBefore:function (target, source) {
+ if (this.children) {
+ if(target.parentNode){
+ target.parentNode.removeChild(target);
+ }
+ for (var i = 0, ci; ci = this.children[i]; i++) {
+ if (ci === source) {
+ this.children.splice(i, 0, target);
+ target.parentNode = this;
+ return target;
+ }
+ }
+
+ }
+ },
+
+ /**
+ * 在传入节点的后面插入一个节点
+ * @method insertAfter
+ * @param { UE.uNode } target 要插入的节点
+ * @param { UE.uNode } source 在该参数节点后面插入
+ * @return { UE.uNode } 返回刚插入的子节点
+ * @example
+ * ```javascript
+ * node.parentNode.insertAfter(newNode, node); //在node节点后面插入newNode
+ * ```
+ */
+ insertAfter:function (target, source) {
+ if (this.children) {
+ if(target.parentNode){
+ target.parentNode.removeChild(target);
+ }
+ for (var i = 0, ci; ci = this.children[i]; i++) {
+ if (ci === source) {
+ this.children.splice(i + 1, 0, target);
+ target.parentNode = this;
+ return target;
+ }
+
+ }
+ }
+ },
+
+ /**
+ * 从当前节点的子节点列表中,移除节点
+ * @method removeChild
+ * @param { UE.uNode } node 要移除的节点引用
+ * @param { Boolean } keepChildren 是否保留移除节点的子节点,若传入true,自动把移除节点的子节点插入到移除的位置
+ * @return { * } 返回刚移除的子节点
+ * @example
+ * ```javascript
+ * node.removeChild(childNode,true); //在node的子节点列表中移除child节点,并且吧child的子节点插入到移除的位置
+ * ```
+ */
+ removeChild:function (node,keepChildren) {
+ if (this.children) {
+ for (var i = 0, ci; ci = this.children[i]; i++) {
+ if (ci === node) {
+ this.children.splice(i, 1);
+ ci.parentNode = null;
+ if(keepChildren && ci.children && ci.children.length){
+ for(var j= 0,cj;cj=ci.children[j];j++){
+ this.children.splice(i+j,0,cj);
+ cj.parentNode = this;
+
+ }
+ }
+ return ci;
+ }
+ }
+ }
+ },
+
+ /**
+ * 获取当前节点所代表的元素属性,即获取attrs对象下的属性值
+ * @method getAttr
+ * @param { String } attrName 要获取的属性名称
+ * @return { * } 返回attrs对象下的属性值
+ * @example
+ * ```javascript
+ * node.getAttr('title');
+ * ```
+ */
+ getAttr:function (attrName) {
+ return this.attrs && this.attrs[attrName.toLowerCase()]
+ },
+
+ /**
+ * 设置当前节点所代表的元素属性,即设置attrs对象下的属性值
+ * @method setAttr
+ * @param { String } attrName 要设置的属性名称
+ * @param { * } attrVal 要设置的属性值,类型视设置的属性而定
+ * @return { * } 返回attrs对象下的属性值
+ * @example
+ * ```javascript
+ * node.setAttr('title','标题');
+ * ```
+ */
+ setAttr:function (attrName, attrVal) {
+ if (!attrName) {
+ delete this.attrs;
+ return;
+ }
+ if(!this.attrs){
+ this.attrs = {};
+ }
+ if (utils.isObject(attrName)) {
+ for (var a in attrName) {
+ if (!attrName[a]) {
+ delete this.attrs[a]
+ } else {
+ this.attrs[a.toLowerCase()] = attrName[a];
+ }
+ }
+ } else {
+ if (!attrVal) {
+ delete this.attrs[attrName]
+ } else {
+ this.attrs[attrName.toLowerCase()] = attrVal;
+ }
+
+ }
+ },
+
+ /**
+ * 获取当前节点在父节点下的位置索引
+ * @method getIndex
+ * @return { Number } 返回索引数值,如果没有父节点,返回-1
+ * @example
+ * ```javascript
+ * node.getIndex();
+ * ```
+ */
+ getIndex:function(){
+ var parent = this.parentNode;
+ for(var i= 0,ci;ci=parent.children[i];i++){
+ if(ci === this){
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ /**
+ * 在当前节点下,根据id查找节点
+ * @method getNodeById
+ * @param { String } id 要查找的id
+ * @return { UE.uNode } 返回找到的节点
+ * @example
+ * ```javascript
+ * node.getNodeById('textId');
+ * ```
+ */
+ getNodeById:function (id) {
+ var node;
+ if (this.children && this.children.length) {
+ for (var i = 0, ci; ci = this.children[i++];) {
+ if (node = getNodeById(ci, id)) {
+ return node;
+ }
+ }
+ }
+ },
+
+ /**
+ * 在当前节点下,根据元素名称查找节点列表
+ * @method getNodesByTagName
+ * @param { String } tagNames 要查找的元素名称
+ * @return { Array } 返回找到的节点列表
+ * @example
+ * ```javascript
+ * node.getNodesByTagName('span');
+ * ```
+ */
+ getNodesByTagName:function (tagNames) {
+ tagNames = utils.trim(tagNames).replace(/[ ]{2,}/g, ' ').split(' ');
+ var arr = [], me = this;
+ utils.each(tagNames, function (tagName) {
+ if (me.children && me.children.length) {
+ for (var i = 0, ci; ci = me.children[i++];) {
+ getNodesByTagName(ci, tagName, arr)
+ }
+ }
+ });
+ return arr;
+ },
+
+ /**
+ * 根据样式名称,获取节点的样式值
+ * @method getStyle
+ * @param { String } name 要获取的样式名称
+ * @return { String } 返回样式值
+ * @example
+ * ```javascript
+ * node.getStyle('font-size');
+ * ```
+ */
+ getStyle:function (name) {
+ var cssStyle = this.getAttr('style');
+ if (!cssStyle) {
+ return ''
+ }
+ var reg = new RegExp('(^|;)\\s*' + name + ':([^;]+)','i');
+ var match = cssStyle.match(reg);
+ if (match && match[0]) {
+ return match[2]
+ }
+ return '';
+ },
+
+ /**
+ * 给节点设置样式
+ * @method setStyle
+ * @param { String } name 要设置的的样式名称
+ * @param { String } val 要设置的的样值
+ * @example
+ * ```javascript
+ * node.setStyle('font-size', '12px');
+ * ```
+ */
+ setStyle:function (name, val) {
+ function exec(name, val) {
+ var reg = new RegExp('(^|;)\\s*' + name + ':([^;]+;?)', 'gi');
+ cssStyle = cssStyle.replace(reg, '$1');
+ if (val) {
+ cssStyle = name + ':' + utils.unhtml(val) + ';' + cssStyle
+ }
+
+ }
+
+ var cssStyle = this.getAttr('style');
+ if (!cssStyle) {
+ cssStyle = '';
+ }
+ if (utils.isObject(name)) {
+ for (var a in name) {
+ exec(a, name[a])
+ }
+ } else {
+ exec(name, val)
+ }
+ this.setAttr('style', utils.trim(cssStyle))
+ },
+
+ /**
+ * 传入一个函数,递归遍历当前节点下的所有节点
+ * @method traversal
+ * @param { Function } fn 遍历到节点的时,传入节点作为参数,运行此函数
+ * @example
+ * ```javascript
+ * traversal(node, function(){
+ * console.log(node.type);
+ * });
+ * ```
+ */
+ traversal:function(fn){
+ if(this.children && this.children.length){
+ nodeTraversal(this,fn);
+ }
+ return this;
+ }
+ }
+})();
+
+
+// core/htmlparser.js
+/**
+ * html字符串转换成uNode节点
+ * @file
+ * @module UE
+ * @since 1.2.6.1
+ */
+
+/**
+ * UEditor公用空间,UEditor所有的功能都挂载在该空间下
+ * @unfile
+ * @module UE
+ */
+
+/**
+ * html字符串转换成uNode节点的静态方法
+ * @method htmlparser
+ * @param { String } htmlstr 要转换的html代码
+ * @param { Boolean } ignoreBlank 若设置为true,转换的时候忽略\n\r\t等空白字符
+ * @return { uNode } 给定的html片段转换形成的uNode对象
+ * @example
+ * ```javascript
+ * var root = UE.htmlparser('htmlparser ', true);
+ * ```
+ */
+
+var htmlparser = UE.htmlparser = function (htmlstr,ignoreBlank) {
+ //todo 原来的方式 [^"'<>\/] 有\/就不能配对上 这样的标签了
+ //先去掉了,加上的原因忘了,这里先记录
+ 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;
+
+ }
+ //如果结束是文本,就有可能丢掉,所以这里手动判断一下
+ //例如 sdfsdfsdfsdfsdfsdfsdf
+ 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 {
+ return {
+
+ getItem: function (key) {
+
+ var result = null;
+
+ try {
+ document.body.appendChild(container);
+ container.load(LOCAL_FILE);
+ result = container.getAttribute(key);
+ document.body.removeChild(container);
+ } catch (e) {
+ }
+
+ return result;
+
+ },
+
+ setItem: function (key, value) {
+
+ document.body.appendChild(container);
+ container.setAttribute(key, value);
+ container.save(LOCAL_FILE);
+ document.body.removeChild(container);
+
+ },
+
+ //// 暂时没有用到
+ //clear: function () {
+ //
+ // var expiresTime = new Date();
+ // expiresTime.setFullYear(expiresTime.getFullYear() - 1);
+ // document.body.appendChild(container);
+ // container.expires = expiresTime.toUTCString();
+ // container.save(LOCAL_FILE);
+ // document.body.removeChild(container);
+ //
+ //},
+
+ removeItem: function (key) {
+
+ document.body.appendChild(container);
+ container.removeAttribute(key);
+ container.save(LOCAL_FILE);
+ document.body.removeChild(container);
+
+ }
+
+ };
+
+ }
+
+})();
+
+(function () {
+
+ var ROOTKEY = 'ueditor_preference';
+
+ UE.Editor.prototype.setPreferences = function(key,value){
+ var obj = {};
+ if (utils.isString(key)) {
+ obj[ key ] = value;
+ } else {
+ obj = key;
+ }
+ var data = LocalStorage.getLocalData(ROOTKEY);
+ if (data && (data = utils.str2json(data))) {
+ utils.extend(data, obj);
+ } else {
+ data = obj;
+ }
+ data && LocalStorage.saveLocalData(ROOTKEY, utils.json2str(data));
+ };
+
+ UE.Editor.prototype.getPreferences = function(key){
+ var data = LocalStorage.getLocalData(ROOTKEY);
+ if (data && (data = utils.str2json(data))) {
+ return key ? data[key] : data
+ }
+ return null;
+ };
+
+ UE.Editor.prototype.removePreferences = function (key) {
+ var data = LocalStorage.getLocalData(ROOTKEY);
+ if (data && (data = utils.str2json(data))) {
+ data[key] = undefined;
+ delete data[key]
+ }
+ data && LocalStorage.saveLocalData(ROOTKEY, utils.json2str(data));
+ };
+
+})();
+
+
+// plugins/defaultfilter.js
+///import core
+///plugin 编辑器默认的过滤转换机制
+
+UE.plugins['defaultfilter'] = function () {
+ var me = this;
+ me.setOpt({
+ 'allowDivTransToP':true,
+ 'disabledTableInTable':true
+ });
+ //默认的过滤处理
+ //进入编辑器的内容处理
+ me.addInputRule(function (root) {
+ var allowDivTransToP = this.options.allowDivTransToP;
+ var val;
+ function tdParent(node){
+ while(node && node.type == 'element'){
+ if(node.tagName == 'td'){
+ return true;
+ }
+ node = node.parentNode;
+ }
+ return false;
+ }
+ //进行默认的处理
+ root.traversal(function (node) {
+ if (node.type == 'element') {
+ if (!dtd.$cdata[node.tagName] && me.options.autoClearEmptyNode && dtd.$inline[node.tagName] && !dtd.$empty[node.tagName] && (!node.attrs || utils.isEmptyObject(node.attrs))) {
+ if (!node.firstChild()) node.parentNode.removeChild(node);
+ else if (node.tagName == 'span' && (!node.attrs || utils.isEmptyObject(node.attrs))) {
+ node.parentNode.removeChild(node, true)
+ }
+ return;
+ }
+ switch (node.tagName) {
+ case 'style':
+ case 'script':
+ node.setAttr({
+ cdata_tag: node.tagName,
+ cdata_data: (node.innerHTML() || ''),
+ '_ue_custom_node_':'true'
+ });
+ node.tagName = 'div';
+ node.innerHTML('');
+ break;
+ case 'a':
+ if (val = node.getAttr('href')) {
+ node.setAttr('_href', val)
+ }
+ break;
+ case 'img':
+ //todo base64暂时去掉,后边做远程图片上传后,干掉这个
+ if (val = node.getAttr('src')) {
+ if (/^data:/.test(val)) {
+ node.parentNode.removeChild(node);
+ break;
+ }
+ }
+ node.setAttr('_src', node.getAttr('src'));
+ break;
+ case 'span':
+ if (browser.webkit && (val = node.getStyle('white-space'))) {
+ if (/nowrap|normal/.test(val)) {
+ node.setStyle('white-space', '');
+ if (me.options.autoClearEmptyNode && utils.isEmptyObject(node.attrs)) {
+ node.parentNode.removeChild(node, true)
+ }
+ }
+ }
+ val = node.getAttr('id');
+ if(val && /^_baidu_bookmark_/i.test(val)){
+ node.parentNode.removeChild(node)
+ }
+ break;
+ case 'p':
+ if (val = node.getAttr('align')) {
+ node.setAttr('align');
+ node.setStyle('text-align', val)
+ }
+ //trace:3431
+// var cssStyle = node.getAttr('style');
+// if (cssStyle) {
+// cssStyle = cssStyle.replace(/(margin|padding)[^;]+/g, '');
+// node.setAttr('style', cssStyle)
+//
+// }
+ //p标签不允许嵌套
+ utils.each(node.children,function(n){
+ if(n.type == 'element' && n.tagName == 'p'){
+ var next = n.nextSibling();
+ node.parentNode.insertAfter(n,node);
+ var last = n;
+ while(next){
+ var tmp = next.nextSibling();
+ node.parentNode.insertAfter(next,last);
+ last = next;
+ next = tmp;
+ }
+ return false;
+ }
+ });
+ if (!node.firstChild()) {
+ node.innerHTML(browser.ie ? ' ' : ' ')
+ }
+ break;
+ case 'div':
+ if(node.getAttr('cdata_tag')){
+ break;
+ }
+ //针对代码这里不处理插入代码的div
+ val = node.getAttr('class');
+ if(val && /^line number\d+/.test(val)){
+ break;
+ }
+ if(!allowDivTransToP){
+ break;
+ }
+ var tmpNode, p = UE.uNode.createElement('p');
+ while (tmpNode = node.firstChild()) {
+ if (tmpNode.type == 'text' || !UE.dom.dtd.$block[tmpNode.tagName]) {
+ p.appendChild(tmpNode);
+ } else {
+ if (p.firstChild()) {
+ node.parentNode.insertBefore(p, node);
+ p = UE.uNode.createElement('p');
+ } else {
+ node.parentNode.insertBefore(tmpNode, node);
+ }
+ }
+ }
+ if (p.firstChild()) {
+ node.parentNode.insertBefore(p, node);
+ }
+ node.parentNode.removeChild(node);
+ break;
+ case 'dl':
+ node.tagName = 'ul';
+ break;
+ case 'dt':
+ case 'dd':
+ node.tagName = 'li';
+ break;
+ case 'li':
+ var className = node.getAttr('class');
+ if (!className || !/list\-/.test(className)) {
+ node.setAttr()
+ }
+ var tmpNodes = node.getNodesByTagName('ol ul');
+ UE.utils.each(tmpNodes, function (n) {
+ node.parentNode.insertAfter(n, node);
+ });
+ break;
+ case 'td':
+ case 'th':
+ case 'caption':
+ if(!node.children || !node.children.length){
+ node.appendChild(browser.ie11below ? UE.uNode.createText(' ') : UE.uNode.createElement('br'))
+ }
+ break;
+ case 'table':
+ if(me.options.disabledTableInTable && tdParent(node)){
+ node.parentNode.insertBefore(UE.uNode.createText(node.innerText()),node);
+ node.parentNode.removeChild(node)
+ }
+ }
+
+ }
+// if(node.type == 'comment'){
+// node.parentNode.removeChild(node);
+// }
+ })
+
+ });
+
+ //从编辑器出去的内容处理
+ me.addOutputRule(function (root) {
+
+ var val;
+ root.traversal(function (node) {
+ if (node.type == 'element') {
+
+ if (me.options.autoClearEmptyNode && dtd.$inline[node.tagName] && !dtd.$empty[node.tagName] && (!node.attrs || utils.isEmptyObject(node.attrs))) {
+
+ if (!node.firstChild()) node.parentNode.removeChild(node);
+ else if (node.tagName == 'span' && (!node.attrs || utils.isEmptyObject(node.attrs))) {
+ node.parentNode.removeChild(node, true)
+ }
+ return;
+ }
+ switch (node.tagName) {
+ case 'div':
+ if (val = node.getAttr('cdata_tag')) {
+ node.tagName = val;
+ node.appendChild(UE.uNode.createText(node.getAttr('cdata_data')));
+ node.setAttr({cdata_tag: '', cdata_data: '','_ue_custom_node_':''});
+ }
+ break;
+ case 'a':
+ if (val = node.getAttr('_href')) {
+ node.setAttr({
+ 'href': utils.html(val),
+ '_href': ''
+ })
+ }
+ break;
+ break;
+ case 'span':
+ val = node.getAttr('id');
+ if(val && /^_baidu_bookmark_/i.test(val)){
+ node.parentNode.removeChild(node)
+ }
+ break;
+ case 'img':
+ if (val = node.getAttr('_src')) {
+ node.setAttr({
+ 'src': node.getAttr('_src'),
+ '_src': ''
+ })
+ }
+
+
+ }
+ }
+
+ })
+
+
+ });
+};
+
+
+// plugins/inserthtml.js
+/**
+ * 插入html字符串插件
+ * @file
+ * @since 1.2.6.1
+ */
+
+/**
+ * 插入html代码
+ * @command inserthtml
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @param { String } html 插入的html字符串
+ * @remaind 插入的标签内容是在当前的选区位置上插入,如果当前是闭合状态,那直接插入内容, 如果当前是选中状态,将先清除当前选中内容后,再做插入
+ * @warning 注意:该命令会对当前选区的位置,对插入的内容进行过滤转换处理。 过滤的规则遵循html语意化的原则。
+ * @example
+ * ```javascript
+ * //xxx[BB]xxx 当前选区为非闭合选区,选中BB这两个文本
+ * //执行命令,插入CC
+ * //插入后的效果 xxxCCxxx
+ * //xx|xxx 当前选区为闭合状态
+ * //插入CC
+ * //结果 xx CC xxx
+ * //xxxx |xxx 当前选区在两个p标签之间
+ * //插入 xxxx
+ * //结果 xxxx xxxx xxx
+ * ```
+ */
+
+UE.commands['inserthtml'] = {
+ execCommand: function (command,html,notNeedFilter){
+ var me = this,
+ range,
+ div;
+ if(!html){
+ return;
+ }
+ if(me.fireEvent('beforeinserthtml',html) === true){
+ return;
+ }
+ range = me.selection.getRange();
+ div = range.document.createElement( 'div' );
+ div.style.display = 'inline';
+
+ if (!notNeedFilter) {
+ var root = UE.htmlparser(html);
+ //如果给了过滤规则就先进行过滤
+ if(me.options.filterRules){
+ UE.filterNode(root,me.options.filterRules);
+ }
+ //执行默认的处理
+ me.filterInputRule(root);
+ html = root.toHtml()
+ }
+ div.innerHTML = utils.trim( html );
+
+ if ( !range.collapsed ) {
+ var tmpNode = range.startContainer;
+ if(domUtils.isFillChar(tmpNode)){
+ range.setStartBefore(tmpNode)
+ }
+ tmpNode = range.endContainer;
+ if(domUtils.isFillChar(tmpNode)){
+ range.setEndAfter(tmpNode)
+ }
+ range.txtToElmBoundary();
+ //结束边界可能放到了br的前边,要把br包含进来
+ // x[xxx]
+ if(range.endContainer && range.endContainer.nodeType == 1){
+ tmpNode = range.endContainer.childNodes[range.endOffset];
+ if(tmpNode && domUtils.isBr(tmpNode)){
+ range.setEndAfter(tmpNode);
+ }
+ }
+ if(range.startOffset == 0){
+ tmpNode = range.startContainer;
+ if(domUtils.isBoundaryNode(tmpNode,'firstChild') ){
+ tmpNode = range.endContainer;
+ if(range.endOffset == (tmpNode.nodeType == 3 ? tmpNode.nodeValue.length : tmpNode.childNodes.length) && domUtils.isBoundaryNode(tmpNode,'lastChild')){
+ me.body.innerHTML = ''+(browser.ie ? '' : ' ')+' ';
+ range.setStart(me.body.firstChild,0).collapse(true)
+
+ }
+ }
+ }
+ !range.collapsed && range.deleteContents();
+ if(range.startContainer.nodeType == 1){
+ var child = range.startContainer.childNodes[range.startOffset],pre;
+ if(child && domUtils.isBlockElm(child) && (pre = child.previousSibling) && domUtils.isBlockElm(pre)){
+ range.setEnd(pre,pre.childNodes.length).collapse();
+ while(child.firstChild){
+ pre.appendChild(child.firstChild);
+ }
+ domUtils.remove(child);
+ }
+ }
+
+ }
+
+
+ var child,parent,pre,tmp,hadBreak = 0, nextNode;
+ //如果当前位置选中了fillchar要干掉,要不会产生空行
+ if(range.inFillChar()){
+ child = range.startContainer;
+ if(domUtils.isFillChar(child)){
+ range.setStartBefore(child).collapse(true);
+ domUtils.remove(child);
+ }else if(domUtils.isFillChar(child,true)){
+ child.nodeValue = child.nodeValue.replace(fillCharReg,'');
+ range.startOffset--;
+ range.collapsed && range.collapse(true)
+ }
+ }
+ //列表单独处理
+ var li = domUtils.findParentByTagName(range.startContainer,'li',true);
+ if(li){
+ var next,last;
+ while(child = div.firstChild){
+ //针对hr单独处理一下先
+ while(child && (child.nodeType == 3 || !domUtils.isBlockElm(child) || child.tagName=='HR' )){
+ next = child.nextSibling;
+ range.insertNode( child).collapse();
+ last = child;
+ child = next;
+
+ }
+ if(child){
+ if(/^(ol|ul)$/i.test(child.tagName)){
+ while(child.firstChild){
+ last = child.firstChild;
+ domUtils.insertAfter(li,child.firstChild);
+ li = li.nextSibling;
+ }
+ domUtils.remove(child)
+ }else{
+ var tmpLi;
+ next = child.nextSibling;
+ tmpLi = me.document.createElement('li');
+ domUtils.insertAfter(li,tmpLi);
+ tmpLi.appendChild(child);
+ last = child;
+ child = next;
+ li = tmpLi;
+ }
+ }
+ }
+ li = domUtils.findParentByTagName(range.startContainer,'li',true);
+ if(domUtils.isEmptyBlock(li)){
+ domUtils.remove(li)
+ }
+ if(last){
+
+ range.setStartAfter(last).collapse(true).select(true)
+ }
+ }else{
+ while ( child = div.firstChild ) {
+ if(hadBreak){
+ var p = me.document.createElement('p');
+ while(child && (child.nodeType == 3 || !dtd.$block[child.tagName])){
+ nextNode = child.nextSibling;
+ p.appendChild(child);
+ child = nextNode;
+ }
+ if(p.firstChild){
+
+ child = p
+ }
+ }
+ range.insertNode( child );
+ nextNode = child.nextSibling;
+ if ( !hadBreak && child.nodeType == domUtils.NODE_ELEMENT && domUtils.isBlockElm( child ) ){
+
+ parent = domUtils.findParent( child,function ( node ){ return domUtils.isBlockElm( node ); } );
+ if ( parent && parent.tagName.toLowerCase() != 'body' && !(dtd[parent.tagName][child.nodeName] && child.parentNode === parent)){
+ if(!dtd[parent.tagName][child.nodeName]){
+ pre = parent;
+ }else{
+ tmp = child.parentNode;
+ while (tmp !== parent){
+ pre = tmp;
+ tmp = tmp.parentNode;
+
+ }
+ }
+
+
+ domUtils.breakParent( child, pre || tmp );
+ //去掉break后前一个多余的节点 |<[p> ==> |
+ var pre = child.previousSibling;
+ domUtils.trimWhiteTextNode(pre);
+ if(!pre.childNodes.length){
+ domUtils.remove(pre);
+ }
+ //trace:2012,在非ie的情况,切开后剩下的节点有可能不能点入光标添加br占位
+
+ if(!browser.ie &&
+ (next = child.nextSibling) &&
+ domUtils.isBlockElm(next) &&
+ next.lastChild &&
+ !domUtils.isBr(next.lastChild)){
+ next.appendChild(me.document.createElement('br'));
+ }
+ hadBreak = 1;
+ }
+ }
+ var next = child.nextSibling;
+ if(!div.firstChild && next && domUtils.isBlockElm(next)){
+
+ range.setStart(next,0).collapse(true);
+ break;
+ }
+ range.setEndAfter( child ).collapse();
+
+ }
+
+ child = range.startContainer;
+
+ if(nextNode && domUtils.isBr(nextNode)){
+ domUtils.remove(nextNode)
+ }
+ //用chrome可能有空白展位符
+ if(domUtils.isBlockElm(child) && domUtils.isEmptyNode(child)){
+ if(nextNode = child.nextSibling){
+ domUtils.remove(child);
+ if(nextNode.nodeType == 1 && dtd.$block[nextNode.tagName]){
+
+ range.setStart(nextNode,0).collapse(true).shrinkBoundary()
+ }
+ }else{
+
+ try{
+ child.innerHTML = browser.ie ? domUtils.fillChar : ' ';
+ }catch(e){
+ range.setStartBefore(child);
+ domUtils.remove(child)
+ }
+
+ }
+
+ }
+ //加上true因为在删除表情等时会删两次,第一次是删的fillData
+ try{
+ range.select(true);
+ }catch(e){}
+
+ }
+
+
+
+ setTimeout(function(){
+ range = me.selection.getRange();
+ range.scrollToView(me.autoHeightEnabled,me.autoHeightEnabled ? domUtils.getXY(me.iframe).y:0);
+ me.fireEvent('afterinserthtml', html);
+ },200);
+ }
+};
+
+
+// plugins/autotypeset.js
+/**
+ * 自动排版
+ * @file
+ * @since 1.2.6.1
+ */
+
+/**
+ * 对当前编辑器的内容执行自动排版, 排版的行为根据config配置文件里的“autotypeset”选项进行控制。
+ * @command autotypeset
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * editor.execCommand( 'autotypeset' );
+ * ```
+ */
+
+UE.plugins['autotypeset'] = function(){
+
+ this.setOpt({'autotypeset': {
+ mergeEmptyline: true, //合并空行
+ removeClass: true, //去掉冗余的class
+ removeEmptyline: false, //去掉空行
+ textAlign:"left", //段落的排版方式,可以是 left,right,center,justify 去掉这个属性表示不执行排版
+ imageBlockLine: 'center', //图片的浮动方式,独占一行剧中,左右浮动,默认: center,left,right,none 去掉这个属性表示不执行排版
+ pasteFilter: false, //根据规则过滤没事粘贴进来的内容
+ clearFontSize: false, //去掉所有的内嵌字号,使用编辑器默认的字号
+ clearFontFamily: false, //去掉所有的内嵌字体,使用编辑器默认的字体
+ removeEmptyNode: false, // 去掉空节点
+ //可以去掉的标签
+ removeTagNames: utils.extend({div:1},dtd.$removeEmpty),
+ indent: false, // 行首缩进
+ indentValue : '2em', //行首缩进的大小
+ bdc2sb: false,
+ tobdc: false
+ }});
+
+ var me = this,
+ opt = me.options.autotypeset,
+ remainClass = {
+ 'selectTdClass':1,
+ 'pagebreak':1,
+ 'anchorclass':1
+ },
+ remainTag = {
+ 'li':1
+ },
+ tags = {
+ div:1,
+ p:1,
+ //trace:2183 这些也认为是行
+ blockquote:1,center:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,
+ span:1
+ },
+ highlightCont;
+ //升级了版本,但配置项目里没有autotypeset
+ if(!opt){
+ return;
+ }
+
+ readLocalOpts();
+
+ function isLine(node,notEmpty){
+ if(!node || node.nodeType == 3)
+ return 0;
+ if(domUtils.isBr(node))
+ return 1;
+ if(node && node.parentNode && tags[node.tagName.toLowerCase()]){
+ if(highlightCont && highlightCont.contains(node)
+ ||
+ node.getAttribute('pagebreak')
+ ){
+ return 0;
+ }
+
+ return notEmpty ? !domUtils.isEmptyBlock(node) : domUtils.isEmptyBlock(node,new RegExp('[\\s'+domUtils.fillChar
+ +']','g'));
+ }
+ }
+
+ function removeNotAttributeSpan(node){
+ if(!node.style.cssText){
+ domUtils.removeAttributes(node,['style']);
+ if(node.tagName.toLowerCase() == 'span' && domUtils.hasNoAttributes(node)){
+ domUtils.remove(node,true);
+ }
+ }
+ }
+ function autotype(type,html){
+
+ var me = this,cont;
+ if(html){
+ if(!opt.pasteFilter){
+ return;
+ }
+ cont = me.document.createElement('div');
+ cont.innerHTML = html.html;
+ }else{
+ cont = me.document.body;
+ }
+ var nodes = domUtils.getElementsByTagName(cont,'*');
+
+ // 行首缩进,段落方向,段间距,段内间距
+ for(var i=0,ci;ci=nodes[i++];){
+
+ if(me.fireEvent('excludeNodeinautotype',ci) === true){
+ continue;
+ }
+ //font-size
+ if(opt.clearFontSize && ci.style.fontSize){
+ domUtils.removeStyle(ci,'font-size');
+
+ removeNotAttributeSpan(ci);
+
+ }
+ //font-family
+ if(opt.clearFontFamily && ci.style.fontFamily){
+ domUtils.removeStyle(ci,'font-family');
+ removeNotAttributeSpan(ci);
+ }
+
+ if(isLine(ci)){
+ //合并空行
+ if(opt.mergeEmptyline ){
+ var next = ci.nextSibling,tmpNode,isBr = domUtils.isBr(ci);
+ while(isLine(next)){
+ tmpNode = next;
+ next = tmpNode.nextSibling;
+ if(isBr && (!next || next && !domUtils.isBr(next))){
+ break;
+ }
+ domUtils.remove(tmpNode);
+ }
+
+ }
+ //去掉空行,保留占位的空行
+ if(opt.removeEmptyline && domUtils.inDoc(ci,cont) && !remainTag[ci.parentNode.tagName.toLowerCase()] ){
+ if(domUtils.isBr(ci)){
+ next = ci.nextSibling;
+ if(next && !domUtils.isBr(next)){
+ continue;
+ }
+ }
+ domUtils.remove(ci);
+ continue;
+
+ }
+
+ }
+ if(isLine(ci,true) && ci.tagName != 'SPAN'){
+ if(opt.indent){
+ ci.style.textIndent = opt.indentValue;
+ }
+ if(opt.textAlign){
+ ci.style.textAlign = opt.textAlign;
+ }
+ // if(opt.lineHeight)
+ // ci.style.lineHeight = opt.lineHeight + 'cm';
+
+ }
+
+ //去掉class,保留的class不去掉
+ if(opt.removeClass && ci.className && !remainClass[ci.className.toLowerCase()]){
+
+ if(highlightCont && highlightCont.contains(ci)){
+ continue;
+ }
+ domUtils.removeAttributes(ci,['class']);
+ }
+
+ //表情不处理
+ if(opt.imageBlockLine && ci.tagName.toLowerCase() == 'img' && !ci.getAttribute('emotion')){
+ if(html){
+ var img = ci;
+ switch (opt.imageBlockLine){
+ case 'left':
+ case 'right':
+ case 'none':
+ var pN = img.parentNode,tmpNode,pre,next;
+ while(dtd.$inline[pN.tagName] || pN.tagName == 'A'){
+ pN = pN.parentNode;
+ }
+ tmpNode = pN;
+ if(tmpNode.tagName == 'P' && domUtils.getStyle(tmpNode,'text-align') == 'center'){
+ if(!domUtils.isBody(tmpNode) && domUtils.getChildCount(tmpNode,function(node){return !domUtils.isBr(node) && !domUtils.isWhitespace(node)}) == 1){
+ pre = tmpNode.previousSibling;
+ next = tmpNode.nextSibling;
+ if(pre && next && pre.nodeType == 1 && next.nodeType == 1 && pre.tagName == next.tagName && domUtils.isBlockElm(pre)){
+ pre.appendChild(tmpNode.firstChild);
+ while(next.firstChild){
+ pre.appendChild(next.firstChild);
+ }
+ domUtils.remove(tmpNode);
+ domUtils.remove(next);
+ }else{
+ domUtils.setStyle(tmpNode,'text-align','');
+ }
+
+
+ }
+
+
+ }
+ domUtils.setStyle(img,'float', opt.imageBlockLine);
+ break;
+ case 'center':
+ if(me.queryCommandValue('imagefloat') != 'center'){
+ pN = img.parentNode;
+ domUtils.setStyle(img,'float','none');
+ tmpNode = img;
+ while(pN && domUtils.getChildCount(pN,function(node){return !domUtils.isBr(node) && !domUtils.isWhitespace(node)}) == 1
+ && (dtd.$inline[pN.tagName] || pN.tagName == 'A')){
+ tmpNode = pN;
+ pN = pN.parentNode;
+ }
+ var pNode = me.document.createElement('p');
+ domUtils.setAttributes(pNode,{
+
+ style:'text-align:center'
+ });
+ tmpNode.parentNode.insertBefore(pNode,tmpNode);
+ pNode.appendChild(tmpNode);
+ domUtils.setStyle(tmpNode,'float','');
+
+ }
+
+
+ }
+ } else {
+ var range = me.selection.getRange();
+ range.selectNode(ci).select();
+ me.execCommand('imagefloat', opt.imageBlockLine);
+ }
+
+ }
+
+ //去掉冗余的标签
+ if(opt.removeEmptyNode){
+ if(opt.removeTagNames[ci.tagName.toLowerCase()] && domUtils.hasNoAttributes(ci) && domUtils.isEmptyBlock(ci)){
+ domUtils.remove(ci);
+ }
+ }
+ }
+ if(opt.tobdc){
+ var root = UE.htmlparser(cont.innerHTML);
+ root.traversal(function(node){
+ if(node.type == 'text'){
+ node.data = ToDBC(node.data)
+ }
+ });
+ cont.innerHTML = root.toHtml()
+ }
+ if(opt.bdc2sb){
+ var root = UE.htmlparser(cont.innerHTML);
+ root.traversal(function(node){
+ if(node.type == 'text'){
+ node.data = DBC2SB(node.data)
+ }
+ });
+ cont.innerHTML = root.toHtml()
+ }
+ if(html){
+ html.html = cont.innerHTML;
+ }
+ }
+ if(opt.pasteFilter){
+ me.addListener('beforepaste',autotype);
+ }
+
+ function DBC2SB(str) {
+ var result = '';
+ for (var i = 0; i < str.length; i++) {
+ var code = str.charCodeAt(i); //获取当前字符的unicode编码
+ if (code >= 65281 && code <= 65373)//在这个unicode编码范围中的是所有的英文字母已经各种字符
+ {
+ result += String.fromCharCode(str.charCodeAt(i) - 65248); //把全角字符的unicode编码转换为对应半角字符的unicode码
+ } else if (code == 12288)//空格
+ {
+ result += String.fromCharCode(str.charCodeAt(i) - 12288 + 32);
+ } else {
+ result += str.charAt(i);
+ }
+ }
+ return result;
+ }
+ function ToDBC(txtstring) {
+ txtstring = utils.html(txtstring);
+ var tmp = "";
+ var mark = "";/*用于判断,如果是html尖括里的标记,则不进行全角的转换*/
+ for (var i = 0; i < txtstring.length; i++) {
+ if (txtstring.charCodeAt(i) == 32) {
+ tmp = tmp + String.fromCharCode(12288);
+ }
+ else if (txtstring.charCodeAt(i) < 127) {
+ tmp = tmp + String.fromCharCode(txtstring.charCodeAt(i) + 65248);
+ }
+ else {
+ tmp += txtstring.charAt(i);
+ }
+ }
+ return tmp;
+ }
+
+ function readLocalOpts() {
+ var cookieOpt = me.getPreferences('autotypeset');
+ utils.extend(me.options.autotypeset, cookieOpt);
+ }
+
+ me.commands['autotypeset'] = {
+ execCommand:function () {
+ me.removeListener('beforepaste',autotype);
+ if(opt.pasteFilter){
+ me.addListener('beforepaste',autotype);
+ }
+ autotype.call(me)
+ }
+
+ };
+
+};
+
+
+
+// plugins/autosubmit.js
+/**
+ * 快捷键提交
+ * @file
+ * @since 1.2.6.1
+ */
+
+/**
+ * 提交表单
+ * @command autosubmit
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * editor.execCommand( 'autosubmit' );
+ * ```
+ */
+
+UE.plugin.register('autosubmit',function(){
+ return {
+ shortcutkey:{
+ "autosubmit":"ctrl+13" //手动提交
+ },
+ commands:{
+ 'autosubmit':{
+ execCommand:function () {
+ var me=this,
+ form = domUtils.findParentByTagName(me.iframe,"form", false);
+ if (form){
+ if(me.fireEvent("beforesubmit")===false){
+ return;
+ }
+ me.sync();
+ form.submit();
+ }
+ }
+ }
+ }
+ }
+});
+
+// plugins/background.js
+/**
+ * 背景插件,为UEditor提供设置背景功能
+ * @file
+ * @since 1.2.6.1
+ */
+UE.plugin.register('background', function () {
+ var me = this,
+ cssRuleId = 'editor_background',
+ isSetColored,
+ reg = new RegExp('body[\\s]*\\{(.+)\\}', 'i');
+
+ function stringToObj(str) {
+ var obj = {}, styles = str.split(';');
+ utils.each(styles, function (v) {
+ var index = v.indexOf(':'),
+ key = utils.trim(v.substr(0, index)).toLowerCase();
+ key && (obj[key] = utils.trim(v.substr(index + 1) || ''));
+ });
+ return obj;
+ }
+
+ function setBackground(obj) {
+ if (obj) {
+ var styles = [];
+ for (var name in obj) {
+ if (obj.hasOwnProperty(name)) {
+ styles.push(name + ":" + obj[name] + '; ');
+ }
+ }
+ utils.cssRule(cssRuleId, styles.length ? ('body{' + styles.join("") + '}') : '', me.document);
+ } else {
+ utils.cssRule(cssRuleId, '', me.document)
+ }
+ }
+ //重写editor.hasContent方法
+
+ var orgFn = me.hasContents;
+ me.hasContents = function(){
+ if(me.queryCommandValue('background')){
+ return true
+ }
+ return orgFn.apply(me,arguments);
+ };
+ return {
+ bindEvents: {
+ 'getAllHtml': function (type, headHtml) {
+ var body = this.body,
+ su = domUtils.getComputedStyle(body, "background-image"),
+ url = "";
+ if (su.indexOf(me.options.imagePath) > 0) {
+ url = su.substring(su.indexOf(me.options.imagePath), su.length - 1).replace(/"|\(|\)/ig, "");
+ } else {
+ url = su != "none" ? su.replace(/url\("?|"?\)/ig, "") : "";
+ }
+ var html = ' ';
+ headHtml.push(html);
+ },
+ 'aftersetcontent': function () {
+ if(isSetColored == false) setBackground();
+ }
+ },
+ inputRule: function (root) {
+ isSetColored = false;
+ utils.each(root.getNodesByTagName('p'), function (p) {
+ var styles = p.getAttr('data-background');
+ if (styles) {
+ isSetColored = true;
+ setBackground(stringToObj(styles));
+ p.parentNode.removeChild(p);
+ }
+ })
+ },
+ outputRule: function (root) {
+ var me = this,
+ styles = (utils.cssRule(cssRuleId, me.document) || '').replace(/[\n\r]+/g, '').match(reg);
+ if (styles) {
+ root.appendChild(UE.uNode.createElement('
'));
+ }
+ },
+ commands: {
+ 'background': {
+ execCommand: function (cmd, obj) {
+ setBackground(obj);
+ },
+ queryCommandValue: function () {
+ var me = this,
+ styles = (utils.cssRule(cssRuleId, me.document) || '').replace(/[\n\r]+/g, '').match(reg);
+ return styles ? stringToObj(styles[1]) : null;
+ },
+ notNeedUndo: true
+ }
+ }
+ }
+});
+
+// plugins/image.js
+/**
+ * 图片插入、排版插件
+ * @file
+ * @since 1.2.6.1
+ */
+
+/**
+ * 图片对齐方式
+ * @command imagefloat
+ * @method execCommand
+ * @remind 值center为独占一行居中
+ * @param { String } cmd 命令字符串
+ * @param { String } align 对齐方式,可传left、right、none、center
+ * @remaind center表示图片独占一行
+ * @example
+ * ```javascript
+ * editor.execCommand( 'imagefloat', 'center' );
+ * ```
+ */
+
+/**
+ * 如果选区所在位置是图片区域
+ * @command imagefloat
+ * @method queryCommandValue
+ * @param { String } cmd 命令字符串
+ * @return { String } 返回图片对齐方式
+ * @example
+ * ```javascript
+ * editor.queryCommandValue( 'imagefloat' );
+ * ```
+ */
+
+UE.commands['imagefloat'] = {
+ execCommand:function (cmd, align) {
+ var me = this,
+ range = me.selection.getRange();
+ if (!range.collapsed) {
+ var img = range.getClosedNode();
+ if (img && img.tagName == 'IMG') {
+ switch (align) {
+ case 'left':
+ case 'right':
+ case 'none':
+ var pN = img.parentNode, tmpNode, pre, next;
+ while (dtd.$inline[pN.tagName] || pN.tagName == 'A') {
+ pN = pN.parentNode;
+ }
+ tmpNode = pN;
+ if (tmpNode.tagName == 'P' && domUtils.getStyle(tmpNode, 'text-align') == 'center') {
+ if (!domUtils.isBody(tmpNode) && domUtils.getChildCount(tmpNode, function (node) {
+ return !domUtils.isBr(node) && !domUtils.isWhitespace(node);
+ }) == 1) {
+ pre = tmpNode.previousSibling;
+ next = tmpNode.nextSibling;
+ if (pre && next && pre.nodeType == 1 && next.nodeType == 1 && pre.tagName == next.tagName && domUtils.isBlockElm(pre)) {
+ pre.appendChild(tmpNode.firstChild);
+ while (next.firstChild) {
+ pre.appendChild(next.firstChild);
+ }
+ domUtils.remove(tmpNode);
+ domUtils.remove(next);
+ } else {
+ domUtils.setStyle(tmpNode, 'text-align', '');
+ }
+
+
+ }
+
+ range.selectNode(img).select();
+ }
+ domUtils.setStyle(img, 'float', align == 'none' ? '' : align);
+ if(align == 'none'){
+ domUtils.removeAttributes(img,'align');
+ }
+
+ break;
+ case 'center':
+ if (me.queryCommandValue('imagefloat') != 'center') {
+ pN = img.parentNode;
+ domUtils.setStyle(img, 'float', '');
+ domUtils.removeAttributes(img,'align');
+ tmpNode = img;
+ while (pN && domUtils.getChildCount(pN, function (node) {
+ return !domUtils.isBr(node) && !domUtils.isWhitespace(node);
+ }) == 1
+ && (dtd.$inline[pN.tagName] || pN.tagName == 'A')) {
+ tmpNode = pN;
+ pN = pN.parentNode;
+ }
+ range.setStartBefore(tmpNode).setCursor(false);
+ pN = me.document.createElement('div');
+ pN.appendChild(tmpNode);
+ domUtils.setStyle(tmpNode, 'float', '');
+
+ me.execCommand('insertHtml', '' + pN.innerHTML + ' ');
+
+ tmpNode = me.document.getElementById('_img_parent_tmp');
+ tmpNode.removeAttribute('id');
+ tmpNode = tmpNode.firstChild;
+ range.selectNode(tmpNode).select();
+ //去掉后边多余的元素
+ next = tmpNode.parentNode.nextSibling;
+ if (next && domUtils.isEmptyNode(next)) {
+ domUtils.remove(next);
+ }
+
+ }
+
+ break;
+ }
+
+ }
+ }
+ },
+ queryCommandValue:function () {
+ var range = this.selection.getRange(),
+ startNode, floatStyle;
+ if (range.collapsed) {
+ return 'none';
+ }
+ startNode = range.getClosedNode();
+ if (startNode && startNode.nodeType == 1 && startNode.tagName == 'IMG') {
+ floatStyle = domUtils.getComputedStyle(startNode, 'float') || startNode.getAttribute('align');
+
+ if (floatStyle == 'none') {
+ floatStyle = domUtils.getComputedStyle(startNode.parentNode, 'text-align') == 'center' ? 'center' : floatStyle;
+ }
+ return {
+ left:1,
+ right:1,
+ center:1
+ }[floatStyle] ? floatStyle : 'none';
+ }
+ return 'none';
+
+
+ },
+ queryCommandState:function () {
+ var range = this.selection.getRange(),
+ startNode;
+
+ if (range.collapsed) return -1;
+
+ startNode = range.getClosedNode();
+ if (startNode && startNode.nodeType == 1 && startNode.tagName == 'IMG') {
+ return 0;
+ }
+ return -1;
+ }
+};
+
+
+/**
+ * 插入图片
+ * @command insertimage
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @param { Object } opt 属性键值对,这些属性都将被复制到当前插入图片
+ * @remind 该命令第二个参数可接受一个图片配置项对象的数组,可以插入多张图片,
+ * 此时数组的每一个元素都是一个Object类型的图片属性集合。
+ * @example
+ * ```javascript
+ * editor.execCommand( 'insertimage', {
+ * src:'a/b/c.jpg',
+ * width:'100',
+ * height:'100'
+ * } );
+ * ```
+ * @example
+ * ```javascript
+ * editor.execCommand( 'insertimage', [{
+ * src:'a/b/c.jpg',
+ * width:'100',
+ * height:'100'
+ * },{
+ * src:'a/b/d.jpg',
+ * width:'100',
+ * height:'100'
+ * }] );
+ * ```
+ */
+
+UE.commands['insertimage'] = {
+ execCommand:function (cmd, opt) {
+
+ opt = utils.isArray(opt) ? opt : [opt];
+ if (!opt.length) {
+ return;
+ }
+ var me = this,
+ range = me.selection.getRange(),
+ img = range.getClosedNode();
+
+ if(me.fireEvent('beforeinsertimage', opt) === true){
+ return;
+ }
+
+ function unhtmlData(imgCi) {
+
+ utils.each('width,height,border,hspace,vspace'.split(','), function (item) {
+
+ if (imgCi[item]) {
+ imgCi[item] = parseInt(imgCi[item], 10) || 0;
+ }
+ });
+
+ utils.each('src,_src'.split(','), function (item) {
+
+ if (imgCi[item]) {
+ imgCi[item] = utils.unhtmlForUrl(imgCi[item]);
+ }
+ });
+ utils.each('title,alt'.split(','), function (item) {
+
+ if (imgCi[item]) {
+ imgCi[item] = utils.unhtml(imgCi[item]);
+ }
+ });
+ }
+
+ if (img && /img/i.test(img.tagName) && (img.className != "edui-faked-video" || img.className.indexOf("edui-upload-video")!=-1) && !img.getAttribute("word_img")) {
+ var first = opt.shift();
+ var floatStyle = first['floatStyle'];
+ delete first['floatStyle'];
+//// img.style.border = (first.border||0) +"px solid #000";
+//// img.style.margin = (first.margin||0) +"px";
+// img.style.cssText += ';margin:' + (first.margin||0) +"px;" + 'border:' + (first.border||0) +"px solid #000";
+ domUtils.setAttributes(img, first);
+ me.execCommand('imagefloat', floatStyle);
+ if (opt.length > 0) {
+ range.setStartAfter(img).setCursor(false, true);
+ me.execCommand('insertimage', opt);
+ }
+
+ } else {
+ var html = [], str = '', ci;
+ ci = opt[0];
+ if (opt.length == 1) {
+ unhtmlData(ci);
+
+ str = '';
+ if (ci['floatStyle'] == 'center') {
+ str = '' + str + ' ';
+ }
+ html.push(str);
+
+ } else {
+ for (var i = 0; ci = opt[i++];) {
+ unhtmlData(ci);
+ str = '';
+ html.push(str);
+ }
+ }
+
+ me.execCommand('insertHtml', html.join(''));
+ }
+
+ me.fireEvent('afterinsertimage', opt)
+ }
+};
+
+
+// plugins/justify.js
+/**
+ * 段落格式
+ * @file
+ * @since 1.2.6.1
+ */
+
+/**
+ * 段落对齐方式
+ * @command justify
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @param { String } align 对齐方式:left => 居左,right => 居右,center => 居中,justify => 两端对齐
+ * @example
+ * ```javascript
+ * editor.execCommand( 'justify', 'center' );
+ * ```
+ */
+/**
+ * 如果选区所在位置是段落区域,返回当前段落对齐方式
+ * @command justify
+ * @method queryCommandValue
+ * @param { String } cmd 命令字符串
+ * @return { String } 返回段落对齐方式
+ * @example
+ * ```javascript
+ * editor.queryCommandValue( 'justify' );
+ * ```
+ */
+
+UE.plugins['justify']=function(){
+ var me=this,
+ block = domUtils.isBlockElm,
+ defaultValue = {
+ left:1,
+ right:1,
+ center:1,
+ justify:1
+ },
+ doJustify = function (range, style) {
+ var bookmark = range.createBookmark(),
+ filterFn = function (node) {
+ return node.nodeType == 1 ? node.tagName.toLowerCase() != 'br' && !domUtils.isBookmarkNode(node) : !domUtils.isWhitespace(node);
+ };
+
+ range.enlarge(true);
+ var bookmark2 = range.createBookmark(),
+ current = domUtils.getNextDomNode(bookmark2.start, false, filterFn),
+ tmpRange = range.cloneRange(),
+ tmpNode;
+ while (current && !(domUtils.getPosition(current, bookmark2.end) & domUtils.POSITION_FOLLOWING)) {
+ if (current.nodeType == 3 || !block(current)) {
+ tmpRange.setStartBefore(current);
+ while (current && current !== bookmark2.end && !block(current)) {
+ tmpNode = current;
+ current = domUtils.getNextDomNode(current, false, null, function (node) {
+ return !block(node);
+ });
+ }
+ tmpRange.setEndAfter(tmpNode);
+ var common = tmpRange.getCommonAncestor();
+ if (!domUtils.isBody(common) && block(common)) {
+ domUtils.setStyles(common, utils.isString(style) ? {'text-align':style} : style);
+ current = common;
+ } else {
+ var p = range.document.createElement('p');
+ domUtils.setStyles(p, utils.isString(style) ? {'text-align':style} : style);
+ var frag = tmpRange.extractContents();
+ p.appendChild(frag);
+ tmpRange.insertNode(p);
+ current = p;
+ }
+ current = domUtils.getNextDomNode(current, false, filterFn);
+ } else {
+ current = domUtils.getNextDomNode(current, true, filterFn);
+ }
+ }
+ return range.moveToBookmark(bookmark2).moveToBookmark(bookmark);
+ };
+
+ UE.commands['justify'] = {
+ execCommand:function (cmdName, align) {
+ var range = this.selection.getRange(),
+ txt;
+
+ //闭合时单独处理
+ if (range.collapsed) {
+ txt = this.document.createTextNode('p');
+ range.insertNode(txt);
+ }
+ doJustify(range, align);
+ if (txt) {
+ range.setStartBefore(txt).collapse(true);
+ domUtils.remove(txt);
+ }
+
+ range.select();
+
+
+ return true;
+ },
+ queryCommandValue:function () {
+ var startNode = this.selection.getStart(),
+ value = domUtils.getComputedStyle(startNode, 'text-align');
+ return defaultValue[value] ? value : 'left';
+ },
+ queryCommandState:function () {
+ var start = this.selection.getStart(),
+ cell = start && domUtils.findParentByTagName(start, ["td", "th","caption"], true);
+
+ return cell? -1:0;
+ }
+
+ };
+};
+
+
+// plugins/font.js
+/**
+ * 字体颜色,背景色,字号,字体,下划线,删除线
+ * @file
+ * @since 1.2.6.1
+ */
+
+/**
+ * 字体颜色
+ * @command forecolor
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @param { String } value 色值(必须十六进制)
+ * @example
+ * ```javascript
+ * editor.execCommand( 'forecolor', '#000' );
+ * ```
+ */
+/**
+ * 返回选区字体颜色
+ * @command forecolor
+ * @method queryCommandValue
+ * @param { String } cmd 命令字符串
+ * @return { String } 返回字体颜色
+ * @example
+ * ```javascript
+ * editor.queryCommandValue( 'forecolor' );
+ * ```
+ */
+
+/**
+ * 字体背景颜色
+ * @command backcolor
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @param { String } value 色值(必须十六进制)
+ * @example
+ * ```javascript
+ * editor.execCommand( 'backcolor', '#000' );
+ * ```
+ */
+/**
+ * 返回选区字体颜色
+ * @command backcolor
+ * @method queryCommandValue
+ * @param { String } cmd 命令字符串
+ * @return { String } 返回字体背景颜色
+ * @example
+ * ```javascript
+ * editor.queryCommandValue( 'backcolor' );
+ * ```
+ */
+
+/**
+ * 字体大小
+ * @command fontsize
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @param { String } value 字体大小
+ * @example
+ * ```javascript
+ * editor.execCommand( 'fontsize', '14px' );
+ * ```
+ */
+/**
+ * 返回选区字体大小
+ * @command fontsize
+ * @method queryCommandValue
+ * @param { String } cmd 命令字符串
+ * @return { String } 返回字体大小
+ * @example
+ * ```javascript
+ * editor.queryCommandValue( 'fontsize' );
+ * ```
+ */
+
+/**
+ * 字体样式
+ * @command fontfamily
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @param { String } value 字体样式
+ * @example
+ * ```javascript
+ * editor.execCommand( 'fontfamily', '微软雅黑' );
+ * ```
+ */
+/**
+ * 返回选区字体样式
+ * @command fontfamily
+ * @method queryCommandValue
+ * @param { String } cmd 命令字符串
+ * @return { String } 返回字体样式
+ * @example
+ * ```javascript
+ * editor.queryCommandValue( 'fontfamily' );
+ * ```
+ */
+
+/**
+ * 字体下划线,与删除线互斥
+ * @command underline
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * editor.execCommand( 'underline' );
+ * ```
+ */
+
+/**
+ * 字体删除线,与下划线互斥
+ * @command strikethrough
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * editor.execCommand( 'strikethrough' );
+ * ```
+ */
+
+/**
+ * 字体边框
+ * @command fontborder
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * editor.execCommand( 'fontborder' );
+ * ```
+ */
+
+UE.plugins['font'] = function () {
+ var me = this,
+ fonts = {
+ 'forecolor': 'color',
+ 'backcolor': 'background-color',
+ 'fontsize': 'font-size',
+ 'fontfamily': 'font-family',
+ 'underline': 'text-decoration',
+ 'strikethrough': 'text-decoration',
+ 'fontborder': 'border'
+ },
+ needCmd = {'underline': 1, 'strikethrough': 1, 'fontborder': 1},
+ needSetChild = {
+ 'forecolor': 'color',
+ 'backcolor': 'background-color',
+ 'fontsize': 'font-size',
+ 'fontfamily': 'font-family'
+
+ };
+ me.setOpt({
+ 'fontfamily': [
+ { name: 'songti', val: '宋体,SimSun'},
+ { name: 'yahei', val: '微软雅黑,Microsoft YaHei'},
+ { name: 'kaiti', val: '楷体,楷体_GB2312, SimKai'},
+ { name: 'heiti', val: '黑体, SimHei'},
+ { name: 'lishu', val: '隶书, SimLi'},
+ { name: 'andaleMono', val: 'andale mono'},
+ { name: 'arial', val: 'arial, helvetica,sans-serif'},
+ { name: 'arialBlack', val: 'arial black,avant garde'},
+ { name: 'comicSansMs', val: 'comic sans ms'},
+ { name: 'impact', val: 'impact,chicago'},
+ { name: 'timesNewRoman', val: 'times new roman'}
+ ],
+ 'fontsize': [10, 11, 12, 14, 16, 18, 20, 24, 36]
+ });
+
+ function mergeWithParent(node){
+ var parent;
+ while(parent = node.parentNode){
+ if(parent.tagName == 'SPAN' && domUtils.getChildCount(parent,function(child){
+ return !domUtils.isBookmarkNode(child) && !domUtils.isBr(child)
+ }) == 1) {
+ parent.style.cssText += node.style.cssText;
+ domUtils.remove(node,true);
+ node = parent;
+
+ }else{
+ break;
+ }
+ }
+
+ }
+ function mergeChild(rng,cmdName,value){
+ if(needSetChild[cmdName]){
+ rng.adjustmentBoundary();
+ if(!rng.collapsed && rng.startContainer.nodeType == 1){
+ var start = rng.startContainer.childNodes[rng.startOffset];
+ if(start && domUtils.isTagNode(start,'span')){
+ var bk = rng.createBookmark();
+ utils.each(domUtils.getElementsByTagName(start, 'span'), function (span) {
+ if (!span.parentNode || domUtils.isBookmarkNode(span))return;
+ if(cmdName == 'backcolor' && domUtils.getComputedStyle(span,'background-color').toLowerCase() === value){
+ return;
+ }
+ domUtils.removeStyle(span,needSetChild[cmdName]);
+ if(span.style.cssText.replace(/^\s+$/,'').length == 0){
+ domUtils.remove(span,true)
+ }
+ });
+ rng.moveToBookmark(bk)
+ }
+ }
+ }
+
+ }
+ function mergesibling(rng,cmdName,value) {
+ var collapsed = rng.collapsed,
+ bk = rng.createBookmark(), common;
+ if (collapsed) {
+ common = bk.start.parentNode;
+ while (dtd.$inline[common.tagName]) {
+ common = common.parentNode;
+ }
+ } else {
+ common = domUtils.getCommonAncestor(bk.start, bk.end);
+ }
+ utils.each(domUtils.getElementsByTagName(common, 'span'), function (span) {
+ if (!span.parentNode || domUtils.isBookmarkNode(span))return;
+ if (/\s*border\s*:\s*none;?\s*/i.test(span.style.cssText)) {
+ if(/^\s*border\s*:\s*none;?\s*$/.test(span.style.cssText)){
+ domUtils.remove(span, true);
+ }else{
+ domUtils.removeStyle(span,'border');
+ }
+ return
+ }
+ if (/border/i.test(span.style.cssText) && span.parentNode.tagName == 'SPAN' && /border/i.test(span.parentNode.style.cssText)) {
+ span.style.cssText = span.style.cssText.replace(/border[^:]*:[^;]+;?/gi, '');
+ }
+ if(!(cmdName=='fontborder' && value=='none')){
+ var next = span.nextSibling;
+ while (next && next.nodeType == 1 && next.tagName == 'SPAN' ) {
+ if(domUtils.isBookmarkNode(next) && cmdName == 'fontborder') {
+ span.appendChild(next);
+ next = span.nextSibling;
+ continue;
+ }
+ if (next.style.cssText == span.style.cssText) {
+ domUtils.moveChild(next, span);
+ domUtils.remove(next);
+ }
+ if (span.nextSibling === next)
+ break;
+ next = span.nextSibling;
+ }
+ }
+
+
+ mergeWithParent(span);
+ if(browser.ie && browser.version > 8 ){
+ //拷贝父亲们的特别的属性,这里只做背景颜色的处理
+ var parent = domUtils.findParent(span,function(n){return n.tagName == 'SPAN' && /background-color/.test(n.style.cssText)});
+ if(parent && !/background-color/.test(span.style.cssText)){
+ span.style.backgroundColor = parent.style.backgroundColor;
+ }
+ }
+
+ });
+ rng.moveToBookmark(bk);
+ mergeChild(rng,cmdName,value)
+ }
+
+ me.addInputRule(function (root) {
+ utils.each(root.getNodesByTagName('u s del font strike'), function (node) {
+ if (node.tagName == 'font') {
+ var cssStyle = [];
+ for (var p in node.attrs) {
+ switch (p) {
+ case 'size':
+ cssStyle.push('font-size:' +
+ ({
+ '1':'10',
+ '2':'12',
+ '3':'16',
+ '4':'18',
+ '5':'24',
+ '6':'32',
+ '7':'48'
+ }[node.attrs[p]] || node.attrs[p]) + 'px');
+ break;
+ case 'color':
+ cssStyle.push('color:' + node.attrs[p]);
+ break;
+ case 'face':
+ cssStyle.push('font-family:' + node.attrs[p]);
+ break;
+ case 'style':
+ cssStyle.push(node.attrs[p]);
+ }
+ }
+ node.attrs = {
+ 'style': cssStyle.join(';')
+ };
+ } else {
+ var val = node.tagName == 'u' ? 'underline' : 'line-through';
+ node.attrs = {
+ 'style': (node.getAttr('style') || '') + 'text-decoration:' + val + ';'
+ }
+ }
+ node.tagName = 'span';
+ });
+// utils.each(root.getNodesByTagName('span'), function (node) {
+// var val;
+// if(val = node.getAttr('class')){
+// if(/fontstrikethrough/.test(val)){
+// node.setStyle('text-decoration','line-through');
+// if(node.attrs['class']){
+// node.attrs['class'] = node.attrs['class'].replace(/fontstrikethrough/,'');
+// }else{
+// node.setAttr('class')
+// }
+// }
+// if(/fontborder/.test(val)){
+// node.setStyle('border','1px solid #000');
+// if(node.attrs['class']){
+// node.attrs['class'] = node.attrs['class'].replace(/fontborder/,'');
+// }else{
+// node.setAttr('class')
+// }
+// }
+// }
+// });
+ });
+// me.addOutputRule(function(root){
+// utils.each(root.getNodesByTagName('span'), function (node) {
+// var val;
+// if(val = node.getStyle('text-decoration')){
+// if(/line-through/.test(val)){
+// if(node.attrs['class']){
+// node.attrs['class'] += ' fontstrikethrough';
+// }else{
+// node.setAttr('class','fontstrikethrough')
+// }
+// }
+//
+// node.setStyle('text-decoration')
+// }
+// if(val = node.getStyle('border')){
+// if(/1px/.test(val) && /solid/.test(val)){
+// if(node.attrs['class']){
+// node.attrs['class'] += ' fontborder';
+//
+// }else{
+// node.setAttr('class','fontborder')
+// }
+// }
+// node.setStyle('border')
+//
+// }
+// });
+// });
+ for (var p in fonts) {
+ (function (cmd, style) {
+ UE.commands[cmd] = {
+ execCommand: function (cmdName, value) {
+ value = value || (this.queryCommandState(cmdName) ? 'none' : cmdName == 'underline' ? 'underline' :
+ cmdName == 'fontborder' ? '1px solid #000' :
+ 'line-through');
+ var me = this,
+ range = this.selection.getRange(),
+ text;
+
+ if (value == 'default') {
+
+ if (range.collapsed) {
+ text = me.document.createTextNode('font');
+ range.insertNode(text).select();
+
+ }
+ me.execCommand('removeFormat', 'span,a', style);
+ if (text) {
+ range.setStartBefore(text).collapse(true);
+ domUtils.remove(text);
+ }
+ mergesibling(range,cmdName,value);
+ range.select()
+ } else {
+ if (!range.collapsed) {
+ if (needCmd[cmd] && me.queryCommandValue(cmd)) {
+ me.execCommand('removeFormat', 'span,a', style);
+ }
+ range = me.selection.getRange();
+
+ range.applyInlineStyle('span', {'style': style + ':' + value});
+ mergesibling(range, cmdName,value);
+ range.select();
+ } else {
+
+ var span = domUtils.findParentByTagName(range.startContainer, 'span', true);
+ text = me.document.createTextNode('font');
+ if (span && !span.children.length && !span[browser.ie ? 'innerText' : 'textContent'].replace(fillCharReg, '').length) {
+ //for ie hack when enter
+ range.insertNode(text);
+ if (needCmd[cmd]) {
+ range.selectNode(text).select();
+ me.execCommand('removeFormat', 'span,a', style, null);
+
+ span = domUtils.findParentByTagName(text, 'span', true);
+ range.setStartBefore(text);
+
+ }
+ span && (span.style.cssText += ';' + style + ':' + value);
+ range.collapse(true).select();
+
+
+ } else {
+ range.insertNode(text);
+ range.selectNode(text).select();
+ span = range.document.createElement('span');
+
+ if (needCmd[cmd]) {
+ //a标签内的不处理跳过
+ if (domUtils.findParentByTagName(text, 'a', true)) {
+ range.setStartBefore(text).setCursor();
+ domUtils.remove(text);
+ return;
+ }
+ me.execCommand('removeFormat', 'span,a', style);
+ }
+
+ span.style.cssText = style + ':' + value;
+
+
+ text.parentNode.insertBefore(span, text);
+ //修复,span套span 但样式不继承的问题
+ if (!browser.ie || browser.ie && browser.version == 9) {
+ var spanParent = span.parentNode;
+ while (!domUtils.isBlockElm(spanParent)) {
+ if (spanParent.tagName == 'SPAN') {
+ //opera合并style不会加入";"
+ span.style.cssText = spanParent.style.cssText + ";" + span.style.cssText;
+ }
+ spanParent = spanParent.parentNode;
+ }
+ }
+
+
+ if (opera) {
+ setTimeout(function () {
+ range.setStart(span, 0).collapse(true);
+ mergesibling(range, cmdName,value);
+ range.select();
+ });
+ } else {
+ range.setStart(span, 0).collapse(true);
+ mergesibling(range,cmdName,value);
+ range.select();
+ }
+
+ //trace:981
+ //domUtils.mergeToParent(span)
+ }
+ domUtils.remove(text);
+ }
+
+
+ }
+ return true;
+ },
+ queryCommandValue: function (cmdName) {
+ var startNode = this.selection.getStart();
+
+ //trace:946
+ if (cmdName == 'underline' || cmdName == 'strikethrough') {
+ var tmpNode = startNode, value;
+ while (tmpNode && !domUtils.isBlockElm(tmpNode) && !domUtils.isBody(tmpNode)) {
+ if (tmpNode.nodeType == 1) {
+ value = domUtils.getComputedStyle(tmpNode, style);
+ if (value != 'none') {
+ return value;
+ }
+ }
+
+ tmpNode = tmpNode.parentNode;
+ }
+ return 'none';
+ }
+ if (cmdName == 'fontborder') {
+ var tmp = startNode, val;
+ while (tmp && dtd.$inline[tmp.tagName]) {
+ if (val = domUtils.getComputedStyle(tmp, 'border')) {
+
+ if (/1px/.test(val) && /solid/.test(val)) {
+ return val;
+ }
+ }
+ tmp = tmp.parentNode;
+ }
+ return ''
+ }
+
+ if( cmdName == 'FontSize' ) {
+ var styleVal = domUtils.getComputedStyle(startNode, style),
+ tmp = /^([\d\.]+)(\w+)$/.exec( styleVal );
+
+ if( tmp ) {
+
+ return Math.floor( tmp[1] ) + tmp[2];
+
+ }
+
+ return styleVal;
+
+ }
+
+ return domUtils.getComputedStyle(startNode, style);
+ },
+ queryCommandState: function (cmdName) {
+ if (!needCmd[cmdName])
+ return 0;
+ var val = this.queryCommandValue(cmdName);
+ if (cmdName == 'fontborder') {
+ return /1px/.test(val) && /solid/.test(val)
+ } else {
+ return cmdName == 'underline' ? /underline/.test(val) : /line\-through/.test(val);
+
+ }
+
+ }
+ };
+ })(p, fonts[p]);
+ }
+};
+
+// plugins/link.js
+/**
+ * 超链接
+ * @file
+ * @since 1.2.6.1
+ */
+
+/**
+ * 插入超链接
+ * @command link
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @param { Object } options 设置自定义属性,例如:url、title、target
+ * @example
+ * ```javascript
+ * editor.execCommand( 'link', '{
+ * url:'ueditor.baidu.com',
+ * title:'ueditor',
+ * target:'_blank'
+ * }' );
+ * ```
+ */
+/**
+ * 返回当前选中的第一个超链接节点
+ * @command link
+ * @method queryCommandValue
+ * @param { String } cmd 命令字符串
+ * @return { Element } 超链接节点
+ * @example
+ * ```javascript
+ * editor.queryCommandValue( 'link' );
+ * ```
+ */
+
+/**
+ * 取消超链接
+ * @command unlink
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * editor.execCommand( 'unlink');
+ * ```
+ */
+
+UE.plugins['link'] = function(){
+ function optimize( range ) {
+ var start = range.startContainer,end = range.endContainer;
+
+ if ( start = domUtils.findParentByTagName( start, 'a', true ) ) {
+ range.setStartBefore( start );
+ }
+ if ( end = domUtils.findParentByTagName( end, 'a', true ) ) {
+ range.setEndAfter( end );
+ }
+ }
+
+
+ UE.commands['unlink'] = {
+ execCommand : function() {
+ var range = this.selection.getRange(),
+ bookmark;
+ if(range.collapsed && !domUtils.findParentByTagName( range.startContainer, 'a', true )){
+ return;
+ }
+ bookmark = range.createBookmark();
+ optimize( range );
+ range.removeInlineStyle( 'a' ).moveToBookmark( bookmark ).select();
+ },
+ queryCommandState : function(){
+ return !this.highlight && this.queryCommandValue('link') ? 0 : -1;
+ }
+
+ };
+ function doLink(range,opt,me){
+ var rngClone = range.cloneRange(),
+ link = me.queryCommandValue('link');
+ optimize( range = range.adjustmentBoundary() );
+ var start = range.startContainer;
+ if(start.nodeType == 1 && link){
+ start = start.childNodes[range.startOffset];
+ if(start && start.nodeType == 1 && start.tagName == 'A' && /^(?:https?|ftp|file)\s*:\s*\/\//.test(start[browser.ie?'innerText':'textContent'])){
+ start[browser.ie ? 'innerText' : 'textContent'] = utils.html(opt.textValue||opt.href);
+
+ }
+ }
+ if( !rngClone.collapsed || link){
+ range.removeInlineStyle( 'a' );
+ rngClone = range.cloneRange();
+ }
+
+ if ( rngClone.collapsed ) {
+ var a = range.document.createElement( 'a'),
+ text = '';
+ if(opt.textValue){
+
+ text = utils.html(opt.textValue);
+ delete opt.textValue;
+ }else{
+ text = utils.html(opt.href);
+
+ }
+ domUtils.setAttributes( a, opt );
+ start = domUtils.findParentByTagName( rngClone.startContainer, 'a', true );
+ if(start && domUtils.isInNodeEndBoundary(rngClone,start)){
+ range.setStartAfter(start).collapse(true);
+
+ }
+ a[browser.ie ? 'innerText' : 'textContent'] = text;
+ range.insertNode(a).selectNode( a );
+ } else {
+ range.applyInlineStyle( 'a', opt );
+
+ }
+ }
+ UE.commands['link'] = {
+ execCommand : function( cmdName, opt ) {
+ var range;
+ opt._href && (opt._href = utils.unhtml(opt._href,/[<">]/g));
+ opt.href && (opt.href = utils.unhtml(opt.href,/[<">]/g));
+ opt.textValue && (opt.textValue = utils.unhtml(opt.textValue,/[<">]/g));
+ doLink(range=this.selection.getRange(),opt,this);
+ //闭合都不加占位符,如果加了会在a后边多个占位符节点,导致a是图片背景组成的列表,出现空白问题
+ range.collapse().select(true);
+
+ },
+ queryCommandValue : function() {
+ var range = this.selection.getRange(),
+ node;
+ if ( range.collapsed ) {
+// node = this.selection.getStart();
+ //在ie下getstart()取值偏上了
+ node = range.startContainer;
+ node = node.nodeType == 1 ? node : node.parentNode;
+
+ if ( node && (node = domUtils.findParentByTagName( node, 'a', true )) && ! domUtils.isInNodeEndBoundary(range,node)) {
+
+ return node;
+ }
+ } else {
+ //trace:1111 如果是xx startContainer是p就会找不到a
+ range.shrinkBoundary();
+ var start = range.startContainer.nodeType == 3 || !range.startContainer.childNodes[range.startOffset] ? range.startContainer : range.startContainer.childNodes[range.startOffset],
+ end = range.endContainer.nodeType == 3 || range.endOffset == 0 ? range.endContainer : range.endContainer.childNodes[range.endOffset-1],
+ common = range.getCommonAncestor();
+ node = domUtils.findParentByTagName( common, 'a', true );
+ if ( !node && common.nodeType == 1){
+
+ var as = common.getElementsByTagName( 'a' ),
+ ps,pe;
+
+ for ( var i = 0,ci; ci = as[i++]; ) {
+ ps = domUtils.getPosition( ci, start ),pe = domUtils.getPosition( ci,end);
+ if ( (ps & domUtils.POSITION_FOLLOWING || ps & domUtils.POSITION_CONTAINS)
+ &&
+ (pe & domUtils.POSITION_PRECEDING || pe & domUtils.POSITION_CONTAINS)
+ ) {
+ node = ci;
+ break;
+ }
+ }
+ }
+ return node;
+ }
+
+ },
+ queryCommandState : function() {
+ //判断如果是视频的话连接不可用
+ //fix 853
+ var img = this.selection.getRange().getClosedNode(),
+ flag = img && (img.className == "edui-faked-video" || img.className.indexOf("edui-upload-video")!=-1);
+ return flag ? -1 : 0;
+ }
+ };
+};
+
+// plugins/iframe.js
+///import core
+///import plugins\inserthtml.js
+///commands 插入框架
+///commandsName InsertFrame
+///commandsTitle 插入Iframe
+///commandsDialog dialogs\insertframe
+
+UE.plugins['insertframe'] = function() {
+ var me =this;
+ function deleteIframe(){
+ me._iframe && delete me._iframe;
+ }
+
+ me.addListener("selectionchange",function(){
+ deleteIframe();
+ });
+
+};
+
+
+
+// plugins/scrawl.js
+///import core
+///commands 涂鸦
+///commandsName Scrawl
+///commandsTitle 涂鸦
+///commandsDialog dialogs\scrawl
+UE.commands['scrawl'] = {
+ queryCommandState : function(){
+ return ( browser.ie && browser.version <= 8 ) ? -1 :0;
+ }
+};
+
+
+// plugins/removeformat.js
+/**
+ * 清除格式
+ * @file
+ * @since 1.2.6.1
+ */
+
+/**
+ * 清除文字样式
+ * @command removeformat
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @param {String} tags 以逗号隔开的标签。如:strong
+ * @param {String} style 样式如:color
+ * @param {String} attrs 属性如:width
+ * @example
+ * ```javascript
+ * editor.execCommand( 'removeformat', 'strong','color','width' );
+ * ```
+ */
+
+UE.plugins['removeformat'] = function(){
+ var me = this;
+ me.setOpt({
+ 'removeFormatTags': 'b,big,code,del,dfn,em,font,i,ins,kbd,q,samp,small,span,strike,strong,sub,sup,tt,u,var',
+ 'removeFormatAttributes':'class,style,lang,width,height,align,hspace,valign'
+ });
+ me.commands['removeformat'] = {
+ execCommand : function( cmdName, tags, style, attrs,notIncludeA ) {
+
+ var tagReg = new RegExp( '^(?:' + (tags || this.options.removeFormatTags).replace( /,/g, '|' ) + ')$', 'i' ) ,
+ removeFormatAttributes = style ? [] : (attrs || this.options.removeFormatAttributes).split( ',' ),
+ range = new dom.Range( this.document ),
+ bookmark,node,parent,
+ filter = function( node ) {
+ return node.nodeType == 1;
+ };
+
+ function isRedundantSpan (node) {
+ if (node.nodeType == 3 || node.tagName.toLowerCase() != 'span'){
+ return 0;
+ }
+ if (browser.ie) {
+ //ie 下判断实效,所以只能简单用style来判断
+ //return node.style.cssText == '' ? 1 : 0;
+ var attrs = node.attributes;
+ if ( attrs.length ) {
+ for ( var i = 0,l = attrs.length; i
+ var node = range.startContainer,
+ tmp,
+ collapsed = range.collapsed;
+ while(node.nodeType == 1 && domUtils.isEmptyNode(node) && dtd.$removeEmpty[node.tagName]){
+ tmp = node.parentNode;
+ range.setStartBefore(node);
+ //trace:937
+ //更新结束边界
+ if(range.startContainer === range.endContainer){
+ range.endOffset--;
+ }
+ domUtils.remove(node);
+ node = tmp;
+ }
+
+ if(!collapsed){
+ node = range.endContainer;
+ while(node.nodeType == 1 && domUtils.isEmptyNode(node) && dtd.$removeEmpty[node.tagName]){
+ tmp = node.parentNode;
+ range.setEndBefore(node);
+ domUtils.remove(node);
+
+ node = tmp;
+ }
+
+
+ }
+ }
+
+
+
+ range = this.selection.getRange();
+ doRemove( range );
+ range.select();
+
+ }
+
+ };
+
+};
+
+
+// plugins/blockquote.js
+/**
+ * 添加引用
+ * @file
+ * @since 1.2.6.1
+ */
+
+/**
+ * 添加引用
+ * @command blockquote
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * editor.execCommand( 'blockquote' );
+ * ```
+ */
+
+/**
+ * 添加引用
+ * @command blockquote
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @param { Object } attrs 节点属性
+ * @example
+ * ```javascript
+ * editor.execCommand( 'blockquote',{
+ * style: "color: red;"
+ * } );
+ * ```
+ */
+
+
+UE.plugins['blockquote'] = function(){
+ var me = this;
+ function getObj(editor){
+ return domUtils.filterNodeList(editor.selection.getStartElementPath(),'blockquote');
+ }
+ me.commands['blockquote'] = {
+ execCommand : function( cmdName, attrs ) {
+ var range = this.selection.getRange(),
+ obj = getObj(this),
+ blockquote = dtd.blockquote,
+ bookmark = range.createBookmark();
+
+ if ( obj ) {
+
+ var start = range.startContainer,
+ startBlock = domUtils.isBlockElm(start) ? start : domUtils.findParent(start,function(node){return domUtils.isBlockElm(node)}),
+
+ end = range.endContainer,
+ endBlock = domUtils.isBlockElm(end) ? end : domUtils.findParent(end,function(node){return domUtils.isBlockElm(node)});
+
+ //处理一下li
+ startBlock = domUtils.findParentByTagName(startBlock,'li',true) || startBlock;
+ endBlock = domUtils.findParentByTagName(endBlock,'li',true) || endBlock;
+
+
+ if(startBlock.tagName == 'LI' || startBlock.tagName == 'TD' || startBlock === obj || domUtils.isBody(startBlock)){
+ domUtils.remove(obj,true);
+ }else{
+ domUtils.breakParent(startBlock,obj);
+ }
+
+ if(startBlock !== endBlock){
+ obj = domUtils.findParentByTagName(endBlock,'blockquote');
+ if(obj){
+ if(endBlock.tagName == 'LI' || endBlock.tagName == 'TD'|| domUtils.isBody(endBlock)){
+ obj.parentNode && domUtils.remove(obj,true);
+ }else{
+ domUtils.breakParent(endBlock,obj);
+ }
+
+ }
+ }
+
+ var blockquotes = domUtils.getElementsByTagName(this.document,'blockquote');
+ for(var i=0,bi;bi=blockquotes[i++];){
+ if(!bi.childNodes.length){
+ domUtils.remove(bi);
+ }else if(domUtils.getPosition(bi,startBlock)&domUtils.POSITION_FOLLOWING && domUtils.getPosition(bi,endBlock)&domUtils.POSITION_PRECEDING){
+ domUtils.remove(bi,true);
+ }
+ }
+
+
+
+
+ } else {
+
+ var tmpRange = range.cloneRange(),
+ node = tmpRange.startContainer.nodeType == 1 ? tmpRange.startContainer : tmpRange.startContainer.parentNode,
+ preNode = node,
+ doEnd = 1;
+
+ //调整开始
+ while ( 1 ) {
+ if ( domUtils.isBody(node) ) {
+ if ( preNode !== node ) {
+ if ( range.collapsed ) {
+ tmpRange.selectNode( preNode );
+ doEnd = 0;
+ } else {
+ tmpRange.setStartBefore( preNode );
+ }
+ }else{
+ tmpRange.setStart(node,0);
+ }
+
+ break;
+ }
+ if ( !blockquote[node.tagName] ) {
+ if ( range.collapsed ) {
+ tmpRange.selectNode( preNode );
+ } else{
+ tmpRange.setStartBefore( preNode);
+ }
+ break;
+ }
+
+ preNode = node;
+ node = node.parentNode;
+ }
+
+ //调整结束
+ if ( doEnd ) {
+ preNode = node = node = tmpRange.endContainer.nodeType == 1 ? tmpRange.endContainer : tmpRange.endContainer.parentNode;
+ while ( 1 ) {
+
+ if ( domUtils.isBody( node ) ) {
+ if ( preNode !== node ) {
+
+ tmpRange.setEndAfter( preNode );
+
+ } else {
+ tmpRange.setEnd( node, node.childNodes.length );
+ }
+
+ break;
+ }
+ if ( !blockquote[node.tagName] ) {
+ tmpRange.setEndAfter( preNode );
+ break;
+ }
+
+ preNode = node;
+ node = node.parentNode;
+ }
+
+ }
+
+
+ node = range.document.createElement( 'blockquote' );
+ domUtils.setAttributes( node, attrs );
+ node.appendChild( tmpRange.extractContents() );
+ tmpRange.insertNode( node );
+ //去除重复的
+ var childs = domUtils.getElementsByTagName(node,'blockquote');
+ for(var i=0,ci;ci=childs[i++];){
+ if(ci.parentNode){
+ domUtils.remove(ci,true);
+ }
+ }
+
+ }
+ range.moveToBookmark( bookmark ).select();
+ },
+ queryCommandState : function() {
+ return getObj(this) ? 1 : 0;
+ }
+ };
+};
+
+
+
+// plugins/convertcase.js
+/**
+ * 大小写转换
+ * @file
+ * @since 1.2.6.1
+ */
+
+/**
+ * 把选区内文本变大写,与“tolowercase”命令互斥
+ * @command touppercase
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * editor.execCommand( 'touppercase' );
+ * ```
+ */
+
+/**
+ * 把选区内文本变小写,与“touppercase”命令互斥
+ * @command tolowercase
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * editor.execCommand( 'tolowercase' );
+ * ```
+ */
+UE.commands['touppercase'] =
+UE.commands['tolowercase'] = {
+ execCommand:function (cmd) {
+ var me = this;
+ var rng = me.selection.getRange();
+ if(rng.collapsed){
+ return rng;
+ }
+ var bk = rng.createBookmark(),
+ bkEnd = bk.end,
+ filterFn = function( node ) {
+ return !domUtils.isBr(node) && !domUtils.isWhitespace( node );
+ },
+ curNode = domUtils.getNextDomNode( bk.start, false, filterFn );
+ while ( curNode && (domUtils.getPosition( curNode, bkEnd ) & domUtils.POSITION_PRECEDING) ) {
+
+ if ( curNode.nodeType == 3 ) {
+ curNode.nodeValue = curNode.nodeValue[cmd == 'touppercase' ? 'toUpperCase' : 'toLowerCase']();
+ }
+ curNode = domUtils.getNextDomNode( curNode, true, filterFn );
+ if(curNode === bkEnd){
+ break;
+ }
+
+ }
+ rng.moveToBookmark(bk).select();
+ }
+};
+
+
+
+// plugins/indent.js
+/**
+ * 首行缩进
+ * @file
+ * @since 1.2.6.1
+ */
+
+/**
+ * 缩进
+ * @command indent
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * editor.execCommand( 'indent' );
+ * ```
+ */
+UE.commands['indent'] = {
+ execCommand : function() {
+ var me = this,value = me.queryCommandState("indent") ? "0em" : (me.options.indentValue || '2em');
+ me.execCommand('Paragraph','p',{style:'text-indent:'+ value});
+ },
+ queryCommandState : function() {
+ var pN = domUtils.filterNodeList(this.selection.getStartElementPath(),'p h1 h2 h3 h4 h5 h6');
+ return pN && pN.style.textIndent && parseInt(pN.style.textIndent) ? 1 : 0;
+ }
+
+};
+
+
+// plugins/print.js
+/**
+ * 打印
+ * @file
+ * @since 1.2.6.1
+ */
+
+/**
+ * 打印
+ * @command print
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * editor.execCommand( 'print' );
+ * ```
+ */
+UE.commands['print'] = {
+ execCommand : function(){
+ this.window.print();
+ },
+ notNeedUndo : 1
+};
+
+
+
+// plugins/preview.js
+/**
+ * 预览
+ * @file
+ * @since 1.2.6.1
+ */
+
+/**
+ * 预览
+ * @command preview
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * editor.execCommand( 'preview' );
+ * ```
+ */
+UE.commands['preview'] = {
+ execCommand : function(){
+ var w = window.open('', '_blank', ''),
+ d = w.document;
+ d.open();
+ d.write(''+this.getContent(null,null,true)+' ');
+ d.close();
+ },
+ notNeedUndo : 1
+};
+
+
+// plugins/selectall.js
+/**
+ * 全选
+ * @file
+ * @since 1.2.6.1
+ */
+
+/**
+ * 选中所有内容
+ * @command selectall
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * editor.execCommand( 'selectall' );
+ * ```
+ */
+UE.plugins['selectall'] = function(){
+ var me = this;
+ me.commands['selectall'] = {
+ execCommand : function(){
+ //去掉了原生的selectAll,因为会出现报错和当内容为空时,不能出现闭合状态的光标
+ var me = this,body = me.body,
+ range = me.selection.getRange();
+ range.selectNodeContents(body);
+ if(domUtils.isEmptyBlock(body)){
+ //opera不能自动合并到元素的里边,要手动处理一下
+ if(browser.opera && body.firstChild && body.firstChild.nodeType == 1){
+ range.setStartAtFirst(body.firstChild);
+ }
+ range.collapse(true);
+ }
+ range.select(true);
+ },
+ notNeedUndo : 1
+ };
+
+
+ //快捷键
+ me.addshortcutkey({
+ "selectAll" : "ctrl+65"
+ });
+};
+
+
+// plugins/paragraph.js
+/**
+ * 段落样式
+ * @file
+ * @since 1.2.6.1
+ */
+
+/**
+ * 段落格式
+ * @command paragraph
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @param {String} style 标签值为:'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
+ * @param {Object} attrs 标签的属性
+ * @example
+ * ```javascript
+ * editor.execCommand( 'Paragraph','h1','{
+ * class:'test'
+ * }' );
+ * ```
+ */
+
+/**
+ * 返回选区内节点标签名
+ * @command paragraph
+ * @method queryCommandValue
+ * @param { String } cmd 命令字符串
+ * @return { String } 节点标签名
+ * @example
+ * ```javascript
+ * editor.queryCommandValue( 'Paragraph' );
+ * ```
+ */
+
+UE.plugins['paragraph'] = function() {
+ var me = this,
+ block = domUtils.isBlockElm,
+ notExchange = ['TD','LI','PRE'],
+
+ doParagraph = function(range,style,attrs,sourceCmdName){
+ var bookmark = range.createBookmark(),
+ filterFn = function( node ) {
+ return node.nodeType == 1 ? node.tagName.toLowerCase() != 'br' && !domUtils.isBookmarkNode(node) : !domUtils.isWhitespace( node );
+ },
+ para;
+
+ range.enlarge( true );
+ var bookmark2 = range.createBookmark(),
+ current = domUtils.getNextDomNode( bookmark2.start, false, filterFn ),
+ tmpRange = range.cloneRange(),
+ tmpNode;
+ while ( current && !(domUtils.getPosition( current, bookmark2.end ) & domUtils.POSITION_FOLLOWING) ) {
+ if ( current.nodeType == 3 || !block( current ) ) {
+ tmpRange.setStartBefore( current );
+ while ( current && current !== bookmark2.end && !block( current ) ) {
+ tmpNode = current;
+ current = domUtils.getNextDomNode( current, false, null, function( node ) {
+ return !block( node );
+ } );
+ }
+ tmpRange.setEndAfter( tmpNode );
+
+ para = range.document.createElement( style );
+ if(attrs){
+ domUtils.setAttributes(para,attrs);
+ if(sourceCmdName && sourceCmdName == 'customstyle' && attrs.style){
+ para.style.cssText = attrs.style;
+ }
+ }
+ para.appendChild( tmpRange.extractContents() );
+ //需要内容占位
+ if(domUtils.isEmptyNode(para)){
+ domUtils.fillChar(range.document,para);
+
+ }
+
+ tmpRange.insertNode( para );
+
+ var parent = para.parentNode;
+ //如果para上一级是一个block元素且不是body,td就删除它
+ if ( block( parent ) && !domUtils.isBody( para.parentNode ) && utils.indexOf(notExchange,parent.tagName)==-1) {
+ //存储dir,style
+ if(!(sourceCmdName && sourceCmdName == 'customstyle')){
+ parent.getAttribute('dir') && para.setAttribute('dir',parent.getAttribute('dir'));
+ //trace:1070
+ parent.style.cssText && (para.style.cssText = parent.style.cssText + ';' + para.style.cssText);
+ //trace:1030
+ parent.style.textAlign && !para.style.textAlign && (para.style.textAlign = parent.style.textAlign);
+ parent.style.textIndent && !para.style.textIndent && (para.style.textIndent = parent.style.textIndent);
+ parent.style.padding && !para.style.padding && (para.style.padding = parent.style.padding);
+ }
+
+ //trace:1706 选择的就是h1-6要删除
+ if(attrs && /h\d/i.test(parent.tagName) && !/h\d/i.test(para.tagName) ){
+ domUtils.setAttributes(parent,attrs);
+ if(sourceCmdName && sourceCmdName == 'customstyle' && attrs.style){
+ parent.style.cssText = attrs.style;
+ }
+ domUtils.remove(para,true);
+ para = parent;
+ }else{
+ domUtils.remove( para.parentNode, true );
+ }
+
+ }
+ if( utils.indexOf(notExchange,parent.tagName)!=-1){
+ current = parent;
+ }else{
+ current = para;
+ }
+
+
+ current = domUtils.getNextDomNode( current, false, filterFn );
+ } else {
+ current = domUtils.getNextDomNode( current, true, filterFn );
+ }
+ }
+ return range.moveToBookmark( bookmark2 ).moveToBookmark( bookmark );
+ };
+ me.setOpt('paragraph',{'p':'', 'h1':'', 'h2':'', 'h3':'', 'h4':'', 'h5':'', 'h6':''});
+ me.commands['paragraph'] = {
+ execCommand : function( cmdName, style,attrs,sourceCmdName ) {
+ var range = this.selection.getRange();
+ //闭合时单独处理
+ if(range.collapsed){
+ var txt = this.document.createTextNode('p');
+ range.insertNode(txt);
+ //去掉冗余的fillchar
+ if(browser.ie){
+ var node = txt.previousSibling;
+ if(node && domUtils.isWhitespace(node)){
+ domUtils.remove(node);
+ }
+ node = txt.nextSibling;
+ if(node && domUtils.isWhitespace(node)){
+ domUtils.remove(node);
+ }
+ }
+
+ }
+ range = doParagraph(range,style,attrs,sourceCmdName);
+ if(txt){
+ range.setStartBefore(txt).collapse(true);
+ pN = txt.parentNode;
+
+ domUtils.remove(txt);
+
+ if(domUtils.isBlockElm(pN)&&domUtils.isEmptyNode(pN)){
+ domUtils.fillNode(this.document,pN);
+ }
+
+ }
+
+ if(browser.gecko && range.collapsed && range.startContainer.nodeType == 1){
+ var child = range.startContainer.childNodes[range.startOffset];
+ if(child && child.nodeType == 1 && child.tagName.toLowerCase() == style){
+ range.setStart(child,0).collapse(true);
+ }
+ }
+ //trace:1097 原来有true,原因忘了,但去了就不能清除多余的占位符了
+ range.select();
+
+
+ return true;
+ },
+ queryCommandValue : function() {
+ var node = domUtils.filterNodeList(this.selection.getStartElementPath(),'p h1 h2 h3 h4 h5 h6');
+ return node ? node.tagName.toLowerCase() : '';
+ }
+ };
+};
+
+
+// plugins/directionality.js
+/**
+ * 设置文字输入的方向的插件
+ * @file
+ * @since 1.2.6.1
+ */
+(function() {
+ var block = domUtils.isBlockElm ,
+ getObj = function(editor){
+// var startNode = editor.selection.getStart(),
+// parents;
+// if ( startNode ) {
+// //查找所有的是block的父亲节点
+// parents = domUtils.findParents( startNode, true, block, true );
+// for ( var i = 0,ci; ci = parents[i++]; ) {
+// if ( ci.getAttribute( 'dir' ) ) {
+// return ci;
+// }
+// }
+// }
+ return domUtils.filterNodeList(editor.selection.getStartElementPath(),function(n){return n && n.nodeType == 1 && n.getAttribute('dir')});
+
+ },
+ doDirectionality = function(range,editor,forward){
+
+ var bookmark,
+ filterFn = function( node ) {
+ return node.nodeType == 1 ? !domUtils.isBookmarkNode(node) : !domUtils.isWhitespace(node);
+ },
+
+ obj = getObj( editor );
+
+ if ( obj && range.collapsed ) {
+ obj.setAttribute( 'dir', forward );
+ return range;
+ }
+ bookmark = range.createBookmark();
+ range.enlarge( true );
+ var bookmark2 = range.createBookmark(),
+ current = domUtils.getNextDomNode( bookmark2.start, false, filterFn ),
+ tmpRange = range.cloneRange(),
+ tmpNode;
+ while ( current && !(domUtils.getPosition( current, bookmark2.end ) & domUtils.POSITION_FOLLOWING) ) {
+ if ( current.nodeType == 3 || !block( current ) ) {
+ tmpRange.setStartBefore( current );
+ while ( current && current !== bookmark2.end && !block( current ) ) {
+ tmpNode = current;
+ current = domUtils.getNextDomNode( current, false, null, function( node ) {
+ return !block( node );
+ } );
+ }
+ tmpRange.setEndAfter( tmpNode );
+ var common = tmpRange.getCommonAncestor();
+ if ( !domUtils.isBody( common ) && block( common ) ) {
+ //遍历到了block节点
+ common.setAttribute( 'dir', forward );
+ current = common;
+ } else {
+ //没有遍历到,添加一个block节点
+ var p = range.document.createElement( 'p' );
+ p.setAttribute( 'dir', forward );
+ var frag = tmpRange.extractContents();
+ p.appendChild( frag );
+ tmpRange.insertNode( p );
+ current = p;
+ }
+
+ current = domUtils.getNextDomNode( current, false, filterFn );
+ } else {
+ current = domUtils.getNextDomNode( current, true, filterFn );
+ }
+ }
+ return range.moveToBookmark( bookmark2 ).moveToBookmark( bookmark );
+ };
+
+ /**
+ * 文字输入方向
+ * @command directionality
+ * @method execCommand
+ * @param { String } cmdName 命令字符串
+ * @param { String } forward 传入'ltr'表示从左向右输入,传入'rtl'表示从右向左输入
+ * @example
+ * ```javascript
+ * editor.execCommand( 'directionality', 'ltr');
+ * ```
+ */
+
+ /**
+ * 查询当前选区的文字输入方向
+ * @command directionality
+ * @method queryCommandValue
+ * @param { String } cmdName 命令字符串
+ * @return { String } 返回'ltr'表示从左向右输入,返回'rtl'表示从右向左输入
+ * @example
+ * ```javascript
+ * editor.queryCommandValue( 'directionality');
+ * ```
+ */
+ UE.commands['directionality'] = {
+ execCommand : function( cmdName,forward ) {
+ var range = this.selection.getRange();
+ //闭合时单独处理
+ if(range.collapsed){
+ var txt = this.document.createTextNode('d');
+ range.insertNode(txt);
+ }
+ doDirectionality(range,this,forward);
+ if(txt){
+ range.setStartBefore(txt).collapse(true);
+ domUtils.remove(txt);
+ }
+
+ range.select();
+ return true;
+ },
+ queryCommandValue : function() {
+ var node = getObj(this);
+ return node ? node.getAttribute('dir') : 'ltr';
+ }
+ };
+})();
+
+
+
+// plugins/horizontal.js
+/**
+ * 插入分割线插件
+ * @file
+ * @since 1.2.6.1
+ */
+
+/**
+ * 插入分割线
+ * @command horizontal
+ * @method execCommand
+ * @param { String } cmdName 命令字符串
+ * @example
+ * ```javascript
+ * editor.execCommand( 'horizontal' );
+ * ```
+ */
+UE.plugins['horizontal'] = function(){
+ var me = this;
+ me.commands['horizontal'] = {
+ execCommand : function( cmdName ) {
+ var me = this;
+ if(me.queryCommandState(cmdName)!==-1){
+ me.execCommand('insertHtml',' ');
+ var range = me.selection.getRange(),
+ start = range.startContainer;
+ if(start.nodeType == 1 && !start.childNodes[range.startOffset] ){
+
+ var tmp;
+ if(tmp = start.childNodes[range.startOffset - 1]){
+ if(tmp.nodeType == 1 && tmp.tagName == 'HR'){
+ if(me.options.enterTag == 'p'){
+ tmp = me.document.createElement('p');
+ range.insertNode(tmp);
+ range.setStart(tmp,0).setCursor();
+
+ }else{
+ tmp = me.document.createElement('br');
+ range.insertNode(tmp);
+ range.setStartBefore(tmp).setCursor();
+ }
+ }
+ }
+
+ }
+ return true;
+ }
+
+ },
+ //边界在table里不能加分隔线
+ queryCommandState : function() {
+ return domUtils.filterNodeList(this.selection.getStartElementPath(),'table') ? -1 : 0;
+ }
+ };
+// me.addListener('delkeyup',function(){
+// var rng = this.selection.getRange();
+// if(browser.ie && browser.version > 8){
+// rng.txtToElmBoundary(true);
+// if(domUtils.isStartInblock(rng)){
+// var tmpNode = rng.startContainer;
+// var pre = tmpNode.previousSibling;
+// if(pre && domUtils.isTagNode(pre,'hr')){
+// domUtils.remove(pre);
+// rng.select();
+// return;
+// }
+// }
+// }
+// if(domUtils.isBody(rng.startContainer)){
+// var hr = rng.startContainer.childNodes[rng.startOffset -1];
+// if(hr && hr.nodeName == 'HR'){
+// var next = hr.nextSibling;
+// if(next){
+// rng.setStart(next,0)
+// }else if(hr.previousSibling){
+// rng.setStartAtLast(hr.previousSibling)
+// }else{
+// var p = this.document.createElement('p');
+// hr.parentNode.insertBefore(p,hr);
+// domUtils.fillNode(this.document,p);
+// rng.setStart(p,0);
+// }
+// domUtils.remove(hr);
+// rng.setCursor(false,true);
+// }
+// }
+// })
+ me.addListener('delkeydown',function(name,evt){
+ var rng = this.selection.getRange();
+ rng.txtToElmBoundary(true);
+ if(domUtils.isStartInblock(rng)){
+ var tmpNode = rng.startContainer;
+ var pre = tmpNode.previousSibling;
+ if(pre && domUtils.isTagNode(pre,'hr')){
+ domUtils.remove(pre);
+ rng.select();
+ domUtils.preventDefault(evt);
+ return true;
+
+ }
+ }
+
+ })
+};
+
+
+
+// plugins/time.js
+/**
+ * 插入时间和日期
+ * @file
+ * @since 1.2.6.1
+ */
+
+/**
+ * 插入时间,默认格式:12:59:59
+ * @command time
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * editor.execCommand( 'time');
+ * ```
+ */
+
+/**
+ * 插入日期,默认格式:2013-08-30
+ * @command date
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * editor.execCommand( 'date');
+ * ```
+ */
+UE.commands['time'] = UE.commands["date"] = {
+ execCommand : function(cmd, format){
+ var date = new Date;
+
+ function formatTime(date, format) {
+ var hh = ('0' + date.getHours()).slice(-2),
+ ii = ('0' + date.getMinutes()).slice(-2),
+ ss = ('0' + date.getSeconds()).slice(-2);
+ format = format || 'hh:ii:ss';
+ return format.replace(/hh/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 ? ' ':''):' ';
+ }else{
+ var frag = rng.extractContents();
+ var div = me.document.createElement('div');
+ div.appendChild(frag);
+
+ utils.each(UE.filterNode(UE.htmlparser(div.innerHTML.replace(/[\r\t]/g,'')),me.options.filterTxtRules).children,function(node){
+ if(browser.ie && browser.ie11below && browser.version > 8){
+
+ if(node.type =='element'){
+ if(node.tagName == 'br'){
+ code += '\n'
+ }else if(!dtd.$empty[node.tagName]){
+ utils.each(node.children,function(cn){
+ if(cn.type =='element'){
+ if(cn.tagName == 'br'){
+ code += '\n'
+ }else if(!dtd.$empty[node.tagName]){
+ code += cn.innerText();
+ }
+ }else{
+ code += cn.data
+ }
+ })
+ if(!/\n$/.test(code)){
+ code += '\n';
+ }
+ }
+ }else{
+ code += node.data + '\n'
+ }
+ if(!node.nextSibling() && /\n$/.test(code)){
+ code = code.replace(/\n$/,'');
+ }
+ }else{
+ if(browser.ie && browser.ie11below){
+
+ if(node.type =='element'){
+ if(node.tagName == 'br'){
+ code += ' '
+ }else if(!dtd.$empty[node.tagName]){
+ utils.each(node.children,function(cn){
+ if(cn.type =='element'){
+ if(cn.tagName == 'br'){
+ code += ' '
+ }else if(!dtd.$empty[node.tagName]){
+ code += cn.innerText();
+ }
+ }else{
+ code += cn.data
+ }
+ });
+ if(!/br>$/.test(code)){
+ code += ' ';
+ }
+ }
+ }else{
+ code += node.data + ' '
+ }
+ if(!node.nextSibling() && / $/.test(code)){
+ code = code.replace(/ $/,'');
+ }
+
+ }else{
+ code += (node.type == 'element' ? (dtd.$empty[node.tagName] ? '' : node.innerText()) : node.data);
+ if(!/br\/?\s*>$/.test(code)){
+ if(!node.nextSibling())
+ return;
+ code += ' '
+ }
+ }
+
+ }
+
+ });
+ }
+ me.execCommand('inserthtml',''+code+' ',true);
+
+ pre = me.document.getElementById('coder');
+ domUtils.removeAttributes(pre,'id');
+ var tmpNode = pre.previousSibling;
+
+ if(tmpNode && (tmpNode.nodeType == 3 && tmpNode.nodeValue.length == 1 && browser.ie && browser.version == 6 || domUtils.isEmptyBlock(tmpNode))){
+
+ domUtils.remove(tmpNode)
+ }
+ var rng = me.selection.getRange();
+ if(domUtils.isEmptyBlock(pre)){
+ rng.setStart(pre,0).setCursor(false,true)
+ }else{
+ rng.selectNodeContents(pre).select()
+ }
+ }
+
+
+
+ },
+ queryCommandValue : function(){
+ var path = this.selection.getStartElementPath();
+ var lang = '';
+ utils.each(path,function(node){
+ if(node.nodeName =='PRE'){
+ var match = node.className.match(/brush:([^;]+)/);
+ lang = match && match[1] ? match[1] : '';
+ return false;
+ }
+ });
+ return lang;
+ }
+ };
+
+ me.addInputRule(function(root){
+ utils.each(root.getNodesByTagName('pre'),function(pre){
+ var brs = pre.getNodesByTagName('br');
+ if(brs.length){
+ browser.ie && browser.ie11below && browser.version > 8 && utils.each(brs,function(br){
+ var txt = UE.uNode.createText('\n');
+ br.parentNode.insertBefore(txt,br);
+ br.parentNode.removeChild(br);
+ });
+ return;
+ }
+ if(browser.ie && browser.ie11below && browser.version > 8)
+ return;
+ var code = pre.innerText().split(/\n/);
+ pre.innerHTML('');
+ utils.each(code,function(c){
+ if(c.length){
+ pre.appendChild(UE.uNode.createText(c));
+ }
+ pre.appendChild(UE.uNode.createElement('br'))
+ })
+ })
+ });
+ me.addOutputRule(function(root){
+ utils.each(root.getNodesByTagName('pre'),function(pre){
+ var code = '';
+ utils.each(pre.children,function(n){
+ if(n.type == 'text'){
+ //在ie下文本内容有可能末尾带有\n要去掉
+ //trace:3396
+ code += n.data.replace(/[ ]/g,' ').replace(/\n$/,'');
+ }else{
+ if(n.tagName == 'br'){
+ code += '\n'
+ }else{
+ code += (!dtd.$empty[n.tagName] ? '' : n.innerText());
+ }
+
+ }
+
+ });
+
+ pre.innerText(code.replace(/( |\n)+$/,''))
+ })
+ });
+ //不需要判断highlight的command列表
+ me.notNeedCodeQuery ={
+ help:1,
+ undo:1,
+ redo:1,
+ source:1,
+ print:1,
+ searchreplace:1,
+ fullscreen:1,
+ preview:1,
+ insertparagraph:1,
+ elementpath:1,
+ insertcode:1,
+ inserthtml:1,
+ selectall:1
+ };
+ //将queyCommamndState重置
+ var orgQuery = me.queryCommandState;
+ me.queryCommandState = function(cmd){
+ var me = this;
+
+ if(!me.notNeedCodeQuery[cmd.toLowerCase()] && me.selection && me.queryCommandValue('insertcode')){
+ return -1;
+ }
+ return UE.Editor.prototype.queryCommandState.apply(this,arguments)
+ };
+ me.addListener('beforeenterkeydown',function(){
+ var rng = me.selection.getRange();
+ var pre = domUtils.findParentByTagName(rng.startContainer,'pre',true);
+ if(pre){
+ me.fireEvent('saveScene');
+ if(!rng.collapsed){
+ rng.deleteContents();
+ }
+ if(!browser.ie || browser.ie9above){
+ var tmpNode = me.document.createElement('br'),pre;
+ rng.insertNode(tmpNode).setStartAfter(tmpNode).collapse(true);
+ var next = tmpNode.nextSibling;
+ if(!next && (!browser.ie || browser.version > 10)){
+ rng.insertNode(tmpNode.cloneNode(false));
+ }else{
+ rng.setStartAfter(tmpNode);
+ }
+ pre = tmpNode.previousSibling;
+ var tmp;
+ while(pre ){
+ tmp = pre;
+ pre = pre.previousSibling;
+ if(!pre || pre.nodeName == 'BR'){
+ pre = tmp;
+ break;
+ }
+ }
+ if(pre){
+ var str = '';
+ while(pre && pre.nodeName != 'BR' && new RegExp('^[\\s'+domUtils.fillChar+']*$').test(pre.nodeValue)){
+ str += pre.nodeValue;
+ pre = pre.nextSibling;
+ }
+ if(pre.nodeName != 'BR'){
+ var match = pre.nodeValue.match(new RegExp('^([\\s'+domUtils.fillChar+']+)'));
+ if(match && match[1]){
+ str += match[1]
+ }
+
+ }
+ if(str){
+ str = me.document.createTextNode(str);
+ rng.insertNode(str).setStartAfter(str);
+ }
+ }
+ rng.collapse(true).select(true);
+ }else{
+ if(browser.version > 8){
+
+ var txt = me.document.createTextNode('\n');
+ var start = rng.startContainer;
+ if(rng.startOffset == 0){
+ var preNode = start.previousSibling;
+ if(preNode){
+ rng.insertNode(txt);
+ var fillchar = me.document.createTextNode(' ');
+ rng.setStartAfter(txt).insertNode(fillchar).setStart(fillchar,0).collapse(true).select(true)
+ }
+ }else{
+ rng.insertNode(txt).setStartAfter(txt);
+ var fillchar = me.document.createTextNode(' ');
+ start = rng.startContainer.childNodes[rng.startOffset];
+ if(start && !/^\n/.test(start.nodeValue)){
+ rng.setStartBefore(txt)
+ }
+ rng.insertNode(fillchar).setStart(fillchar,0).collapse(true).select(true)
+ }
+
+ }else{
+ var tmpNode = me.document.createElement('br');
+ rng.insertNode(tmpNode);
+ rng.insertNode(me.document.createTextNode(domUtils.fillChar));
+ rng.setStartAfter(tmpNode);
+ pre = tmpNode.previousSibling;
+ var tmp;
+ while(pre ){
+ tmp = pre;
+ pre = pre.previousSibling;
+ if(!pre || pre.nodeName == 'BR'){
+ pre = tmp;
+ break;
+ }
+ }
+ if(pre){
+ var str = '';
+ while(pre && pre.nodeName != 'BR' && new RegExp('^[ '+domUtils.fillChar+']*$').test(pre.nodeValue)){
+ str += pre.nodeValue;
+ pre = pre.nextSibling;
+ }
+ if(pre.nodeName != 'BR'){
+ var match = pre.nodeValue.match(new RegExp('^([ '+domUtils.fillChar+']+)'));
+ if(match && match[1]){
+ str += match[1]
+ }
+
+ }
+
+ str = me.document.createTextNode(str);
+ rng.insertNode(str).setStartAfter(str);
+ }
+ rng.collapse(true).select();
+ }
+
+
+ }
+ me.fireEvent('saveScene');
+ return true;
+ }
+
+
+ });
+
+ me.addListener('tabkeydown',function(cmd,evt){
+ var rng = me.selection.getRange();
+ var pre = domUtils.findParentByTagName(rng.startContainer,'pre',true);
+ if(pre){
+ me.fireEvent('saveScene');
+ if(evt.shiftKey){
+
+ }else{
+ if(!rng.collapsed){
+ var bk = rng.createBookmark();
+ var start = bk.start.previousSibling;
+
+ while(start){
+ if(pre.firstChild === start && !domUtils.isBr(start)){
+ pre.insertBefore(me.document.createTextNode(' '),start);
+
+ break;
+ }
+ if(domUtils.isBr(start)){
+ pre.insertBefore(me.document.createTextNode(' '),start.nextSibling);
+
+ break;
+ }
+ start = start.previousSibling;
+ }
+ var end = bk.end;
+ start = bk.start.nextSibling;
+ if(pre.firstChild === bk.start){
+ pre.insertBefore(me.document.createTextNode(' '),start.nextSibling)
+
+ }
+ while(start && start !== end){
+ if(domUtils.isBr(start) && start.nextSibling){
+ if(start.nextSibling === end){
+ break;
+ }
+ pre.insertBefore(me.document.createTextNode(' '),start.nextSibling)
+ }
+
+ start = start.nextSibling;
+ }
+ rng.moveToBookmark(bk).select();
+ }else{
+ var tmpNode = me.document.createTextNode(' ');
+ rng.insertNode(tmpNode).setStartAfter(tmpNode).collapse(true).select(true);
+ }
+ }
+
+
+ me.fireEvent('saveScene');
+ return true;
+ }
+
+
+ });
+
+
+ me.addListener('beforeinserthtml',function(evtName,html){
+ var me = this,
+ rng = me.selection.getRange(),
+ pre = domUtils.findParentByTagName(rng.startContainer,'pre',true);
+ if(pre){
+ if(!rng.collapsed){
+ rng.deleteContents()
+ }
+ var htmlstr = '';
+ if(browser.ie && browser.version > 8){
+
+ utils.each(UE.filterNode(UE.htmlparser(html),me.options.filterTxtRules).children,function(node){
+ if(node.type =='element'){
+ if(node.tagName == 'br'){
+ htmlstr += '\n'
+ }else if(!dtd.$empty[node.tagName]){
+ utils.each(node.children,function(cn){
+ if(cn.type =='element'){
+ if(cn.tagName == 'br'){
+ htmlstr += '\n'
+ }else if(!dtd.$empty[node.tagName]){
+ htmlstr += cn.innerText();
+ }
+ }else{
+ htmlstr += cn.data
+ }
+ })
+ if(!/\n$/.test(htmlstr)){
+ htmlstr += '\n';
+ }
+ }
+ }else{
+ htmlstr += node.data + '\n'
+ }
+ if(!node.nextSibling() && /\n$/.test(htmlstr)){
+ htmlstr = htmlstr.replace(/\n$/,'');
+ }
+ });
+ var tmpNode = me.document.createTextNode(utils.html(htmlstr.replace(/ /g,' ')));
+ rng.insertNode(tmpNode).selectNode(tmpNode).select();
+ }else{
+ var frag = me.document.createDocumentFragment();
+
+ utils.each(UE.filterNode(UE.htmlparser(html),me.options.filterTxtRules).children,function(node){
+ if(node.type =='element'){
+ if(node.tagName == 'br'){
+ frag.appendChild(me.document.createElement('br'))
+ }else if(!dtd.$empty[node.tagName]){
+ utils.each(node.children,function(cn){
+ if(cn.type =='element'){
+ if(cn.tagName == 'br'){
+
+ frag.appendChild(me.document.createElement('br'))
+ }else if(!dtd.$empty[node.tagName]){
+ frag.appendChild(me.document.createTextNode(utils.html(cn.innerText().replace(/ /g,' '))));
+
+ }
+ }else{
+ frag.appendChild(me.document.createTextNode(utils.html( cn.data.replace(/ /g,' '))));
+
+ }
+ })
+ if(frag.lastChild.nodeName != 'BR'){
+ frag.appendChild(me.document.createElement('br'))
+ }
+ }
+ }else{
+ frag.appendChild(me.document.createTextNode(utils.html( node.data.replace(/ /g,' '))));
+ }
+ if(!node.nextSibling() && frag.lastChild.nodeName == 'BR'){
+ frag.removeChild(frag.lastChild)
+ }
+
+
+ });
+ rng.insertNode(frag).select();
+
+ }
+
+ return true;
+ }
+ });
+ //方向键的处理
+ me.addListener('keydown',function(cmd,evt){
+ var me = this,keyCode = evt.keyCode || evt.which;
+ if(keyCode == 40){
+ var rng = me.selection.getRange(),pre,start = rng.startContainer;
+ if(rng.collapsed && (pre = domUtils.findParentByTagName(rng.startContainer,'pre',true)) && !pre.nextSibling){
+ var last = pre.lastChild
+ while(last && last.nodeName == 'BR'){
+ last = last.previousSibling;
+ }
+ if(last === start || rng.startContainer === pre && rng.startOffset == pre.childNodes.length){
+ me.execCommand('insertparagraph');
+ domUtils.preventDefault(evt)
+ }
+
+ }
+ }
+ });
+ //trace:3395
+ me.addListener('delkeydown',function(type,evt){
+ var rng = this.selection.getRange();
+ rng.txtToElmBoundary(true);
+ var start = rng.startContainer;
+ if(domUtils.isTagNode(start,'pre') && rng.collapsed && domUtils.isStartInblock(rng)){
+ var p = me.document.createElement('p');
+ domUtils.fillNode(me.document,p);
+ start.parentNode.insertBefore(p,start);
+ domUtils.remove(start);
+ rng.setStart(p,0).setCursor(false,true);
+ domUtils.preventDefault(evt);
+ return true;
+ }
+ })
+};
+
+
+// plugins/cleardoc.js
+/**
+ * 清空文档插件
+ * @file
+ * @since 1.2.6.1
+ */
+
+/**
+ * 清空文档
+ * @command cleardoc
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * //editor 是编辑器实例
+ * editor.execCommand('cleardoc');
+ * ```
+ */
+
+UE.commands['cleardoc'] = {
+ execCommand : function( cmdName) {
+ var me = this,
+ enterTag = me.options.enterTag,
+ range = me.selection.getRange();
+ if(enterTag == "br"){
+ me.body.innerHTML = " ";
+ range.setStart(me.body,0).setCursor();
+ }else{
+ me.body.innerHTML = ""+(ie ? "" : " ")+" ";
+ range.setStart(me.body.firstChild,0).setCursor(false,true);
+ }
+ setTimeout(function(){
+ me.fireEvent("clearDoc");
+ },0);
+
+ }
+};
+
+
+
+// plugins/anchor.js
+/**
+ * 锚点插件,为UEditor提供插入锚点支持
+ * @file
+ * @since 1.2.6.1
+ */
+UE.plugin.register('anchor', function (){
+
+ return {
+ bindEvents:{
+ 'ready':function(){
+ utils.cssRule('anchor',
+ '.anchorclass{background: url(\''
+ + this.options.themePath
+ + this.options.theme +'/images/anchor.gif\') no-repeat scroll left center transparent;cursor: auto;display: inline-block;height: 16px;width: 15px;}',
+ this.document);
+ }
+ },
+ outputRule: function(root){
+ utils.each(root.getNodesByTagName('img'),function(a){
+ var val;
+ if(val = a.getAttr('anchorname')){
+ a.tagName = 'a';
+ a.setAttr({
+ anchorname : '',
+ name : val,
+ 'class' : ''
+ })
+ }
+ })
+ },
+ inputRule:function(root){
+ utils.each(root.getNodesByTagName('a'),function(a){
+ var val;
+ if((val = a.getAttr('name')) && !a.getAttr('href')){
+ a.tagName = 'img';
+ a.setAttr({
+ anchorname :a.getAttr('name'),
+ 'class' : 'anchorclass'
+ });
+ a.setAttr('name')
+
+ }
+ })
+
+ },
+ commands:{
+ /**
+ * 插入锚点
+ * @command anchor
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @param { String } name 锚点名称字符串
+ * @example
+ * ```javascript
+ * //editor 是编辑器实例
+ * editor.execCommand('anchor', 'anchor1');
+ * ```
+ */
+ 'anchor':{
+ execCommand:function (cmd, name) {
+ var range = this.selection.getRange(),img = range.getClosedNode();
+ if (img && img.getAttribute('anchorname')) {
+ if (name) {
+ img.setAttribute('anchorname', name);
+ } else {
+ range.setStartBefore(img).setCursor();
+ domUtils.remove(img);
+ }
+ } else {
+ if (name) {
+ //只在选区的开始插入
+ var anchor = this.document.createElement('img');
+ range.collapse(true);
+ domUtils.setAttributes(anchor,{
+ 'anchorname':name,
+ 'class':'anchorclass'
+ });
+ range.insertNode(anchor).setStartAfter(anchor).setCursor(false,true);
+ }
+ }
+ }
+ }
+ }
+ }
+});
+
+
+// plugins/wordcount.js
+///import core
+///commands 字数统计
+///commandsName WordCount,wordCount
+///commandsTitle 字数统计
+/*
+ * Created by JetBrains WebStorm.
+ * User: taoqili
+ * Date: 11-9-7
+ * Time: 下午8:18
+ * To change this template use File | Settings | File Templates.
+ */
+
+UE.plugins['wordcount'] = function(){
+ var me = this;
+ me.setOpt('wordCount',true);
+ me.addListener('contentchange',function(){
+ me.fireEvent('wordcount');
+ });
+ var timer;
+ me.addListener('ready',function(){
+ var me = this;
+ domUtils.on(me.body,"keyup",function(evt){
+ var code = evt.keyCode||evt.which,
+ //忽略的按键,ctr,alt,shift,方向键
+ ignores = {"16":1,"18":1,"20":1,"37":1,"38":1,"39":1,"40":1};
+ if(code in ignores) return;
+ clearTimeout(timer);
+ timer = setTimeout(function(){
+ me.fireEvent('wordcount');
+ },200)
+ })
+ });
+};
+
+
+// plugins/pagebreak.js
+/**
+ * 分页功能插件
+ * @file
+ * @since 1.2.6.1
+ */
+UE.plugins['pagebreak'] = function () {
+ var me = this,
+ notBreakTags = ['td'];
+ me.setOpt('pageBreakTag','_ueditor_page_break_tag_');
+
+ function fillNode(node){
+ if(domUtils.isEmptyBlock(node)){
+ var firstChild = node.firstChild,tmpNode;
+
+ while(firstChild && firstChild.nodeType == 1 && domUtils.isEmptyBlock(firstChild)){
+ tmpNode = firstChild;
+ firstChild = firstChild.firstChild;
+ }
+ !tmpNode && (tmpNode = node);
+ domUtils.fillNode(me.document,tmpNode);
+ }
+ }
+ //分页符样式添加
+
+ me.ready(function(){
+ utils.cssRule('pagebreak','.pagebreak{display:block;clear:both !important;cursor:default !important;width: 100% !important;margin:0;}',me.document);
+ });
+ function isHr(node){
+ return node && node.nodeType == 1 && node.tagName == 'HR' && node.className == 'pagebreak';
+ }
+ me.addInputRule(function(root){
+ root.traversal(function(node){
+ if(node.type == 'text' && node.data == me.options.pageBreakTag){
+ var hr = UE.uNode.createElement(' ');
+ node.parentNode.insertBefore(hr,node);
+ node.parentNode.removeChild(node)
+ }
+ })
+ });
+ me.addOutputRule(function(node){
+ utils.each(node.getNodesByTagName('hr'),function(n){
+ if(n.getAttr('class') == 'pagebreak'){
+ var txt = UE.uNode.createText(me.options.pageBreakTag);
+ n.parentNode.insertBefore(txt,n);
+ n.parentNode.removeChild(n);
+ }
+ })
+
+ });
+
+ /**
+ * 插入分页符
+ * @command pagebreak
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @remind 在表格中插入分页符会把表格切分成两部分
+ * @remind 获取编辑器内的数据时, 编辑器会把分页符转换成“_ueditor_page_break_tag_”字符串,
+ * 以便于提交数据到服务器端后处理分页。
+ * @example
+ * ```javascript
+ * editor.execCommand( 'pagebreak'); //插入一个hr标签,带有样式类名pagebreak
+ * ```
+ */
+
+ me.commands['pagebreak'] = {
+ execCommand:function () {
+ var range = me.selection.getRange(),hr = me.document.createElement('hr');
+ domUtils.setAttributes(hr,{
+ 'class' : 'pagebreak',
+ noshade:"noshade",
+ size:"5"
+ });
+ domUtils.unSelectable(hr);
+ //table单独处理
+ var node = domUtils.findParentByTagName(range.startContainer, notBreakTags, true),
+
+ parents = [], pN;
+ if (node) {
+ switch (node.tagName) {
+ case 'TD':
+ pN = node.parentNode;
+ if (!pN.previousSibling) {
+ var table = domUtils.findParentByTagName(pN, 'table');
+// var tableWrapDiv = table.parentNode;
+// if(tableWrapDiv && tableWrapDiv.nodeType == 1
+// && tableWrapDiv.tagName == 'DIV'
+// && tableWrapDiv.getAttribute('dropdrag')
+// ){
+// domUtils.remove(tableWrapDiv,true);
+// }
+ table.parentNode.insertBefore(hr, table);
+ parents = domUtils.findParents(hr, true);
+
+ } else {
+ pN.parentNode.insertBefore(hr, pN);
+ parents = domUtils.findParents(hr);
+
+ }
+ pN = parents[1];
+ if (hr !== pN) {
+ domUtils.breakParent(hr, pN);
+
+ }
+ //table要重写绑定一下拖拽
+ me.fireEvent('afteradjusttable',me.document);
+ }
+
+ } else {
+
+ if (!range.collapsed) {
+ range.deleteContents();
+ var start = range.startContainer;
+ while ( !domUtils.isBody(start) && domUtils.isBlockElm(start) && domUtils.isEmptyNode(start)) {
+ range.setStartBefore(start).collapse(true);
+ domUtils.remove(start);
+ start = range.startContainer;
+ }
+
+ }
+ range.insertNode(hr);
+
+ var pN = hr.parentNode, nextNode;
+ while (!domUtils.isBody(pN)) {
+ domUtils.breakParent(hr, pN);
+ nextNode = hr.nextSibling;
+ if (nextNode && domUtils.isEmptyBlock(nextNode)) {
+ domUtils.remove(nextNode);
+ }
+ pN = hr.parentNode;
+ }
+ nextNode = hr.nextSibling;
+ var pre = hr.previousSibling;
+ if(isHr(pre)){
+ domUtils.remove(pre);
+ }else{
+ pre && fillNode(pre);
+ }
+
+ if(!nextNode){
+ var p = me.document.createElement('p');
+
+ hr.parentNode.appendChild(p);
+ domUtils.fillNode(me.document,p);
+ range.setStart(p,0).collapse(true);
+ }else{
+ if(isHr(nextNode)){
+ domUtils.remove(nextNode);
+ }else{
+ fillNode(nextNode);
+ }
+ range.setEndAfter(hr).collapse(false);
+ }
+
+ range.select(true);
+
+ }
+
+ }
+ };
+};
+
+// plugins/wordimage.js
+///import core
+///commands 本地图片引导上传
+///commandsName WordImage
+///commandsTitle 本地图片引导上传
+///commandsDialog dialogs\wordimage
+
+UE.plugin.register('wordimage',function(){
+ var me = this,
+ images = [];
+ return {
+ commands : {
+ 'wordimage':{
+ execCommand:function () {
+ var images = domUtils.getElementsByTagName(me.body, "img");
+ var urlList = [];
+ for (var i = 0, ci; ci = images[i++];) {
+ var url = ci.getAttribute("word_img");
+ url && urlList.push(url);
+ }
+ return urlList;
+ },
+ queryCommandState:function () {
+ images = domUtils.getElementsByTagName(me.body, "img");
+ for (var i = 0, ci; ci = images[i++];) {
+ if (ci.getAttribute("word_img")) {
+ return 1;
+ }
+ }
+ return -1;
+ },
+ notNeedUndo:true
+ }
+ },
+ inputRule : function (root) {
+ utils.each(root.getNodesByTagName('img'), function (img) {
+ var attrs = img.attrs,
+ flag = parseInt(attrs.width) < 128 || parseInt(attrs.height) < 43,
+ opt = me.options,
+ src = opt.UEDITOR_HOME_URL + 'themes/default/images/spacer.gif';
+ if (attrs['src'] && /^(?:(file:\/+))/.test(attrs['src'])) {
+ img.setAttr({
+ width:attrs.width,
+ height:attrs.height,
+ alt:attrs.alt,
+ word_img: attrs.src,
+ src:src,
+ 'style':'background:url(' + ( flag ? opt.themePath + opt.theme + '/images/word.gif' : opt.langPath + opt.lang + '/images/localimage.png') + ') no-repeat center center;border:1px solid #ddd'
+ })
+ }
+ })
+ }
+ }
+});
+
+// plugins/dragdrop.js
+UE.plugins['dragdrop'] = function (){
+
+ var me = this;
+ me.ready(function(){
+ domUtils.on(this.body,'dragend',function(){
+ var rng = me.selection.getRange();
+ var node = rng.getClosedNode()||me.selection.getStart();
+
+ if(node && node.tagName == 'IMG'){
+
+ var pre = node.previousSibling,next;
+ while(next = node.nextSibling){
+ if(next.nodeType == 1 && next.tagName == 'SPAN' && !next.firstChild){
+ domUtils.remove(next)
+ }else{
+ break;
+ }
+ }
+
+
+ if((pre && pre.nodeType == 1 && !domUtils.isEmptyBlock(pre) || !pre) && (!next || next && !domUtils.isEmptyBlock(next))){
+ if(pre && pre.tagName == 'P' && !domUtils.isEmptyBlock(pre)){
+ pre.appendChild(node);
+ domUtils.moveChild(next,pre);
+ domUtils.remove(next);
+ }else if(next && next.tagName == 'P' && !domUtils.isEmptyBlock(next)){
+ next.insertBefore(node,next.firstChild);
+ }
+
+ if(pre && pre.tagName == 'P' && domUtils.isEmptyBlock(pre)){
+ domUtils.remove(pre)
+ }
+ if(next && next.tagName == 'P' && domUtils.isEmptyBlock(next)){
+ domUtils.remove(next)
+ }
+ rng.selectNode(node).select();
+ me.fireEvent('saveScene');
+
+ }
+
+ }
+
+ })
+ });
+ me.addListener('keyup', function(type, evt) {
+ var keyCode = evt.keyCode || evt.which;
+ if (keyCode == 13) {
+ var rng = me.selection.getRange(),node;
+ if(node = domUtils.findParentByTagName(rng.startContainer,'p',true)){
+ if(domUtils.getComputedStyle(node,'text-align') == 'center'){
+ domUtils.removeStyle(node,'text-align')
+ }
+ }
+ }
+ })
+};
+
+
+// plugins/undo.js
+/**
+ * undo redo
+ * @file
+ * @since 1.2.6.1
+ */
+
+/**
+ * 撤销上一次执行的命令
+ * @command undo
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * editor.execCommand( 'undo' );
+ * ```
+ */
+
+/**
+ * 重做上一次执行的命令
+ * @command redo
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * editor.execCommand( 'redo' );
+ * ```
+ */
+
+UE.plugins['undo'] = function () {
+ var saveSceneTimer;
+ var me = this,
+ maxUndoCount = me.options.maxUndoCount || 20,
+ maxInputCount = me.options.maxInputCount || 20,
+ fillchar = new RegExp(domUtils.fillChar + '|<\/hr>', 'gi');// ie会产生多余的
+ var noNeedFillCharTags = {
+ ol:1,ul:1,table:1,tbody:1,tr:1,body:1
+ };
+ var orgState = me.options.autoClearEmptyNode;
+ function compareAddr(indexA, indexB) {
+ if (indexA.length != indexB.length)
+ return 0;
+ for (var i = 0, l = indexA.length; i < l; i++) {
+ if (indexA[i] != indexB[i])
+ return 0
+ }
+ return 1;
+ }
+
+ function compareRangeAddress(rngAddrA, rngAddrB) {
+ if (rngAddrA.collapsed != rngAddrB.collapsed) {
+ return 0;
+ }
+ if (!compareAddr(rngAddrA.startAddress, rngAddrB.startAddress) || !compareAddr(rngAddrA.endAddress, rngAddrB.endAddress)) {
+ return 0;
+ }
+ return 1;
+ }
+
+ function UndoManager() {
+ this.list = [];
+ this.index = 0;
+ this.hasUndo = false;
+ this.hasRedo = false;
+ this.undo = function () {
+ if (this.hasUndo) {
+ if (!this.list[this.index - 1] && this.list.length == 1) {
+ this.reset();
+ return;
+ }
+ while (this.list[this.index].content == this.list[this.index - 1].content) {
+ this.index--;
+ if (this.index == 0) {
+ return this.restore(0);
+ }
+ }
+ this.restore(--this.index);
+ }
+ };
+ this.redo = function () {
+ if (this.hasRedo) {
+ while (this.list[this.index].content == this.list[this.index + 1].content) {
+ this.index++;
+ if (this.index == this.list.length - 1) {
+ return this.restore(this.index);
+ }
+ }
+ this.restore(++this.index);
+ }
+ };
+
+ this.restore = function () {
+ var me = this.editor;
+ var scene = this.list[this.index];
+ var root = UE.htmlparser(scene.content.replace(fillchar, ''));
+ me.options.autoClearEmptyNode = false;
+ me.filterInputRule(root);
+ me.options.autoClearEmptyNode = orgState;
+ //trace:873
+ //去掉展位符
+ me.document.body.innerHTML = root.toHtml();
+ me.fireEvent('afterscencerestore');
+ //处理undo后空格不展位的问题
+ if (browser.ie) {
+ utils.each(domUtils.getElementsByTagName(me.document,'td th caption p'),function(node){
+ if(domUtils.isEmptyNode(node)){
+ domUtils.fillNode(me.document, node);
+ }
+ })
+ }
+
+ try{
+ var rng = new dom.Range(me.document).moveToAddress(scene.address);
+ rng.select(noNeedFillCharTags[rng.startContainer.nodeName.toLowerCase()]);
+ }catch(e){}
+
+ this.update();
+ this.clearKey();
+ //不能把自己reset了
+ me.fireEvent('reset', true);
+ };
+
+ this.getScene = function () {
+ var me = this.editor;
+ var rng = me.selection.getRange(),
+ rngAddress = rng.createAddress(false,true);
+ me.fireEvent('beforegetscene');
+ var root = UE.htmlparser(me.body.innerHTML);
+ me.options.autoClearEmptyNode = false;
+ me.filterOutputRule(root);
+ me.options.autoClearEmptyNode = orgState;
+ var cont = root.toHtml();
+ //trace:3461
+ //这个会引起回退时导致空格丢失的情况
+// browser.ie && (cont = cont.replace(/> <').replace(/\s*\s*/g, '>'));
+ me.fireEvent('aftergetscene');
+
+ return {
+ address:rngAddress,
+ content:cont
+ }
+ };
+ this.save = function (notCompareRange,notSetCursor) {
+ clearTimeout(saveSceneTimer);
+ var currentScene = this.getScene(notSetCursor),
+ lastScene = this.list[this.index];
+
+ if(lastScene && lastScene.content != currentScene.content){
+ me.trigger('contentchange')
+ }
+ //内容相同位置相同不存
+ if (lastScene && lastScene.content == currentScene.content &&
+ ( notCompareRange ? 1 : compareRangeAddress(lastScene.address, currentScene.address) )
+ ) {
+ return;
+ }
+ this.list = this.list.slice(0, this.index + 1);
+ this.list.push(currentScene);
+ //如果大于最大数量了,就把最前的剔除
+ if (this.list.length > maxUndoCount) {
+ this.list.shift();
+ }
+ this.index = this.list.length - 1;
+ this.clearKey();
+ //跟新undo/redo状态
+ this.update();
+
+ };
+ this.update = function () {
+ this.hasRedo = !!this.list[this.index + 1];
+ this.hasUndo = !!this.list[this.index - 1];
+ };
+ this.reset = function () {
+ this.list = [];
+ this.index = 0;
+ this.hasUndo = false;
+ this.hasRedo = false;
+ this.clearKey();
+ };
+ this.clearKey = function () {
+ keycont = 0;
+ lastKeyCode = null;
+ };
+ }
+
+ me.undoManger = new UndoManager();
+ me.undoManger.editor = me;
+ function saveScene() {
+ this.undoManger.save();
+ }
+
+ me.addListener('saveScene', function () {
+ var args = Array.prototype.splice.call(arguments,1);
+ this.undoManger.save.apply(this.undoManger,args);
+ });
+
+// me.addListener('beforeexeccommand', saveScene);
+// me.addListener('afterexeccommand', saveScene);
+
+ me.addListener('reset', function (type, exclude) {
+ if (!exclude) {
+ this.undoManger.reset();
+ }
+ });
+ me.commands['redo'] = me.commands['undo'] = {
+ execCommand:function (cmdName) {
+ this.undoManger[cmdName]();
+ },
+ queryCommandState:function (cmdName) {
+ return this.undoManger['has' + (cmdName.toLowerCase() == 'undo' ? 'Undo' : 'Redo')] ? 0 : -1;
+ },
+ notNeedUndo:1
+ };
+
+ var keys = {
+ // /*Backspace*/ 8:1, /*Delete*/ 46:1,
+ /*Shift*/ 16:1, /*Ctrl*/ 17:1, /*Alt*/ 18:1,
+ 37:1, 38:1, 39:1, 40:1
+
+ },
+ keycont = 0,
+ lastKeyCode;
+ //输入法状态下不计算字符数
+ var inputType = false;
+ me.addListener('ready', function () {
+ domUtils.on(this.body, 'compositionstart', function () {
+ inputType = true;
+ });
+ domUtils.on(this.body, 'compositionend', function () {
+ inputType = false;
+ })
+ });
+ //快捷键
+ me.addshortcutkey({
+ "Undo":"ctrl+90", //undo
+ "Redo":"ctrl+89" //redo
+
+ });
+ var isCollapsed = true;
+ me.addListener('keydown', function (type, evt) {
+
+ var me = this;
+ var keyCode = evt.keyCode || evt.which;
+ if (!keys[keyCode] && !evt.ctrlKey && !evt.metaKey && !evt.shiftKey && !evt.altKey) {
+ if (inputType)
+ return;
+
+ if(!me.selection.getRange().collapsed){
+ me.undoManger.save(false,true);
+ isCollapsed = false;
+ return;
+ }
+ if (me.undoManger.list.length == 0) {
+ me.undoManger.save(true);
+ }
+ clearTimeout(saveSceneTimer);
+ function save(cont){
+ cont.undoManger.save(false,true);
+ cont.fireEvent('selectionchange');
+ }
+ saveSceneTimer = setTimeout(function(){
+ if(inputType){
+ var interalTimer = setInterval(function(){
+ if(!inputType){
+ save(me);
+ clearInterval(interalTimer)
+ }
+ },300)
+ return;
+ }
+ save(me);
+ },200);
+
+ lastKeyCode = keyCode;
+ keycont++;
+ if (keycont >= maxInputCount ) {
+ save(me)
+ }
+ }
+ });
+ me.addListener('keyup', function (type, evt) {
+ var keyCode = evt.keyCode || evt.which;
+ if (!keys[keyCode] && !evt.ctrlKey && !evt.metaKey && !evt.shiftKey && !evt.altKey) {
+ if (inputType)
+ return;
+ if(!isCollapsed){
+ this.undoManger.save(false,true);
+ isCollapsed = true;
+ }
+ }
+ });
+ //扩展实例,添加关闭和开启命令undo
+ me.stopCmdUndo = function(){
+ me.__hasEnterExecCommand = true;
+ };
+ me.startCmdUndo = function(){
+ me.__hasEnterExecCommand = false;
+ }
+};
+
+
+// plugins/copy.js
+UE.plugin.register('copy', function () {
+
+ var me = this;
+
+ function initZeroClipboard() {
+
+ ZeroClipboard.config({
+ debug: false,
+ swfPath: me.options.UEDITOR_HOME_URL + 'third-party/zeroclipboard/ZeroClipboard.swf'
+ });
+
+ var client = me.zeroclipboard = new ZeroClipboard();
+
+ // 复制内容
+ client.on('copy', function (e) {
+ var client = e.client,
+ rng = me.selection.getRange(),
+ div = document.createElement('div');
+
+ div.appendChild(rng.cloneContents());
+ client.setText(div.innerText || div.textContent);
+ client.setHtml(div.innerHTML);
+ rng.select();
+ });
+ // hover事件传递到target
+ client.on('mouseover mouseout', function (e) {
+ var target = e.target;
+ if (e.type == 'mouseover') {
+ domUtils.addClass(target, 'edui-state-hover');
+ } else if (e.type == 'mouseout') {
+ domUtils.removeClasses(target, 'edui-state-hover');
+ }
+ });
+ // flash加载不成功
+ client.on('wrongflash noflash', function () {
+ ZeroClipboard.destroy();
+ });
+ }
+
+ return {
+ bindEvents: {
+ 'ready': function () {
+ if (!browser.ie) {
+ if (window.ZeroClipboard) {
+ initZeroClipboard();
+ } else {
+ utils.loadFile(document, {
+ src: me.options.UEDITOR_HOME_URL + "third-party/zeroclipboard/ZeroClipboard.js",
+ tag: "script",
+ type: "text/javascript",
+ defer: "defer"
+ }, function () {
+ initZeroClipboard();
+ });
+ }
+ }
+ }
+ },
+ commands: {
+ 'copy': {
+ execCommand: function (cmd) {
+ if (!me.document.execCommand('copy')) {
+ alert(me.getLang('copymsg'));
+ }
+ }
+ }
+ }
+ }
+});
+
+
+// plugins/paste.js
+///import core
+///import plugins/inserthtml.js
+///import plugins/undo.js
+///import plugins/serialize.js
+///commands 粘贴
+///commandsName PastePlain
+///commandsTitle 纯文本粘贴模式
+/**
+ * @description 粘贴
+ * @author zhanyi
+ */
+UE.plugins['paste'] = function () {
+ function getClipboardData(callback) {
+ var doc = this.document;
+ if (doc.getElementById('baidu_pastebin')) {
+ return;
+ }
+ var range = this.selection.getRange(),
+ bk = range.createBookmark(),
+ //创建剪贴的容器div
+ pastebin = doc.createElement('div');
+ pastebin.id = 'baidu_pastebin';
+ // Safari 要求div必须有内容,才能粘贴内容进来
+ browser.webkit && pastebin.appendChild(doc.createTextNode(domUtils.fillChar + domUtils.fillChar));
+ doc.body.appendChild(pastebin);
+ //trace:717 隐藏的span不能得到top
+ //bk.start.innerHTML = ' ';
+ bk.start.style.display = '';
+ pastebin.style.cssText = "position:absolute;width:1px;height:1px;overflow:hidden;left:-1000px;white-space:nowrap;top:" +
+ //要在现在光标平行的位置加入,否则会出现跳动的问题
+ domUtils.getXY(bk.start).y + 'px';
+
+ range.selectNodeContents(pastebin).select(true);
+
+ setTimeout(function () {
+ if (browser.webkit) {
+ for (var i = 0, pastebins = doc.querySelectorAll('#baidu_pastebin'), pi; pi = pastebins[i++];) {
+ if (domUtils.isEmptyNode(pi)) {
+ domUtils.remove(pi);
+ } else {
+ pastebin = pi;
+ break;
+ }
+ }
+ }
+ try {
+ pastebin.parentNode.removeChild(pastebin);
+ } catch (e) {
+ }
+ range.moveToBookmark(bk).select(true);
+ callback(pastebin);
+ }, 0);
+ }
+
+ var me = this;
+
+ me.setOpt({
+ retainOnlyLabelPasted : false
+ });
+
+ var txtContent, htmlContent, address;
+
+ function getPureHtml(html){
+ return html.replace(/<(\/?)([\w\-]+)([^>]*)>/gi, function (a, b, tagName, attrs) {
+ tagName = tagName.toLowerCase();
+ if ({img: 1}[tagName]) {
+ return a;
+ }
+ attrs = attrs.replace(/([\w\-]*?)\s*=\s*(("([^"]*)")|('([^']*)')|([^\s>]+))/gi, function (str, atr, val) {
+ if ({
+ 'src': 1,
+ 'href': 1,
+ 'name': 1
+ }[atr.toLowerCase()]) {
+ return atr + '=' + val + ' '
+ }
+ return ''
+ });
+ if ({
+ 'span': 1,
+ 'div': 1
+ }[tagName]) {
+ return ''
+ } else {
+
+ return '<' + b + tagName + ' ' + utils.trim(attrs) + '>'
+ }
+
+ });
+ }
+ function filter(div) {
+ var html;
+ if (div.firstChild) {
+ //去掉cut中添加的边界值
+ var nodes = domUtils.getElementsByTagName(div, 'span');
+ for (var i = 0, ni; ni = nodes[i++];) {
+ if (ni.id == '_baidu_cut_start' || ni.id == '_baidu_cut_end') {
+ domUtils.remove(ni);
+ }
+ }
+
+ if (browser.webkit) {
+
+ var brs = div.querySelectorAll('div br');
+ for (var i = 0, bi; bi = brs[i++];) {
+ var pN = bi.parentNode;
+ if (pN.tagName == 'DIV' && pN.childNodes.length == 1) {
+ pN.innerHTML = '
';
+ domUtils.remove(pN);
+ }
+ }
+ var divs = div.querySelectorAll('#baidu_pastebin');
+ for (var i = 0, di; di = divs[i++];) {
+ var tmpP = me.document.createElement('p');
+ di.parentNode.insertBefore(tmpP, di);
+ while (di.firstChild) {
+ tmpP.appendChild(di.firstChild);
+ }
+ domUtils.remove(di);
+ }
+
+ var metas = div.querySelectorAll('meta');
+ for (var i = 0, ci; ci = metas[i++];) {
+ domUtils.remove(ci);
+ }
+
+ var brs = div.querySelectorAll('br');
+ for (i = 0; ci = brs[i++];) {
+ if (/^apple-/i.test(ci.className)) {
+ domUtils.remove(ci);
+ }
+ }
+ }
+ if (browser.gecko) {
+ var dirtyNodes = div.querySelectorAll('[_moz_dirty]');
+ for (i = 0; ci = dirtyNodes[i++];) {
+ ci.removeAttribute('_moz_dirty');
+ }
+ }
+ if (!browser.ie) {
+ var spans = div.querySelectorAll('span.Apple-style-span');
+ for (var i = 0, ci; ci = spans[i++];) {
+ domUtils.remove(ci, true);
+ }
+ }
+
+ //ie下使用innerHTML会产生多余的\r\n字符,也会产生 这里过滤掉
+ html = div.innerHTML;//.replace(/>(?:(\s| )*?)<');
+
+ //过滤word粘贴过来的冗余属性
+ html = UE.filterWord(html);
+ //取消了忽略空白的第二个参数,粘贴过来的有些是有空白的,会被套上相关的标签
+ var root = UE.htmlparser(html);
+ //如果给了过滤规则就先进行过滤
+ if (me.options.filterRules) {
+ UE.filterNode(root, me.options.filterRules);
+ }
+ //执行默认的处理
+ me.filterInputRule(root);
+ //针对chrome的处理
+ if (browser.webkit) {
+ var br = root.lastChild();
+ if (br && br.type == 'element' && br.tagName == 'br') {
+ root.removeChild(br)
+ }
+ utils.each(me.body.querySelectorAll('div'), function (node) {
+ if (domUtils.isEmptyBlock(node)) {
+ domUtils.remove(node,true)
+ }
+ })
+ }
+ html = {'html': root.toHtml()};
+ me.fireEvent('beforepaste', html, root);
+ //抢了默认的粘贴,那后边的内容就不执行了,比如表格粘贴
+ if(!html.html){
+ return;
+ }
+ root = UE.htmlparser(html.html,true);
+ //如果开启了纯文本模式
+ if (me.queryCommandState('pasteplain') === 1) {
+ me.execCommand('insertHtml', UE.filterNode(root, me.options.filterTxtRules).toHtml(), true);
+ } else {
+ //文本模式
+ UE.filterNode(root, me.options.filterTxtRules);
+ txtContent = root.toHtml();
+ //完全模式
+ htmlContent = html.html;
+
+ address = me.selection.getRange().createAddress(true);
+ me.execCommand('insertHtml', me.getOpt('retainOnlyLabelPasted') === true ? getPureHtml(htmlContent) : htmlContent, true);
+ }
+ me.fireEvent("afterpaste", html);
+ }
+ }
+
+ me.addListener('pasteTransfer', function (cmd, plainType) {
+
+ if (address && txtContent && htmlContent && txtContent != htmlContent) {
+ var range = me.selection.getRange();
+ range.moveToAddress(address, true);
+
+ if (!range.collapsed) {
+
+ while (!domUtils.isBody(range.startContainer)
+ ) {
+ var start = range.startContainer;
+ if(start.nodeType == 1){
+ start = start.childNodes[range.startOffset];
+ if(!start){
+ range.setStartBefore(range.startContainer);
+ continue;
+ }
+ var pre = start.previousSibling;
+
+ if(pre && pre.nodeType == 3 && new RegExp('^[\n\r\t '+domUtils.fillChar+']*$').test(pre.nodeValue)){
+ range.setStartBefore(pre)
+ }
+ }
+ if(range.startOffset == 0){
+ range.setStartBefore(range.startContainer);
+ }else{
+ break;
+ }
+
+ }
+ while (!domUtils.isBody(range.endContainer)
+ ) {
+ var end = range.endContainer;
+ if(end.nodeType == 1){
+ end = end.childNodes[range.endOffset];
+ if(!end){
+ range.setEndAfter(range.endContainer);
+ continue;
+ }
+ var next = end.nextSibling;
+ if(next && next.nodeType == 3 && new RegExp('^[\n\r\t'+domUtils.fillChar+']*$').test(next.nodeValue)){
+ range.setEndAfter(next)
+ }
+ }
+ if(range.endOffset == range.endContainer[range.endContainer.nodeType == 3 ? 'nodeValue' : 'childNodes'].length){
+ range.setEndAfter(range.endContainer);
+ }else{
+ break;
+ }
+
+ }
+
+ }
+
+ range.deleteContents();
+ range.select(true);
+ me.__hasEnterExecCommand = true;
+ var html = htmlContent;
+ if (plainType === 2 ) {
+ html = getPureHtml(html);
+ } else if (plainType) {
+ html = txtContent;
+ }
+ me.execCommand('inserthtml', html, true);
+ me.__hasEnterExecCommand = false;
+ var rng = me.selection.getRange();
+ while (!domUtils.isBody(rng.startContainer) && !rng.startOffset &&
+ rng.startContainer[rng.startContainer.nodeType == 3 ? 'nodeValue' : 'childNodes'].length
+ ) {
+ rng.setStartBefore(rng.startContainer);
+ }
+ var tmpAddress = rng.createAddress(true);
+ address.endAddress = tmpAddress.startAddress;
+ }
+ });
+
+ me.addListener('ready', function () {
+ domUtils.on(me.body, 'cut', function () {
+ var range = me.selection.getRange();
+ if (!range.collapsed && me.undoManger) {
+ me.undoManger.save();
+ }
+ });
+
+ //ie下beforepaste在点击右键时也会触发,所以用监控键盘才处理
+ domUtils.on(me.body, browser.ie || browser.opera ? 'keydown' : 'paste', function (e) {
+ if ((browser.ie || browser.opera) && ((!e.ctrlKey && !e.metaKey) || e.keyCode != '86')) {
+ return;
+ }
+ getClipboardData.call(me, function (div) {
+ filter(div);
+ });
+ });
+
+ });
+
+ me.commands['paste'] = {
+ execCommand: function (cmd) {
+ if (browser.ie) {
+ getClipboardData.call(me, function (div) {
+ filter(div);
+ });
+ me.document.execCommand('paste');
+ } else {
+ alert(me.getLang('pastemsg'));
+ }
+ }
+ }
+};
+
+
+
+// plugins/puretxtpaste.js
+/**
+ * 纯文本粘贴插件
+ * @file
+ * @since 1.2.6.1
+ */
+
+UE.plugins['pasteplain'] = function(){
+ var me = this;
+ me.setOpt({
+ 'pasteplain':false,
+ 'filterTxtRules' : function(){
+ function transP(node){
+ node.tagName = 'p';
+ node.setStyle();
+ }
+ function removeNode(node){
+ node.parentNode.removeChild(node,true)
+ }
+ return {
+ //直接删除及其字节点内容
+ '-' : 'script style object iframe embed input select',
+ 'p': {$:{}},
+ 'br':{$:{}},
+ div: function (node) {
+ var tmpNode, p = UE.uNode.createElement('p');
+ while (tmpNode = node.firstChild()) {
+ if (tmpNode.type == 'text' || !UE.dom.dtd.$block[tmpNode.tagName]) {
+ p.appendChild(tmpNode);
+ } else {
+ if (p.firstChild()) {
+ node.parentNode.insertBefore(p, node);
+ p = UE.uNode.createElement('p');
+ } else {
+ node.parentNode.insertBefore(tmpNode, node);
+ }
+ }
+ }
+ if (p.firstChild()) {
+ node.parentNode.insertBefore(p, node);
+ }
+ node.parentNode.removeChild(node);
+ },
+ ol: removeNode,
+ ul: removeNode,
+ dl:removeNode,
+ dt:removeNode,
+ dd:removeNode,
+ 'li':removeNode,
+ 'caption':transP,
+ 'th':transP,
+ 'tr':transP,
+ 'h1':transP,'h2':transP,'h3':transP,'h4':transP,'h5':transP,'h6':transP,
+ 'td':function(node){
+ //没有内容的td直接删掉
+ var txt = !!node.innerText();
+ if(txt){
+ node.parentNode.insertAfter(UE.uNode.createText(' '),node);
+ }
+ node.parentNode.removeChild(node,node.innerText())
+ }
+ }
+ }()
+ });
+ //暂时这里支持一下老版本的属性
+ var pasteplain = me.options.pasteplain;
+
+ /**
+ * 启用或取消纯文本粘贴模式
+ * @command pasteplain
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * editor.queryCommandState( 'pasteplain' );
+ * ```
+ */
+
+ /**
+ * 查询当前是否处于纯文本粘贴模式
+ * @command pasteplain
+ * @method queryCommandState
+ * @param { String } cmd 命令字符串
+ * @return { int } 如果处于纯文本模式,返回1,否则,返回0
+ * @example
+ * ```javascript
+ * editor.queryCommandState( 'pasteplain' );
+ * ```
+ */
+ me.commands['pasteplain'] = {
+ queryCommandState: function (){
+ return pasteplain ? 1 : 0;
+ },
+ execCommand: function (){
+ pasteplain = !pasteplain|0;
+ },
+ notNeedUndo : 1
+ };
+};
+
+// plugins/list.js
+/**
+ * 有序列表,无序列表插件
+ * @file
+ * @since 1.2.6.1
+ */
+
+UE.plugins['list'] = function () {
+ var me = this,
+ notExchange = {
+ 'TD':1,
+ 'PRE':1,
+ 'BLOCKQUOTE':1
+ };
+ var customStyle = {
+ 'cn' : 'cn-1-',
+ 'cn1' : 'cn-2-',
+ 'cn2' : 'cn-3-',
+ 'num': 'num-1-',
+ 'num1' : 'num-2-',
+ 'num2' : 'num-3-',
+ 'dash' : 'dash',
+ 'dot':'dot'
+ };
+
+ me.setOpt( {
+ 'autoTransWordToList':false,
+ 'insertorderedlist':{
+ 'num':'',
+ 'num1':'',
+ 'num2':'',
+ 'cn':'',
+ 'cn1':'',
+ 'cn2':'',
+ 'decimal':'',
+ 'lower-alpha':'',
+ 'lower-roman':'',
+ 'upper-alpha':'',
+ 'upper-roman':''
+ },
+ 'insertunorderedlist':{
+ 'circle':'',
+ 'disc':'',
+ 'square':'',
+ 'dash' : '',
+ 'dot':''
+ },
+ listDefaultPaddingLeft : '30',
+ listiconpath : 'http://bs.baidu.com/listicon/',
+ maxListLevel : -1,//-1不限制
+ disablePInList:false
+ } );
+ function listToArray(list){
+ var arr = [];
+ for(var p in list){
+ arr.push(p)
+ }
+ return arr;
+ }
+ var listStyle = {
+ 'OL':listToArray(me.options.insertorderedlist),
+ 'UL':listToArray(me.options.insertunorderedlist)
+ };
+ var liiconpath = me.options.listiconpath;
+
+ //根据用户配置,调整customStyle
+ for(var s in customStyle){
+ if(!me.options.insertorderedlist.hasOwnProperty(s) && !me.options.insertunorderedlist.hasOwnProperty(s)){
+ delete customStyle[s];
+ }
+ }
+
+ me.ready(function () {
+ var customCss = [];
+ for(var p in customStyle){
+ if(p == 'dash' || p == 'dot'){
+ customCss.push('li.list-' + customStyle[p] + '{background-image:url(' + liiconpath +customStyle[p]+'.gif)}');
+ customCss.push('ul.custom_'+p+'{list-style:none;}ul.custom_'+p+' li{background-position:0 3px;background-repeat:no-repeat}');
+ }else{
+ for(var i= 0;i<99;i++){
+ customCss.push('li.list-' + customStyle[p] + i + '{background-image:url(' + liiconpath + 'list-'+customStyle[p] + i + '.gif)}')
+ }
+ customCss.push('ol.custom_'+p+'{list-style:none;}ol.custom_'+p+' li{background-position:0 3px;background-repeat:no-repeat}');
+ }
+ switch(p){
+ case 'cn':
+ customCss.push('li.list-'+p+'-paddingleft-1{padding-left:25px}');
+ customCss.push('li.list-'+p+'-paddingleft-2{padding-left:40px}');
+ customCss.push('li.list-'+p+'-paddingleft-3{padding-left:55px}');
+ break;
+ case 'cn1':
+ customCss.push('li.list-'+p+'-paddingleft-1{padding-left:30px}');
+ customCss.push('li.list-'+p+'-paddingleft-2{padding-left:40px}');
+ customCss.push('li.list-'+p+'-paddingleft-3{padding-left:55px}');
+ break;
+ case 'cn2':
+ customCss.push('li.list-'+p+'-paddingleft-1{padding-left:40px}');
+ customCss.push('li.list-'+p+'-paddingleft-2{padding-left:55px}');
+ customCss.push('li.list-'+p+'-paddingleft-3{padding-left:68px}');
+ break;
+ case 'num':
+ case 'num1':
+ customCss.push('li.list-'+p+'-paddingleft-1{padding-left:25px}');
+ break;
+ case 'num2':
+ customCss.push('li.list-'+p+'-paddingleft-1{padding-left:35px}');
+ customCss.push('li.list-'+p+'-paddingleft-2{padding-left:40px}');
+ break;
+ case 'dash':
+ customCss.push('li.list-'+p+'-paddingleft{padding-left:35px}');
+ break;
+ case 'dot':
+ customCss.push('li.list-'+p+'-paddingleft{padding-left:20px}');
+ }
+ }
+ customCss.push('.list-paddingleft-1{padding-left:0}');
+ customCss.push('.list-paddingleft-2{padding-left:'+me.options.listDefaultPaddingLeft+'px}');
+ customCss.push('.list-paddingleft-3{padding-left:'+me.options.listDefaultPaddingLeft*2+'px}');
+ //如果不给宽度会在自定应样式里出现滚动条
+ utils.cssRule('list', 'ol,ul{margin:0;pading:0;'+(browser.ie ? '' : 'width:95%')+'}li{clear:both;}'+customCss.join('\n'), me.document);
+ });
+ //单独处理剪切的问题
+ me.ready(function(){
+ domUtils.on(me.body,'cut',function(){
+ setTimeout(function(){
+ var rng = me.selection.getRange(),li;
+ //trace:3416
+ if(!rng.collapsed){
+ if(li = domUtils.findParentByTagName(rng.startContainer,'li',true)){
+ if(!li.nextSibling && domUtils.isEmptyBlock(li)){
+ var pn = li.parentNode,node;
+ if(node = pn.previousSibling){
+ domUtils.remove(pn);
+ rng.setStartAtLast(node).collapse(true);
+ rng.select(true);
+ }else if(node = pn.nextSibling){
+ domUtils.remove(pn);
+ rng.setStartAtFirst(node).collapse(true);
+ rng.select(true);
+ }else{
+ var tmpNode = me.document.createElement('p');
+ domUtils.fillNode(me.document,tmpNode);
+ pn.parentNode.insertBefore(tmpNode,pn);
+ domUtils.remove(pn);
+ rng.setStart(tmpNode,0).collapse(true);
+ rng.select(true);
+ }
+ }
+ }
+ }
+
+ })
+ })
+ });
+
+ function getStyle(node){
+ var cls = node.className;
+ if(domUtils.hasClass(node,/custom_/)){
+ return cls.match(/custom_(\w+)/)[1]
+ }
+ return domUtils.getStyle(node, 'list-style-type')
+
+ }
+
+ me.addListener('beforepaste',function(type,html){
+ var me = this,
+ rng = me.selection.getRange(),li;
+ var root = UE.htmlparser(html.html,true);
+ if(li = domUtils.findParentByTagName(rng.startContainer,'li',true)){
+ var list = li.parentNode,tagName = list.tagName == 'OL' ? 'ul':'ol';
+ utils.each(root.getNodesByTagName(tagName),function(n){
+ n.tagName = list.tagName;
+ n.setAttr();
+ if(n.parentNode === root){
+ type = getStyle(list) || (list.tagName == 'OL' ? 'decimal' : 'disc')
+ }else{
+ var className = n.parentNode.getAttr('class');
+ if(className && /custom_/.test(className)){
+ type = className.match(/custom_(\w+)/)[1]
+ }else{
+ type = n.parentNode.getStyle('list-style-type');
+ }
+ if(!type){
+ type = list.tagName == 'OL' ? 'decimal' : 'disc';
+ }
+ }
+ var index = utils.indexOf(listStyle[list.tagName], type);
+ if(n.parentNode !== root)
+ index = index + 1 == listStyle[list.tagName].length ? 0 : index + 1;
+ var currentStyle = listStyle[list.tagName][index];
+ if(customStyle[currentStyle]){
+ n.setAttr('class', 'custom_' + currentStyle)
+
+ }else{
+ n.setStyle('list-style-type',currentStyle)
+ }
+ })
+
+ }
+
+ html.html = root.toHtml();
+ });
+ //导出时,去掉p标签
+ me.getOpt('disablePInList') === true && me.addOutputRule(function(root){
+ utils.each(root.getNodesByTagName('li'),function(li){
+ var newChildrens = [],index=0;
+ utils.each(li.children,function(n){
+ if(n.tagName == 'p'){
+ var tmpNode;
+ while(tmpNode = n.children.pop()) {
+ newChildrens.splice(index,0,tmpNode);
+ tmpNode.parentNode = li;
+ lastNode = tmpNode;
+ }
+ tmpNode = newChildrens[newChildrens.length-1];
+ if(!tmpNode || tmpNode.type != 'element' || tmpNode.tagName != 'br'){
+ var br = UE.uNode.createElement('br');
+ br.parentNode = li;
+ newChildrens.push(br);
+ }
+
+ index = newChildrens.length;
+ }
+ });
+ if(newChildrens.length){
+ li.children = newChildrens;
+ }
+ });
+ });
+ //进入编辑器的li要套p标签
+ me.addInputRule(function(root){
+ utils.each(root.getNodesByTagName('li'),function(li){
+ var tmpP = UE.uNode.createElement('p');
+ for(var i= 0,ci;ci=li.children[i];){
+ if(ci.type == 'text' || dtd.p[ci.tagName]){
+ tmpP.appendChild(ci);
+ }else{
+ if(tmpP.firstChild()){
+ li.insertBefore(tmpP,ci);
+ tmpP = UE.uNode.createElement('p');
+ i = i + 2;
+ }else{
+ i++;
+ }
+
+ }
+ }
+ if(tmpP.firstChild() && !tmpP.parentNode || !li.firstChild()){
+ li.appendChild(tmpP);
+ }
+ //trace:3357
+ //p不能为空
+ if (!tmpP.firstChild()) {
+ tmpP.innerHTML(browser.ie ? ' ' : ' ')
+ }
+ //去掉末尾的空白
+ var p = li.firstChild();
+ var lastChild = p.lastChild();
+ if(lastChild && lastChild.type == 'text' && /^\s*$/.test(lastChild.data)){
+ p.removeChild(lastChild)
+ }
+ });
+ if(me.options.autoTransWordToList){
+ var orderlisttype = {
+ 'num1':/^\d+\)/,
+ 'decimal':/^\d+\./,
+ 'lower-alpha':/^[a-z]+\)/,
+ 'upper-alpha':/^[A-Z]+\./,
+ 'cn':/^[\u4E00\u4E8C\u4E09\u56DB\u516d\u4e94\u4e03\u516b\u4e5d]+[\u3001]/,
+ 'cn2':/^\([\u4E00\u4E8C\u4E09\u56DB\u516d\u4e94\u4e03\u516b\u4e5d]+\)/
+ },
+ unorderlisttype = {
+ 'square':'n'
+ };
+ function checkListType(content,container){
+ var span = container.firstChild();
+ if(span && span.type == 'element' && span.tagName == 'span' && /Wingdings|Symbol/.test(span.getStyle('font-family'))){
+ for(var p in unorderlisttype){
+ if(unorderlisttype[p] == span.data){
+ return p
+ }
+ }
+ return 'disc'
+ }
+ for(var p in orderlisttype){
+ if(orderlisttype[p].test(content)){
+ return p;
+ }
+ }
+
+ }
+ utils.each(root.getNodesByTagName('p'),function(node){
+ if(node.getAttr('class') != 'MsoListParagraph'){
+ return
+ }
+
+ //word粘贴过来的会带有margin要去掉,但这样也可能会误命中一些央视
+ node.setStyle('margin','');
+ node.setStyle('margin-left','');
+ node.setAttr('class','');
+
+ function appendLi(list,p,type){
+ if(list.tagName == 'ol'){
+ if(browser.ie){
+ var first = p.firstChild();
+ if(first.type =='element' && first.tagName == 'span' && orderlisttype[type].test(first.innerText())){
+ p.removeChild(first);
+ }
+ }else{
+ p.innerHTML(p.innerHTML().replace(orderlisttype[type],''));
+ }
+ }else{
+ p.removeChild(p.firstChild())
+ }
+
+ var li = UE.uNode.createElement('li');
+ li.appendChild(p);
+ list.appendChild(li);
+ }
+ var tmp = node,type,cacheNode = node;
+
+ if(node.parentNode.tagName != 'li' && (type = checkListType(node.innerText(),node))){
+
+ var list = UE.uNode.createElement(me.options.insertorderedlist.hasOwnProperty(type) ? 'ol' : 'ul');
+ if(customStyle[type]){
+ list.setAttr('class','custom_'+type)
+ }else{
+ list.setStyle('list-style-type',type)
+ }
+ while(node && node.parentNode.tagName != 'li' && checkListType(node.innerText(),node)){
+ tmp = node.nextSibling();
+ if(!tmp){
+ node.parentNode.insertBefore(list,node)
+ }
+ appendLi(list,node,type);
+ node = tmp;
+ }
+ if(!list.parentNode && node && node.parentNode){
+ node.parentNode.insertBefore(list,node)
+ }
+ }
+ var span = cacheNode.firstChild();
+ if(span && span.type == 'element' && span.tagName == 'span' && /^\s*( )+\s*$/.test(span.innerText())){
+ span.parentNode.removeChild(span)
+ }
+ })
+ }
+
+ });
+
+ //调整索引标签
+ me.addListener('contentchange',function(){
+ adjustListStyle(me.document)
+ });
+
+ function adjustListStyle(doc,ignore){
+ utils.each(domUtils.getElementsByTagName(doc,'ol ul'),function(node){
+
+ if(!domUtils.inDoc(node,doc))
+ return;
+
+ var parent = node.parentNode;
+ if(parent.tagName == node.tagName){
+ var nodeStyleType = getStyle(node) || (node.tagName == 'OL' ? 'decimal' : 'disc'),
+ parentStyleType = getStyle(parent) || (parent.tagName == 'OL' ? 'decimal' : 'disc');
+ if(nodeStyleType == parentStyleType){
+ var styleIndex = utils.indexOf(listStyle[node.tagName], nodeStyleType);
+ styleIndex = styleIndex + 1 == listStyle[node.tagName].length ? 0 : styleIndex + 1;
+ setListStyle(node,listStyle[node.tagName][styleIndex])
+ }
+
+ }
+ var index = 0,type = 2;
+ if( domUtils.hasClass(node,/custom_/)){
+ if(!(/[ou]l/i.test(parent.tagName) && domUtils.hasClass(parent,/custom_/))){
+ type = 1;
+ }
+ }else{
+ if(/[ou]l/i.test(parent.tagName) && domUtils.hasClass(parent,/custom_/)){
+ type = 3;
+ }
+ }
+
+ var style = domUtils.getStyle(node, 'list-style-type');
+ style && (node.style.cssText = 'list-style-type:' + style);
+ node.className = utils.trim(node.className.replace(/list-paddingleft-\w+/,'')) + ' list-paddingleft-' + type;
+ utils.each(domUtils.getElementsByTagName(node,'li'),function(li){
+ li.style.cssText && (li.style.cssText = '');
+ if(!li.firstChild){
+ domUtils.remove(li);
+ return;
+ }
+ if(li.parentNode !== node){
+ return;
+ }
+ index++;
+ if(domUtils.hasClass(node,/custom_/) ){
+ var paddingLeft = 1,currentStyle = getStyle(node);
+ if(node.tagName == 'OL'){
+ if(currentStyle){
+ switch(currentStyle){
+ case 'cn' :
+ case 'cn1':
+ case 'cn2':
+ if(index > 10 && (index % 10 == 0 || index > 10 && index < 20)){
+ paddingLeft = 2
+ }else if(index > 20){
+ paddingLeft = 3
+ }
+ break;
+ case 'num2' :
+ if(index > 9){
+ paddingLeft = 2
+ }
+ }
+ }
+ li.className = 'list-'+customStyle[currentStyle]+ index + ' ' + 'list-'+currentStyle+'-paddingleft-' + paddingLeft;
+ }else{
+ li.className = 'list-'+customStyle[currentStyle] + ' ' + 'list-'+currentStyle+'-paddingleft';
+ }
+ }else{
+ li.className = li.className.replace(/list-[\w\-]+/gi,'');
+ }
+ var className = li.getAttribute('class');
+ if(className !== null && !className.replace(/\s/g,'')){
+ domUtils.removeAttributes(li,'class')
+ }
+ });
+ !ignore && adjustList(node,node.tagName.toLowerCase(),getStyle(node)||domUtils.getStyle(node, 'list-style-type'),true);
+ })
+ }
+ function adjustList(list, tag, style,ignoreEmpty) {
+ var nextList = list.nextSibling;
+ if (nextList && nextList.nodeType == 1 && nextList.tagName.toLowerCase() == tag && (getStyle(nextList) || domUtils.getStyle(nextList, 'list-style-type') || (tag == 'ol' ? 'decimal' : 'disc')) == style) {
+ domUtils.moveChild(nextList, list);
+ if (nextList.childNodes.length == 0) {
+ domUtils.remove(nextList);
+ }
+ }
+ if(nextList && domUtils.isFillChar(nextList)){
+ domUtils.remove(nextList);
+ }
+ var preList = list.previousSibling;
+ if (preList && preList.nodeType == 1 && preList.tagName.toLowerCase() == tag && (getStyle(preList) || domUtils.getStyle(preList, 'list-style-type') || (tag == 'ol' ? 'decimal' : 'disc')) == style) {
+ domUtils.moveChild(list, preList);
+ }
+ if(preList && domUtils.isFillChar(preList)){
+ domUtils.remove(preList);
+ }
+ !ignoreEmpty && domUtils.isEmptyBlock(list) && domUtils.remove(list);
+ if(getStyle(list)){
+ adjustListStyle(list.ownerDocument,true)
+ }
+ }
+
+ function setListStyle(list,style){
+ if(customStyle[style]){
+ list.className = 'custom_' + style;
+ }
+ try{
+ domUtils.setStyle(list, 'list-style-type', style);
+ }catch(e){}
+ }
+ function clearEmptySibling(node) {
+ var tmpNode = node.previousSibling;
+ if (tmpNode && domUtils.isEmptyBlock(tmpNode)) {
+ domUtils.remove(tmpNode);
+ }
+ tmpNode = node.nextSibling;
+ if (tmpNode && domUtils.isEmptyBlock(tmpNode)) {
+ domUtils.remove(tmpNode);
+ }
+ }
+
+ me.addListener('keydown', function (type, evt) {
+ function preventAndSave() {
+ evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);
+ me.fireEvent('contentchange');
+ me.undoManger && me.undoManger.save();
+ }
+ function findList(node,filterFn){
+ while(node && !domUtils.isBody(node)){
+ if(filterFn(node)){
+ return null
+ }
+ if(node.nodeType == 1 && /[ou]l/i.test(node.tagName)){
+ return node;
+ }
+ node = node.parentNode;
+ }
+ return null;
+ }
+ var keyCode = evt.keyCode || evt.which;
+ if (keyCode == 13 && !evt.shiftKey) {//回车
+ var rng = me.selection.getRange(),
+ parent = domUtils.findParent(rng.startContainer,function(node){return domUtils.isBlockElm(node)},true),
+ li = domUtils.findParentByTagName(rng.startContainer,'li',true);
+ if(parent && parent.tagName != 'PRE' && !li){
+ var html = parent.innerHTML.replace(new RegExp(domUtils.fillChar, 'g'),'');
+ if(/^\s*1\s*\.[^\d]/.test(html)){
+ parent.innerHTML = html.replace(/^\s*1\s*\./,'');
+ rng.setStartAtLast(parent).collapse(true).select();
+ me.__hasEnterExecCommand = true;
+ me.execCommand('insertorderedlist');
+ me.__hasEnterExecCommand = false;
+ }
+ }
+ var range = me.selection.getRange(),
+ start = findList(range.startContainer,function (node) {
+ return node.tagName == 'TABLE';
+ }),
+ end = range.collapsed ? start : findList(range.endContainer,function (node) {
+ return node.tagName == 'TABLE';
+ });
+
+ if (start && end && start === end) {
+
+ if (!range.collapsed) {
+ start = domUtils.findParentByTagName(range.startContainer, 'li', true);
+ end = domUtils.findParentByTagName(range.endContainer, 'li', true);
+ if (start && end && start === end) {
+ range.deleteContents();
+ li = domUtils.findParentByTagName(range.startContainer, 'li', true);
+ if (li && domUtils.isEmptyBlock(li)) {
+
+ pre = li.previousSibling;
+ next = li.nextSibling;
+ p = me.document.createElement('p');
+
+ domUtils.fillNode(me.document, p);
+ parentList = li.parentNode;
+ if (pre && next) {
+ range.setStart(next, 0).collapse(true).select(true);
+ domUtils.remove(li);
+
+ } else {
+ if (!pre && !next || !pre) {
+
+ parentList.parentNode.insertBefore(p, parentList);
+
+
+ } else {
+ li.parentNode.parentNode.insertBefore(p, parentList.nextSibling);
+ }
+ domUtils.remove(li);
+ if (!parentList.firstChild) {
+ domUtils.remove(parentList);
+ }
+ range.setStart(p, 0).setCursor();
+
+
+ }
+ preventAndSave();
+ return;
+
+ }
+ } else {
+ var tmpRange = range.cloneRange(),
+ bk = tmpRange.collapse(false).createBookmark();
+
+ range.deleteContents();
+ tmpRange.moveToBookmark(bk);
+ var li = domUtils.findParentByTagName(tmpRange.startContainer, 'li', true);
+
+ clearEmptySibling(li);
+ tmpRange.select();
+ preventAndSave();
+ return;
+ }
+ }
+
+
+ li = domUtils.findParentByTagName(range.startContainer, 'li', true);
+
+ if (li) {
+ if (domUtils.isEmptyBlock(li)) {
+ bk = range.createBookmark();
+ var parentList = li.parentNode;
+ if (li !== parentList.lastChild) {
+ domUtils.breakParent(li, parentList);
+ clearEmptySibling(li);
+ } else {
+
+ parentList.parentNode.insertBefore(li, parentList.nextSibling);
+ if (domUtils.isEmptyNode(parentList)) {
+ domUtils.remove(parentList);
+ }
+ }
+ //嵌套不处理
+ if (!dtd.$list[li.parentNode.tagName]) {
+
+ if (!domUtils.isBlockElm(li.firstChild)) {
+ p = me.document.createElement('p');
+ li.parentNode.insertBefore(p, li);
+ while (li.firstChild) {
+ p.appendChild(li.firstChild);
+ }
+ domUtils.remove(li);
+ } else {
+ domUtils.remove(li, true);
+ }
+ }
+ range.moveToBookmark(bk).select();
+
+
+ } else {
+ var first = li.firstChild;
+ if (!first || !domUtils.isBlockElm(first)) {
+ var p = me.document.createElement('p');
+
+ !li.firstChild && domUtils.fillNode(me.document, p);
+ while (li.firstChild) {
+
+ p.appendChild(li.firstChild);
+ }
+ li.appendChild(p);
+ first = p;
+ }
+
+ var span = me.document.createElement('span');
+
+ range.insertNode(span);
+ domUtils.breakParent(span, li);
+
+ var nextLi = span.nextSibling;
+ first = nextLi.firstChild;
+
+ if (!first) {
+ p = me.document.createElement('p');
+
+ domUtils.fillNode(me.document, p);
+ nextLi.appendChild(p);
+ first = p;
+ }
+ if (domUtils.isEmptyNode(first)) {
+ first.innerHTML = '';
+ domUtils.fillNode(me.document, first);
+ }
+
+ range.setStart(first, 0).collapse(true).shrinkBoundary().select();
+ domUtils.remove(span);
+ var pre = nextLi.previousSibling;
+ if (pre && domUtils.isEmptyBlock(pre)) {
+ pre.innerHTML = '';
+ domUtils.fillNode(me.document, pre.firstChild);
+ }
+
+ }
+// }
+ preventAndSave();
+ }
+
+
+ }
+
+
+ }
+ if (keyCode == 8) {
+ //修中ie中li下的问题
+ range = me.selection.getRange();
+ if (range.collapsed && domUtils.isStartInblock(range)) {
+ tmpRange = range.cloneRange().trimBoundary();
+ li = domUtils.findParentByTagName(range.startContainer, 'li', true);
+ //要在li的最左边,才能处理
+ if (li && domUtils.isStartInblock(tmpRange)) {
+ start = domUtils.findParentByTagName(range.startContainer, 'p', true);
+ if (start && start !== li.firstChild) {
+ var parentList = domUtils.findParentByTagName(start,['ol','ul']);
+ domUtils.breakParent(start,parentList);
+ clearEmptySibling(start);
+ me.fireEvent('contentchange');
+ range.setStart(start,0).setCursor(false,true);
+ me.fireEvent('saveScene');
+ domUtils.preventDefault(evt);
+ return;
+ }
+
+ if (li && (pre = li.previousSibling)) {
+ if (keyCode == 46 && li.childNodes.length) {
+ return;
+ }
+ //有可能上边的兄弟节点是个2级菜单,要追加到2级菜单的最后的li
+ if (dtd.$list[pre.tagName]) {
+ pre = pre.lastChild;
+ }
+ me.undoManger && me.undoManger.save();
+ first = li.firstChild;
+ if (domUtils.isBlockElm(first)) {
+ if (domUtils.isEmptyNode(first)) {
+// range.setEnd(pre, pre.childNodes.length).shrinkBoundary().collapse().select(true);
+ pre.appendChild(first);
+ range.setStart(first, 0).setCursor(false, true);
+ //first不是唯一的节点
+ while (li.firstChild) {
+ pre.appendChild(li.firstChild);
+ }
+ } else {
+
+ span = me.document.createElement('span');
+ range.insertNode(span);
+ //判断pre是否是空的节点,如果是
类型的空节点,干掉p标签防止它占位
+ if (domUtils.isEmptyBlock(pre)) {
+ pre.innerHTML = '';
+ }
+ domUtils.moveChild(li, pre);
+ range.setStartBefore(span).collapse(true).select(true);
+
+ domUtils.remove(span);
+
+ }
+ } else {
+ if (domUtils.isEmptyNode(li)) {
+ var p = me.document.createElement('p');
+ pre.appendChild(p);
+ range.setStart(p, 0).setCursor();
+// range.setEnd(pre, pre.childNodes.length).shrinkBoundary().collapse().select(true);
+ } else {
+ range.setEnd(pre, pre.childNodes.length).collapse().select(true);
+ while (li.firstChild) {
+ pre.appendChild(li.firstChild);
+ }
+ }
+ }
+ domUtils.remove(li);
+ me.fireEvent('contentchange');
+ me.fireEvent('saveScene');
+ domUtils.preventDefault(evt);
+ return;
+
+ }
+ //trace:980
+
+ if (li && !li.previousSibling) {
+ var parentList = li.parentNode;
+ var bk = range.createBookmark();
+ if(domUtils.isTagNode(parentList.parentNode,'ol ul')){
+ parentList.parentNode.insertBefore(li,parentList);
+ if(domUtils.isEmptyNode(parentList)){
+ domUtils.remove(parentList)
+ }
+ }else{
+
+ while(li.firstChild){
+ parentList.parentNode.insertBefore(li.firstChild,parentList);
+ }
+
+ domUtils.remove(li);
+ if(domUtils.isEmptyNode(parentList)){
+ domUtils.remove(parentList)
+ }
+
+ }
+ range.moveToBookmark(bk).setCursor(false,true);
+ me.fireEvent('contentchange');
+ me.fireEvent('saveScene');
+ domUtils.preventDefault(evt);
+ return;
+
+ }
+
+
+ }
+
+
+ }
+
+ }
+ });
+
+ me.addListener('keyup',function(type, evt){
+ var keyCode = evt.keyCode || evt.which;
+ if (keyCode == 8) {
+ var rng = me.selection.getRange(),list;
+ if(list = domUtils.findParentByTagName(rng.startContainer,['ol', 'ul'],true)){
+ adjustList(list,list.tagName.toLowerCase(),getStyle(list)||domUtils.getComputedStyle(list,'list-style-type'),true)
+ }
+ }
+ });
+ //处理tab键
+ me.addListener('tabkeydown',function(){
+
+ var range = me.selection.getRange();
+
+ //控制级数
+ function checkLevel(li){
+ if(me.options.maxListLevel != -1){
+ var level = li.parentNode,levelNum = 0;
+ while(/[ou]l/i.test(level.tagName)){
+ levelNum++;
+ level = level.parentNode;
+ }
+ if(levelNum >= me.options.maxListLevel){
+ return true;
+ }
+ }
+ }
+ //只以开始为准
+ //todo 后续改进
+ var li = domUtils.findParentByTagName(range.startContainer, 'li', true);
+ if(li){
+
+ var bk;
+ if(range.collapsed){
+ if(checkLevel(li))
+ return true;
+ var parentLi = li.parentNode,
+ list = me.document.createElement(parentLi.tagName),
+ index = utils.indexOf(listStyle[list.tagName], getStyle(parentLi)||domUtils.getComputedStyle(parentLi, 'list-style-type'));
+ index = index + 1 == listStyle[list.tagName].length ? 0 : index + 1;
+ var currentStyle = listStyle[list.tagName][index];
+ setListStyle(list,currentStyle);
+ if(domUtils.isStartInblock(range)){
+ me.fireEvent('saveScene');
+ bk = range.createBookmark();
+ parentLi.insertBefore(list, li);
+ list.appendChild(li);
+ adjustList(list,list.tagName.toLowerCase(),currentStyle);
+ me.fireEvent('contentchange');
+ range.moveToBookmark(bk).select(true);
+ return true;
+ }
+ }else{
+ me.fireEvent('saveScene');
+ bk = range.createBookmark();
+ for(var i= 0,closeList,parents = domUtils.findParents(li),ci;ci=parents[i++];){
+ if(domUtils.isTagNode(ci,'ol ul')){
+ closeList = ci;
+ break;
+ }
+ }
+ var current = li;
+ if(bk.end){
+ while(current && !(domUtils.getPosition(current, bk.end) & domUtils.POSITION_FOLLOWING)){
+ if(checkLevel(current)){
+ current = domUtils.getNextDomNode(current,false,null,function(node){return node !== closeList});
+ continue;
+ }
+ var parentLi = current.parentNode,
+ list = me.document.createElement(parentLi.tagName),
+ index = utils.indexOf(listStyle[list.tagName], getStyle(parentLi)||domUtils.getComputedStyle(parentLi, 'list-style-type'));
+ var currentIndex = index + 1 == listStyle[list.tagName].length ? 0 : index + 1;
+ var currentStyle = listStyle[list.tagName][currentIndex];
+ setListStyle(list,currentStyle);
+ parentLi.insertBefore(list, current);
+ while(current && !(domUtils.getPosition(current, bk.end) & domUtils.POSITION_FOLLOWING)){
+ li = current.nextSibling;
+ list.appendChild(current);
+ if(!li || domUtils.isTagNode(li,'ol ul')){
+ if(li){
+ while(li = li.firstChild){
+ if(li.tagName == 'LI'){
+ break;
+ }
+ }
+ }else{
+ li = domUtils.getNextDomNode(current,false,null,function(node){return node !== closeList});
+ }
+ break;
+ }
+ current = li;
+ }
+ adjustList(list,list.tagName.toLowerCase(),currentStyle);
+ current = li;
+ }
+ }
+ me.fireEvent('contentchange');
+ range.moveToBookmark(bk).select();
+ return true;
+ }
+ }
+
+ });
+ function getLi(start){
+ while(start && !domUtils.isBody(start)){
+ if(start.nodeName == 'TABLE'){
+ return null;
+ }
+ if(start.nodeName == 'LI'){
+ return start
+ }
+ start = start.parentNode;
+ }
+ }
+
+ /**
+ * 有序列表,与“insertunorderedlist”命令互斥
+ * @command insertorderedlist
+ * @method execCommand
+ * @param { String } command 命令字符串
+ * @param { String } style 插入的有序列表类型,值为:decimal,lower-alpha,lower-roman,upper-alpha,upper-roman,cn,cn1,cn2,num,num1,num2
+ * @example
+ * ```javascript
+ * editor.execCommand( 'insertorderedlist','decimal');
+ * ```
+ */
+ /**
+ * 查询当前选区内容是否有序列表
+ * @command insertorderedlist
+ * @method queryCommandState
+ * @param { String } cmd 命令字符串
+ * @return { int } 如果当前选区是有序列表返回1,否则返回0
+ * @example
+ * ```javascript
+ * editor.queryCommandState( 'insertorderedlist' );
+ * ```
+ */
+ /**
+ * 查询当前选区内容是否有序列表
+ * @command insertorderedlist
+ * @method queryCommandValue
+ * @param { String } cmd 命令字符串
+ * @return { String } 返回当前有序列表的类型,值为null或decimal,lower-alpha,lower-roman,upper-alpha,upper-roman,cn,cn1,cn2,num,num1,num2
+ * @example
+ * ```javascript
+ * editor.queryCommandValue( 'insertorderedlist' );
+ * ```
+ */
+
+ /**
+ * 无序列表,与“insertorderedlist”命令互斥
+ * @command insertunorderedlist
+ * @method execCommand
+ * @param { String } command 命令字符串
+ * @param { String } style 插入的无序列表类型,值为:circle,disc,square,dash,dot
+ * @example
+ * ```javascript
+ * editor.execCommand( 'insertunorderedlist','circle');
+ * ```
+ */
+ /**
+ * 查询当前是否有word文档粘贴进来的图片
+ * @command insertunorderedlist
+ * @method insertunorderedlist
+ * @param { String } command 命令字符串
+ * @return { int } 如果当前选区是无序列表返回1,否则返回0
+ * @example
+ * ```javascript
+ * editor.queryCommandState( 'insertunorderedlist' );
+ * ```
+ */
+ /**
+ * 查询当前选区内容是否有序列表
+ * @command insertunorderedlist
+ * @method queryCommandValue
+ * @param { String } command 命令字符串
+ * @return { String } 返回当前无序列表的类型,值为null或circle,disc,square,dash,dot
+ * @example
+ * ```javascript
+ * editor.queryCommandValue( 'insertunorderedlist' );
+ * ```
+ */
+
+ me.commands['insertorderedlist'] =
+ me.commands['insertunorderedlist'] = {
+ execCommand:function (command, style) {
+
+ if (!style) {
+ style = command.toLowerCase() == 'insertorderedlist' ? 'decimal' : 'disc';
+ }
+ var me = this,
+ range = this.selection.getRange(),
+ filterFn = function (node) {
+ return node.nodeType == 1 ? node.tagName.toLowerCase() != 'br' : !domUtils.isWhitespace(node);
+ },
+ tag = command.toLowerCase() == 'insertorderedlist' ? 'ol' : 'ul',
+ frag = me.document.createDocumentFragment();
+ //去掉是因为会出现选到末尾,导致adjustmentBoundary缩到ol/ul的位置
+ //range.shrinkBoundary();//.adjustmentBoundary();
+ range.adjustmentBoundary().shrinkBoundary();
+ var bko = range.createBookmark(true),
+ start = getLi(me.document.getElementById(bko.start)),
+ modifyStart = 0,
+ end = getLi(me.document.getElementById(bko.end)),
+ modifyEnd = 0,
+ startParent, endParent,
+ list, tmp;
+
+ if (start || end) {
+ start && (startParent = start.parentNode);
+ if (!bko.end) {
+ end = start;
+ }
+ end && (endParent = end.parentNode);
+
+ if (startParent === endParent) {
+ while (start !== end) {
+ tmp = start;
+ start = start.nextSibling;
+ if (!domUtils.isBlockElm(tmp.firstChild)) {
+ var p = me.document.createElement('p');
+ while (tmp.firstChild) {
+ p.appendChild(tmp.firstChild);
+ }
+ tmp.appendChild(p);
+ }
+ frag.appendChild(tmp);
+ }
+ tmp = me.document.createElement('span');
+ startParent.insertBefore(tmp, end);
+ if (!domUtils.isBlockElm(end.firstChild)) {
+ p = me.document.createElement('p');
+ while (end.firstChild) {
+ p.appendChild(end.firstChild);
+ }
+ end.appendChild(p);
+ }
+ frag.appendChild(end);
+ domUtils.breakParent(tmp, startParent);
+ if (domUtils.isEmptyNode(tmp.previousSibling)) {
+ domUtils.remove(tmp.previousSibling);
+ }
+ if (domUtils.isEmptyNode(tmp.nextSibling)) {
+ domUtils.remove(tmp.nextSibling)
+ }
+ var nodeStyle = getStyle(startParent) || domUtils.getComputedStyle(startParent, 'list-style-type') || (command.toLowerCase() == 'insertorderedlist' ? 'decimal' : 'disc');
+ if (startParent.tagName.toLowerCase() == tag && nodeStyle == style) {
+ for (var i = 0, ci, tmpFrag = me.document.createDocumentFragment(); ci = frag.firstChild;) {
+ if(domUtils.isTagNode(ci,'ol ul')){
+// 删除时,子列表不处理
+// utils.each(domUtils.getElementsByTagName(ci,'li'),function(li){
+// while(li.firstChild){
+// tmpFrag.appendChild(li.firstChild);
+// }
+//
+// });
+ tmpFrag.appendChild(ci);
+ }else{
+ while (ci.firstChild) {
+
+ tmpFrag.appendChild(ci.firstChild);
+ domUtils.remove(ci);
+ }
+ }
+
+ }
+ tmp.parentNode.insertBefore(tmpFrag, tmp);
+ } else {
+ list = me.document.createElement(tag);
+ setListStyle(list,style);
+ list.appendChild(frag);
+ tmp.parentNode.insertBefore(list, tmp);
+ }
+
+ domUtils.remove(tmp);
+ list && adjustList(list, tag, style);
+ range.moveToBookmark(bko).select();
+ return;
+ }
+ //开始
+ if (start) {
+ while (start) {
+ tmp = start.nextSibling;
+ if (domUtils.isTagNode(start, 'ol ul')) {
+ frag.appendChild(start);
+ } else {
+ var tmpfrag = me.document.createDocumentFragment(),
+ hasBlock = 0;
+ while (start.firstChild) {
+ if (domUtils.isBlockElm(start.firstChild)) {
+ hasBlock = 1;
+ }
+ tmpfrag.appendChild(start.firstChild);
+ }
+ if (!hasBlock) {
+ var tmpP = me.document.createElement('p');
+ tmpP.appendChild(tmpfrag);
+ frag.appendChild(tmpP);
+ } else {
+ frag.appendChild(tmpfrag);
+ }
+ domUtils.remove(start);
+ }
+
+ start = tmp;
+ }
+ startParent.parentNode.insertBefore(frag, startParent.nextSibling);
+ if (domUtils.isEmptyNode(startParent)) {
+ range.setStartBefore(startParent);
+ domUtils.remove(startParent);
+ } else {
+ range.setStartAfter(startParent);
+ }
+ modifyStart = 1;
+ }
+
+ if (end && domUtils.inDoc(endParent, me.document)) {
+ //结束
+ start = endParent.firstChild;
+ while (start && start !== end) {
+ tmp = start.nextSibling;
+ if (domUtils.isTagNode(start, 'ol ul')) {
+ frag.appendChild(start);
+ } else {
+ tmpfrag = me.document.createDocumentFragment();
+ hasBlock = 0;
+ while (start.firstChild) {
+ if (domUtils.isBlockElm(start.firstChild)) {
+ hasBlock = 1;
+ }
+ tmpfrag.appendChild(start.firstChild);
+ }
+ if (!hasBlock) {
+ tmpP = me.document.createElement('p');
+ tmpP.appendChild(tmpfrag);
+ frag.appendChild(tmpP);
+ } else {
+ frag.appendChild(tmpfrag);
+ }
+ domUtils.remove(start);
+ }
+ start = tmp;
+ }
+ var tmpDiv = domUtils.createElement(me.document, 'div', {
+ 'tmpDiv':1
+ });
+ domUtils.moveChild(end, tmpDiv);
+
+ frag.appendChild(tmpDiv);
+ domUtils.remove(end);
+ endParent.parentNode.insertBefore(frag, endParent);
+ range.setEndBefore(endParent);
+ if (domUtils.isEmptyNode(endParent)) {
+ domUtils.remove(endParent);
+ }
+
+ modifyEnd = 1;
+ }
+
+
+ }
+
+ if (!modifyStart) {
+ range.setStartBefore(me.document.getElementById(bko.start));
+ }
+ if (bko.end && !modifyEnd) {
+ range.setEndAfter(me.document.getElementById(bko.end));
+ }
+ range.enlarge(true, function (node) {
+ return notExchange[node.tagName];
+ });
+
+ frag = me.document.createDocumentFragment();
+
+ var bk = range.createBookmark(),
+ current = domUtils.getNextDomNode(bk.start, false, filterFn),
+ tmpRange = range.cloneRange(),
+ tmpNode,
+ block = domUtils.isBlockElm;
+
+ while (current && current !== bk.end && (domUtils.getPosition(current, bk.end) & domUtils.POSITION_PRECEDING)) {
+
+ if (current.nodeType == 3 || dtd.li[current.tagName]) {
+ if (current.nodeType == 1 && dtd.$list[current.tagName]) {
+ while (current.firstChild) {
+ frag.appendChild(current.firstChild);
+ }
+ tmpNode = domUtils.getNextDomNode(current, false, filterFn);
+ domUtils.remove(current);
+ current = tmpNode;
+ continue;
+
+ }
+ tmpNode = current;
+ tmpRange.setStartBefore(current);
+
+ while (current && current !== bk.end && (!block(current) || domUtils.isBookmarkNode(current) )) {
+ tmpNode = current;
+ current = domUtils.getNextDomNode(current, false, null, function (node) {
+ return !notExchange[node.tagName];
+ });
+ }
+
+ if (current && block(current)) {
+ tmp = domUtils.getNextDomNode(tmpNode, false, filterFn);
+ if (tmp && domUtils.isBookmarkNode(tmp)) {
+ current = domUtils.getNextDomNode(tmp, false, filterFn);
+ tmpNode = tmp;
+ }
+ }
+ tmpRange.setEndAfter(tmpNode);
+
+ current = domUtils.getNextDomNode(tmpNode, false, filterFn);
+
+ var li = range.document.createElement('li');
+
+ li.appendChild(tmpRange.extractContents());
+ if(domUtils.isEmptyNode(li)){
+ var tmpNode = range.document.createElement('p');
+ while(li.firstChild){
+ tmpNode.appendChild(li.firstChild)
+ }
+ li.appendChild(tmpNode);
+ }
+ frag.appendChild(li);
+ } else {
+ current = domUtils.getNextDomNode(current, true, filterFn);
+ }
+ }
+ range.moveToBookmark(bk).collapse(true);
+ list = me.document.createElement(tag);
+ setListStyle(list,style);
+ list.appendChild(frag);
+ range.insertNode(list);
+ //当前list上下看能否合并
+ adjustList(list, tag, style);
+ //去掉冗余的tmpDiv
+ for (var i = 0, ci, tmpDivs = domUtils.getElementsByTagName(list, 'div'); ci = tmpDivs[i++];) {
+ if (ci.getAttribute('tmpDiv')) {
+ domUtils.remove(ci, true)
+ }
+ }
+ range.moveToBookmark(bko).select();
+
+ },
+ queryCommandState:function (command) {
+ var tag = command.toLowerCase() == 'insertorderedlist' ? 'ol' : 'ul';
+ var path = this.selection.getStartElementPath();
+ for(var i= 0,ci;ci = path[i++];){
+ if(ci.nodeName == 'TABLE'){
+ return 0
+ }
+ if(tag == ci.nodeName.toLowerCase()){
+ return 1
+ };
+ }
+ return 0;
+
+ },
+ queryCommandValue:function (command) {
+ var tag = command.toLowerCase() == 'insertorderedlist' ? 'ol' : 'ul';
+ var path = this.selection.getStartElementPath(),
+ node;
+ for(var i= 0,ci;ci = path[i++];){
+ if(ci.nodeName == 'TABLE'){
+ node = null;
+ break;
+ }
+ if(tag == ci.nodeName.toLowerCase()){
+ node = ci;
+ break;
+ };
+ }
+ return node ? getStyle(node) || domUtils.getComputedStyle(node, 'list-style-type') : null;
+ }
+ };
+};
+
+
+
+// plugins/source.js
+/**
+ * 源码编辑插件
+ * @file
+ * @since 1.2.6.1
+ */
+
+(function (){
+ var sourceEditors = {
+ textarea: function (editor, holder){
+ var textarea = holder.ownerDocument.createElement('textarea');
+ textarea.style.cssText = 'position:absolute;resize:none;width:100%;height:100%;border:0;padding:0;margin:0;overflow-y:auto;';
+ // todo: IE下只有onresize属性可用... 很纠结
+ if (browser.ie && browser.version < 8) {
+ textarea.style.width = holder.offsetWidth + 'px';
+ textarea.style.height = holder.offsetHeight + 'px';
+ holder.onresize = function (){
+ textarea.style.width = holder.offsetWidth + 'px';
+ textarea.style.height = holder.offsetHeight + 'px';
+ };
+ }
+ holder.appendChild(textarea);
+ return {
+ setContent: function (content){
+ textarea.value = content;
+ },
+ getContent: function (){
+ return textarea.value;
+ },
+ select: function (){
+ var range;
+ if (browser.ie) {
+ range = textarea.createTextRange();
+ range.collapse(true);
+ range.select();
+ } else {
+ //todo: chrome下无法设置焦点
+ textarea.setSelectionRange(0, 0);
+ textarea.focus();
+ }
+ },
+ dispose: function (){
+ holder.removeChild(textarea);
+ // todo
+ holder.onresize = null;
+ textarea = null;
+ holder = null;
+ }
+ };
+ },
+ codemirror: function (editor, holder){
+
+ var codeEditor = window.CodeMirror(holder, {
+ mode: "text/html",
+ tabMode: "indent",
+ lineNumbers: true,
+ lineWrapping:true
+ });
+ var dom = codeEditor.getWrapperElement();
+ dom.style.cssText = 'position:absolute;left:0;top:0;width:100%;height:100%;font-family:consolas,"Courier new",monospace;font-size:13px;';
+ codeEditor.getScrollerElement().style.cssText = 'position:absolute;left:0;top:0;width:100%;height:100%;';
+ codeEditor.refresh();
+ return {
+ getCodeMirror:function(){
+ return codeEditor;
+ },
+ setContent: function (content){
+ codeEditor.setValue(content);
+ },
+ getContent: function (){
+ return codeEditor.getValue();
+ },
+ select: function (){
+ codeEditor.focus();
+ },
+ dispose: function (){
+ holder.removeChild(dom);
+ dom = null;
+ codeEditor = null;
+ }
+ };
+ }
+ };
+
+ UE.plugins['source'] = function (){
+ var me = this;
+ var opt = this.options;
+ var sourceMode = false;
+ var sourceEditor;
+ var orgSetContent;
+ opt.sourceEditor = browser.ie ? 'textarea' : (opt.sourceEditor || 'codemirror');
+
+ me.setOpt({
+ sourceEditorFirst:false
+ });
+ function createSourceEditor(holder){
+ return sourceEditors[opt.sourceEditor == 'codemirror' && window.CodeMirror ? 'codemirror' : 'textarea'](me, holder);
+ }
+
+ var bakCssText;
+ //解决在源码模式下getContent不能得到最新的内容问题
+ var oldGetContent,
+ bakAddress;
+
+ /**
+ * 切换源码模式和编辑模式
+ * @command source
+ * @method execCommand
+ * @param { String } cmd 命令字符串
+ * @example
+ * ```javascript
+ * editor.execCommand( 'source');
+ * ```
+ */
+
+ /**
+ * 查询当前编辑区域的状态是源码模式还是可视化模式
+ * @command source
+ * @method queryCommandState
+ * @param { String } cmd 命令字符串
+ * @return { int } 如果当前是源码编辑模式,返回1,否则返回0
+ * @example
+ * ```javascript
+ * editor.queryCommandState( 'source' );
+ * ```
+ */
+
+ me.commands['source'] = {
+ execCommand: function (){
+
+ sourceMode = !sourceMode;
+ if (sourceMode) {
+ bakAddress = me.selection.getRange().createAddress(false,true);
+ me.undoManger && me.undoManger.save(true);
+ if(browser.gecko){
+ me.body.contentEditable = false;
+ }
+
+ bakCssText = me.iframe.style.cssText;
+ me.iframe.style.cssText += 'position:absolute;left:-32768px;top:-32768px;';
+
+
+ me.fireEvent('beforegetcontent');
+ var root = UE.htmlparser(me.body.innerHTML);
+ me.filterOutputRule(root);
+ root.traversal(function (node) {
+ if (node.type == 'element') {
+ switch (node.tagName) {
+ case 'td':
+ case 'th':
+ case 'caption':
+ if(node.children && node.children.length == 1){
+ if(node.firstChild().tagName == 'br' ){
+ node.removeChild(node.firstChild())
+ }
+ };
+ break;
+ case 'pre':
+ node.innerText(node.innerText().replace(/ /g,' '))
+
+ }
+ }
+ });
+
+ me.fireEvent('aftergetcontent');
+
+ var content = root.toHtml(true);
+
+ sourceEditor = createSourceEditor(me.iframe.parentNode);
+
+ sourceEditor.setContent(content);
+
+ orgSetContent = me.setContent;
+
+ me.setContent = function(html){
+ //这里暂时不触发事件,防止报错
+ var root = UE.htmlparser(html);
+ me.filterInputRule(root);
+ html = root.toHtml();
+ sourceEditor.setContent(html);
+ };
+
+ setTimeout(function (){
+ sourceEditor.select();
+ me.addListener('fullscreenchanged', function(){
+ try{
+ sourceEditor.getCodeMirror().refresh()
+ }catch(e){}
+ });
+ });
+
+ //重置getContent,源码模式下取值也能是最新的数据
+ oldGetContent = me.getContent;
+ me.getContent = function (){
+ return sourceEditor.getContent() || '' + (browser.ie ? '' : ' ')+' ';
+ };
+ } else {
+ me.iframe.style.cssText = bakCssText;
+ var cont = sourceEditor.getContent() || '' + (browser.ie ? '' : ' ')+' ';
+ //处理掉block节点前后的空格,有可能会误命中,暂时不考虑
+ cont = cont.replace(new RegExp('[\\r\\t\\n ]*<\/?(\\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 = ''+(browser.ie?'':' ')+' ';
+ first = me.body.firstChild;
+ }
+
+
+ //要在ifm为显示时ff才能取到selection,否则报错
+ //这里不能比较位置了
+ me.undoManger && me.undoManger.save(true);
+
+ if(browser.gecko){
+
+ var input = document.createElement('input');
+ input.style.cssText = 'position:absolute;left:0;top:-32768px';
+
+ document.body.appendChild(input);
+
+ me.body.contentEditable = false;
+ setTimeout(function(){
+ domUtils.setViewportOffset(input, { left: -32768, top: 0 });
+ input.focus();
+ setTimeout(function(){
+ me.body.contentEditable = true;
+ me.selection.getRange().moveToAddress(bakAddress).select(true);
+ domUtils.remove(input);
+ });
+
+ });
+ }else{
+ //ie下有可能报错,比如在代码顶头的情况
+ try{
+ me.selection.getRange().moveToAddress(bakAddress).select(true);
+ }catch(e){}
+
+ }
+ }
+ this.fireEvent('sourcemodechanged', sourceMode);
+ },
+ queryCommandState: function (){
+ return sourceMode|0;
+ },
+ notNeedUndo : 1
+ };
+ var oldQueryCommandState = me.queryCommandState;
+
+ me.queryCommandState = function (cmdName){
+ cmdName = cmdName.toLowerCase();
+ if (sourceMode) {
+ //源码模式下可以开启的命令
+ return cmdName in {
+ 'source' : 1,
+ 'fullscreen' : 1
+ } ? 1 : -1
+ }
+ return oldQueryCommandState.apply(this, arguments);
+ };
+
+ if(opt.sourceEditor == "codemirror"){
+
+ me.addListener("ready",function(){
+ utils.loadFile(document,{
+ src : opt.codeMirrorJsUrl || opt.UEDITOR_HOME_URL + "third-party/codemirror/codemirror.js",
+ tag : "script",
+ type : "text/javascript",
+ defer : "defer"
+ },function(){
+ if(opt.sourceEditorFirst){
+ setTimeout(function(){
+ me.execCommand("source");
+ },0);
+ }
+ });
+ utils.loadFile(document,{
+ tag : "link",
+ rel : "stylesheet",
+ type : "text/css",
+ href : opt.codeMirrorCssUrl || opt.UEDITOR_HOME_URL + "third-party/codemirror/codemirror.css"
+ });
+
+ });
+ }
+
+ };
+
+})();
+
+// plugins/enterkey.js
+///import core
+///import plugins/undo.js
+///commands 设置回车标签p或br
+///commandsName EnterKey
+///commandsTitle 设置回车标签p或br
+/**
+ * @description 处理回车
+ * @author zhanyi
+ */
+UE.plugins['enterkey'] = function() {
+ var hTag,
+ me = this,
+ tag = me.options.enterTag;
+ me.addListener('keyup', function(type, evt) {
+
+ var keyCode = evt.keyCode || evt.which;
+ if (keyCode == 13) {
+ var range = me.selection.getRange(),
+ start = range.startContainer,
+ doSave;
+
+ //修正在h1-h6里边回车后不能嵌套p的问题
+ if (!browser.ie) {
+
+ if (/h\d/i.test(hTag)) {
+ if (browser.gecko) {
+ var h = domUtils.findParentByTagName(start, [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6','blockquote','caption','table'], true);
+ if (!h) {
+ me.document.execCommand('formatBlock', false, '');
+ doSave = 1;
+ }
+ } else {
+ //chrome remove div
+ if (start.nodeType == 1) {
+ var tmp = me.document.createTextNode(''),div;
+ range.insertNode(tmp);
+ div = domUtils.findParentByTagName(tmp, 'div', true);
+ if (div) {
+ var p = me.document.createElement('p');
+ while (div.firstChild) {
+ p.appendChild(div.firstChild);
+ }
+ div.parentNode.insertBefore(p, div);
+ domUtils.remove(div);
+ range.setStartBefore(tmp).setCursor();
+ doSave = 1;
+ }
+ domUtils.remove(tmp);
+
+ }
+ }
+
+ if (me.undoManger && doSave) {
+ me.undoManger.save();
+ }
+ }
+ //没有站位符,会出现多行的问题
+ browser.opera && range.select();
+ }else{
+ me.fireEvent('saveScene',true,true)
+ }
+ }
+ });
+
+ me.addListener('keydown', function(type, evt) {
+ var keyCode = evt.keyCode || evt.which;
+ if (keyCode == 13) {//回车
+ if(me.fireEvent('beforeenterkeydown')){
+ domUtils.preventDefault(evt);
+ return;
+ }
+ me.fireEvent('saveScene',true,true);
+ hTag = '';
+
+
+ var range = me.selection.getRange();
+
+ if (!range.collapsed) {
+ //跨td不能删
+ var start = range.startContainer,
+ end = range.endContainer,
+ startTd = domUtils.findParentByTagName(start, 'td', true),
+ endTd = domUtils.findParentByTagName(end, 'td', true);
+ if (startTd && endTd && startTd !== endTd || !startTd && endTd || startTd && !endTd) {
+ evt.preventDefault ? evt.preventDefault() : ( evt.returnValue = false);
+ return;
+ }
+ }
+ if (tag == 'p') {
+
+
+ if (!browser.ie) {
+
+ start = domUtils.findParentByTagName(range.startContainer, ['ol','ul','p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6','blockquote','caption'], true);
+
+ //opera下执行formatblock会在table的场景下有问题,回车在opera原生支持很好,所以暂时在opera去掉调用这个原生的command
+ //trace:2431
+ if (!start && !browser.opera) {
+
+ me.document.execCommand('formatBlock', false, ' ');
+
+ if (browser.gecko) {
+ range = me.selection.getRange();
+ start = domUtils.findParentByTagName(range.startContainer, 'p', true);
+ start && domUtils.removeDirtyAttr(start);
+ }
+
+
+ } else {
+ hTag = start.tagName;
+ start.tagName.toLowerCase() == 'p' && browser.gecko && domUtils.removeDirtyAttr(start);
+ }
+
+ }
+
+ } else {
+ evt.preventDefault ? evt.preventDefault() : ( evt.returnValue = false);
+
+ if (!range.collapsed) {
+ range.deleteContents();
+ start = range.startContainer;
+ if (start.nodeType == 1 && (start = start.childNodes[range.startOffset])) {
+ while (start.nodeType == 1) {
+ if (dtd.$empty[start.tagName]) {
+ range.setStartBefore(start).setCursor();
+ if (me.undoManger) {
+ me.undoManger.save();
+ }
+ return false;
+ }
+ if (!start.firstChild) {
+ var br = range.document.createElement('br');
+ start.appendChild(br);
+ range.setStart(start, 0).setCursor();
+ if (me.undoManger) {
+ me.undoManger.save();
+ }
+ return false;
+ }
+ start = start.firstChild;
+ }
+ if (start === range.startContainer.childNodes[range.startOffset]) {
+ br = range.document.createElement('br');
+ range.insertNode(br).setCursor();
+
+ } else {
+ range.setStart(start, 0).setCursor();
+ }
+
+
+ } else {
+ br = range.document.createElement('br');
+ range.insertNode(br).setStartAfter(br).setCursor();
+ }
+
+
+ } else {
+ br = range.document.createElement('br');
+ range.insertNode(br);
+ var parent = br.parentNode;
+ if (parent.lastChild === br) {
+ br.parentNode.insertBefore(br.cloneNode(true), br);
+ range.setStartBefore(br);
+ } else {
+ range.setStartAfter(br);
+ }
+ range.setCursor();
+
+ }
+
+ }
+
+ }
+ });
+};
+
+
+// plugins/keystrokes.js
+/* 处理特殊键的兼容性问题 */
+UE.plugins['keystrokes'] = function() {
+ var me = this;
+ var collapsed = true;
+ me.addListener('keydown', function(type, evt) {
+ var keyCode = evt.keyCode || evt.which,
+ rng = me.selection.getRange();
+
+ //处理全选的情况
+ if(!rng.collapsed && !(evt.ctrlKey || evt.shiftKey || evt.altKey || evt.metaKey) && (keyCode >= 65 && keyCode <=90
+ || keyCode >= 48 && keyCode <= 57 ||
+ keyCode >= 96 && keyCode <= 111 || {
+ 13:1,
+ 8:1,
+ 46:1
+ }[keyCode])
+ ){
+
+ var tmpNode = rng.startContainer;
+ if(domUtils.isFillChar(tmpNode)){
+ rng.setStartBefore(tmpNode)
+ }
+ tmpNode = rng.endContainer;
+ if(domUtils.isFillChar(tmpNode)){
+ rng.setEndAfter(tmpNode)
+ }
+ rng.txtToElmBoundary();
+ //结束边界可能放到了br的前边,要把br包含进来
+ // x[xxx]
+ if(rng.endContainer && rng.endContainer.nodeType == 1){
+ tmpNode = rng.endContainer.childNodes[rng.endOffset];
+ if(tmpNode && domUtils.isBr(tmpNode)){
+ rng.setEndAfter(tmpNode);
+ }
+ }
+ if(rng.startOffset == 0){
+ tmpNode = rng.startContainer;
+ if(domUtils.isBoundaryNode(tmpNode,'firstChild') ){
+ tmpNode = rng.endContainer;
+ if(rng.endOffset == (tmpNode.nodeType == 3 ? tmpNode.nodeValue.length : tmpNode.childNodes.length) && domUtils.isBoundaryNode(tmpNode,'lastChild')){
+ me.fireEvent('saveScene');
+ me.body.innerHTML = ' '+(browser.ie ? '' : ' ')+' ';
+ rng.setStart(me.body.firstChild,0).setCursor(false,true);
+ me._selectionChange();
+ return;
+ }
+ }
+ }
+ }
+
+ //处理backspace
+ if (keyCode == keymap.Backspace) {
+ rng = me.selection.getRange();
+ collapsed = rng.collapsed;
+ if(me.fireEvent('delkeydown',evt)){
+ return;
+ }
+ var start,end;
+ //避免按两次删除才能生效的问题
+ if(rng.collapsed && rng.inFillChar()){
+ start = rng.startContainer;
+
+ if(domUtils.isFillChar(start)){
+ rng.setStartBefore(start).shrinkBoundary(true).collapse(true);
+ domUtils.remove(start)
+ }else{
+ start.nodeValue = start.nodeValue.replace(new RegExp('^' + domUtils.fillChar ),'');
+ rng.startOffset--;
+ rng.collapse(true).select(true)
+ }
+ }
+
+ //解决选中control元素不能删除的问题
+ if (start = rng.getClosedNode()) {
+ me.fireEvent('saveScene');
+ rng.setStartBefore(start);
+ domUtils.remove(start);
+ rng.setCursor();
+ me.fireEvent('saveScene');
+ domUtils.preventDefault(evt);
+ return;
+ }
+ //阻止在table上的删除
+ if (!browser.ie) {
+ start = domUtils.findParentByTagName(rng.startContainer, 'table', true);
+ end = domUtils.findParentByTagName(rng.endContainer, 'table', true);
+ if (start && !end || !start && end || start !== end) {
+ evt.preventDefault();
+ return;
+ }
+ }
+
+ }
+ //处理tab键的逻辑
+ if (keyCode == keymap.Tab) {
+ //不处理以下标签
+ var excludeTagNameForTabKey = {
+ 'ol' : 1,
+ 'ul' : 1,
+ 'table':1
+ };
+ //处理组件里的tab按下事件
+ if(me.fireEvent('tabkeydown',evt)){
+ domUtils.preventDefault(evt);
+ return;
+ }
+ var range = me.selection.getRange();
+ me.fireEvent('saveScene');
+ for (var i = 0,txt = '',tabSize = me.options.tabSize|| 4,tabNode = me.options.tabNode || ' '; i < tabSize; i++) {
+ txt += tabNode;
+ }
+ var span = me.document.createElement('span');
+ span.innerHTML = txt + domUtils.fillChar;
+ if (range.collapsed) {
+ range.insertNode(span.cloneNode(true).firstChild).setCursor(true);
+ } else {
+ var filterFn = function(node) {
+ return domUtils.isBlockElm(node) && !excludeTagNameForTabKey[node.tagName.toLowerCase()]
+
+ };
+ //普通的情况
+ start = domUtils.findParent(range.startContainer, filterFn,true);
+ end = domUtils.findParent(range.endContainer, filterFn,true);
+ if (start && end && start === end) {
+ range.deleteContents();
+ range.insertNode(span.cloneNode(true).firstChild).setCursor(true);
+ } else {
+ var bookmark = range.createBookmark();
+ range.enlarge(true);
+ var bookmark2 = range.createBookmark(),
+ current = domUtils.getNextDomNode(bookmark2.start, false, filterFn);
+ while (current && !(domUtils.getPosition(current, bookmark2.end) & domUtils.POSITION_FOLLOWING)) {
+ current.insertBefore(span.cloneNode(true).firstChild, current.firstChild);
+ current = domUtils.getNextDomNode(current, false, filterFn);
+ }
+ range.moveToBookmark(bookmark2).moveToBookmark(bookmark).select();
+ }
+ }
+ domUtils.preventDefault(evt)
+ }
+ //trace:1634
+ //ff的del键在容器空的时候,也会删除
+ if(browser.gecko && keyCode == 46){
+ range = me.selection.getRange();
+ if(range.collapsed){
+ start = range.startContainer;
+ if(domUtils.isEmptyBlock(start)){
+ var parent = start.parentNode;
+ while(domUtils.getChildCount(parent) == 1 && !domUtils.isBody(parent)){
+ start = parent;
+ parent = parent.parentNode;
+ }
+ if(start === parent.lastChild)
+ evt.preventDefault();
+ return;
+ }
+ }
+ }
+ });
+ me.addListener('keyup', function(type, evt) {
+ var keyCode = evt.keyCode || evt.which,
+ rng,me = this;
+ if(keyCode == keymap.Backspace){
+ if(me.fireEvent('delkeyup')){
+ return;
+ }
+ rng = me.selection.getRange();
+ if(rng.collapsed){
+ var tmpNode,
+ autoClearTagName = ['h1','h2','h3','h4','h5','h6'];
+ if(tmpNode = domUtils.findParentByTagName(rng.startContainer,autoClearTagName,true)){
+ if(domUtils.isEmptyBlock(tmpNode)){
+ var pre = tmpNode.previousSibling;
+ if(pre && pre.nodeName != 'TABLE'){
+ domUtils.remove(tmpNode);
+ rng.setStartAtLast(pre).setCursor(false,true);
+ return;
+ }else{
+ var next = tmpNode.nextSibling;
+ if(next && next.nodeName != 'TABLE'){
+ domUtils.remove(tmpNode);
+ rng.setStartAtFirst(next).setCursor(false,true);
+ return;
+ }
+ }
+ }
+ }
+ //处理当删除到body时,要重新给p标签展位
+ if(domUtils.isBody(rng.startContainer)){
+ var tmpNode = domUtils.createElement(me.document,'p',{
+ 'innerHTML' : browser.ie ? domUtils.fillChar : ' '
+ });
+ rng.insertNode(tmpNode).setStart(tmpNode,0).setCursor(false,true);
+ }
+ }
+
+
+ //chrome下如果删除了inline标签,浏览器会有记忆,在输入文字还是会套上刚才删除的标签,所以这里再选一次就不会了
+ if( !collapsed && (rng.startContainer.nodeType == 3 || rng.startContainer.nodeType == 1 && domUtils.isEmptyBlock(rng.startContainer))){
+ if(browser.ie){
+ var span = rng.document.createElement('span');
+ rng.insertNode(span).setStartBefore(span).collapse(true);
+ rng.select();
+ domUtils.remove(span)
+ }else{
+ rng.select()
+ }
+
+ }
+ }
+
+
+ })
+};
+
+// plugins/fiximgclick.js
+///import core
+///commands 修复chrome下图片不能点击的问题,出现八个角可改变大小
+///commandsName FixImgClick
+///commandsTitle 修复chrome下图片不能点击的问题,出现八个角可改变大小
+//修复chrome下图片不能点击的问题,出现八个角可改变大小
+
+UE.plugins['fiximgclick'] = (function () {
+
+ var elementUpdated = false;
+ function Scale() {
+ this.editor = null;
+ this.resizer = null;
+ this.cover = null;
+ this.doc = document;
+ this.prePos = {x: 0, y: 0};
+ this.startPos = {x: 0, y: 0};
+ }
+
+ (function () {
+ var rect = [
+ //[left, top, width, height]
+ [0, 0, -1, -1],
+ [0, 0, 0, -1],
+ [0, 0, 1, -1],
+ [0, 0, -1, 0],
+ [0, 0, 1, 0],
+ [0, 0, -1, 1],
+ [0, 0, 0, 1],
+ [0, 0, 1, 1]
+ ];
+
+ Scale.prototype = {
+ init: function (editor) {
+ var me = this;
+ me.editor = editor;
+ me.startPos = this.prePos = {x: 0, y: 0};
+ me.dragId = -1;
+
+ var hands = [],
+ cover = me.cover = document.createElement('div'),
+ resizer = me.resizer = document.createElement('div');
+
+ cover.id = me.editor.ui.id + '_imagescale_cover';
+ cover.style.cssText = 'position:absolute;display:none;z-index:' + (me.editor.options.zIndex) + ';filter:alpha(opacity=0); opacity:0;background:#CCC;';
+ domUtils.on(cover, 'mousedown click', function () {
+ me.hide();
+ });
+
+ for (i = 0; i < 8; i++) {
+ hands.push('');
+ }
+ resizer.id = me.editor.ui.id + '_imagescale';
+ resizer.className = 'edui-editor-imagescale';
+ resizer.innerHTML = hands.join('');
+ resizer.style.cssText += ';display:none;border:1px solid #3b77ff;z-index:' + (me.editor.options.zIndex) + ';';
+
+ me.editor.ui.getDom().appendChild(cover);
+ me.editor.ui.getDom().appendChild(resizer);
+
+ me.initStyle();
+ me.initEvents();
+ },
+ initStyle: function () {
+ utils.cssRule('imagescale', '.edui-editor-imagescale{display:none;position:absolute;border:1px solid #38B2CE;cursor:hand;-webkit-box-sizing: content-box;-moz-box-sizing: content-box;box-sizing: content-box;}' +
+ '.edui-editor-imagescale span{position:absolute;width:6px;height:6px;overflow:hidden;font-size:0px;display:block;background-color:#3C9DD0;}'
+ + '.edui-editor-imagescale .edui-editor-imagescale-hand0{cursor:nw-resize;top:0;margin-top:-4px;left:0;margin-left:-4px;}'
+ + '.edui-editor-imagescale .edui-editor-imagescale-hand1{cursor:n-resize;top:0;margin-top:-4px;left:50%;margin-left:-4px;}'
+ + '.edui-editor-imagescale .edui-editor-imagescale-hand2{cursor:ne-resize;top:0;margin-top:-4px;left:100%;margin-left:-3px;}'
+ + '.edui-editor-imagescale .edui-editor-imagescale-hand3{cursor:w-resize;top:50%;margin-top:-4px;left:0;margin-left:-4px;}'
+ + '.edui-editor-imagescale .edui-editor-imagescale-hand4{cursor:e-resize;top:50%;margin-top:-4px;left:100%;margin-left:-3px;}'
+ + '.edui-editor-imagescale .edui-editor-imagescale-hand5{cursor:sw-resize;top:100%;margin-top:-3px;left:0;margin-left:-4px;}'
+ + '.edui-editor-imagescale .edui-editor-imagescale-hand6{cursor:s-resize;top:100%;margin-top:-3px;left:50%;margin-left:-4px;}'
+ + '.edui-editor-imagescale .edui-editor-imagescale-hand7{cursor:se-resize;top:100%;margin-top:-3px;left:100%;margin-left:-3px;}');
+ },
+ initEvents: function () {
+ var me = this;
+
+ me.startPos.x = me.startPos.y = 0;
+ me.isDraging = false;
+ },
+ _eventHandler: function (e) {
+ var me = this;
+ switch (e.type) {
+ case 'mousedown':
+ var hand = e.target || e.srcElement, hand;
+ if (hand.className.indexOf('edui-editor-imagescale-hand') != -1 && me.dragId == -1) {
+ me.dragId = hand.className.slice(-1);
+ me.startPos.x = me.prePos.x = e.clientX;
+ me.startPos.y = me.prePos.y = e.clientY;
+ domUtils.on(me.doc,'mousemove', me.proxy(me._eventHandler, me));
+ }
+ break;
+ case 'mousemove':
+ if (me.dragId != -1) {
+ me.updateContainerStyle(me.dragId, {x: e.clientX - me.prePos.x, y: e.clientY - me.prePos.y});
+ me.prePos.x = e.clientX;
+ me.prePos.y = e.clientY;
+ elementUpdated = true;
+ me.updateTargetElement();
+
+ }
+ break;
+ case 'mouseup':
+ if (me.dragId != -1) {
+ me.updateContainerStyle(me.dragId, {x: e.clientX - me.prePos.x, y: e.clientY - me.prePos.y});
+ me.updateTargetElement();
+ if (me.target.parentNode) me.attachTo(me.target);
+ me.dragId = -1;
+ }
+ domUtils.un(me.doc,'mousemove', me.proxy(me._eventHandler, me));
+ //修复只是点击挪动点,但没有改变大小,不应该触发contentchange
+ if(elementUpdated){
+ elementUpdated = false;
+ me.editor.fireEvent('contentchange');
+ }
+
+ break;
+ default:
+ break;
+ }
+ },
+ updateTargetElement: function () {
+ var me = this;
+ domUtils.setStyles(me.target, {
+ 'width': me.resizer.style.width,
+ 'height': me.resizer.style.height
+ });
+ me.target.width = parseInt(me.resizer.style.width);
+ me.target.height = parseInt(me.resizer.style.height);
+ me.attachTo(me.target);
+ },
+ updateContainerStyle: function (dir, offset) {
+ var me = this,
+ dom = me.resizer, tmp;
+
+ if (rect[dir][0] != 0) {
+ tmp = parseInt(dom.style.left) + offset.x;
+ dom.style.left = me._validScaledProp('left', tmp) + 'px';
+ }
+ if (rect[dir][1] != 0) {
+ tmp = parseInt(dom.style.top) + offset.y;
+ dom.style.top = me._validScaledProp('top', tmp) + 'px';
+ }
+ if (rect[dir][2] != 0) {
+ tmp = dom.clientWidth + rect[dir][2] * offset.x;
+ dom.style.width = me._validScaledProp('width', tmp) + 'px';
+ }
+ if (rect[dir][3] != 0) {
+ tmp = dom.clientHeight + rect[dir][3] * offset.y;
+ dom.style.height = me._validScaledProp('height', tmp) + 'px';
+ }
+ },
+ _validScaledProp: function (prop, value) {
+ var ele = this.resizer,
+ wrap = document;
+
+ value = isNaN(value) ? 0 : value;
+ switch (prop) {
+ case 'left':
+ return value < 0 ? 0 : (value + ele.clientWidth) > wrap.clientWidth ? wrap.clientWidth - ele.clientWidth : value;
+ case 'top':
+ return value < 0 ? 0 : (value + ele.clientHeight) > wrap.clientHeight ? wrap.clientHeight - ele.clientHeight : value;
+ case 'width':
+ return value <= 0 ? 1 : (value + ele.offsetLeft) > wrap.clientWidth ? wrap.clientWidth - ele.offsetLeft : value;
+ case 'height':
+ return value <= 0 ? 1 : (value + ele.offsetTop) > wrap.clientHeight ? wrap.clientHeight - ele.offsetTop : value;
+ }
+ },
+ hideCover: function () {
+ this.cover.style.display = 'none';
+ },
+ showCover: function () {
+ var me = this,
+ editorPos = domUtils.getXY(me.editor.ui.getDom()),
+ iframePos = domUtils.getXY(me.editor.iframe);
+
+ domUtils.setStyles(me.cover, {
+ 'width': me.editor.iframe.offsetWidth + 'px',
+ 'height': me.editor.iframe.offsetHeight + 'px',
+ 'top': iframePos.y - editorPos.y + 'px',
+ 'left': iframePos.x - editorPos.x + 'px',
+ 'position': 'absolute',
+ 'display': ''
+ })
+ },
+ show: function (targetObj) {
+ var me = this;
+ me.resizer.style.display = 'block';
+ if(targetObj) me.attachTo(targetObj);
+
+ domUtils.on(this.resizer, 'mousedown', me.proxy(me._eventHandler, me));
+ domUtils.on(me.doc, 'mouseup', me.proxy(me._eventHandler, me));
+
+ me.showCover();
+ me.editor.fireEvent('afterscaleshow', me);
+ me.editor.fireEvent('saveScene');
+ },
+ hide: function () {
+ var me = this;
+ me.hideCover();
+ me.resizer.style.display = 'none';
+
+ domUtils.un(me.resizer, 'mousedown', me.proxy(me._eventHandler, me));
+ domUtils.un(me.doc, 'mouseup', me.proxy(me._eventHandler, me));
+ me.editor.fireEvent('afterscalehide', me);
+ },
+ proxy: function( fn, context ) {
+ return function(e) {
+ return fn.apply( context || this, arguments);
+ };
+ },
+ attachTo: function (targetObj) {
+ var me = this,
+ target = me.target = targetObj,
+ resizer = this.resizer,
+ imgPos = domUtils.getXY(target),
+ iframePos = domUtils.getXY(me.editor.iframe),
+ editorPos = domUtils.getXY(resizer.parentNode);
+
+ domUtils.setStyles(resizer, {
+ 'width': target.width + 'px',
+ 'height': target.height + 'px',
+ 'left': iframePos.x + imgPos.x - me.editor.document.body.scrollLeft - editorPos.x - parseInt(resizer.style.borderLeftWidth) + 'px',
+ 'top': iframePos.y + imgPos.y - me.editor.document.body.scrollTop - editorPos.y - parseInt(resizer.style.borderTopWidth) + 'px'
+ });
+ }
+ }
+ })();
+
+ return function () {
+ var me = this,
+ imageScale;
+
+ me.setOpt('imageScaleEnabled', true);
+
+ if ( !browser.ie && me.options.imageScaleEnabled) {
+ me.addListener('click', function (type, e) {
+
+ var range = me.selection.getRange(),
+ img = range.getClosedNode();
+
+ if (img && img.tagName == 'IMG' && me.body.contentEditable!="false") {
+
+ if (img.className.indexOf("edui-faked-music") != -1 ||
+ img.getAttribute("anchorname") ||
+ domUtils.hasClass(img, 'loadingclass') ||
+ domUtils.hasClass(img, 'loaderrorclass')) { return }
+
+ if (!imageScale) {
+ imageScale = new Scale();
+ imageScale.init(me);
+ me.ui.getDom().appendChild(imageScale.resizer);
+
+ var _keyDownHandler = function (e) {
+ imageScale.hide();
+ if(imageScale.target) me.selection.getRange().selectNode(imageScale.target).select();
+ }, _mouseDownHandler = function (e) {
+ var ele = e.target || e.srcElement;
+ if (ele && (ele.className===undefined || ele.className.indexOf('edui-editor-imagescale') == -1)) {
+ _keyDownHandler(e);
+ }
+ }, timer;
+
+ me.addListener('afterscaleshow', function (e) {
+ me.addListener('beforekeydown', _keyDownHandler);
+ me.addListener('beforemousedown', _mouseDownHandler);
+ domUtils.on(document, 'keydown', _keyDownHandler);
+ domUtils.on(document,'mousedown', _mouseDownHandler);
+ me.selection.getNative().removeAllRanges();
+ });
+ me.addListener('afterscalehide', function (e) {
+ me.removeListener('beforekeydown', _keyDownHandler);
+ me.removeListener('beforemousedown', _mouseDownHandler);
+ domUtils.un(document, 'keydown', _keyDownHandler);
+ domUtils.un(document,'mousedown', _mouseDownHandler);
+ var target = imageScale.target;
+ if (target.parentNode) {
+ me.selection.getRange().selectNode(target).select();
+ }
+ });
+ //TODO 有iframe的情况,mousedown不能往下传。。
+ domUtils.on(imageScale.resizer, 'mousedown', function (e) {
+ me.selection.getNative().removeAllRanges();
+ var ele = e.target || e.srcElement;
+ if (ele && ele.className.indexOf('edui-editor-imagescale-hand') == -1) {
+ timer = setTimeout(function () {
+ imageScale.hide();
+ if(imageScale.target) me.selection.getRange().selectNode(ele).select();
+ }, 200);
+ }
+ });
+ domUtils.on(imageScale.resizer, 'mouseup', function (e) {
+ var ele = e.target || e.srcElement;
+ if (ele && ele.className.indexOf('edui-editor-imagescale-hand') == -1) {
+ clearTimeout(timer);
+ }
+ });
+ }
+ imageScale.show(img);
+ } else {
+ if (imageScale && imageScale.resizer.style.display != 'none') imageScale.hide();
+ }
+ });
+ }
+
+ if (browser.webkit) {
+ me.addListener('click', function (type, e) {
+ if (e.target.tagName == 'IMG' && me.body.contentEditable!="false") {
+ var range = new dom.Range(me.document);
+ range.selectNode(e.target).select();
+ }
+ });
+ }
+ }
+})();
+
+// plugins/autolink.js
+///import core
+///commands 为非ie浏览器自动添加a标签
+///commandsName AutoLink
+///commandsTitle 自动增加链接
+/**
+ * @description 为非ie浏览器自动添加a标签
+ * @author zhanyi
+ */
+
+UE.plugin.register('autolink',function(){
+ var cont = 0;
+
+ return !browser.ie ? {
+
+ bindEvents:{
+ 'reset' : function(){
+ cont = 0;
+ },
+ 'keydown':function(type, evt) {
+ var me = this;
+ var keyCode = evt.keyCode || evt.which;
+
+ if (keyCode == 32 || keyCode == 13) {
+
+ var sel = me.selection.getNative(),
+ range = sel.getRangeAt(0).cloneRange(),
+ offset,
+ charCode;
+
+ var start = range.startContainer;
+ while (start.nodeType == 1 && range.startOffset > 0) {
+ start = range.startContainer.childNodes[range.startOffset - 1];
+ if (!start){
+ break;
+ }
+ range.setStart(start, start.nodeType == 1 ? start.childNodes.length : start.nodeValue.length);
+ range.collapse(true);
+ start = range.startContainer;
+ }
+
+ do{
+ if (range.startOffset == 0) {
+ start = range.startContainer.previousSibling;
+
+ while (start && start.nodeType == 1) {
+ start = start.lastChild;
+ }
+ if (!start || domUtils.isFillChar(start)){
+ break;
+ }
+ offset = start.nodeValue.length;
+ } else {
+ start = range.startContainer;
+ offset = range.startOffset;
+ }
+ range.setStart(start, offset - 1);
+ charCode = range.toString().charCodeAt(0);
+ } while (charCode != 160 && charCode != 32);
+
+ if (range.toString().replace(new RegExp(domUtils.fillChar, 'g'), '').match(/(?:https?:\/\/|ssh:\/\/|ftp:\/\/|file:\/|www\.)/i)) {
+ while(range.toString().length){
+ if(/^(?:https?:\/\/|ssh:\/\/|ftp:\/\/|file:\/|www\.)/i.test(range.toString())){
+ break;
+ }
+ try{
+ range.setStart(range.startContainer,range.startOffset+1);
+ }catch(e){
+ //trace:2121
+ var start = range.startContainer;
+ while(!(next = start.nextSibling)){
+ if(domUtils.isBody(start)){
+ return;
+ }
+ start = start.parentNode;
+
+ }
+ range.setStart(next,0);
+
+ }
+
+ }
+ //range的开始边界已经在a标签里的不再处理
+ if(domUtils.findParentByTagName(range.startContainer,'a',true)){
+ return;
+ }
+ var a = me.document.createElement('a'),text = me.document.createTextNode(' '),href;
+
+ me.undoManger && me.undoManger.save();
+ a.appendChild(range.extractContents());
+ a.href = a.innerHTML = a.innerHTML.replace(/<[^>]+>/g,'');
+ href = a.getAttribute("href").replace(new RegExp(domUtils.fillChar,'g'),'');
+ href = /^(?:https?:\/\/)/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 = ''
+ break;
+ case 'embed':
+ str = ' | |