10923 lines
379 KiB
JavaScript
10923 lines
379 KiB
JavaScript
/*!
|
||
* UEditor Mini版本
|
||
* version: 1.2.2
|
||
* build: Wed Mar 19 2014 17:08:14 GMT+0800 (中国标准时间)
|
||
*/
|
||
|
||
(function($){
|
||
|
||
UMEDITOR_CONFIG = window.UMEDITOR_CONFIG || {};
|
||
|
||
window.UM = {
|
||
plugins : {},
|
||
|
||
commands : {},
|
||
|
||
I18N : {},
|
||
|
||
version : "1.2.2"
|
||
};
|
||
|
||
var dom = UM.dom = {};
|
||
/**
|
||
* 浏览器判断模块
|
||
* @file
|
||
* @module UE.browser
|
||
* @since 1.2.6.1
|
||
*/
|
||
|
||
/**
|
||
* 提供浏览器检测的模块
|
||
* @unfile
|
||
* @module UE.browser
|
||
*/
|
||
var browser = UM.browser = function(){
|
||
var agent = navigator.userAgent.toLowerCase(),
|
||
opera = window.opera,
|
||
browser = {
|
||
/**
|
||
* @property {boolean} ie 检测当前浏览器是否为IE
|
||
* @example
|
||
* ```javascript
|
||
* if ( UE.browser.ie ) {
|
||
* console.log( '当前浏览器是IE' );
|
||
* }
|
||
* ```
|
||
*/
|
||
ie : /(msie\s|trident.*rv:)([\w.]+)/.test(agent),
|
||
|
||
/**
|
||
* @property {boolean} opera 检测当前浏览器是否为Opera
|
||
* @example
|
||
* ```javascript
|
||
* if ( UE.browser.opera ) {
|
||
* console.log( '当前浏览器是Opera' );
|
||
* }
|
||
* ```
|
||
*/
|
||
opera : ( !!opera && opera.version ),
|
||
|
||
/**
|
||
* @property {boolean} webkit 检测当前浏览器是否是webkit内核的浏览器
|
||
* @example
|
||
* ```javascript
|
||
* if ( UE.browser.webkit ) {
|
||
* console.log( '当前浏览器是webkit内核浏览器' );
|
||
* }
|
||
* ```
|
||
*/
|
||
webkit : ( agent.indexOf( ' applewebkit/' ) > -1 ),
|
||
|
||
/**
|
||
* @property {boolean} mac 检测当前浏览器是否是运行在mac平台下
|
||
* @example
|
||
* ```javascript
|
||
* if ( UE.browser.mac ) {
|
||
* console.log( '当前浏览器运行在mac平台下' );
|
||
* }
|
||
* ```
|
||
*/
|
||
mac : ( agent.indexOf( 'macintosh' ) > -1 ),
|
||
|
||
/**
|
||
* @property {boolean} quirks 检测当前浏览器是否处于“怪异模式”下
|
||
* @example
|
||
* ```javascript
|
||
* if ( UE.browser.quirks ) {
|
||
* console.log( '当前浏览器运行处于“怪异模式”' );
|
||
* }
|
||
* ```
|
||
*/
|
||
quirks : ( document.compatMode == 'BackCompat' )
|
||
};
|
||
|
||
/**
|
||
* @property {boolean} gecko 检测当前浏览器内核是否是gecko内核
|
||
* @example
|
||
* ```javascript
|
||
* if ( UE.browser.gecko ) {
|
||
* console.log( '当前浏览器内核是gecko内核' );
|
||
* }
|
||
* ```
|
||
*/
|
||
browser.gecko =( navigator.product == 'Gecko' && !browser.webkit && !browser.opera && !browser.ie);
|
||
|
||
var version = 0;
|
||
|
||
// Internet Explorer 6.0+
|
||
if ( browser.ie ){
|
||
|
||
|
||
var v1 = agent.match(/(?:msie\s([\w.]+))/);
|
||
var v2 = agent.match(/(?:trident.*rv:([\w.]+))/);
|
||
if(v1 && v2 && v1[1] && v2[1]){
|
||
version = Math.max(v1[1]*1,v2[1]*1);
|
||
}else if(v1 && v1[1]){
|
||
version = v1[1]*1;
|
||
}else if(v2 && v2[1]){
|
||
version = v2[1]*1;
|
||
}else{
|
||
version = 0;
|
||
}
|
||
|
||
browser.ie11Compat = document.documentMode == 11;
|
||
/**
|
||
* @property { boolean } ie9Compat 检测浏览器模式是否为 IE9 兼容模式
|
||
* @warning 如果浏览器不是IE, 则该值为undefined
|
||
* @example
|
||
* ```javascript
|
||
* if ( UE.browser.ie9Compat ) {
|
||
* console.log( '当前浏览器运行在IE9兼容模式下' );
|
||
* }
|
||
* ```
|
||
*/
|
||
browser.ie9Compat = document.documentMode == 9;
|
||
|
||
/**
|
||
* @property { boolean } ie8 检测浏览器是否是IE8浏览器
|
||
* @warning 如果浏览器不是IE, 则该值为undefined
|
||
* @example
|
||
* ```javascript
|
||
* if ( UE.browser.ie8 ) {
|
||
* console.log( '当前浏览器是IE8浏览器' );
|
||
* }
|
||
* ```
|
||
*/
|
||
browser.ie8 = !!document.documentMode;
|
||
|
||
/**
|
||
* @property { boolean } ie8Compat 检测浏览器模式是否为 IE8 兼容模式
|
||
* @warning 如果浏览器不是IE, 则该值为undefined
|
||
* @example
|
||
* ```javascript
|
||
* if ( UE.browser.ie8Compat ) {
|
||
* console.log( '当前浏览器运行在IE8兼容模式下' );
|
||
* }
|
||
* ```
|
||
*/
|
||
browser.ie8Compat = document.documentMode == 8;
|
||
|
||
/**
|
||
* @property { boolean } ie7Compat 检测浏览器模式是否为 IE7 兼容模式
|
||
* @warning 如果浏览器不是IE, 则该值为undefined
|
||
* @example
|
||
* ```javascript
|
||
* if ( UE.browser.ie7Compat ) {
|
||
* console.log( '当前浏览器运行在IE7兼容模式下' );
|
||
* }
|
||
* ```
|
||
*/
|
||
browser.ie7Compat = ( ( version == 7 && !document.documentMode )
|
||
|| document.documentMode == 7 );
|
||
|
||
/**
|
||
* @property { boolean } ie6Compat 检测浏览器模式是否为 IE6 模式 或者怪异模式
|
||
* @warning 如果浏览器不是IE, 则该值为undefined
|
||
* @example
|
||
* ```javascript
|
||
* if ( UE.browser.ie6Compat ) {
|
||
* console.log( '当前浏览器运行在IE6模式或者怪异模式下' );
|
||
* }
|
||
* ```
|
||
*/
|
||
browser.ie6Compat = ( version < 7 || browser.quirks );
|
||
|
||
browser.ie9above = version > 8;
|
||
|
||
browser.ie9below = version < 9;
|
||
|
||
}
|
||
|
||
// Gecko.
|
||
if ( browser.gecko ){
|
||
var geckoRelease = agent.match( /rv:([\d\.]+)/ );
|
||
if ( geckoRelease )
|
||
{
|
||
geckoRelease = geckoRelease[1].split( '.' );
|
||
version = geckoRelease[0] * 10000 + ( geckoRelease[1] || 0 ) * 100 + ( geckoRelease[2] || 0 ) * 1;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @property { Number } chrome 检测当前浏览器是否为Chrome, 如果是,则返回Chrome的大版本号
|
||
* @warning 如果浏览器不是chrome, 则该值为undefined
|
||
* @example
|
||
* ```javascript
|
||
* if ( UE.browser.chrome ) {
|
||
* console.log( '当前浏览器是Chrome' );
|
||
* }
|
||
* ```
|
||
*/
|
||
if (/chrome\/(\d+\.\d)/i.test(agent)) {
|
||
browser.chrome = + RegExp['\x241'];
|
||
}
|
||
|
||
/**
|
||
* @property { Number } safari 检测当前浏览器是否为Safari, 如果是,则返回Safari的大版本号
|
||
* @warning 如果浏览器不是safari, 则该值为undefined
|
||
* @example
|
||
* ```javascript
|
||
* if ( UE.browser.safari ) {
|
||
* console.log( '当前浏览器是Safari' );
|
||
* }
|
||
* ```
|
||
*/
|
||
if(/(\d+\.\d)?(?:\.\d)?\s+safari\/?(\d+\.\d+)?/i.test(agent) && !/chrome/i.test(agent)){
|
||
browser.safari = + (RegExp['\x241'] || RegExp['\x242']);
|
||
}
|
||
|
||
|
||
// Opera 9.50+
|
||
if ( browser.opera )
|
||
version = parseFloat( opera.version() );
|
||
|
||
// WebKit 522+ (Safari 3+)
|
||
if ( browser.webkit )
|
||
version = parseFloat( agent.match( / applewebkit\/(\d+)/ )[1] );
|
||
|
||
/**
|
||
* @property { Number } version 检测当前浏览器版本号
|
||
* @remind
|
||
* <ul>
|
||
* <li>IE系列返回值为5,6,7,8,9,10等</li>
|
||
* <li>gecko系列会返回10900,158900等</li>
|
||
* <li>webkit系列会返回其build号 (如 522等)</li>
|
||
* </ul>
|
||
* @example
|
||
* ```javascript
|
||
* console.log( '当前浏览器版本号是: ' + UE.browser.version );
|
||
* ```
|
||
*/
|
||
browser.version = version;
|
||
|
||
/**
|
||
* @property { boolean } isCompatible 检测当前浏览器是否能够与UEditor良好兼容
|
||
* @example
|
||
* ```javascript
|
||
* if ( UE.browser.isCompatible ) {
|
||
* console.log( '浏览器与UEditor能够良好兼容' );
|
||
* }
|
||
* ```
|
||
*/
|
||
browser.isCompatible =
|
||
!browser.mobile && (
|
||
( browser.ie && version >= 6 ) ||
|
||
( browser.gecko && version >= 10801 ) ||
|
||
( browser.opera && version >= 9.5 ) ||
|
||
( browser.air && version >= 1 ) ||
|
||
( browser.webkit && version >= 522 ) ||
|
||
false );
|
||
return browser;
|
||
}();
|
||
//快捷方式
|
||
var ie = browser.ie,
|
||
webkit = browser.webkit,
|
||
gecko = browser.gecko,
|
||
opera = browser.opera;
|
||
/**
|
||
* @file
|
||
* @name UM.Utils
|
||
* @short Utils
|
||
* @desc UEditor封装使用的静态工具函数
|
||
* @import editor.js
|
||
*/
|
||
var utils = UM.utils = {
|
||
/**
|
||
* 遍历数组,对象,nodeList
|
||
* @name each
|
||
* @grammar UM.utils.each(obj,iterator,[context])
|
||
* @since 1.2.4+
|
||
* @desc
|
||
* * obj 要遍历的对象
|
||
* * iterator 遍历的方法,方法的第一个是遍历的值,第二个是索引,第三个是obj
|
||
* * context iterator的上下文
|
||
* @example
|
||
* UM.utils.each([1,2],function(v,i){
|
||
* console.log(v)//值
|
||
* console.log(i)//索引
|
||
* })
|
||
* UM.utils.each(document.getElementsByTagName('*'),function(n){
|
||
* console.log(n.tagName)
|
||
* })
|
||
*/
|
||
each : function(obj, iterator, context) {
|
||
if (obj == null) return;
|
||
if (obj.length === +obj.length) {
|
||
for (var i = 0, l = obj.length; i < l; i++) {
|
||
if(iterator.call(context, obj[i], i, obj) === false)
|
||
return false;
|
||
}
|
||
} else {
|
||
for (var key in obj) {
|
||
if (obj.hasOwnProperty(key)) {
|
||
if(iterator.call(context, obj[key], key, obj) === false)
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
},
|
||
|
||
makeInstance:function (obj) {
|
||
var noop = new Function();
|
||
noop.prototype = obj;
|
||
obj = new noop;
|
||
noop.prototype = null;
|
||
return obj;
|
||
},
|
||
/**
|
||
* 将source对象中的属性扩展到target对象上
|
||
* @name extend
|
||
* @grammar UM.utils.extend(target,source) => Object //覆盖扩展
|
||
* @grammar UM.utils.extend(target,source,true) ==> Object //保留扩展
|
||
*/
|
||
extend:function (t, s, b) {
|
||
if (s) {
|
||
for (var k in s) {
|
||
if (!b || !t.hasOwnProperty(k)) {
|
||
t[k] = s[k];
|
||
}
|
||
}
|
||
}
|
||
return t;
|
||
},
|
||
extend2:function (t) {
|
||
var a = arguments;
|
||
for (var i = 1; i < a.length; i++) {
|
||
var x = a[i];
|
||
for (var k in x) {
|
||
if (!t.hasOwnProperty(k)) {
|
||
t[k] = x[k];
|
||
}
|
||
}
|
||
}
|
||
return t;
|
||
},
|
||
/**
|
||
* 模拟继承机制,subClass继承superClass
|
||
* @name inherits
|
||
* @grammar UM.utils.inherits(subClass,superClass) => subClass
|
||
* @example
|
||
* function SuperClass(){
|
||
* this.name = "小李";
|
||
* }
|
||
* SuperClass.prototype = {
|
||
* hello:function(str){
|
||
* console.log(this.name + str);
|
||
* }
|
||
* }
|
||
* function SubClass(){
|
||
* this.name = "小张";
|
||
* }
|
||
* UM.utils.inherits(SubClass,SuperClass);
|
||
* var sub = new SubClass();
|
||
* sub.hello("早上好!"); ==> "小张早上好!"
|
||
*/
|
||
inherits:function (subClass, superClass) {
|
||
var oldP = subClass.prototype,
|
||
newP = utils.makeInstance(superClass.prototype);
|
||
utils.extend(newP, oldP, true);
|
||
subClass.prototype = newP;
|
||
return (newP.constructor = subClass);
|
||
},
|
||
|
||
/**
|
||
* 用指定的context作为fn上下文,也就是this
|
||
* @name bind
|
||
* @grammar UM.utils.bind(fn,context) => fn
|
||
*/
|
||
bind:function (fn, context) {
|
||
return function () {
|
||
return fn.apply(context, arguments);
|
||
};
|
||
},
|
||
|
||
/**
|
||
* 创建延迟delay执行的函数fn
|
||
* @name defer
|
||
* @grammar UM.utils.defer(fn,delay) =>fn //延迟delay毫秒执行fn,返回fn
|
||
* @grammar UM.utils.defer(fn,delay,exclusion) =>fn //延迟delay毫秒执行fn,若exclusion为真,则互斥执行fn
|
||
* @example
|
||
* function test(){
|
||
* console.log("延迟输出!");
|
||
* }
|
||
* //非互斥延迟执行
|
||
* var testDefer = UM.utils.defer(test,1000);
|
||
* testDefer(); => "延迟输出!";
|
||
* testDefer(); => "延迟输出!";
|
||
* //互斥延迟执行
|
||
* var testDefer1 = UM.utils.defer(test,1000,true);
|
||
* testDefer1(); => //本次不执行
|
||
* testDefer1(); => "延迟输出!";
|
||
*/
|
||
defer:function (fn, delay, exclusion) {
|
||
var timerID;
|
||
return function () {
|
||
if (exclusion) {
|
||
clearTimeout(timerID);
|
||
}
|
||
timerID = setTimeout(fn, delay);
|
||
};
|
||
},
|
||
|
||
/**
|
||
* 查找元素item在数组array中的索引, 若找不到返回-1
|
||
* @name indexOf
|
||
* @grammar UM.utils.indexOf(array,item) => index|-1 //默认从数组开头部开始搜索
|
||
* @grammar UM.utils.indexOf(array,item,start) => index|-1 //start指定开始查找的位置
|
||
*/
|
||
indexOf:function (array, item, start) {
|
||
var index = -1;
|
||
start = this.isNumber(start) ? start : 0;
|
||
this.each(array, function (v, i) {
|
||
if (i >= start && v === item) {
|
||
index = i;
|
||
return false;
|
||
}
|
||
});
|
||
return index;
|
||
},
|
||
|
||
/**
|
||
* 移除数组array中的元素item
|
||
* @name removeItem
|
||
* @grammar UM.utils.removeItem(array,item)
|
||
*/
|
||
removeItem:function (array, item) {
|
||
for (var i = 0, l = array.length; i < l; i++) {
|
||
if (array[i] === item) {
|
||
array.splice(i, 1);
|
||
i--;
|
||
}
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 删除字符串str的首尾空格
|
||
* @name trim
|
||
* @grammar UM.utils.trim(str) => String
|
||
*/
|
||
trim:function (str) {
|
||
return str.replace(/(^[ \t\n\r]+)|([ \t\n\r]+$)/g, '');
|
||
},
|
||
|
||
/**
|
||
* 将字符串list(以','分隔)或者数组list转成哈希对象
|
||
* @name listToMap
|
||
* @grammar UM.utils.listToMap(list) => Object //Object形如{test:1,br:1,textarea:1}
|
||
*/
|
||
listToMap:function (list) {
|
||
if (!list)return {};
|
||
list = utils.isArray(list) ? list : list.split(',');
|
||
for (var i = 0, ci, obj = {}; ci = list[i++];) {
|
||
obj[ci.toUpperCase()] = obj[ci] = 1;
|
||
}
|
||
return obj;
|
||
},
|
||
|
||
/**
|
||
* 将str中的html符号转义,默认将转义''&<">''四个字符,可自定义reg来确定需要转义的字符
|
||
* @name unhtml
|
||
* @grammar UM.utils.unhtml(str); => String
|
||
* @grammar UM.utils.unhtml(str,reg) => String
|
||
* @example
|
||
* var html = '<body>You say:"你好!Baidu & UEditor!"</body>';
|
||
* UM.utils.unhtml(html); ==> <body>You say:"你好!Baidu & UEditor!"</body>
|
||
* UM.utils.unhtml(html,/[<>]/g) ==> <body>You say:"你好!Baidu & UEditor!"</body>
|
||
*/
|
||
unhtml:function (str, reg) {
|
||
return str ? str.replace(reg || /[&<">'](?:(amp|lt|quot|gt|#39|nbsp);)?/g, function (a, b) {
|
||
if (b) {
|
||
return a;
|
||
} else {
|
||
return {
|
||
'<':'<',
|
||
'&':'&',
|
||
'"':'"',
|
||
'>':'>',
|
||
"'":'''
|
||
}[a]
|
||
}
|
||
|
||
}) : '';
|
||
},
|
||
/**
|
||
* 将str中的转义字符还原成html字符
|
||
* @name html
|
||
* @grammar UM.utils.html(str) => String //详细参见<code><a href = '#unhtml'>unhtml</a></code>
|
||
*/
|
||
html:function (str) {
|
||
return str ? str.replace(/&((g|l|quo)t|amp|#39);/g, function (m) {
|
||
return {
|
||
'<':'<',
|
||
'&':'&',
|
||
'"':'"',
|
||
'>':'>',
|
||
''':"'"
|
||
}[m]
|
||
}) : '';
|
||
},
|
||
/**
|
||
* 将css样式转换为驼峰的形式。如font-size => fontSize
|
||
* @name cssStyleToDomStyle
|
||
* @grammar UM.utils.cssStyleToDomStyle(cssName) => String
|
||
*/
|
||
cssStyleToDomStyle:function () {
|
||
var test = document.createElement('div').style,
|
||
cache = {
|
||
'float':test.cssFloat != undefined ? 'cssFloat' : test.styleFloat != undefined ? 'styleFloat' : 'float'
|
||
};
|
||
|
||
return function (cssName) {
|
||
return cache[cssName] || (cache[cssName] = cssName.toLowerCase().replace(/-./g, function (match) {
|
||
return match.charAt(1).toUpperCase();
|
||
}));
|
||
};
|
||
}(),
|
||
/**
|
||
* 动态加载文件到doc中,并依据obj来设置属性,加载成功后执行回调函数fn
|
||
* @name loadFile
|
||
* @grammar UM.utils.loadFile(doc,obj)
|
||
* @grammar UM.utils.loadFile(doc,obj,fn)
|
||
* @example
|
||
* //指定加载到当前document中一个script文件,加载成功后执行function
|
||
* utils.loadFile( document, {
|
||
* src:"test.js",
|
||
* tag:"script",
|
||
* type:"text/javascript",
|
||
* defer:"defer"
|
||
* }, function () {
|
||
* console.log('加载成功!')
|
||
* });
|
||
*/
|
||
loadFile:function () {
|
||
var tmpList = [];
|
||
|
||
function getItem(doc, obj) {
|
||
try {
|
||
for (var i = 0, ci; ci = tmpList[i++];) {
|
||
if (ci.doc === doc && ci.url == (obj.src || obj.href)) {
|
||
return ci;
|
||
}
|
||
}
|
||
} catch (e) {
|
||
return null;
|
||
}
|
||
|
||
}
|
||
|
||
return function (doc, obj, fn) {
|
||
var item = getItem(doc, obj);
|
||
if (item) {
|
||
if (item.ready) {
|
||
fn && fn();
|
||
} else {
|
||
item.funs.push(fn)
|
||
}
|
||
return;
|
||
}
|
||
tmpList.push({
|
||
doc:doc,
|
||
url:obj.src || obj.href,
|
||
funs:[fn]
|
||
});
|
||
if (!doc.body) {
|
||
var html = [];
|
||
for (var p in obj) {
|
||
if (p == 'tag')continue;
|
||
html.push(p + '="' + obj[p] + '"')
|
||
}
|
||
doc.write('<' + obj.tag + ' ' + html.join(' ') + ' ></' + obj.tag + '>');
|
||
return;
|
||
}
|
||
if (obj.id && doc.getElementById(obj.id)) {
|
||
return;
|
||
}
|
||
var element = doc.createElement(obj.tag);
|
||
delete obj.tag;
|
||
for (var p in obj) {
|
||
element.setAttribute(p, obj[p]);
|
||
}
|
||
element.onload = element.onreadystatechange = function () {
|
||
if (!this.readyState || /loaded|complete/.test(this.readyState)) {
|
||
item = getItem(doc, obj);
|
||
if (item.funs.length > 0) {
|
||
item.ready = 1;
|
||
for (var fi; fi = item.funs.pop();) {
|
||
fi();
|
||
}
|
||
}
|
||
element.onload = element.onreadystatechange = null;
|
||
}
|
||
};
|
||
element.onerror = function () {
|
||
throw Error('The load ' + (obj.href || obj.src) + ' fails,check the url settings of file umeditor.config.js ')
|
||
};
|
||
doc.getElementsByTagName("head")[0].appendChild(element);
|
||
}
|
||
}(),
|
||
/**
|
||
* 判断obj对象是否为空
|
||
* @name isEmptyObject
|
||
* @grammar UM.utils.isEmptyObject(obj) => true|false
|
||
* @example
|
||
* UM.utils.isEmptyObject({}) ==>true
|
||
* UM.utils.isEmptyObject([]) ==>true
|
||
* UM.utils.isEmptyObject("") ==>true
|
||
*/
|
||
isEmptyObject:function (obj) {
|
||
if (obj == null) return true;
|
||
if (this.isArray(obj) || this.isString(obj)) return obj.length === 0;
|
||
for (var key in obj) if (obj.hasOwnProperty(key)) return false;
|
||
return true;
|
||
},
|
||
|
||
/**
|
||
* 统一将颜色值使用16进制形式表示
|
||
* @name fixColor
|
||
* @grammar UM.utils.fixColor(name,value) => value
|
||
* @example
|
||
* rgb(255,255,255) => "#ffffff"
|
||
*/
|
||
fixColor:function (name, value) {
|
||
if (/color/i.test(name) && /rgba?/.test(value)) {
|
||
var array = value.split(",");
|
||
if (array.length > 3)
|
||
return "";
|
||
value = "#";
|
||
for (var i = 0, color; color = array[i++];) {
|
||
color = parseInt(color.replace(/[^\d]/gi, ''), 10).toString(16);
|
||
value += color.length == 1 ? "0" + color : color;
|
||
}
|
||
value = value.toUpperCase();
|
||
}
|
||
return value;
|
||
},
|
||
|
||
/**
|
||
* 深度克隆对象,从source到target
|
||
* @name clone
|
||
* @grammar UM.utils.clone(source) => anthorObj 新的对象是完整的source的副本
|
||
* @grammar UM.utils.clone(source,target) => target包含了source的所有内容,重名会覆盖
|
||
*/
|
||
clone:function (source, target) {
|
||
var tmp;
|
||
target = target || {};
|
||
for (var i in source) {
|
||
if (source.hasOwnProperty(i)) {
|
||
tmp = source[i];
|
||
if (typeof tmp == 'object') {
|
||
target[i] = utils.isArray(tmp) ? [] : {};
|
||
utils.clone(source[i], target[i])
|
||
} else {
|
||
target[i] = tmp;
|
||
}
|
||
}
|
||
}
|
||
return target;
|
||
},
|
||
/**
|
||
* 转换cm/pt到px
|
||
* @name transUnitToPx
|
||
* @grammar UM.utils.transUnitToPx('20pt') => '27px'
|
||
* @grammar UM.utils.transUnitToPx('0pt') => '0'
|
||
*/
|
||
transUnitToPx:function (val) {
|
||
if (!/(pt|cm)/.test(val)) {
|
||
return val
|
||
}
|
||
var unit;
|
||
val.replace(/([\d.]+)(\w+)/, function (str, v, u) {
|
||
val = v;
|
||
unit = u;
|
||
});
|
||
switch (unit) {
|
||
case 'cm':
|
||
val = parseFloat(val) * 25;
|
||
break;
|
||
case 'pt':
|
||
val = Math.round(parseFloat(val) * 96 / 72);
|
||
}
|
||
return val + (val ? 'px' : '');
|
||
},
|
||
/**
|
||
* 动态添加css样式
|
||
* @name cssRule
|
||
* @grammar UM.utils.cssRule('添加的样式的节点名称',['样式','放到哪个document上'])
|
||
* @grammar UM.utils.cssRule('body','body{background:#ccc}') => null //给body添加背景颜色
|
||
* @grammar UM.utils.cssRule('body') =>样式的字符串 //取得key值为body的样式的内容,如果没有找到key值先关的样式将返回空,例如刚才那个背景颜色,将返回 body{background:#ccc}
|
||
* @grammar UM.utils.cssRule('body','') =>null //清空给定的key值的背景颜色
|
||
*/
|
||
cssRule:browser.ie && browser.version != 11 ? function (key, style, doc) {
|
||
var indexList, index;
|
||
doc = doc || document;
|
||
if (doc.indexList) {
|
||
indexList = doc.indexList;
|
||
} else {
|
||
indexList = doc.indexList = {};
|
||
}
|
||
var sheetStyle;
|
||
if (!indexList[key]) {
|
||
if (style === undefined) {
|
||
return ''
|
||
}
|
||
sheetStyle = doc.createStyleSheet('', index = doc.styleSheets.length);
|
||
indexList[key] = index;
|
||
} else {
|
||
sheetStyle = doc.styleSheets[indexList[key]];
|
||
}
|
||
if (style === undefined) {
|
||
return sheetStyle.cssText
|
||
}
|
||
sheetStyle.cssText = style || ''
|
||
} : function (key, style, doc) {
|
||
doc = doc || document;
|
||
var head = doc.getElementsByTagName('head')[0], node;
|
||
if (!(node = doc.getElementById(key))) {
|
||
if (style === undefined) {
|
||
return ''
|
||
}
|
||
node = doc.createElement('style');
|
||
node.id = key;
|
||
head.appendChild(node)
|
||
}
|
||
if (style === undefined) {
|
||
return node.innerHTML
|
||
}
|
||
if (style !== '') {
|
||
node.innerHTML = style;
|
||
} else {
|
||
head.removeChild(node)
|
||
}
|
||
}
|
||
|
||
};
|
||
/**
|
||
* 判断str是否为字符串
|
||
* @name isString
|
||
* @grammar UM.utils.isString(str) => true|false
|
||
*/
|
||
/**
|
||
* 判断array是否为数组
|
||
* @name isArray
|
||
* @grammar UM.utils.isArray(obj) => true|false
|
||
*/
|
||
/**
|
||
* 判断obj对象是否为方法
|
||
* @name isFunction
|
||
* @grammar UM.utils.isFunction(obj) => true|false
|
||
*/
|
||
/**
|
||
* 判断obj对象是否为数字
|
||
* @name isNumber
|
||
* @grammar UM.utils.isNumber(obj) => true|false
|
||
*/
|
||
utils.each(['String', 'Function', 'Array', 'Number', 'RegExp', 'Object'], function (v) {
|
||
UM.utils['is' + v] = function (obj) {
|
||
return Object.prototype.toString.apply(obj) == '[object ' + v + ']';
|
||
}
|
||
});
|
||
/**
|
||
* @file
|
||
* @name UM.EventBase
|
||
* @short EventBase
|
||
* @import editor.js,core/utils.js
|
||
* @desc UE采用的事件基类,继承此类的对应类将获取addListener,removeListener,fireEvent方法。
|
||
* 在UE中,Editor以及所有ui实例都继承了该类,故可以在对应的ui对象以及editor对象上使用上述方法。
|
||
*/
|
||
var EventBase = UM.EventBase = function () {};
|
||
|
||
EventBase.prototype = {
|
||
/**
|
||
* 注册事件监听器
|
||
* @name addListener
|
||
* @grammar editor.addListener(types,fn) //types为事件名称,多个可用空格分隔
|
||
* @example
|
||
* 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是注册的事件的编辑器实例
|
||
* })
|
||
*/
|
||
addListener:function (types, listener) {
|
||
types = utils.trim(types).split(' ');
|
||
for (var i = 0, ti; ti = types[i++];) {
|
||
getListener(this, ti, true).push(listener);
|
||
}
|
||
},
|
||
/**
|
||
* 移除事件监听器
|
||
* @name removeListener
|
||
* @grammar editor.removeListener(types,fn) //types为事件名称,多个可用空格分隔
|
||
* @example
|
||
* //changeCallback为方法体
|
||
* editor.removeListener("selectionchange",changeCallback);
|
||
*/
|
||
removeListener:function (types, listener) {
|
||
types = utils.trim(types).split(' ');
|
||
for (var i = 0, ti; ti = types[i++];) {
|
||
utils.removeItem(getListener(this, ti) || [], listener);
|
||
}
|
||
},
|
||
/**
|
||
* 触发事件
|
||
* @name fireEvent
|
||
* @grammar editor.fireEvent(types) //types为事件名称,多个可用空格分隔
|
||
* @example
|
||
* editor.fireEvent("selectionchange");
|
||
*/
|
||
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;
|
||
}
|
||
};
|
||
/**
|
||
* 获得对象所拥有监听类型的所有监听器
|
||
* @public
|
||
* @function
|
||
* @param {Object} obj 查询监听器的对象
|
||
* @param {String} type 事件类型
|
||
* @param {Boolean} force 为true且当前所有type类型的侦听器不存在时,创建一个空监听器数组
|
||
* @returns {Array} 监听器数组
|
||
*/
|
||
function getListener(obj, type, force) {
|
||
var allListeners;
|
||
type = type.toLowerCase();
|
||
return ( ( allListeners = ( obj.__allListeners || force && ( obj.__allListeners = {} ) ) )
|
||
&& ( allListeners[type] || force && ( allListeners[type] = [] ) ) );
|
||
}
|
||
|
||
|
||
///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
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* @file
|
||
* @name UM.dom.domUtils
|
||
* @short DomUtils
|
||
* @import editor.js, core/utils.js,core/browser.js,core/dom/dtd.js
|
||
* @desc UEditor封装的底层dom操作库
|
||
*/
|
||
|
||
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*/
|
||
},
|
||
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;
|
||
},
|
||
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');
|
||
},
|
||
/**
|
||
* 获取节点A相对于节点B的位置关系
|
||
* @name getPosition
|
||
* @grammar UM.dom.domUtils.getPosition(nodeA,nodeB) => Number
|
||
* @example
|
||
* switch (returnValue) {
|
||
* case 0: //相等,同一节点
|
||
* case 1: //无关,节点不相连
|
||
* case 2: //跟随,即节点A头部位于节点B头部的后面
|
||
* case 4: //前置,即节点A头部位于节点B头部的前面
|
||
* case 8: //被包含,即节点A被节点B包含
|
||
* case 10://组合类型,即节点A满足跟随节点B且被节点B包含。实际上,如果被包含,必定跟随,所以returnValue事实上不会存在8的情况。
|
||
* case 16://包含,即节点A包含节点B
|
||
* case 20://组合类型,即节点A满足前置节点A且包含节点B。同样,如果包含,必定前置,所以returnValue事实上也不会存在16的情况
|
||
* }
|
||
*/
|
||
getPosition: function (nodeA, nodeB) {
|
||
// 如果两个节点是同一个节点
|
||
if (nodeA === nodeB) {
|
||
// domUtils.POSITION_IDENTICAL
|
||
return 0;
|
||
}
|
||
var node,
|
||
parentsA = [nodeA],
|
||
parentsB = [nodeB];
|
||
node = nodeA;
|
||
while (node = node.parentNode) {
|
||
// 如果nodeB是nodeA的祖先节点
|
||
if (node === nodeB) {
|
||
// domUtils.POSITION_IS_CONTAINED + domUtils.POSITION_FOLLOWING
|
||
return 10;
|
||
}
|
||
parentsA.push(node);
|
||
}
|
||
node = nodeB;
|
||
while (node = node.parentNode) {
|
||
// 如果nodeA是nodeB的祖先节点
|
||
if (node === nodeA) {
|
||
// domUtils.POSITION_CONTAINS + domUtils.POSITION_PRECEDING
|
||
return 20;
|
||
}
|
||
parentsB.push(node);
|
||
}
|
||
parentsA.reverse();
|
||
parentsB.reverse();
|
||
if (parentsA[0] !== parentsB[0]) {
|
||
// domUtils.POSITION_DISCONNECTED
|
||
return 1;
|
||
}
|
||
var i = -1;
|
||
while (i++, parentsA[i] === parentsB[i]) {
|
||
}
|
||
nodeA = parentsA[i];
|
||
nodeB = parentsB[i];
|
||
while (nodeA = nodeA.nextSibling) {
|
||
if (nodeA === nodeB) {
|
||
// domUtils.POSITION_PRECEDING
|
||
return 4
|
||
}
|
||
}
|
||
// domUtils.POSITION_FOLLOWING
|
||
return 2;
|
||
},
|
||
|
||
/**
|
||
* 返回节点node在父节点中的索引位置
|
||
* @name getNodeIndex
|
||
* @grammar UM.dom.domUtils.getNodeIndex(node) => Number //索引值从0开始
|
||
*/
|
||
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是否在节点doc的树上,实质上是检测是否被doc包含
|
||
* @name inDoc
|
||
* @grammar UM.dom.domUtils.inDoc(node,doc) => true|false
|
||
*/
|
||
inDoc: function (node, doc) {
|
||
return domUtils.getPosition(node, doc) == 10;
|
||
},
|
||
/**
|
||
* 查找node节点的祖先节点
|
||
* @name findParent
|
||
* @grammar UM.dom.domUtils.findParent(node) => Element // 直接返回node节点的父节点
|
||
* @grammar UM.dom.domUtils.findParent(node,filterFn) => Element //filterFn为过滤函数,node作为参数,返回true时才会将node作为符合要求的节点返回
|
||
* @grammar UM.dom.domUtils.findParent(node,filterFn,includeSelf) => Element //includeSelf指定是否包含自身
|
||
*/
|
||
findParent: function (node, filterFn, includeSelf) {
|
||
if (node && !domUtils.isBody(node)) {
|
||
node = includeSelf ? node : node.parentNode;
|
||
while (node) {
|
||
if (!filterFn || filterFn(node) || domUtils.isBody(node)) {
|
||
return filterFn && !filterFn(node) && domUtils.isBody(node) ? null : node;
|
||
}
|
||
node = node.parentNode;
|
||
}
|
||
}
|
||
return null;
|
||
},
|
||
/**
|
||
* 通过tagName查找node节点的祖先节点
|
||
* @name findParentByTagName
|
||
* @grammar UM.dom.domUtils.findParentByTagName(node,tagNames) => Element //tagNames支持数组,区分大小写
|
||
* @grammar UM.dom.domUtils.findParentByTagName(node,tagNames,includeSelf) => Element //includeSelf指定是否包含自身
|
||
* @grammar UM.dom.domUtils.findParentByTagName(node,tagNames,includeSelf,excludeFn) => Element //excludeFn指定例外过滤条件,返回true时忽略该节点
|
||
*/
|
||
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的祖先节点集合
|
||
* @name findParents
|
||
* @grammar UM.dom.domUtils.findParents(node) => Array //返回一个祖先节点数组集合,不包含自身
|
||
* @grammar UM.dom.domUtils.findParents(node,includeSelf) => Array //返回一个祖先节点数组集合,includeSelf指定是否包含自身
|
||
* @grammar UM.dom.domUtils.findParents(node,includeSelf,filterFn) => Array //返回一个祖先节点数组集合,filterFn指定过滤条件,返回true的node将被选取
|
||
* @grammar UM.dom.domUtils.findParents(node,includeSelf,filterFn,closerFirst) => Array //返回一个祖先节点数组集合,closerFirst为true的话,node的直接父亲节点是数组的第0个
|
||
*/
|
||
findParents: function (node, includeSelf, filterFn, closerFirst) {
|
||
var parents = includeSelf && ( filterFn && filterFn(node) || !filterFn ) ? [node] : [];
|
||
while (node = domUtils.findParent(node, filterFn)) {
|
||
parents.push(node);
|
||
}
|
||
return closerFirst ? parents : parents.reverse();
|
||
},
|
||
|
||
/**
|
||
* 在节点node后面插入新节点newNode
|
||
* @name insertAfter
|
||
* @grammar UM.dom.domUtils.insertAfter(node,newNode) => newNode
|
||
*/
|
||
insertAfter: function (node, newNode) {
|
||
return node.parentNode.insertBefore(newNode, node.nextSibling);
|
||
},
|
||
|
||
/**
|
||
* 删除节点node,并根据keepChildren指定是否保留子节点
|
||
* @name remove
|
||
* @grammar UM.dom.domUtils.remove(node) => node
|
||
* @grammar UM.dom.domUtils.remove(node,keepChildren) => node
|
||
*/
|
||
remove: function (node, keepChildren) {
|
||
|
||
var parent = node.parentNode,
|
||
child;
|
||
if (parent) {
|
||
if (keepChildren && node.hasChildNodes()) {
|
||
while (child = node.firstChild) {
|
||
parent.insertBefore(child, node);
|
||
}
|
||
}
|
||
parent.removeChild(node);
|
||
}
|
||
return node;
|
||
},
|
||
|
||
|
||
/**
|
||
* 取得node节点的下一个兄弟节点, 如果该节点其后没有兄弟节点, 则递归查找其父节点之后的第一个兄弟节点,
|
||
* 直到找到满足条件的节点或者递归到BODY节点之后才会结束。
|
||
* @method getNextDomNode
|
||
* @param { Node } node 需要获取其后的兄弟节点的节点对象
|
||
* @return { Node | NULL } 如果找满足条件的节点, 则返回该节点, 否则返回NULL
|
||
* @example
|
||
* ```html
|
||
* <body>
|
||
* <div id="test">
|
||
* <span></span>
|
||
* </div>
|
||
* <i>xxx</i>
|
||
* </body>
|
||
* <script>
|
||
*
|
||
* //output: i节点
|
||
* console.log( UE.dom.domUtils.getNextDomNode( document.getElementById( "test" ) ) );
|
||
*
|
||
* </script>
|
||
* ```
|
||
* @example
|
||
* ```html
|
||
* <body>
|
||
* <div>
|
||
* <span></span>
|
||
* <i id="test">xxx</i>
|
||
* </div>
|
||
* <b>xxx</b>
|
||
* </body>
|
||
* <script>
|
||
*
|
||
* //由于id为test的i节点之后没有兄弟节点, 则查找其父节点(div)后面的兄弟节点
|
||
* //output: b节点
|
||
* console.log( UE.dom.domUtils.getNextDomNode( document.getElementById( "test" ) ) );
|
||
*
|
||
* </script>
|
||
* ```
|
||
*/
|
||
|
||
/**
|
||
* 取得node节点的下一个兄弟节点, 如果startFromChild的值为ture,则先获取其子节点,
|
||
* 如果有子节点则直接返回第一个子节点;如果没有子节点或者startFromChild的值为false,
|
||
* 则执行<a href="#UE.dom.domUtils.getNextDomNode(Node)">getNextDomNode(Node node)</a>的查找过程。
|
||
* @method getNextDomNode
|
||
* @param { Node } node 需要获取其后的兄弟节点的节点对象
|
||
* @param { Boolean } startFromChild 查找过程是否从其子节点开始
|
||
* @return { Node | NULL } 如果找满足条件的节点, 则返回该节点, 否则返回NULL
|
||
* @see UE.dom.domUtils.getNextDomNode(Node)
|
||
*/
|
||
getNextDomNode:function (node, startFromChild, filterFn, guard) {
|
||
return getDomNode(node, 'firstChild', 'nextSibling', startFromChild, filterFn, guard);
|
||
},
|
||
getPreDomNode:function (node, startFromChild, filterFn, guard) {
|
||
return getDomNode(node, 'lastChild', 'previousSibling', startFromChild, filterFn, guard);
|
||
},
|
||
|
||
/**
|
||
* 检测节点node是否属于bookmark节点
|
||
* @name isBookmarkNode
|
||
* @grammar UM.dom.domUtils.isBookmarkNode(node) => true|false
|
||
*/
|
||
isBookmarkNode: function (node) {
|
||
return node.nodeType == 1 && node.id && /^_baidu_bookmark_/i.test(node.id);
|
||
},
|
||
/**
|
||
* 获取节点node所在的window对象
|
||
* @name getWindow
|
||
* @grammar UM.dom.domUtils.getWindow(node) => window对象
|
||
*/
|
||
getWindow: function (node) {
|
||
var doc = node.ownerDocument || node;
|
||
return doc.defaultView || doc.parentWindow;
|
||
},
|
||
|
||
/**
|
||
* 获取离nodeA与nodeB最近的公共的祖先节点
|
||
* @method getCommonAncestor
|
||
* @param { Node } nodeA 第一个节点
|
||
* @param { Node } nodeB 第二个节点
|
||
* @remind 如果给定的两个节点是同一个节点, 将直接返回该节点。
|
||
* @return { Node | NULL } 如果未找到公共节点, 返回NULL, 否则返回最近的公共祖先节点。
|
||
* @example
|
||
* ```javascript
|
||
* var commonAncestor = UE.dom.domUtils.getCommonAncestor( document.body, document.body.firstChild );
|
||
* //output: true
|
||
* console.log( commonAncestor.tagName.toLowerCase() === 'body' );
|
||
* ```
|
||
*/
|
||
getCommonAncestor:function (nodeA, nodeB) {
|
||
if (nodeA === nodeB)
|
||
return nodeA;
|
||
var parentsA = [nodeA] , parentsB = [nodeB], parent = nodeA, i = -1;
|
||
while (parent = parent.parentNode) {
|
||
if (parent === nodeB) {
|
||
return parent;
|
||
}
|
||
parentsA.push(parent);
|
||
}
|
||
parent = nodeB;
|
||
while (parent = parent.parentNode) {
|
||
if (parent === nodeA)
|
||
return parent;
|
||
parentsB.push(parent);
|
||
}
|
||
parentsA.reverse();
|
||
parentsB.reverse();
|
||
while (i++, parentsA[i] === parentsB[i]) {
|
||
}
|
||
return i == 0 ? null : parentsA[i - 1];
|
||
|
||
},
|
||
/**
|
||
* 清除node节点左右连续为空的兄弟inline节点
|
||
* @method clearEmptySibling
|
||
* @param { Node } node 执行的节点对象, 如果该节点的左右连续的兄弟节点是空的inline节点,
|
||
* 则这些兄弟节点将被删除
|
||
* @grammar UE.dom.domUtils.clearEmptySibling(node,ignoreNext) //ignoreNext指定是否忽略右边空节点
|
||
* @grammar UE.dom.domUtils.clearEmptySibling(node,ignoreNext,ignorePre) //ignorePre指定是否忽略左边空节点
|
||
* @example
|
||
* ```html
|
||
* <body>
|
||
* <div></div>
|
||
* <span id="test"></span>
|
||
* <i></i>
|
||
* <b></b>
|
||
* <em>xxx</em>
|
||
* <span></span>
|
||
* </body>
|
||
* <script>
|
||
*
|
||
* UE.dom.domUtils.clearEmptySibling( document.getElementById( "test" ) );
|
||
*
|
||
* //output: <div></div><span id="test"></span><em>xxx</em><span></span>
|
||
* console.log( document.body.innerHTML );
|
||
*
|
||
* </script>
|
||
* ```
|
||
*/
|
||
|
||
/**
|
||
* 清除node节点左右连续为空的兄弟inline节点, 如果ignoreNext的值为true,
|
||
* 则忽略对右边兄弟节点的操作。
|
||
* @method clearEmptySibling
|
||
* @param { Node } node 执行的节点对象, 如果该节点的左右连续的兄弟节点是空的inline节点,
|
||
* @param { Boolean } ignoreNext 是否忽略忽略对右边的兄弟节点的操作
|
||
* 则这些兄弟节点将被删除
|
||
* @see UE.dom.domUtils.clearEmptySibling(Node)
|
||
*/
|
||
|
||
/**
|
||
* 清除node节点左右连续为空的兄弟inline节点, 如果ignoreNext的值为true,
|
||
* 则忽略对右边兄弟节点的操作, 如果ignorePre的值为true,则忽略对左边兄弟节点的操作。
|
||
* @method clearEmptySibling
|
||
* @param { Node } node 执行的节点对象, 如果该节点的左右连续的兄弟节点是空的inline节点,
|
||
* @param { Boolean } ignoreNext 是否忽略忽略对右边的兄弟节点的操作
|
||
* @param { Boolean } ignorePre 是否忽略忽略对左边的兄弟节点的操作
|
||
* 则这些兄弟节点将被删除
|
||
* @see UE.dom.domUtils.clearEmptySibling(Node)
|
||
*/
|
||
clearEmptySibling:function (node, ignoreNext, ignorePre) {
|
||
function clear(next, dir) {
|
||
var tmpNode;
|
||
while (next && !domUtils.isBookmarkNode(next) && (domUtils.isEmptyInlineElement(next)
|
||
//这里不能把空格算进来会吧空格干掉,出现文字间的空格丢掉了
|
||
|| !new RegExp('[^\t\n\r' + domUtils.fillChar + ']').test(next.nodeValue) )) {
|
||
tmpNode = next[dir];
|
||
domUtils.remove(next);
|
||
next = tmpNode;
|
||
}
|
||
}
|
||
!ignoreNext && clear(node.nextSibling, 'nextSibling');
|
||
!ignorePre && clear(node.previousSibling, 'previousSibling');
|
||
},
|
||
|
||
/**
|
||
* 将一个文本节点node拆分成两个文本节点,offset指定拆分位置
|
||
* @name split
|
||
* @grammar UM.dom.domUtils.split(node,offset) => TextNode //返回从切分位置开始的后一个文本节点
|
||
*/
|
||
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;
|
||
},
|
||
|
||
/**
|
||
* 检测节点node是否为空节点(包括空格、换行、占位符等字符)
|
||
* @name isWhitespace
|
||
* @grammar UM.dom.domUtils.isWhitespace(node) => true|false
|
||
*/
|
||
isWhitespace: function (node) {
|
||
return !new RegExp('[^ \t\n\r' + domUtils.fillChar + ']').test(node.nodeValue);
|
||
},
|
||
/**
|
||
* 获取元素element相对于viewport的位置坐标
|
||
* @name getXY
|
||
* @grammar UM.dom.domUtils.getXY(element) => Object //返回坐标对象{x:left,y:top}
|
||
*/
|
||
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};
|
||
},
|
||
/**
|
||
* 检查节点node是否是空inline节点
|
||
* @name isEmptyInlineElement
|
||
* @grammar UM.dom.domUtils.isEmptyInlineElement(node) => 1|0
|
||
* @example
|
||
* <b><i></i></b> => 1
|
||
* <b><i></i><u></u></b> => 1
|
||
* <b></b> => 1
|
||
* <b>xx<i></i></b> => 0
|
||
*/
|
||
isEmptyInlineElement: function (node) {
|
||
if (node.nodeType != 1 || !dtd.$removeEmpty[ node.tagName ]) {
|
||
return 0;
|
||
}
|
||
node = node.firstChild;
|
||
while (node) {
|
||
//如果是创建的bookmark就跳过
|
||
if (domUtils.isBookmarkNode(node)) {
|
||
return 0;
|
||
}
|
||
if (node.nodeType == 1 && !domUtils.isEmptyInlineElement(node) ||
|
||
node.nodeType == 3 && !domUtils.isWhitespace(node)
|
||
) {
|
||
return 0;
|
||
}
|
||
node = node.nextSibling;
|
||
}
|
||
return 1;
|
||
|
||
},
|
||
|
||
|
||
/**
|
||
* 检查节点node是否为块元素
|
||
* @name isBlockElm
|
||
* @grammar UM.dom.domUtils.isBlockElm(node) => true|false
|
||
*/
|
||
isBlockElm: function (node) {
|
||
return node.nodeType == 1 && (dtd.$block[node.tagName] || styleBlock[domUtils.getComputedStyle(node, 'display')]) && !dtd.$nonChild[node.tagName];
|
||
},
|
||
|
||
|
||
/**
|
||
* 原生方法getElementsByTagName的封装
|
||
* @name getElementsByTagName
|
||
* @grammar UM.dom.domUtils.getElementsByTagName(node,tagName) => Array //节点集合数组
|
||
*/
|
||
getElementsByTagName: function (node, name, filter) {
|
||
if (filter && utils.isString(filter)) {
|
||
var className = filter;
|
||
filter = function (node) {
|
||
var result = false;
|
||
$.each(utils.trim(className).replace(/[ ]{2,}/g, ' ').split(' '), function (i, v) {
|
||
if ($(node).hasClass(v)) {
|
||
result = true;
|
||
return false;
|
||
}
|
||
})
|
||
return result;
|
||
}
|
||
}
|
||
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及其子节点不会被选中
|
||
* @name unSelectable
|
||
* @grammar UM.dom.domUtils.unSelectable(node)
|
||
*/
|
||
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上的属性attrNames,attrNames为属性名称数组
|
||
* @name removeAttributes
|
||
* @grammar UM.dom.domUtils.removeAttributes(node,attrNames)
|
||
* @example
|
||
* //Before remove
|
||
* <span style="font-size:14px;" id="test" name="followMe">xxxxx</span>
|
||
* //Remove
|
||
* UM.dom.domUtils.removeAttributes(node,["id","name"]);
|
||
* //After remove
|
||
* <span style="font-size:14px;">xxxxx</span>
|
||
*/
|
||
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 = '';
|
||
!browser.ie && node.removeAttributeNode(node.getAttributeNode('style'))
|
||
}
|
||
node.removeAttribute(ci);
|
||
}
|
||
},
|
||
/**
|
||
* 在doc下创建一个标签名为tag,属性为attrs的元素
|
||
* @name createElement
|
||
* @grammar UM.dom.domUtils.createElement(doc,tag,attrs) => Node //返回创建的节点
|
||
*/
|
||
createElement: function (doc, tag, attrs) {
|
||
return domUtils.setAttributes(doc.createElement(tag), attrs)
|
||
},
|
||
/**
|
||
* 为节点node添加属性attrs,attrs为属性键值对
|
||
* @name setAttributes
|
||
* @grammar UM.dom.domUtils.setAttributes(node,attrs) => node
|
||
*/
|
||
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的计算样式
|
||
* @name getComputedStyle
|
||
* @grammar UM.dom.domUtils.getComputedStyle(element,styleName) => String //返回对应样式名称的样式值
|
||
* @example
|
||
* getComputedStyle(document.body,"font-size") => "15px"
|
||
* getComputedStyle(form,"color") => "#ffccdd"
|
||
*/
|
||
getComputedStyle: function (element, styleName) {
|
||
return utils.transUnitToPx(utils.fixColor(styleName, $(element).css(styleName)));
|
||
},
|
||
|
||
/**
|
||
* 阻止事件默认行为
|
||
* @param {Event} evt 需要组织的事件对象
|
||
*/
|
||
preventDefault: function (evt) {
|
||
evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);
|
||
},
|
||
|
||
/**
|
||
* 删除元素element指定的样式
|
||
* @method removeStyle
|
||
* @param { Element } element 需要删除样式的元素
|
||
* @param { String } styleName 需要删除的样式名
|
||
* @example
|
||
* ```html
|
||
* <span id="test" style="color: red; background: blue;"></span>
|
||
*
|
||
* <script>
|
||
*
|
||
* var testNode = document.getElementById("test");
|
||
*
|
||
* UE.dom.domUtils.removeStyle( testNode, 'color' );
|
||
*
|
||
* //output: background: blue;
|
||
* console.log( testNode.style.cssText );
|
||
*
|
||
* </script>
|
||
* ```
|
||
*/
|
||
removeStyle:function (element, name) {
|
||
if(browser.ie ){
|
||
//针对color先单独处理一下
|
||
if(name == 'color'){
|
||
name = '(^|;)' + name;
|
||
}
|
||
element.style.cssText = element.style.cssText.replace(new RegExp(name + '[^:]*:[^;]+;?','ig'),'')
|
||
}else{
|
||
if (element.style.removeProperty) {
|
||
element.style.removeProperty (name);
|
||
}else {
|
||
element.style.removeAttribute (utils.cssStyleToDomStyle(name));
|
||
}
|
||
}
|
||
|
||
|
||
if (!element.style.cssText) {
|
||
domUtils.removeAttributes(element, ['style']);
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 获取元素element的某个样式值
|
||
* @name getStyle
|
||
* @grammar UM.dom.domUtils.getStyle(element,name) => String
|
||
*/
|
||
getStyle: function (element, name) {
|
||
var value = element.style[ utils.cssStyleToDomStyle(name) ];
|
||
return utils.fixColor(name, value);
|
||
},
|
||
/**
|
||
* 为元素element设置样式属性值
|
||
* @name setStyle
|
||
* @grammar UM.dom.domUtils.setStyle(element,name,value)
|
||
*/
|
||
setStyle: function (element, name, value) {
|
||
element.style[utils.cssStyleToDomStyle(name)] = value;
|
||
if (!utils.trim(element.style.cssText)) {
|
||
this.removeAttributes(element, 'style')
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 删除_moz_dirty属性
|
||
* @function
|
||
*/
|
||
removeDirtyAttr: function (node) {
|
||
for (var i = 0, ci, nodes = node.getElementsByTagName('*'); ci = nodes[i++];) {
|
||
ci.removeAttribute('_moz_dirty');
|
||
}
|
||
node.removeAttribute('_moz_dirty');
|
||
},
|
||
/**
|
||
* 返回子节点的数量
|
||
* @function
|
||
* @param {Node} node 父节点
|
||
* @param {Function} fn 过滤子节点的规则,若为空,则得到所有子节点的数量
|
||
* @return {Number} 符合条件子节点的数量
|
||
*/
|
||
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;
|
||
},
|
||
|
||
/**
|
||
* 判断是否为空节点
|
||
* @function
|
||
* @param {Node} node 节点
|
||
* @return {Boolean} 是否为空节点
|
||
*/
|
||
isEmptyNode: function (node) {
|
||
return !node.firstChild || domUtils.getChildCount(node, function (node) {
|
||
return !domUtils.isBr(node) && !domUtils.isBookmarkNode(node) && !domUtils.isWhitespace(node)
|
||
}) == 0
|
||
},
|
||
|
||
/**
|
||
* 判断节点是否为br
|
||
* @function
|
||
* @param {Node} node 节点
|
||
*/
|
||
isBr: function (node) {
|
||
return node.nodeType == 1 && node.tagName == 'BR';
|
||
},
|
||
isFillChar: function (node, isInStart) {
|
||
return node.nodeType == 3 && !node.nodeValue.replace(new RegExp((isInStart ? '^' : '' ) + domUtils.fillChar), '').length
|
||
},
|
||
|
||
isEmptyBlock: function (node, reg) {
|
||
if (node.nodeType != 1)
|
||
return 0;
|
||
reg = reg || new RegExp('[ \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;
|
||
},
|
||
|
||
//判断是否是编辑器自定义的参数
|
||
isCustomeNode: function (node) {
|
||
return node.nodeType == 1 && node.getAttribute('_ue_custom_node_');
|
||
},
|
||
fillNode: function (doc, node) {
|
||
var tmpNode = browser.ie ? doc.createTextNode(domUtils.fillChar) : doc.createElement('br');
|
||
node.innerHTML = '';
|
||
node.appendChild(tmpNode);
|
||
},
|
||
isBoundaryNode: function (node, dir) {
|
||
var tmp;
|
||
while (!domUtils.isBody(node)) {
|
||
tmp = node;
|
||
node = node.parentNode;
|
||
if (tmp !== node[dir]) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
},
|
||
isFillChar: function (node, isInStart) {
|
||
return node.nodeType == 3 && !node.nodeValue.replace(new RegExp((isInStart ? '^' : '' ) + domUtils.fillChar), '').length
|
||
},
|
||
isBody: function(node){
|
||
return $(node).hasClass('edui-body-container');
|
||
}
|
||
};
|
||
var fillCharReg = new RegExp(domUtils.fillChar, 'g');
|
||
///import editor.js
|
||
///import core/utils.js
|
||
///import core/browser.js
|
||
///import core/dom/dom.js
|
||
///import core/dom/dtd.js
|
||
///import core/dom/domUtils.js
|
||
/**
|
||
* @file
|
||
* @name UM.dom.Range
|
||
* @anthor zhanyi
|
||
* @short Range
|
||
* @import editor.js,core/utils.js,core/browser.js,core/dom/domUtils.js,core/dom/dtd.js
|
||
* @desc Range范围实现类,本类是UEditor底层核心类,统一w3cRange和ieRange之间的差异,包括接口和属性
|
||
*/
|
||
(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;
|
||
}
|
||
|
||
|
||
/**
|
||
* @name Range
|
||
* @grammar new UM.dom.Range(document) => Range 实例
|
||
* @desc 创建一个跟document绑定的空的Range实例
|
||
* - ***startContainer*** 开始边界的容器节点,可以是elementNode或者是textNode
|
||
* - ***startOffset*** 容器节点中的偏移量,如果是elementNode就是childNodes中的第几个,如果是textNode就是nodeValue的第几个字符
|
||
* - ***endContainer*** 结束边界的容器节点,可以是elementNode或者是textNode
|
||
* - ***endOffset*** 容器节点中的偏移量,如果是elementNode就是childNodes中的第几个,如果是textNode就是nodeValue的第几个字符
|
||
* - ***document*** 跟range关联的document对象
|
||
* - ***collapsed*** 是否是闭合状态
|
||
*/
|
||
var Range = dom.Range = function (document,body) {
|
||
var me = this;
|
||
me.startContainer =
|
||
me.startOffset =
|
||
me.endContainer =
|
||
me.endOffset = null;
|
||
me.document = document;
|
||
me.collapsed = true;
|
||
me.body = body;
|
||
};
|
||
|
||
/**
|
||
* 删除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;
|
||
}
|
||
}
|
||
|
||
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;
|
||
}
|
||
Range.prototype = {
|
||
/**
|
||
* @name deleteContents
|
||
* @grammar range.deleteContents() => Range
|
||
* @desc 删除当前选区范围中的所有内容并返回range实例,这时的range已经变成了闭合状态
|
||
* @example
|
||
* DOM Element :
|
||
* <b>x<i>x[x<i>xx]x</b>
|
||
* //执行方法后
|
||
* <b>x<i>x<i>|x</b>
|
||
* 注意range改变了
|
||
* range.startContainer => b
|
||
* range.startOffset => 2
|
||
* range.endContainer => b
|
||
* range.endOffset => 2
|
||
* range.collapsed => true
|
||
*/
|
||
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;
|
||
},
|
||
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;
|
||
},
|
||
/**
|
||
* @name setStart
|
||
* @grammar range.setStart(node,offset) => Range
|
||
* @desc 设置range的开始位置位于node节点内,偏移量为offset
|
||
* 如果node是elementNode那offset指的是childNodes中的第几个,如果是textNode那offset指的是nodeValue的第几个字符
|
||
*/
|
||
setStart:function (node, offset) {
|
||
return setEndPoint(true, node, offset, this);
|
||
},
|
||
/**
|
||
* 设置range的结束位置位于node节点,偏移量为offset
|
||
* 如果node是elementNode那offset指的是childNodes中的第几个,如果是textNode那offset指的是nodeValue的第几个字符
|
||
* @name setEnd
|
||
* @grammar range.setEnd(node,offset) => Range
|
||
*/
|
||
setEnd:function (node, offset) {
|
||
return setEndPoint(false, node, offset, this);
|
||
},
|
||
/**
|
||
* 将Range开始位置设置到node节点之后
|
||
* @name setStartAfter
|
||
* @grammar range.setStartAfter(node) => Range
|
||
* @example
|
||
* <b>xx<i>x|x</i>x</b>
|
||
* 执行setStartAfter(i)后
|
||
* range.startContainer =>b
|
||
* range.startOffset =>2
|
||
*/
|
||
setStartAfter:function (node) {
|
||
return this.setStart(node.parentNode, domUtils.getNodeIndex(node) + 1);
|
||
},
|
||
/**
|
||
* 将Range开始位置设置到node节点之前
|
||
* @name setStartBefore
|
||
* @grammar range.setStartBefore(node) => Range
|
||
* @example
|
||
* <b>xx<i>x|x</i>x</b>
|
||
* 执行setStartBefore(i)后
|
||
* range.startContainer =>b
|
||
* range.startOffset =>1
|
||
*/
|
||
setStartBefore:function (node) {
|
||
return this.setStart(node.parentNode, domUtils.getNodeIndex(node));
|
||
},
|
||
/**
|
||
* 将Range结束位置设置到node节点之后
|
||
* @name setEndAfter
|
||
* @grammar range.setEndAfter(node) => Range
|
||
* @example
|
||
* <b>xx<i>x|x</i>x</b>
|
||
* setEndAfter(i)后
|
||
* range.endContainer =>b
|
||
* range.endtOffset =>2
|
||
*/
|
||
setEndAfter:function (node) {
|
||
return this.setEnd(node.parentNode, domUtils.getNodeIndex(node) + 1);
|
||
},
|
||
/**
|
||
* 将Range结束位置设置到node节点之前
|
||
* @name setEndBefore
|
||
* @grammar range.setEndBefore(node) => Range
|
||
* @example
|
||
* <b>xx<i>x|x</i>x</b>
|
||
* 执行setEndBefore(i)后
|
||
* range.endContainer =>b
|
||
* range.endtOffset =>1
|
||
*/
|
||
setEndBefore:function (node) {
|
||
return this.setEnd(node.parentNode, domUtils.getNodeIndex(node));
|
||
},
|
||
/**
|
||
* 将Range开始位置设置到node节点内的开始位置
|
||
* @name setStartAtFirst
|
||
* @grammar range.setStartAtFirst(node) => Range
|
||
*/
|
||
setStartAtFirst:function (node) {
|
||
return this.setStart(node, 0);
|
||
},
|
||
/**
|
||
* 将Range开始位置设置到node节点内的结束位置
|
||
* @name setStartAtLast
|
||
* @grammar range.setStartAtLast(node) => Range
|
||
*/
|
||
setStartAtLast:function (node) {
|
||
return this.setStart(node, node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length);
|
||
},
|
||
/**
|
||
* 将Range结束位置设置到node节点内的开始位置
|
||
* @name setEndAtFirst
|
||
* @grammar range.setEndAtFirst(node) => Range
|
||
*/
|
||
setEndAtFirst:function (node) {
|
||
return this.setEnd(node, 0);
|
||
},
|
||
/**
|
||
* 将Range结束位置设置到node节点内的结束位置
|
||
* @name setEndAtLast
|
||
* @grammar range.setEndAtLast(node) => Range
|
||
*/
|
||
setEndAtLast:function (node) {
|
||
return this.setEnd(node, node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length);
|
||
},
|
||
|
||
/**
|
||
* 选中完整的指定节点,并返回包含该节点的range
|
||
* @name selectNode
|
||
* @grammar range.selectNode(node) => Range
|
||
*/
|
||
selectNode:function (node) {
|
||
return this.setStartBefore(node).setEndAfter(node);
|
||
},
|
||
/**
|
||
* 选中node内部的所有节点,并返回对应的range
|
||
* @name selectNodeContents
|
||
* @grammar range.selectNodeContents(node) => Range
|
||
* @example
|
||
* <b>xx[x<i>xxx</i>]xxx</b>
|
||
* 执行后
|
||
* <b>[xxx<i>xxx</i>xxx]</b>
|
||
* range.startContainer =>b
|
||
* range.startOffset =>0
|
||
* range.endContainer =>b
|
||
* range.endOffset =>3
|
||
*/
|
||
selectNodeContents:function (node) {
|
||
return this.setStart(node, 0).setEndAtLast(node);
|
||
},
|
||
|
||
/**
|
||
* 克隆一个新的range对象
|
||
* @name cloneRange
|
||
* @grammar range.cloneRange() => Range
|
||
*/
|
||
cloneRange:function () {
|
||
var me = this;
|
||
return new Range(me.document).setStart(me.startContainer, me.startOffset).setEnd(me.endContainer, me.endOffset);
|
||
|
||
},
|
||
|
||
/**
|
||
* 让选区闭合到尾部,若toStart为真,则闭合到头部
|
||
* @name collapse
|
||
* @grammar range.collapse() => Range
|
||
* @grammar range.collapse(true) => Range //闭合选区到头部
|
||
*/
|
||
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的边界,使其"收缩"到最小的位置
|
||
* @name shrinkBoundary
|
||
* @grammar range.shrinkBoundary() => Range //range开始位置和结束位置都调整,参见<code><a href="#adjustmentboundary">adjustmentBoundary</a></code>
|
||
* @grammar range.shrinkBoundary(true) => Range //仅调整开始位置,忽略结束位置
|
||
* @example
|
||
* <b>xx[</b>xxxxx] ==> <b>xx</b>[xxxxx]
|
||
* <b>x[xx</b><i>]xxx</i> ==> <b>x[xx]</b><i>xxx</i>
|
||
* [<b><i>xxxx</i>xxxxxxx</b>] ==> <b><i>[xxxx</i>xxxxxxx]</b>
|
||
*/
|
||
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;
|
||
},
|
||
|
||
/**
|
||
* 调整边界容器,如果是textNode,就调整到elementNode上
|
||
* @name trimBoundary
|
||
* @grammar range.trimBoundary([ignoreEnd]) => Range //true忽略结束边界
|
||
* @example
|
||
* DOM Element :
|
||
* <b>|xxx</b>
|
||
* startContainer = xxx; startOffset = 0
|
||
* //执行后本方法后
|
||
* startContainer = <b>; startOffset = 0
|
||
* @example
|
||
* Dom Element :
|
||
* <b>xx|x</b>
|
||
* startContainer = xxx; startOffset = 2
|
||
* //执行本方法后,xxx被实实在在地切分成两个TextNode
|
||
* startContainer = <b>; startOffset = 1
|
||
*/
|
||
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;
|
||
},
|
||
/**
|
||
* 如果选区在文本的边界上,就扩展选区到文本的父节点上
|
||
* @name txtToElmBoundary
|
||
* @example
|
||
* Dom Element :
|
||
* <b> |xxx</b>
|
||
* startContainer = xxx; startOffset = 0
|
||
* //本方法执行后
|
||
* startContainer = <b>; startOffset = 0
|
||
* @example
|
||
* Dom Element :
|
||
* <b> xxx| </b>
|
||
* startContainer = xxx; startOffset = 3
|
||
* //本方法执行后
|
||
* startContainer = <b>; startOffset = 1
|
||
*/
|
||
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;
|
||
},
|
||
|
||
/**
|
||
* 在当前选区的开始位置前插入一个节点或者fragment,range的开始位置会在插入节点的前边
|
||
* @name insertNode
|
||
* @grammar range.insertNode(node) => Range //node可以是textNode,elementNode,fragment
|
||
* @example
|
||
* Range :
|
||
* xxx[x<p>xxxx</p>xxxx]x<p>sdfsdf</p>
|
||
* 待插入Node :
|
||
* <p>ssss</p>
|
||
* 执行本方法后的Range :
|
||
* xxx[<p>ssss</p>x<p>xxxx</p>xxxx]x<p>sdfsdf</p>
|
||
*/
|
||
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);
|
||
},
|
||
/**
|
||
* 设置光标闭合位置,toEnd设置为true时光标将闭合到选区的结尾
|
||
* @name setCursor
|
||
* @grammar range.setCursor([toEnd]) => Range //toEnd为true时,光标闭合到选区的末尾
|
||
*/
|
||
setCursor:function (toEnd, noFillData) {
|
||
return this.collapse(!toEnd).select(noFillData);
|
||
},
|
||
/**
|
||
* 创建当前range的一个书签,记录下当前range的位置,方便当dom树改变时,还能找回原来的选区位置
|
||
* @name createBookmark
|
||
* @grammar range.createBookmark([serialize]) => Object //{start:开始标记,end:结束标记,id:serialize} serialize为真时,开始结束标记是插入节点的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
|
||
}
|
||
},
|
||
/**
|
||
* 移动边界到书签位置,并删除插入的书签节点
|
||
* @name moveToBookmark
|
||
* @grammar range.moveToBookmark(bookmark) => Range //让当前的range选到给定bookmark的位置,bookmark对象是由range.createBookmark创建的
|
||
*/
|
||
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的边界,使其"缩小"到最合适的位置
|
||
* @name adjustmentBoundary
|
||
* @grammar range.adjustmentBoundary() => Range //参见<code><a href="#shrinkboundary">shrinkBoundary</a></code>
|
||
* @example
|
||
* <b>xx[</b>xxxxx] ==> <b>xx</b>[xxxxx]
|
||
* <b>x[xx</b><i>]xxx</i> ==> <b>x[xx</b>]<i>xxx</i>
|
||
*/
|
||
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;
|
||
},
|
||
|
||
/**
|
||
* 得到一个自闭合的节点,常用于获取自闭和的节点,例如图片节点
|
||
* @name getClosedNode
|
||
* @grammar range.getClosedNode() => node|null
|
||
* @example
|
||
* <b>xxxx[<img />]xxx</b>
|
||
*/
|
||
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选中内容节点(在页面上表现为反白显示)
|
||
* @name select
|
||
* @grammar range.select(); => Range
|
||
*/
|
||
select:browser.ie ? function (noFillData, textRange) {
|
||
var nativeRange;
|
||
if (!this.collapsed)
|
||
this.shrinkBoundary();
|
||
var node = this.getClosedNode();
|
||
if (node && !textRange) {
|
||
try {
|
||
nativeRange = this.document.body.createControlRange();
|
||
nativeRange.addElement(node);
|
||
nativeRange.select();
|
||
} catch (e) {}
|
||
return this;
|
||
}
|
||
var bookmark = this.createBookmark(),
|
||
start = bookmark.start,
|
||
end;
|
||
nativeRange = this.document.body.createTextRange();
|
||
nativeRange.moveToElementText(start);
|
||
nativeRange.moveStart('character', 1);
|
||
if (!this.collapsed) {
|
||
var nativeRangeEnd = this.document.body.createTextRange();
|
||
end = bookmark.end;
|
||
nativeRangeEnd.moveToElementText(end);
|
||
nativeRange.setEndPoint('EndToEnd', nativeRangeEnd);
|
||
} else {
|
||
if (!noFillData && this.startContainer.nodeType != 3) {
|
||
//使用<span>|x<span>固定住光标
|
||
var tmpText = this.document.createTextNode(fillChar),
|
||
tmp = this.document.createElement('span');
|
||
tmp.appendChild(this.document.createTextNode(fillChar));
|
||
start.parentNode.insertBefore(tmp, start);
|
||
start.parentNode.insertBefore(tmpText, start);
|
||
//当点b,i,u时,不能清除i上边的b
|
||
removeFillData(this.document, tmpText);
|
||
fillData = tmpText;
|
||
mergeSibling(tmp, 'previousSibling');
|
||
mergeSibling(start, 'nextSibling');
|
||
nativeRange.moveStart('character', -1);
|
||
nativeRange.collapse(true);
|
||
}
|
||
}
|
||
this.moveToBookmark(bookmark);
|
||
tmp && domUtils.remove(tmp);
|
||
//IE在隐藏状态下不支持range操作,catch一下
|
||
try {
|
||
nativeRange.select();
|
||
} catch (e) {
|
||
}
|
||
return this;
|
||
} : function (notInsertFillData) {
|
||
function checkOffset(rng){
|
||
|
||
function check(node,offset,dir){
|
||
if(node.nodeType == 3 && node.nodeValue.length < offset){
|
||
rng[dir + 'Offset'] = node.nodeValue.length
|
||
}
|
||
}
|
||
check(rng.startContainer,rng.startOffset,'start');
|
||
check(rng.endContainer,rng.endOffset,'end');
|
||
}
|
||
var win = domUtils.getWindow(this.document),
|
||
sel = win.getSelection(),
|
||
txtNode;
|
||
//FF下关闭自动长高时滚动条在关闭dialog时会跳
|
||
//ff下如果不body.focus将不能定位闭合光标到编辑器内
|
||
browser.gecko ? this.body.focus() : win.focus();
|
||
if (sel) {
|
||
sel.removeAllRanges();
|
||
// trace:870 chrome/safari后边是br对于闭合得range不能定位 所以去掉了判断
|
||
// this.startContainer.nodeType != 3 &&! ((child = this.startContainer.childNodes[this.startOffset]) && child.nodeType == 1 && child.tagName == 'BR'
|
||
if (this.collapsed && !notInsertFillData) {
|
||
// //opear如果没有节点接着,原生的不能够定位,不能在body的第一级插入空白节点
|
||
// if (notInsertFillData && browser.opera && !domUtils.isBody(this.startContainer) && this.startContainer.nodeType == 1) {
|
||
// var tmp = this.document.createTextNode('');
|
||
// this.insertNode(tmp).setStart(tmp, 0).collapse(true);
|
||
// }
|
||
//
|
||
//处理光标落在文本节点的情况
|
||
//处理以下的情况
|
||
//<b>|xxxx</b>
|
||
//<b>xxxx</b>|xxxx
|
||
//xxxx<b>|</b>
|
||
var start = this.startContainer,child = start;
|
||
if(start.nodeType == 1){
|
||
child = start.childNodes[this.startOffset];
|
||
|
||
}
|
||
if( !(start.nodeType == 3 && this.startOffset) &&
|
||
(child ?
|
||
(!child.previousSibling || child.previousSibling.nodeType != 3)
|
||
:
|
||
(!start.lastChild || start.lastChild.nodeType != 3)
|
||
)
|
||
){
|
||
txtNode = this.document.createTextNode(fillChar);
|
||
//跟着前边走
|
||
this.insertNode(txtNode);
|
||
removeFillData(this.document, txtNode);
|
||
mergeSibling(txtNode, 'previousSibling');
|
||
mergeSibling(txtNode, 'nextSibling');
|
||
fillData = txtNode;
|
||
this.setStart(txtNode, browser.webkit ? 1 : 0).collapse(true);
|
||
}
|
||
}
|
||
var nativeRange = this.document.createRange();
|
||
if(this.collapsed && browser.opera && this.startContainer.nodeType == 1){
|
||
var child = this.startContainer.childNodes[this.startOffset];
|
||
if(!child){
|
||
//往前靠拢
|
||
child = this.startContainer.lastChild;
|
||
if( child && domUtils.isBr(child)){
|
||
this.setStartBefore(child).collapse(true);
|
||
}
|
||
}else{
|
||
//向后靠拢
|
||
while(child && domUtils.isBlockElm(child)){
|
||
if(child.nodeType == 1 && child.childNodes[0]){
|
||
child = child.childNodes[0]
|
||
}else{
|
||
break;
|
||
}
|
||
}
|
||
child && this.setStartBefore(child).collapse(true)
|
||
}
|
||
|
||
}
|
||
//是createAddress最后一位算的不准,现在这里进行微调
|
||
checkOffset(this);
|
||
nativeRange.setStart(this.startContainer, this.startOffset);
|
||
nativeRange.setEnd(this.endContainer, this.endOffset);
|
||
sel.addRange(nativeRange);
|
||
}
|
||
return this;
|
||
},
|
||
|
||
|
||
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;
|
||
},
|
||
moveToAddress : function(addr,ignoreEnd){
|
||
var me = this;
|
||
function getNode(address,isStart){
|
||
var tmpNode = me.body,
|
||
parentNode,offset;
|
||
for(var i= 0,ci,l=address.length;i<l;i++){
|
||
ci = address[i];
|
||
parentNode = tmpNode;
|
||
tmpNode = tmpNode.childNodes[ci];
|
||
if(!tmpNode){
|
||
offset = ci;
|
||
break;
|
||
}
|
||
}
|
||
if(isStart){
|
||
if(tmpNode){
|
||
me.setStartBefore(tmpNode)
|
||
}else{
|
||
me.setStart(parentNode,offset)
|
||
}
|
||
}else{
|
||
if(tmpNode){
|
||
me.setEndBefore(tmpNode)
|
||
}else{
|
||
me.setEnd(parentNode,offset)
|
||
}
|
||
}
|
||
}
|
||
getNode(addr.startAddress,true);
|
||
!ignoreEnd && addr.endAddress && getNode(addr.endAddress);
|
||
return me;
|
||
},
|
||
equals : function(rng){
|
||
for(var p in this){
|
||
if(this.hasOwnProperty(p)){
|
||
if(this[p] !== rng[p])
|
||
return false
|
||
}
|
||
}
|
||
return true;
|
||
|
||
},
|
||
scrollIntoView : function(){
|
||
var $span = $('<span style="padding:0;margin:0;display:block;border:0"> </span>');
|
||
this.cloneRange().insertNode($span.get(0));
|
||
var winScrollTop = $(window).scrollTop(),
|
||
winHeight = $(window).height(),
|
||
spanTop = $span.offset().top;
|
||
if(spanTop < winScrollTop-winHeight || spanTop > winScrollTop + winHeight ){
|
||
if(spanTop > winScrollTop + winHeight){
|
||
window.scrollTo(0,spanTop - winHeight + $span.height())
|
||
}else{
|
||
window.scrollTo(0,winScrollTop - spanTop)
|
||
}
|
||
|
||
}
|
||
$span.remove();
|
||
},
|
||
getOffset : function(){
|
||
var bk = this.createBookmark();
|
||
var offset = $(bk.start).css('display','inline-block').offset();
|
||
this.moveToBookmark(bk);
|
||
return offset
|
||
}
|
||
};
|
||
})();
|
||
///import editor.js
|
||
///import core/browser.js
|
||
///import core/dom/dom.js
|
||
///import core/dom/dtd.js
|
||
///import core/dom/domUtils.js
|
||
///import core/dom/Range.js
|
||
/**
|
||
* @class UM.dom.Selection 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,txtRange ) {
|
||
var ieRange;
|
||
//ie下有可能报错
|
||
try {
|
||
ieRange = sel.getNative(txtRange).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,body ) {
|
||
var me = this;
|
||
me.document = doc;
|
||
me.body = body;
|
||
if ( browser.ie9below ) {
|
||
$( body).on('beforedeactivate', function () {
|
||
me._bakIERange = me.getIERange();
|
||
} ).on('activate', function () {
|
||
try {
|
||
var ieNativRng = _getIERange( me );
|
||
if ( (!ieNativRng || !me.rangeInBody(ieNativRng)) && me._bakIERange ) {
|
||
me._bakIERange.select();
|
||
}
|
||
} catch ( ex ) {
|
||
}
|
||
me._bakIERange = null;
|
||
} );
|
||
}
|
||
};
|
||
|
||
Selection.prototype = {
|
||
hasNativeRange : function(){
|
||
var rng;
|
||
if(!browser.ie || browser.ie9above){
|
||
var nativeSel = this.getNative();
|
||
if(!nativeSel.rangeCount){
|
||
return false;
|
||
}
|
||
rng = nativeSel.getRangeAt(0);
|
||
}else{
|
||
rng = _getIERange(this);
|
||
}
|
||
return this.rangeInBody(rng);
|
||
},
|
||
/**
|
||
* 获取原生seleciton对象
|
||
* @public
|
||
* @function
|
||
* @name UM.dom.Selection.getNative
|
||
* @return {Selection} 获得selection对象
|
||
*/
|
||
getNative:function (txtRange) {
|
||
var doc = this.document;
|
||
try {
|
||
return !doc ? null : browser.ie9below || txtRange? doc.selection : domUtils.getWindow( doc ).getSelection();
|
||
} catch ( e ) {
|
||
return null;
|
||
}
|
||
},
|
||
/**
|
||
* 获得ieRange
|
||
* @public
|
||
* @function
|
||
* @name UM.dom.Selection.getIERange
|
||
* @return {ieRange} 返回ie原生的Range
|
||
*/
|
||
getIERange:function (txtRange) {
|
||
var ieRange = _getIERange( this,txtRange );
|
||
if ( !ieRange || !this.rangeInBody(ieRange,txtRange)) {
|
||
if ( this._bakIERange ) {
|
||
return this._bakIERange;
|
||
}
|
||
}
|
||
return ieRange;
|
||
},
|
||
rangeInBody : function(rng,txtRange){
|
||
var node = browser.ie9below || txtRange ? rng.item ? rng.item() : rng.parentElement() : rng.startContainer;
|
||
|
||
return node === this.body || domUtils.inDoc(node,this.body);
|
||
},
|
||
/**
|
||
* 缓存当前选区的range和选区的开始节点
|
||
* @public
|
||
* @function
|
||
* @name UM.dom.Selection.cache
|
||
*/
|
||
cache:function () {
|
||
this.clear();
|
||
this._cachedRange = this.getRange();
|
||
this._cachedStartElement = this.getStart();
|
||
this._cachedStartElementPath = this.getStartElementPath();
|
||
},
|
||
|
||
getStartElementPath:function () {
|
||
if ( this._cachedStartElementPath ) {
|
||
return this._cachedStartElementPath;
|
||
}
|
||
var start = this.getStart();
|
||
if ( start ) {
|
||
return domUtils.findParents( start, true, null, true )
|
||
}
|
||
return [];
|
||
},
|
||
/**
|
||
* 清空缓存
|
||
* @public
|
||
* @function
|
||
* @name UM.dom.Selection.clear
|
||
*/
|
||
clear:function () {
|
||
this._cachedStartElementPath = this._cachedRange = this._cachedStartElement = null;
|
||
},
|
||
/**
|
||
* 编辑器是否得到了选区
|
||
*/
|
||
isFocus:function () {
|
||
return this.hasNativeRange()
|
||
|
||
},
|
||
/**
|
||
* 获取选区对应的Range
|
||
* @public
|
||
* @function
|
||
* @name UM.dom.Selection.getRange
|
||
* @returns {UM.dom.Range} 得到Range对象
|
||
*/
|
||
getRange:function () {
|
||
var me = this;
|
||
function optimze( range ) {
|
||
var child = me.body.firstChild,
|
||
collapsed = range.collapsed;
|
||
while ( child && child.firstChild ) {
|
||
range.setStart( child, 0 );
|
||
child = child.firstChild;
|
||
}
|
||
if ( !range.startContainer ) {
|
||
range.setStart( me.body, 0 )
|
||
}
|
||
if ( collapsed ) {
|
||
range.collapse( true );
|
||
}
|
||
}
|
||
|
||
if ( me._cachedRange != null ) {
|
||
return this._cachedRange;
|
||
}
|
||
var range = new dom.Range( me.document,me.body );
|
||
if ( browser.ie9below ) {
|
||
var nativeRange = me.getIERange();
|
||
if ( nativeRange && this.rangeInBody(nativeRange)) {
|
||
|
||
try{
|
||
transformIERangeToRange( nativeRange, range );
|
||
}catch(e){
|
||
optimze( range );
|
||
}
|
||
|
||
} else {
|
||
optimze( range );
|
||
}
|
||
} else {
|
||
var sel = me.getNative();
|
||
if ( sel && sel.rangeCount && me.rangeInBody(sel.getRangeAt( 0 ))) {
|
||
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 && (this._bakRange.startContainer === this.body || domUtils.inDoc( this._bakRange.startContainer, this.body )) ){
|
||
return this._bakRange;
|
||
}
|
||
optimze( range );
|
||
}
|
||
}
|
||
|
||
return this._bakRange = range;
|
||
},
|
||
|
||
/**
|
||
* 获取开始元素,用于状态反射
|
||
* @public
|
||
* @function
|
||
* @name UM.dom.Selection.getStart
|
||
* @return {Element} 获得开始元素
|
||
*/
|
||
getStart:function () {
|
||
if ( this._cachedStartElement ) {
|
||
return this._cachedStartElement;
|
||
}
|
||
var range = browser.ie9below ? this.getIERange() : this.getRange(),
|
||
tmpRange,
|
||
start, tmp, parent;
|
||
if ( browser.ie9below ) {
|
||
if ( !range ) {
|
||
//todo 给第一个值可能会有问题
|
||
return this.document.body.firstChild;
|
||
}
|
||
//control元素
|
||
if ( range.item ){
|
||
return range.item( 0 );
|
||
}
|
||
tmpRange = range.duplicate();
|
||
//修正ie下<b>x</b>[xx] 闭合后 <b>x|</b>xx
|
||
tmpRange.text.length > 0 && tmpRange.moveStart( 'character', 1 );
|
||
tmpRange.collapse( 1 );
|
||
start = tmpRange.parentElement();
|
||
parent = tmp = range.parentElement();
|
||
while ( tmp = tmp.parentNode ) {
|
||
if ( tmp == start ) {
|
||
start = parent;
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
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;
|
||
},
|
||
/**
|
||
* 得到选区中的文本
|
||
* @public
|
||
* @function
|
||
* @name UM.dom.Selection.getText
|
||
* @return {String} 选区中包含的文本
|
||
*/
|
||
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 '';
|
||
}
|
||
};
|
||
})();
|
||
/**
|
||
* @file
|
||
* @name UM.Editor
|
||
* @short Editor
|
||
* @import editor.js,core/utils.js,core/EventBase.js,core/browser.js,core/dom/dtd.js,core/dom/domUtils.js,core/dom/Range.js,core/dom/Selection.js,plugins/serialize.js
|
||
* @desc 编辑器主类,包含编辑器提供的大部分公用接口
|
||
*/
|
||
(function () {
|
||
var uid = 0, _selectionChangeTimer;
|
||
|
||
/**
|
||
* @private
|
||
* @ignore
|
||
* @param form 编辑器所在的form元素
|
||
* @param 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 == 'umeditor_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': 'umeditor_textarea_' + editor.options.textarea,
|
||
'style': "display:none"
|
||
}));
|
||
//不要产生多个textarea
|
||
editor.textarea = textarea;
|
||
}
|
||
textarea.value = editor.hasContents() ?
|
||
(editor.options.allHtmlEnabled ? editor.getAllHtml() : editor.getContent(null, null, true)) :
|
||
''
|
||
}
|
||
function loadPlugins(me){
|
||
//初始化插件
|
||
for (var pi in UM.plugins) {
|
||
if(me.options.excludePlugins.indexOf(pi) == -1){
|
||
UM.plugins[pi].call(me);
|
||
me.plugins[pi] = 1;
|
||
}
|
||
}
|
||
me.langIsReady = true;
|
||
|
||
me.fireEvent("langReady");
|
||
}
|
||
function checkCurLang(I18N){
|
||
for(var lang in I18N){
|
||
return lang
|
||
}
|
||
}
|
||
/**
|
||
* UEditor编辑器类
|
||
* @name Editor
|
||
* @desc 创建一个跟编辑器实例
|
||
* - ***container*** 编辑器容器对象
|
||
* - ***iframe*** 编辑区域所在的iframe对象
|
||
* - ***window*** 编辑区域所在的window
|
||
* - ***document*** 编辑区域所在的document对象
|
||
* - ***body*** 编辑区域所在的body对象
|
||
* - ***selection*** 编辑区域的选区对象
|
||
*/
|
||
var Editor = UM.Editor = function (options) {
|
||
var me = this;
|
||
me.uid = uid++;
|
||
EventBase.call(me);
|
||
me.commands = {};
|
||
me.options = utils.extend(utils.clone(options || {}), UMEDITOR_CONFIG, true);
|
||
me.shortcutkeys = {};
|
||
me.inputRules = [];
|
||
me.outputRules = [];
|
||
//设置默认的常用属性
|
||
me.setOpt({
|
||
isShow: true,
|
||
initialContent: '',
|
||
initialStyle:'',
|
||
autoClearinitialContent: false,
|
||
textarea: 'editorValue',
|
||
focus: false,
|
||
focusInEnd: true,
|
||
autoClearEmptyNode: true,
|
||
fullscreen: false,
|
||
readonly: false,
|
||
zIndex: 999,
|
||
enterTag: 'p',
|
||
lang: 'zh-cn',
|
||
langPath: me.options.UMEDITOR_HOME_URL + 'lang/',
|
||
theme: 'default',
|
||
themePath: me.options.UMEDITOR_HOME_URL + 'themes/',
|
||
allHtmlEnabled: false,
|
||
autoSyncData : true,
|
||
autoHeightEnabled : true,
|
||
excludePlugins:''
|
||
});
|
||
me.plugins = {};
|
||
if(!utils.isEmptyObject(UM.I18N)){
|
||
//修改默认的语言类型
|
||
me.options.lang = checkCurLang(UM.I18N);
|
||
loadPlugins(me)
|
||
}else{
|
||
utils.loadFile(document, {
|
||
src: me.options.langPath + me.options.lang + "/" + me.options.lang + ".js",
|
||
tag: "script",
|
||
type: "text/javascript",
|
||
defer: "defer"
|
||
}, function () {
|
||
loadPlugins(me)
|
||
});
|
||
}
|
||
|
||
};
|
||
Editor.prototype = {
|
||
/**
|
||
* 当编辑器ready后执行传入的fn,如果编辑器已经完成ready,就马上执行fn,fn的中的this是编辑器实例。
|
||
* 大部分的实例接口都需要放在该方法内部执行,否则在IE下可能会报错。
|
||
* @name ready
|
||
* @grammar editor.ready(fn) fn是当编辑器渲染好后执行的function
|
||
* @example
|
||
* var editor = new UM.ui.Editor();
|
||
* editor.render("myEditor");
|
||
* editor.ready(function(){
|
||
* editor.setContent("欢迎使用UEditor!");
|
||
* })
|
||
*/
|
||
ready: function (fn) {
|
||
var me = this;
|
||
if (fn) {
|
||
me.isReady ? fn.apply(me) : me.addListener('ready', fn);
|
||
}
|
||
},
|
||
/**
|
||
* 为编辑器设置默认参数值。若用户配置为空,则以默认配置为准
|
||
* @grammar editor.setOpt(key,value); //传入一个键、值对
|
||
* @grammar editor.setOpt({ key:value}); //传入一个json对象
|
||
*/
|
||
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] || ''
|
||
},
|
||
/**
|
||
* 销毁编辑器实例对象
|
||
* @name destroy
|
||
* @grammar editor.destroy();
|
||
*/
|
||
destroy: function () {
|
||
|
||
var me = this;
|
||
me.fireEvent('destroy');
|
||
var container = me.container.parentNode;
|
||
if(container === document.body){
|
||
container = me.container;
|
||
}
|
||
var textarea = me.textarea;
|
||
if (!textarea) {
|
||
textarea = document.createElement('textarea');
|
||
container.parentNode.insertBefore(textarea, container);
|
||
} else {
|
||
textarea.style.display = ''
|
||
}
|
||
|
||
textarea.style.width = me.body.offsetWidth + 'px';
|
||
textarea.style.height = me.body.offsetHeight + 'px';
|
||
textarea.value = me.getContent();
|
||
textarea.id = me.key;
|
||
if(container.contains(textarea)){
|
||
$(textarea).insertBefore(container);
|
||
}
|
||
container.innerHTML = '';
|
||
|
||
domUtils.remove(container);
|
||
UM.clearCache(me.id);
|
||
//trace:2004
|
||
for (var p in me) {
|
||
if (me.hasOwnProperty(p)) {
|
||
delete this[p];
|
||
}
|
||
}
|
||
|
||
},
|
||
initialCont : function(holder){
|
||
|
||
if(holder){
|
||
holder.getAttribute('name') && ( this.options.textarea = holder.getAttribute('name'));
|
||
if (holder && /script|textarea/ig.test(holder.tagName)) {
|
||
var newDiv = document.createElement('div');
|
||
holder.parentNode.insertBefore(newDiv, holder);
|
||
this.options.initialContent = UM.htmlparser(holder.value || holder.innerHTML|| this.options.initialContent).toHtml();
|
||
holder.className && (newDiv.className = holder.className);
|
||
holder.style.cssText && (newDiv.style.cssText = holder.style.cssText);
|
||
|
||
if (/textarea/i.test(holder.tagName)) {
|
||
this.textarea = holder;
|
||
this.textarea.style.display = 'none';
|
||
|
||
} else {
|
||
holder.parentNode.removeChild(holder);
|
||
holder.id && (newDiv.id = holder.id);
|
||
}
|
||
holder = newDiv;
|
||
holder.innerHTML = '';
|
||
}
|
||
return holder;
|
||
}else{
|
||
return null;
|
||
}
|
||
|
||
},
|
||
/**
|
||
* 渲染编辑器的DOM到指定容器,必须且只能调用一次
|
||
* @name render
|
||
* @grammar editor.render(containerId); //可以指定一个容器ID
|
||
* @grammar editor.render(containerDom); //也可以直接指定容器对象
|
||
*/
|
||
render: function (container) {
|
||
var me = this,
|
||
options = me.options,
|
||
getStyleValue=function(attr){
|
||
return parseInt($(container).css(attr));
|
||
};
|
||
|
||
if (utils.isString(container)) {
|
||
container = document.getElementById(container);
|
||
}
|
||
if (container) {
|
||
this.id = container.getAttribute('id');
|
||
UM.setEditor(this);
|
||
utils.cssRule('edui-style-body',me.options.initialStyle,document);
|
||
|
||
container = this.initialCont(container);
|
||
|
||
container.className += ' edui-body-container';
|
||
|
||
if(options.initialFrameWidth){
|
||
options.minFrameWidth = options.initialFrameWidth
|
||
}else{
|
||
//都没给值,先写死了
|
||
options.minFrameWidth = options.initialFrameWidth = $(container).width() || UM.defaultWidth;
|
||
}
|
||
if(options.initialFrameHeight){
|
||
options.minFrameHeight = options.initialFrameHeight
|
||
}else{
|
||
|
||
options.initialFrameHeight = options.minFrameHeight = $(container).height() || UM.defaultHeight;
|
||
}
|
||
|
||
container.style.width = /%$/.test(options.initialFrameWidth) ? '100%' : options.initialFrameWidth -
|
||
getStyleValue("padding-left")-
|
||
getStyleValue("padding-right") +'px';
|
||
|
||
var height = /%$/.test(options.initialFrameHeight) ? '100%' : (options.initialFrameHeight - getStyleValue("padding-top")- getStyleValue("padding-bottom") );
|
||
if(this.options.autoHeightEnabled){
|
||
container.style.minHeight = height +'px';
|
||
container.style.height = '';
|
||
if(browser.ie && browser.version <= 6){
|
||
container.style.height = height ;
|
||
container.style.setExpression('height', 'this.scrollHeight <= ' + height + ' ? "' + height + 'px" : "auto"');
|
||
}
|
||
}else{
|
||
$(container).height(height)
|
||
}
|
||
container.style.zIndex = options.zIndex;
|
||
this._setup(container);
|
||
|
||
}
|
||
},
|
||
/**
|
||
* 编辑器初始化
|
||
* @private
|
||
* @ignore
|
||
* @param {Element} doc 编辑器Iframe中的文档对象
|
||
*/
|
||
_setup: function (cont) {
|
||
var me = this,
|
||
options = me.options;
|
||
|
||
cont.contentEditable = true;
|
||
document.body.spellcheck = false;
|
||
|
||
me.document = document;
|
||
me.window = document.defaultView || document.parentWindow;
|
||
me.body = cont;
|
||
me.$body = $(cont);
|
||
me.selection = new dom.Selection(document,me.body);
|
||
me._isEnabled = false;
|
||
//gecko初始化就能得到range,无法判断isFocus了
|
||
var geckoSel;
|
||
if (browser.gecko && (geckoSel = this.selection.getNative())) {
|
||
geckoSel.removeAllRanges();
|
||
}
|
||
this._initEvents();
|
||
//为form提交提供一个隐藏的textarea
|
||
for (var form = cont.parentNode; form && !domUtils.isBody(form); form = form.parentNode) {
|
||
if (form.tagName == 'FORM') {
|
||
me.form = form;
|
||
if(me.options.autoSyncData){
|
||
$(cont).on('blur',function(){
|
||
setValue(form,me);
|
||
})
|
||
}else{
|
||
$(form).on('submit', function () {
|
||
setValue(this, me);
|
||
})
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
if (options.initialContent) {
|
||
if (options.autoClearinitialContent) {
|
||
var oldExecCommand = me.execCommand;
|
||
me.execCommand = function () {
|
||
me.fireEvent('firstBeforeExecCommand');
|
||
return oldExecCommand.apply(me, arguments);
|
||
};
|
||
this._setDefaultContent(options.initialContent);
|
||
} else
|
||
this.setContent(options.initialContent, false, true);
|
||
}
|
||
|
||
//编辑器不能为空内容
|
||
|
||
if (domUtils.isEmptyNode(me.body)) {
|
||
me.body.innerHTML = '<p>' + (browser.ie ? '' : '<br/>') + '</p>';
|
||
}
|
||
//如果要求focus, 就把光标定位到内容开始
|
||
if (options.focus) {
|
||
setTimeout(function () {
|
||
me.focus(me.options.focusInEnd);
|
||
//如果自动清除开着,就不需要做selectionchange;
|
||
!me.options.autoClearinitialContent && me._selectionChange();
|
||
}, 0);
|
||
}
|
||
if (!me.container) {
|
||
me.container = cont.parentNode;
|
||
}
|
||
|
||
me._bindshortcutKeys();
|
||
me.isReady = 1;
|
||
me.fireEvent('ready');
|
||
options.onready && options.onready.call(me);
|
||
if(!browser.ie || browser.ie9above){
|
||
|
||
$(me.body).on( 'blur focus', function (e) {
|
||
var nSel = me.selection.getNative();
|
||
//chrome下会出现alt+tab切换时,导致选区位置不对
|
||
if (e.type == 'blur') {
|
||
if(nSel.rangeCount > 0 ){
|
||
me._bakRange = nSel.getRangeAt(0);
|
||
}
|
||
} else {
|
||
try {
|
||
me._bakRange && nSel.addRange(me._bakRange)
|
||
} catch (e) {
|
||
}
|
||
me._bakRange = null;
|
||
}
|
||
});
|
||
}
|
||
|
||
!options.isShow && me.setHide();
|
||
options.readonly && me.setDisabled();
|
||
},
|
||
/**
|
||
* 同步编辑器的数据,为提交数据做准备,主要用于你是手动提交的情况
|
||
* @name sync
|
||
* @grammar editor.sync(); //从编辑器的容器向上查找,如果找到就同步数据
|
||
* @grammar editor.sync(formID); //formID制定一个要同步数据的form的id,编辑器的数据会同步到你指定form下
|
||
* @desc
|
||
* 后台取得数据得键值使用你容器上得''name''属性,如果没有就使用参数传入的''textarea''
|
||
* @example
|
||
* editor.sync();
|
||
* form.sumbit(); //form变量已经指向了form元素
|
||
*
|
||
*/
|
||
sync: function (formId) {
|
||
var me = this,
|
||
form = formId ? document.getElementById(formId) :
|
||
domUtils.findParent(me.body.parentNode, function (node) {
|
||
return node.tagName == 'FORM'
|
||
}, true);
|
||
form && setValue(form, me);
|
||
},
|
||
/**
|
||
* 设置编辑器高度
|
||
* @name setHeight
|
||
* @grammar editor.setHeight(number); //纯数值,不带单位
|
||
*/
|
||
setHeight: function (height,notSetHeight) {
|
||
!notSetHeight && (this.options.initialFrameHeight = height);
|
||
if(this.options.autoHeightEnabled){
|
||
$(this.body).css({
|
||
'min-height':height + 'px'
|
||
});
|
||
if(browser.ie && browser.version <= 6 && this.container){
|
||
this.container.style.height = height ;
|
||
this.container.style.setExpression('height', 'this.scrollHeight <= ' + height + ' ? "' + height + 'px" : "auto"');
|
||
}
|
||
}else{
|
||
$(this.body).height(height)
|
||
}
|
||
this.fireEvent('resize');
|
||
},
|
||
/**
|
||
* 设置编辑器宽度
|
||
* @name setWidth
|
||
* @grammar editor.setWidth(number); //纯数值,不带单位
|
||
*/
|
||
setWidth:function(width){
|
||
this.$container && this.$container.width(width);
|
||
$(this.body).width(width - $(this.body).css('padding-left').replace('px','') * 1 - $(this.body).css('padding-right').replace('px','') * 1);
|
||
this.fireEvent('resize');
|
||
},
|
||
addshortcutkey: function (cmd, keys) {
|
||
var obj = {};
|
||
if (keys) {
|
||
obj[cmd] = keys
|
||
} else {
|
||
obj = cmd;
|
||
}
|
||
utils.extend(this.shortcutkeys, obj)
|
||
},
|
||
_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);
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
});
|
||
},
|
||
/**
|
||
* 获取编辑器内容
|
||
* @name getContent
|
||
* @grammar editor.getContent() => String //若编辑器中只包含字符"<p><br /></p/>"会返回空。
|
||
* @grammar editor.getContent(fn) => String
|
||
* @example
|
||
* getContent默认是会现调用hasContents来判断编辑器是否为空,如果是,就直接返回空字符串
|
||
* 你也可以传入一个fn来接替hasContents的工作,定制判断的规则
|
||
* editor.getContent(function(){
|
||
* return false //编辑器没有内容 ,getContent直接返回空
|
||
* })
|
||
*/
|
||
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 = UM.htmlparser(me.body.innerHTML,ignoreBlank);
|
||
me.filterOutputRule(root);
|
||
me.fireEvent('aftergetcontent',root);
|
||
return root.toHtml(formatter);
|
||
},
|
||
/**
|
||
* 取得完整的html代码,可以直接显示成完整的html文档
|
||
* @name getAllHtml
|
||
* @grammar editor.getAllHtml() => String
|
||
*/
|
||
getAllHtml: function () {
|
||
var me = this,
|
||
headHtml = [],
|
||
html = '';
|
||
me.fireEvent('getAllHtml', headHtml);
|
||
if (browser.ie && browser.version > 8) {
|
||
var headHtmlForIE9 = '';
|
||
utils.each(me.document.styleSheets, function (si) {
|
||
headHtmlForIE9 += ( si.href ? '<link rel="stylesheet" type="text/css" href="' + si.href + '" />' : '<style>' + si.cssText + '</style>');
|
||
});
|
||
utils.each(me.document.getElementsByTagName('script'), function (si) {
|
||
headHtmlForIE9 += si.outerHTML;
|
||
});
|
||
}
|
||
return '<html><head>' + (me.options.charset ? '<meta http-equiv="Content-Type" content="text/html; charset=' + me.options.charset + '"/>' : '')
|
||
+ (headHtmlForIE9 || me.document.getElementsByTagName('head')[0].innerHTML) + headHtml.join('\n') + '</head>'
|
||
+ '<body ' + (ie && browser.version < 9 ? 'class="view"' : '') + '>' + me.getContent(null, null, true) + '</body></html>';
|
||
},
|
||
/**
|
||
* 得到编辑器的纯文本内容,但会保留段落格式
|
||
* @name getPlainTxt
|
||
* @grammar editor.getPlainTxt() => String
|
||
*/
|
||
getPlainTxt: function () {
|
||
var reg = new RegExp(domUtils.fillChar, 'g'),
|
||
html = this.body.innerHTML.replace(/[\n\r]/g, '');//ie要先去了\n在处理
|
||
html = html.replace(/<(p|div)[^>]*>(<br\/?>| )<\/\1>/gi, '\n')
|
||
.replace(/<br\/?>/gi, '\n')
|
||
.replace(/<[^>/]+>/g, '')
|
||
.replace(/(\n)?<\/([^>]+)>/g, function (a, b, c) {
|
||
return dtd.$block[c] ? '\n' : b ? b : '';
|
||
});
|
||
//取出来的空格会有c2a0会变成乱码,处理这种情况\u00a0
|
||
return html.replace(reg, '').replace(/\u00a0/g, ' ').replace(/ /g, ' ');
|
||
},
|
||
|
||
/**
|
||
* 获取编辑器中的纯文本内容,没有段落格式
|
||
* @name getContentTxt
|
||
* @grammar editor.getContentTxt() => String
|
||
*/
|
||
getContentTxt: function () {
|
||
var reg = new RegExp(domUtils.fillChar, 'g');
|
||
//取出来的空格会有c2a0会变成乱码,处理这种情况\u00a0
|
||
return this.body[browser.ie ? 'innerText' : 'textContent'].replace(reg, '').replace(/\u00a0/g, ' ');
|
||
},
|
||
|
||
/**
|
||
* 将html设置到编辑器中, 如果是用于初始化时给编辑器赋初值,则必须放在ready方法内部执行
|
||
* @name setContent
|
||
* @grammar editor.setContent(html)
|
||
* @example
|
||
* var editor = new UM.ui.Editor()
|
||
* editor.ready(function(){
|
||
* //需要ready后执行,否则可能报错
|
||
* editor.setContent("欢迎使用UEditor!");
|
||
* })
|
||
*/
|
||
setContent: function (html, isAppendTo, notFireSelectionchange) {
|
||
var me = this;
|
||
|
||
me.fireEvent('beforesetcontent', html);
|
||
var root = UM.htmlparser(html);
|
||
me.filterInputRule(root);
|
||
html = root.toHtml();
|
||
|
||
|
||
me.body.innerHTML = (isAppendTo ? me.body.innerHTML : '') + html;
|
||
|
||
|
||
function isCdataDiv(node){
|
||
return node.tagName == 'DIV' && node.getAttribute('cdata_tag');
|
||
}
|
||
//给文本或者inline节点套p标签
|
||
if (me.options.enterTag == 'p') {
|
||
|
||
var child = this.body.firstChild, tmpNode;
|
||
if (!child || child.nodeType == 1 &&
|
||
(dtd.$cdata[child.tagName] || isCdataDiv(child) ||
|
||
domUtils.isCustomeNode(child)
|
||
)
|
||
&& child === this.body.lastChild) {
|
||
this.body.innerHTML = '<p>' + (browser.ie ? ' ' : '<br/>') + '</p>' + this.body.innerHTML;
|
||
|
||
} 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);
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 让编辑器获得焦点,toEnd确定focus位置
|
||
* @name focus
|
||
* @grammar editor.focus([toEnd]) //默认focus到编辑器头部,toEnd为true时focus到内容尾部
|
||
*/
|
||
focus: function (toEnd) {
|
||
try {
|
||
var me = this,
|
||
rng = me.selection.getRange();
|
||
if (toEnd) {
|
||
rng.setStartAtLast(me.body.lastChild).setCursor(false, true);
|
||
} else {
|
||
rng.select(true);
|
||
}
|
||
this.fireEvent('focus');
|
||
} catch (e) {
|
||
}
|
||
},
|
||
/**
|
||
* 使编辑区域失去焦点
|
||
*/
|
||
blur:function(){
|
||
var sel = this.selection.getNative();
|
||
sel.empty ? sel.empty() : sel.removeAllRanges();
|
||
this.fireEvent('blur')
|
||
},
|
||
/**
|
||
* 判断编辑器当前是否获得了焦点
|
||
*/
|
||
isFocus : function(){
|
||
if(this.fireEvent('isfocus')===true){
|
||
return true;
|
||
}
|
||
return this.selection.isFocus();
|
||
},
|
||
|
||
/**
|
||
* 初始化UE事件及部分事件代理
|
||
* @private
|
||
* @ignore
|
||
*/
|
||
_initEvents: function () {
|
||
var me = this,
|
||
cont = me.body,
|
||
_proxyDomEvent = function(){
|
||
me._proxyDomEvent.apply(me, arguments);
|
||
};
|
||
|
||
$(cont)
|
||
.on( 'click contextmenu mousedown keydown keyup keypress mouseup mouseover mouseout selectstart', _proxyDomEvent)
|
||
.on( 'focus blur', _proxyDomEvent)
|
||
.on('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);
|
||
});
|
||
},
|
||
/**
|
||
* 触发事件代理
|
||
* @private
|
||
* @ignore
|
||
*/
|
||
_proxyDomEvent: function (evt) {
|
||
return this.fireEvent(evt.type.replace(/^on/, ''), evt);
|
||
},
|
||
/**
|
||
* 变化选区
|
||
* @private
|
||
* @ignore
|
||
*/
|
||
_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.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);
|
||
},
|
||
_callCmdFn: function (fnName, args) {
|
||
args = Array.prototype.slice.call(args,0);
|
||
var cmdName = args.shift().toLowerCase(),
|
||
cmd, cmdFn;
|
||
cmd = this.commands[cmdName] || UM.commands[cmdName];
|
||
cmdFn = cmd && cmd[fnName];
|
||
//没有querycommandstate或者没有command的都默认返回0
|
||
if ((!cmd || !cmdFn) && fnName == 'queryCommandState') {
|
||
return 0;
|
||
} else if (cmdFn) {
|
||
return cmdFn.apply(this, [cmdName].concat(args));
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 执行编辑命令cmdName,完成富文本编辑效果
|
||
* @name execCommand
|
||
* @grammar editor.execCommand(cmdName) => {*}
|
||
*/
|
||
execCommand: function (cmdName) {
|
||
if(!this.isFocus()){
|
||
var bakRange = this.selection._bakRange;
|
||
if(bakRange){
|
||
bakRange.select()
|
||
}else{
|
||
this.focus(true)
|
||
}
|
||
|
||
}
|
||
cmdName = cmdName.toLowerCase();
|
||
var me = this,
|
||
result,
|
||
cmd = me.commands[cmdName] || UM.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('beforeexeccommand', cmdName);
|
||
result = this._callCmdFn('execCommand', arguments);
|
||
(!cmd.ignoreContentChange && !me._ignoreContentChange) && me.fireEvent('contentchange');
|
||
me.fireEvent('afterexeccommand', cmdName);
|
||
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命令,查选编辑器当前的选区,返回命令的状态
|
||
* @name queryCommandState
|
||
* @grammar editor.queryCommandState(cmdName) => (-1|0|1)
|
||
* @desc
|
||
* * ''-1'' 当前命令不可用
|
||
* * ''0'' 当前命令可用
|
||
* * ''1'' 当前命令已经执行过了
|
||
*/
|
||
queryCommandState: function (cmdName) {
|
||
try{
|
||
return this._callCmdFn('queryCommandState', arguments);
|
||
}catch(e){
|
||
return 0
|
||
}
|
||
|
||
},
|
||
|
||
/**
|
||
* 根据传入的command命令,查选编辑器当前的选区,根据命令返回相关的值
|
||
* @name queryCommandValue
|
||
* @grammar editor.queryCommandValue(cmdName) => {*}
|
||
*/
|
||
queryCommandValue: function (cmdName) {
|
||
try{
|
||
return this._callCmdFn('queryCommandValue', arguments);
|
||
}catch(e){
|
||
return null
|
||
}
|
||
},
|
||
/**
|
||
* 检查编辑区域中是否有内容,若包含tags中的节点类型,直接返回true
|
||
* @name hasContents
|
||
* @desc
|
||
* 默认有文本内容,或者有以下节点都不认为是空
|
||
* <code>{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}</code>
|
||
* @grammar editor.hasContents() => (true|false)
|
||
* @grammar editor.hasContents(tags) => (true|false) //若文档中包含tags数组里对应的tag,直接返回true
|
||
* @example
|
||
* editor.hasContents(['span']) //如果编辑器里有这些,不认为是空
|
||
*/
|
||
hasContents: function (tags) {
|
||
if (tags) {
|
||
for (var i = 0, ci; ci = tags[i++];) {
|
||
if (this.body.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.body, ci);
|
||
for (var n = 0, cn; cn = nodes[n++];) {
|
||
if (domUtils.isCustomeNode(cn)) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
},
|
||
/**
|
||
* 重置编辑器,可用来做多个tab使用同一个编辑器实例
|
||
* @name reset
|
||
* @desc
|
||
* * 清空编辑器内容
|
||
* * 清空回退列表
|
||
* @grammar editor.reset()
|
||
*/
|
||
reset: function () {
|
||
this.fireEvent('reset');
|
||
},
|
||
isEnabled: function(){
|
||
return this._isEnabled != true;
|
||
},
|
||
|
||
setEnabled: function () {
|
||
var me = this, range;
|
||
|
||
me.body.contentEditable = true;
|
||
|
||
/* 恢复选区 */
|
||
if (me.lastBk) {
|
||
range = me.selection.getRange();
|
||
try {
|
||
range.moveToBookmark(me.lastBk);
|
||
delete me.lastBk
|
||
} catch (e) {
|
||
range.setStartAtFirst(me.body).collapse(true)
|
||
}
|
||
range.select(true);
|
||
}
|
||
|
||
/* 恢复query函数 */
|
||
if (me.bkqueryCommandState) {
|
||
me.queryCommandState = me.bkqueryCommandState;
|
||
delete me.bkqueryCommandState;
|
||
}
|
||
|
||
/* 恢复原生事件 */
|
||
if (me._bkproxyDomEvent) {
|
||
me._proxyDomEvent = me._bkproxyDomEvent;
|
||
delete me._bkproxyDomEvent;
|
||
}
|
||
|
||
/* 触发事件 */
|
||
me.fireEvent('setEnabled');
|
||
},
|
||
/**
|
||
* 设置当前编辑区域可以编辑
|
||
* @name enable
|
||
* @grammar editor.enable()
|
||
*/
|
||
enable: function () {
|
||
return this.setEnabled();
|
||
},
|
||
setDisabled: function (except, keepDomEvent) {
|
||
var me = this;
|
||
|
||
me.body.contentEditable = false;
|
||
me._except = except ? utils.isArray(except) ? except : [except] : [];
|
||
|
||
/* 备份最后的选区 */
|
||
if (!me.lastBk) {
|
||
me.lastBk = me.selection.getRange().createBookmark(true);
|
||
}
|
||
|
||
/* 备份并重置query函数 */
|
||
if(!me.bkqueryCommandState) {
|
||
me.bkqueryCommandState = me.queryCommandState;
|
||
me.queryCommandState = function (type) {
|
||
if (utils.indexOf(me._except, type) != -1) {
|
||
return me.bkqueryCommandState.apply(me, arguments);
|
||
}
|
||
return -1;
|
||
};
|
||
}
|
||
|
||
/* 备份并墙原生事件 */
|
||
if(!keepDomEvent && !me._bkproxyDomEvent) {
|
||
me._bkproxyDomEvent = me._proxyDomEvent;
|
||
me._proxyDomEvent = function () {
|
||
return false;
|
||
};
|
||
}
|
||
|
||
/* 触发事件 */
|
||
me.fireEvent('selectionchange');
|
||
me.fireEvent('setDisabled', me._except);
|
||
},
|
||
/** 设置当前编辑区域不可编辑,except中的命令除外
|
||
* @name disable
|
||
* @grammar editor.disable()
|
||
* @grammar editor.disable(except) //例外的命令,也即即使设置了disable,此处配置的命令仍然可以执行
|
||
* @example
|
||
* //禁用工具栏中除加粗和插入图片之外的所有功能
|
||
* editor.disable(['bold','insertimage']);//可以是单一的String,也可以是Array
|
||
*/
|
||
disable: function (except) {
|
||
return this.setDisabled(except);
|
||
},
|
||
/**
|
||
* 设置默认内容
|
||
* @ignore
|
||
* @private
|
||
* @param {String} cont 要存入的内容
|
||
*/
|
||
_setDefaultContent: function () {
|
||
function clear() {
|
||
var me = this;
|
||
if (me.document.getElementById('initContent')) {
|
||
me.body.innerHTML = '<p>' + (ie ? '' : '<br/>') + '</p>';
|
||
me.removeListener('firstBeforeExecCommand focus', clear);
|
||
setTimeout(function () {
|
||
me.focus();
|
||
me._selectionChange();
|
||
}, 0)
|
||
}
|
||
}
|
||
|
||
return function (cont) {
|
||
var me = this;
|
||
me.body.innerHTML = '<p id="initContent">' + cont + '</p>';
|
||
|
||
me.addListener('firstBeforeExecCommand focus', clear);
|
||
}
|
||
}(),
|
||
/**
|
||
* show方法的兼容版本
|
||
* @private
|
||
* @ignore
|
||
*/
|
||
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 = '';
|
||
}
|
||
|
||
},
|
||
/**
|
||
* 显示编辑器
|
||
* @name show
|
||
* @grammar editor.show()
|
||
*/
|
||
show: function () {
|
||
return this.setShow();
|
||
},
|
||
/**
|
||
* hide方法的兼容版本
|
||
* @private
|
||
* @ignore
|
||
*/
|
||
setHide: function () {
|
||
var me = this;
|
||
if (!me.lastBk) {
|
||
me.lastBk = me.selection.getRange().createBookmark(true);
|
||
}
|
||
me.container.style.display = 'none'
|
||
},
|
||
/**
|
||
* 隐藏编辑器
|
||
* @name hide
|
||
* @grammar editor.hide()
|
||
*/
|
||
hide: function () {
|
||
return this.setHide();
|
||
},
|
||
/**
|
||
* 根据制定的路径,获取对应的语言资源
|
||
* @name getLang
|
||
* @grammar editor.getLang(path) => (JSON|String) 路径根据的是lang目录下的语言文件的路径结构
|
||
* @example
|
||
* editor.getLang('contextMenu.delete') //如果当前是中文,那返回是的是删除
|
||
*/
|
||
getLang: function (path) {
|
||
var lang = UM.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;
|
||
},
|
||
/**
|
||
* 计算编辑器当前内容的长度
|
||
* @name getContentLength
|
||
* @grammar editor.getContentLength(ingoneHtml,tagNames) =>
|
||
* @example
|
||
* editor.getLang(true)
|
||
*/
|
||
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.body.getElementsByTagName(ci).length;
|
||
}
|
||
}
|
||
return count;
|
||
},
|
||
addInputRule: function (rule,ignoreUndo) {
|
||
rule.ignoreUndo = ignoreUndo;
|
||
this.inputRules.push(rule);
|
||
},
|
||
filterInputRule: function (root,isUndoLoad) {
|
||
for (var i = 0, ci; ci = this.inputRules[i++];) {
|
||
if(isUndoLoad && ci.ignoreUndo){
|
||
continue;
|
||
}
|
||
ci.call(this, root)
|
||
}
|
||
},
|
||
addOutputRule: function (rule,ignoreUndo) {
|
||
rule.ignoreUndo = ignoreUndo;
|
||
this.outputRules.push(rule)
|
||
},
|
||
filterOutputRule: function (root,isUndoLoad) {
|
||
for (var i = 0, ci; ci = this.outputRules[i++];) {
|
||
if(isUndoLoad && ci.ignoreUndo){
|
||
continue;
|
||
}
|
||
ci.call(this, root)
|
||
}
|
||
}
|
||
};
|
||
utils.inherits(Editor, EventBase);
|
||
})();
|
||
|
||
/**
|
||
* @file
|
||
* @name UM.filterWord
|
||
* @short filterWord
|
||
* @desc 用来过滤word粘贴过来的字符串
|
||
* @import editor.js,core/utils.js
|
||
* @anthor zhanyi
|
||
*/
|
||
var filterWord = UM.filterWord = function () {
|
||
|
||
//是否是word过来的内容
|
||
function isWordDocument( str ) {
|
||
return /(class="?Mso|style="[^"]*\bmso\-|w:WordDocument|<(v|o):|lang=)/ig.test( str );
|
||
}
|
||
//去掉小数
|
||
function transUnit( v ) {
|
||
v = v.replace( /[\d.]+\w+/g, function ( m ) {
|
||
return utils.transUnitToPx(m);
|
||
} );
|
||
return v;
|
||
}
|
||
|
||
function filterPasteWord( str ) {
|
||
return str.replace(/[\t\r\n]+/g,' ')
|
||
.replace( /<!--[\s\S]*?-->/ig, "" )
|
||
//转换图片
|
||
.replace(/<v:shape [^>]*>[\s\S]*?.<\/v:shape>/gi,function(str){
|
||
//opera能自己解析出image所这里直接返回空
|
||
if(browser.opera){
|
||
return '';
|
||
}
|
||
try{
|
||
//有可能是bitmap占为图,无用,直接过滤掉,主要体现在粘贴excel表格中
|
||
if(/Bitmap/i.test(str)){
|
||
return '';
|
||
}
|
||
var width = str.match(/width:([ \d.]*p[tx])/i)[1],
|
||
height = str.match(/height:([ \d.]*p[tx])/i)[1],
|
||
src = str.match(/src=\s*"([^"]*)"/i)[1];
|
||
return '<img width="'+ transUnit(width) +'" height="'+transUnit(height) +'" src="' + src + '" />';
|
||
} catch(e){
|
||
return '';
|
||
}
|
||
})
|
||
//针对wps添加的多余标签处理
|
||
.replace(/<\/?div[^>]*>/g,'')
|
||
//去掉多余的属性
|
||
.replace( /v:\w+=(["']?)[^'"]+\1/g, '' )
|
||
.replace( /<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|xml|meta|link|style|\w+:\w+)(?=[\s\/>]))[^>]*>/gi, "" )
|
||
.replace( /<p [^>]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi, "<p><strong>$1</strong></p>" )
|
||
//去掉多余的属性
|
||
.replace( /\s+(class|lang|align)\s*=\s*(['"]?)([\w-]+)\2/ig, function(str,name,marks,val){
|
||
//保留list的标示
|
||
return name == 'class' && val == 'MsoListParagraph' ? str : ''
|
||
})
|
||
//清除多余的font/span不能匹配 有可能是空格
|
||
.replace( /<(font|span)[^>]*>(\s*)<\/\1>/gi, function(a,b,c){
|
||
return c.replace(/[\t\r\n ]+/g,' ')
|
||
})
|
||
//处理style的问题
|
||
.replace( /(<[a-z][^>]*)\sstyle=(["'])([^\2]*?)\2/gi, function( str, tag, tmp, style ) {
|
||
var n = [],
|
||
s = style.replace( /^\s+|\s+$/, '' )
|
||
.replace(/'/g,'\'')
|
||
.replace( /"/gi, "'" )
|
||
.split( /;\s*/g );
|
||
|
||
for ( var i = 0,v; v = s[i];i++ ) {
|
||
|
||
var name, value,
|
||
parts = v.split( ":" );
|
||
|
||
if ( parts.length == 2 ) {
|
||
name = parts[0].toLowerCase();
|
||
value = parts[1].toLowerCase();
|
||
if(/^(background)\w*/.test(name) && value.replace(/(initial|\s)/g,'').length == 0
|
||
||
|
||
/^(margin)\w*/.test(name) && /^0\w+$/.test(value)
|
||
){
|
||
continue;
|
||
}
|
||
|
||
switch ( name ) {
|
||
case "mso-padding-alt":
|
||
case "mso-padding-top-alt":
|
||
case "mso-padding-right-alt":
|
||
case "mso-padding-bottom-alt":
|
||
case "mso-padding-left-alt":
|
||
case "mso-margin-alt":
|
||
case "mso-margin-top-alt":
|
||
case "mso-margin-right-alt":
|
||
case "mso-margin-bottom-alt":
|
||
case "mso-margin-left-alt":
|
||
//ie下会出现挤到一起的情况
|
||
//case "mso-table-layout-alt":
|
||
case "mso-height":
|
||
case "mso-width":
|
||
case "mso-vertical-align-alt":
|
||
//trace:1819 ff下会解析出padding在table上
|
||
if(!/<table/.test(tag))
|
||
n[i] = name.replace( /^mso-|-alt$/g, "" ) + ":" + transUnit( value );
|
||
continue;
|
||
case "horiz-align":
|
||
n[i] = "text-align:" + value;
|
||
continue;
|
||
|
||
case "vert-align":
|
||
n[i] = "vertical-align:" + value;
|
||
continue;
|
||
|
||
case "font-color":
|
||
case "mso-foreground":
|
||
n[i] = "color:" + value;
|
||
continue;
|
||
|
||
case "mso-background":
|
||
case "mso-highlight":
|
||
n[i] = "background:" + value;
|
||
continue;
|
||
|
||
case "mso-default-height":
|
||
n[i] = "min-height:" + transUnit( value );
|
||
continue;
|
||
|
||
case "mso-default-width":
|
||
n[i] = "min-width:" + transUnit( value );
|
||
continue;
|
||
|
||
case "mso-padding-between-alt":
|
||
n[i] = "border-collapse:separate;border-spacing:" + transUnit( value );
|
||
continue;
|
||
|
||
case "text-line-through":
|
||
if ( (value == "single") || (value == "double") ) {
|
||
n[i] = "text-decoration:line-through";
|
||
}
|
||
continue;
|
||
case "mso-zero-height":
|
||
if ( value == "yes" ) {
|
||
n[i] = "display:none";
|
||
}
|
||
continue;
|
||
// case 'background':
|
||
// break;
|
||
case 'margin':
|
||
if ( !/[1-9]/.test( value ) ) {
|
||
continue;
|
||
}
|
||
|
||
}
|
||
|
||
if ( /^(mso|column|font-emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|table-border|text-(?:decor|trans)|top-bar|version|vnd|word-break)/.test( name )
|
||
||
|
||
/text\-indent|padding|margin/.test(name) && /\-[\d.]+/.test(value)
|
||
) {
|
||
continue;
|
||
}
|
||
|
||
n[i] = name + ":" + parts[1];
|
||
}
|
||
}
|
||
return tag + (n.length ? ' style="' + n.join( ';').replace(/;{2,}/g,';') + '"' : '');
|
||
})
|
||
.replace(/[\d.]+(cm|pt)/g,function(str){
|
||
return utils.transUnitToPx(str)
|
||
})
|
||
|
||
}
|
||
|
||
return function ( html ) {
|
||
return (isWordDocument( html ) ? filterPasteWord( html ) : html);
|
||
};
|
||
}();
|
||
///import editor.js
|
||
///import core/utils.js
|
||
///import core/dom/dom.js
|
||
///import core/dom/dtd.js
|
||
///import core/htmlparser.js
|
||
//模拟的节点类
|
||
//by zhanyi
|
||
(function () {
|
||
var uNode = UM.uNode = function (obj) {
|
||
this.type = obj.type;
|
||
this.data = obj.data;
|
||
this.tagName = obj.tagName;
|
||
this.parentNode = obj.parentNode;
|
||
this.attrs = obj.attrs || {};
|
||
this.children = obj.children;
|
||
};
|
||
var notTransAttrs = {
|
||
'href':1,
|
||
'src':1,
|
||
'_src':1,
|
||
'_href':1,
|
||
'cdata_data':1
|
||
};
|
||
|
||
var notTransTagName = {
|
||
style:1,
|
||
script:1
|
||
};
|
||
|
||
var indentChar = ' ',
|
||
breakChar = '\n';
|
||
|
||
function insertLine(arr, current, begin) {
|
||
arr.push(breakChar);
|
||
return current + (begin ? 1 : -1);
|
||
}
|
||
|
||
function insertIndent(arr, current) {
|
||
//插入缩进
|
||
for (var i = 0; i < current; i++) {
|
||
arr.push(indentChar);
|
||
}
|
||
}
|
||
|
||
//创建uNode的静态方法
|
||
//支持标签和html
|
||
uNode.createElement = function (html) {
|
||
if (/[<>]/.test(html)) {
|
||
return UM.htmlparser(html).children[0]
|
||
} else {
|
||
return new uNode({
|
||
type:'element',
|
||
children:[],
|
||
tagName:html
|
||
})
|
||
}
|
||
};
|
||
uNode.createText = function (data,noTrans) {
|
||
return new UM.uNode({
|
||
type:'text',
|
||
'data':noTrans ? data : utils.unhtml(data || '')
|
||
})
|
||
};
|
||
function nodeToHtml(node, arr, formatter, current) {
|
||
switch (node.type) {
|
||
case 'root':
|
||
for (var i = 0, ci; ci = node.children[i++];) {
|
||
//插入新行
|
||
if (formatter && ci.type == 'element' && !dtd.$inlineWithA[ci.tagName] && i > 1) {
|
||
insertLine(arr, current, true);
|
||
insertIndent(arr, current)
|
||
}
|
||
nodeToHtml(ci, arr, formatter, current)
|
||
}
|
||
break;
|
||
case 'text':
|
||
isText(node, arr);
|
||
break;
|
||
case 'element':
|
||
isElement(node, arr, formatter, current);
|
||
break;
|
||
case 'comment':
|
||
isComment(node, arr, formatter);
|
||
}
|
||
return arr;
|
||
}
|
||
|
||
function isText(node, arr) {
|
||
if(node.parentNode.tagName == 'pre'){
|
||
//源码模式下输入html标签,不能做转换处理,直接输出
|
||
arr.push(node.data)
|
||
}else{
|
||
arr.push(notTransTagName[node.parentNode.tagName] ? utils.html(node.data) : node.data.replace(/[ ]{2}/g,' '))
|
||
}
|
||
|
||
}
|
||
|
||
function isElement(node, arr, formatter, current) {
|
||
var attrhtml = '';
|
||
if (node.attrs) {
|
||
attrhtml = [];
|
||
var attrs = node.attrs;
|
||
for (var a in attrs) {
|
||
//这里就针对
|
||
//<p>'<img src='http://nsclick.baidu.com/u.gif?&asdf=\"sdf&asdfasdfs;asdf'></p>
|
||
//这里边的\"做转换,要不用innerHTML直接被截断了,属性src
|
||
//有可能做的不够
|
||
attrhtml.push(a + (attrs[a] !== undefined ? '="' + (notTransAttrs[a] ? utils.html(attrs[a]).replace(/["]/g, function (a) {
|
||
return '"'
|
||
}) : utils.unhtml(attrs[a])) + '"' : ''))
|
||
}
|
||
attrhtml = attrhtml.join(' ');
|
||
}
|
||
arr.push('<' + node.tagName +
|
||
(attrhtml ? ' ' + attrhtml : '') +
|
||
(dtd.$empty[node.tagName] ? '\/' : '' ) + '>'
|
||
);
|
||
//插入新行
|
||
if (formatter && !dtd.$inlineWithA[node.tagName] && node.tagName != 'pre') {
|
||
if(node.children && node.children.length){
|
||
current = insertLine(arr, current, true);
|
||
insertIndent(arr, current)
|
||
}
|
||
|
||
}
|
||
if (node.children && node.children.length) {
|
||
for (var i = 0, ci; ci = node.children[i++];) {
|
||
if (formatter && ci.type == 'element' && !dtd.$inlineWithA[ci.tagName] && i > 1) {
|
||
insertLine(arr, current);
|
||
insertIndent(arr, current)
|
||
}
|
||
nodeToHtml(ci, arr, formatter, current)
|
||
}
|
||
}
|
||
if (!dtd.$empty[node.tagName]) {
|
||
if (formatter && !dtd.$inlineWithA[node.tagName] && node.tagName != 'pre') {
|
||
|
||
if(node.children && node.children.length){
|
||
current = insertLine(arr, current);
|
||
insertIndent(arr, current)
|
||
}
|
||
}
|
||
arr.push('<\/' + node.tagName + '>');
|
||
}
|
||
|
||
}
|
||
|
||
function isComment(node, arr) {
|
||
arr.push('<!--' + node.data + '-->');
|
||
}
|
||
|
||
function getNodeById(root, id) {
|
||
var node;
|
||
if (root.type == 'element' && root.getAttr('id') == id) {
|
||
return root;
|
||
}
|
||
if (root.children && root.children.length) {
|
||
for (var i = 0, ci; ci = root.children[i++];) {
|
||
if (node = getNodeById(ci, id)) {
|
||
return node;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function getNodesByTagName(node, tagName, arr) {
|
||
if (node.type == 'element' && node.tagName == tagName) {
|
||
arr.push(node);
|
||
}
|
||
if (node.children && node.children.length) {
|
||
for (var i = 0, ci; ci = node.children[i++];) {
|
||
getNodesByTagName(ci, tagName, arr)
|
||
}
|
||
}
|
||
}
|
||
function nodeTraversal(root,fn){
|
||
if(root.children && root.children.length){
|
||
for(var i= 0,ci;ci=root.children[i];){
|
||
nodeTraversal(ci,fn);
|
||
//ci被替换的情况,这里就不再走 fn了
|
||
if(ci.parentNode ){
|
||
if(ci.children && ci.children.length){
|
||
fn(ci)
|
||
}
|
||
if(ci.parentNode) i++
|
||
}
|
||
}
|
||
}else{
|
||
fn(root)
|
||
}
|
||
|
||
}
|
||
uNode.prototype = {
|
||
|
||
/**
|
||
* 当前节点对象,转换成html文本
|
||
* @method toHtml
|
||
* @return { String } 返回转换后的html字符串
|
||
* @example
|
||
* ```javascript
|
||
* node.toHtml();
|
||
* ```
|
||
*/
|
||
|
||
/**
|
||
* 当前节点对象,转换成html文本
|
||
* @method toHtml
|
||
* @param { Boolean } formatter 是否格式化返回值
|
||
* @return { String } 返回转换后的html字符串
|
||
* @example
|
||
* ```javascript
|
||
* node.toHtml( true );
|
||
* ```
|
||
*/
|
||
toHtml:function (formatter) {
|
||
var arr = [];
|
||
nodeToHtml(this, arr, formatter, 0);
|
||
return arr.join('')
|
||
},
|
||
|
||
/**
|
||
* 获取节点的html内容
|
||
* @method innerHTML
|
||
* @warning 假如节点的type不是'element',或节点的标签名称不在dtd列表里,直接返回当前节点
|
||
* @return { String } 返回节点的html内容
|
||
* @example
|
||
* ```javascript
|
||
* var htmlstr = node.innerHTML();
|
||
* ```
|
||
*/
|
||
|
||
/**
|
||
* 设置节点的html内容
|
||
* @method innerHTML
|
||
* @warning 假如节点的type不是'element',或节点的标签名称不在dtd列表里,直接返回当前节点
|
||
* @param { String } htmlstr 传入要设置的html内容
|
||
* @return { UM.uNode } 返回节点本身
|
||
* @example
|
||
* ```javascript
|
||
* node.innerHTML('<span>text</span>');
|
||
* ```
|
||
*/
|
||
innerHTML:function (htmlstr) {
|
||
if (this.type != 'element' || dtd.$empty[this.tagName]) {
|
||
return this;
|
||
}
|
||
if (utils.isString(htmlstr)) {
|
||
if(this.children){
|
||
for (var i = 0, ci; ci = this.children[i++];) {
|
||
ci.parentNode = null;
|
||
}
|
||
}
|
||
this.children = [];
|
||
var tmpRoot = UM.htmlparser(htmlstr);
|
||
for (var i = 0, ci; ci = tmpRoot.children[i++];) {
|
||
this.children.push(ci);
|
||
ci.parentNode = this;
|
||
}
|
||
return this;
|
||
} else {
|
||
var tmpRoot = new UM.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 { UM.uNode } 返回节点本身
|
||
* @example
|
||
* ```javascript
|
||
* node.innerText('<span>text</span>');
|
||
* ```
|
||
*/
|
||
innerText:function (textStr,noTrans) {
|
||
if (this.type != 'element' || dtd.$empty[this.tagName]) {
|
||
return this;
|
||
}
|
||
if (textStr) {
|
||
if(this.children){
|
||
for (var i = 0, ci; ci = this.children[i++];) {
|
||
ci.parentNode = null;
|
||
}
|
||
}
|
||
this.children = [];
|
||
this.appendChild(uNode.createText(textStr,noTrans));
|
||
return this;
|
||
} else {
|
||
return this.toHtml().replace(/<[^>]+>/g, '');
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 获取当前对象的data属性
|
||
* @method getData
|
||
* @return { Object } 若节点的type值是elemenet,返回空字符串,否则返回节点的data属性
|
||
* @example
|
||
* ```javascript
|
||
* node.getData();
|
||
* ```
|
||
*/
|
||
getData:function () {
|
||
if (this.type == 'element')
|
||
return '';
|
||
return this.data
|
||
},
|
||
|
||
/**
|
||
* 获取当前节点下的第一个子节点
|
||
* @method firstChild
|
||
* @return { UM.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 { UM.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 { UM.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 { UM.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 { UM.uNode } target 要替换成该节点参数
|
||
* @param { UM.uNode } source 要被替换掉的节点
|
||
* @return { UM.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 { UM.uNode } node 要插入的节点
|
||
* @return { UM.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 { UM.uNode } target 要插入的节点
|
||
* @param { UM.uNode } source 在该参数节点前面插入
|
||
* @return { UM.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 { UM.uNode } target 要插入的节点
|
||
* @param { UM.uNode } source 在该参数节点后面插入
|
||
* @return { UM.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 { UM.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;
|
||
}
|
||
|
||
}
|
||
},
|
||
hasAttr: function( attrName ){
|
||
var attrVal = this.getAttr( attrName );
|
||
return ( attrVal !== null ) && ( attrVal !== undefined );
|
||
},
|
||
/**
|
||
* 获取当前节点在父节点下的位置索引
|
||
* @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 { UM.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))
|
||
},
|
||
hasClass: function( className ){
|
||
if( this.hasAttr('class') ) {
|
||
var classNames = this.getAttr('class').split(/\s+/),
|
||
hasClass = false;
|
||
$.each(classNames, function(key, item){
|
||
if( item === className ) {
|
||
hasClass = true;
|
||
}
|
||
});
|
||
return hasClass;
|
||
} else {
|
||
return false;
|
||
}
|
||
},
|
||
addClass: function( className ){
|
||
|
||
var classes = null,
|
||
hasClass = false;
|
||
|
||
if( this.hasAttr('class') ) {
|
||
|
||
classes = this.getAttr('class');
|
||
classes = classes.split(/\s+/);
|
||
|
||
classes.forEach( function( item ){
|
||
|
||
if( item===className ) {
|
||
hasClass = true;
|
||
return;
|
||
}
|
||
|
||
} );
|
||
|
||
!hasClass && classes.push( className );
|
||
|
||
this.setAttr('class', classes.join(" "));
|
||
|
||
} else {
|
||
this.setAttr('class', className);
|
||
}
|
||
|
||
},
|
||
removeClass: function( className ){
|
||
if( this.hasAttr('class') ) {
|
||
var cl = this.getAttr('class');
|
||
cl = cl.replace(new RegExp('\\b' + className + '\\b', 'g'),'');
|
||
this.setAttr('class', utils.trim(cl).replace(/[ ]{2,}/g,' '));
|
||
}
|
||
},
|
||
/**
|
||
* 传入一个函数,递归遍历当前节点下的所有节点
|
||
* @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;
|
||
}
|
||
}
|
||
})();
|
||
|
||
//html字符串转换成uNode节点
|
||
//by zhanyi
|
||
var htmlparser = UM.htmlparser = function (htmlstr,ignoreBlank) {
|
||
//todo 原来的方式 [^"'<>\/] 有\/就不能配对上 <TD vAlign=top background=../AAA.JPG> 这样的标签了
|
||
//先去掉了,加上的原因忘了,这里先记录
|
||
var re_tag = /<(?:(?:\/([^>]+)>)|(?:!--([\S|\s]*?)-->)|(?:([^\s\/>]+)\s*((?:(?:"[^"]*")|(?:'[^']*')|[^"'<>])*)\/?>))/g,
|
||
re_attr = /([\w\-:.]+)(?:(?:\s*=\s*(?:(?:"([^"]*)")|(?:'([^']*)')|([^\s>]+)))|(?=\s|$))/g;
|
||
|
||
//ie下取得的html可能会有\n存在,要去掉,在处理replace(/[\t\r\n]*/g,'');代码高量的\n不能去除
|
||
var allowEmptyTags = {
|
||
b:1,code:1,i:1,u:1,strike:1,s:1,tt:1,strong:1,q:1,samp:1,em:1,span:1,
|
||
sub:1,img:1,sup:1,font:1,big:1,small:1,iframe:1,a:1,br:1,pre:1
|
||
};
|
||
htmlstr = htmlstr.replace(new RegExp(domUtils.fillChar, 'g'), '');
|
||
if(!ignoreBlank){
|
||
htmlstr = htmlstr.replace(new RegExp('[\\r\\t\\n'+(ignoreBlank?'':' ')+']*<\/?(\\w+)\\s*(?:[^>]*)>[\\r\\t\\n'+(ignoreBlank?'':' ')+']*','g'), function(a,b){
|
||
//br暂时单独处理
|
||
if(b && allowEmptyTags[b.toLowerCase()]){
|
||
return a.replace(/(^[\n\r]+)|([\n\r]+$)/g,'');
|
||
}
|
||
return a.replace(new RegExp('^[\\r\\n'+(ignoreBlank?'':' ')+']+'),'').replace(new RegExp('[\\r\\n'+(ignoreBlank?'':' ')+']+$'),'');
|
||
});
|
||
}
|
||
|
||
var notTransAttrs = {
|
||
'href':1,
|
||
'src':1
|
||
};
|
||
|
||
var uNode = UM.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;
|
||
}
|
||
|
||
parent.children.push(elm);
|
||
//如果是自闭合节点返回父亲节点
|
||
return dtd.$empty[tagName] ? parent : elm
|
||
}
|
||
|
||
function comment(parent, data) {
|
||
parent.children.push(new uNode({
|
||
type:'comment',
|
||
data:data,
|
||
parentNode:parent
|
||
}));
|
||
}
|
||
|
||
var match, currentIndex = 0, nextIndex = 0;
|
||
//设置根节点
|
||
var root = new uNode({
|
||
type:'root',
|
||
children:[]
|
||
});
|
||
var currentParent = root;
|
||
|
||
while (match = re_tag.exec(htmlstr)) {
|
||
currentIndex = match.index;
|
||
try{
|
||
if (currentIndex > nextIndex) {
|
||
//text node
|
||
text(currentParent, htmlstr.slice(nextIndex, currentIndex));
|
||
}
|
||
if (match[3]) {
|
||
|
||
if(dtd.$cdata[currentParent.tagName]){
|
||
text(currentParent, match[0]);
|
||
}else{
|
||
//start tag
|
||
currentParent = element(currentParent, match[3].toLowerCase(), match[4]);
|
||
}
|
||
|
||
|
||
} else if (match[1]) {
|
||
if(currentParent.type != 'root'){
|
||
if(dtd.$cdata[currentParent.tagName] && !dtd.$cdata[match[1]]){
|
||
text(currentParent, match[0]);
|
||
}else{
|
||
var tmpParent = currentParent;
|
||
while(currentParent.type == 'element' && currentParent.tagName != match[1].toLowerCase()){
|
||
currentParent = currentParent.parentNode;
|
||
if(currentParent.type == 'root'){
|
||
currentParent = tmpParent;
|
||
throw 'break'
|
||
}
|
||
}
|
||
//end tag
|
||
currentParent = currentParent.parentNode;
|
||
}
|
||
|
||
}
|
||
|
||
} else if (match[2]) {
|
||
//comment
|
||
comment(currentParent, match[2])
|
||
}
|
||
}catch(e){}
|
||
|
||
nextIndex = re_tag.lastIndex;
|
||
|
||
}
|
||
//如果结束是文本,就有可能丢掉,所以这里手动判断一下
|
||
//例如 <li>sdfsdfsdf<li>sdfsdfsdfsdf
|
||
if (nextIndex < htmlstr.length) {
|
||
text(currentParent, htmlstr.slice(nextIndex));
|
||
}
|
||
return root;
|
||
};
|
||
/**
|
||
* @file
|
||
* @name UM.filterNode
|
||
* @short filterNode
|
||
* @desc 根据给定的规则过滤节点
|
||
* @import editor.js,core/utils.js
|
||
* @anthor zhanyi
|
||
*/
|
||
var filterNode = UM.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;
|
||
}
|
||
}();
|
||
///import core
|
||
/**
|
||
* @description 插入内容
|
||
* @name baidu.editor.execCommand
|
||
* @param {String} cmdName inserthtml插入内容的命令
|
||
* @param {String} html 要插入的内容
|
||
* @author zhanyi
|
||
*/
|
||
UM.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 = UM.htmlparser(html);
|
||
//如果给了过滤规则就先进行过滤
|
||
if(me.options.filterRules){
|
||
UM.filterNode(root,me.options.filterRules);
|
||
}
|
||
//执行默认的处理
|
||
me.filterInputRule(root);
|
||
html = root.toHtml()
|
||
}
|
||
div.innerHTML = utils.trim( html );
|
||
|
||
if ( !range.collapsed ) {
|
||
var tmpNode = range.startContainer;
|
||
if(domUtils.isFillChar(tmpNode)){
|
||
range.setStartBefore(tmpNode)
|
||
}
|
||
tmpNode = range.endContainer;
|
||
if(domUtils.isFillChar(tmpNode)){
|
||
range.setEndAfter(tmpNode)
|
||
}
|
||
range.txtToElmBoundary();
|
||
//结束边界可能放到了br的前边,要把br包含进来
|
||
// x[xxx]<br/>
|
||
if(range.endContainer && range.endContainer.nodeType == 1){
|
||
tmpNode = range.endContainer.childNodes[range.endOffset];
|
||
if(tmpNode && domUtils.isBr(tmpNode)){
|
||
range.setEndAfter(tmpNode);
|
||
}
|
||
}
|
||
if(range.startOffset == 0){
|
||
tmpNode = range.startContainer;
|
||
if(domUtils.isBoundaryNode(tmpNode,'firstChild') ){
|
||
tmpNode = range.endContainer;
|
||
if(range.endOffset == (tmpNode.nodeType == 3 ? tmpNode.nodeValue.length : tmpNode.childNodes.length) && domUtils.isBoundaryNode(tmpNode,'lastChild')){
|
||
me.body.innerHTML = '<p>'+(browser.ie ? '' : '<br/>')+'</p>';
|
||
range.setStart(me.body.firstChild,0).collapse(true)
|
||
|
||
}
|
||
}
|
||
}
|
||
!range.collapsed && range.deleteContents();
|
||
if(range.startContainer.nodeType == 1){
|
||
var child = range.startContainer.childNodes[range.startOffset],pre;
|
||
if(child && domUtils.isBlockElm(child) && (pre = child.previousSibling) && domUtils.isBlockElm(pre)){
|
||
range.setEnd(pre,pre.childNodes.length).collapse();
|
||
while(child.firstChild){
|
||
pre.appendChild(child.firstChild);
|
||
}
|
||
domUtils.remove(child);
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
|
||
var child,parent,pre,tmp,hadBreak = 0, nextNode;
|
||
//如果当前位置选中了fillchar要干掉,要不会产生空行
|
||
if(range.inFillChar()){
|
||
child = range.startContainer;
|
||
if(domUtils.isFillChar(child)){
|
||
range.setStartBefore(child).collapse(true);
|
||
domUtils.remove(child);
|
||
}else if(domUtils.isFillChar(child,true)){
|
||
child.nodeValue = child.nodeValue.replace(fillCharReg,'');
|
||
range.startOffset--;
|
||
range.collapsed && range.collapse(true)
|
||
}
|
||
}
|
||
while ( child = div.firstChild ) {
|
||
if(hadBreak){
|
||
var p = me.document.createElement('p');
|
||
while(child && (child.nodeType == 3 || !dtd.$block[child.tagName])){
|
||
nextNode = child.nextSibling;
|
||
p.appendChild(child);
|
||
child = nextNode;
|
||
}
|
||
if(p.firstChild){
|
||
|
||
child = p
|
||
}
|
||
}
|
||
range.insertNode( child );
|
||
nextNode = child.nextSibling;
|
||
if ( !hadBreak && child.nodeType == domUtils.NODE_ELEMENT && domUtils.isBlockElm( child ) ){
|
||
|
||
parent = domUtils.findParent( child,function ( node ){ return domUtils.isBlockElm( node ); } );
|
||
if ( parent && parent.tagName.toLowerCase() != 'body' && !(dtd[parent.tagName][child.nodeName] && child.parentNode === parent)){
|
||
if(!dtd[parent.tagName][child.nodeName]){
|
||
pre = parent;
|
||
}else{
|
||
tmp = child.parentNode;
|
||
while (tmp !== parent){
|
||
pre = tmp;
|
||
tmp = tmp.parentNode;
|
||
|
||
}
|
||
}
|
||
|
||
|
||
domUtils.breakParent( child, pre || tmp );
|
||
//去掉break后前一个多余的节点 <p>|<[p> ==> <p></p><div></div><p>|</p>
|
||
var pre = child.previousSibling;
|
||
domUtils.trimWhiteTextNode(pre);
|
||
if(!pre.childNodes.length){
|
||
domUtils.remove(pre);
|
||
}
|
||
//trace:2012,在非ie的情况,切开后剩下的节点有可能不能点入光标添加br占位
|
||
|
||
if(!browser.ie &&
|
||
(next = child.nextSibling) &&
|
||
domUtils.isBlockElm(next) &&
|
||
next.lastChild &&
|
||
!domUtils.isBr(next.lastChild)){
|
||
next.appendChild(me.document.createElement('br'));
|
||
}
|
||
hadBreak = 1;
|
||
}
|
||
}
|
||
var next = child.nextSibling;
|
||
if(!div.firstChild && next && domUtils.isBlockElm(next)){
|
||
|
||
range.setStart(next,0).collapse(true);
|
||
break;
|
||
}
|
||
range.setEndAfter( child ).collapse();
|
||
|
||
}
|
||
|
||
child = range.startContainer;
|
||
|
||
if(nextNode && domUtils.isBr(nextNode)){
|
||
domUtils.remove(nextNode)
|
||
}
|
||
//用chrome可能有空白展位符
|
||
if(domUtils.isBlockElm(child) && domUtils.isEmptyNode(child)){
|
||
if(nextNode = child.nextSibling){
|
||
domUtils.remove(child);
|
||
if(nextNode.nodeType == 1 && dtd.$block[nextNode.tagName]){
|
||
|
||
range.setStart(nextNode,0).collapse(true).shrinkBoundary()
|
||
}
|
||
}else{
|
||
|
||
try{
|
||
child.innerHTML = browser.ie ? domUtils.fillChar : '<br/>';
|
||
}catch(e){
|
||
range.setStartBefore(child);
|
||
domUtils.remove(child)
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
//加上true因为在删除表情等时会删两次,第一次是删的fillData
|
||
try{
|
||
if(browser.ie9below && range.startContainer.nodeType == 1 && !range.startContainer.childNodes[range.startOffset]){
|
||
var start = range.startContainer,pre = start.childNodes[range.startOffset-1];
|
||
if(pre && pre.nodeType == 1 && dtd.$empty[pre.tagName]){
|
||
var txt = this.document.createTextNode(domUtils.fillChar);
|
||
range.insertNode(txt).setStart(txt,0).collapse(true);
|
||
}
|
||
}
|
||
setTimeout(function(){
|
||
range.select(true);
|
||
})
|
||
|
||
}catch(e){}
|
||
|
||
|
||
setTimeout(function(){
|
||
range = me.selection.getRange();
|
||
range.scrollIntoView();
|
||
me.fireEvent('afterinserthtml');
|
||
},200);
|
||
}
|
||
};
|
||
|
||
///import core
|
||
///import plugins\inserthtml.js
|
||
///commands 插入图片,操作图片的对齐方式
|
||
///commandsName InsertImage,ImageNone,ImageLeft,ImageRight,ImageCenter
|
||
///commandsTitle 图片,默认,居左,居右,居中
|
||
///commandsDialog dialogs\image
|
||
/**
|
||
* Created by .
|
||
* User: zhanyi
|
||
* for image
|
||
*/
|
||
UM.commands['insertimage'] = {
|
||
execCommand:function (cmd, opt) {
|
||
opt = utils.isArray(opt) ? opt : [opt];
|
||
if (!opt.length) {
|
||
return;
|
||
}
|
||
var me = this;
|
||
var html = [], str = '', ci;
|
||
ci = opt[0];
|
||
if (opt.length == 1) {
|
||
str = '<img src="' + ci.src + '" ' + (ci._src ? ' _src="' + ci._src + '" ' : '') +
|
||
(ci.width ? 'width="' + ci.width + '" ' : '') +
|
||
(ci.height ? ' height="' + ci.height + '" ' : '') +
|
||
(ci['floatStyle'] == 'left' || ci['floatStyle'] == 'right' ? ' style="float:' + ci['floatStyle'] + ';"' : '') +
|
||
(ci.title && ci.title != "" ? ' title="' + ci.title + '"' : '') +
|
||
(ci.border && ci.border != "0" ? ' border="' + ci.border + '"' : '') +
|
||
(ci.alt && ci.alt != "" ? ' alt="' + ci.alt + '"' : '') +
|
||
(ci.hspace && ci.hspace != "0" ? ' hspace = "' + ci.hspace + '"' : '') +
|
||
(ci.vspace && ci.vspace != "0" ? ' vspace = "' + ci.vspace + '"' : '') + '/>';
|
||
if (ci['floatStyle'] == 'center') {
|
||
str = '<p style="text-align: center">' + str + '</p>';
|
||
}
|
||
html.push(str);
|
||
|
||
} else {
|
||
for (var i = 0; ci = opt[i++];) {
|
||
str = '<p ' + (ci['floatStyle'] == 'center' ? 'style="text-align: center" ' : '') + '><img src="' + ci.src + '" ' +
|
||
(ci.width ? 'width="' + ci.width + '" ' : '') + (ci._src ? ' _src="' + ci._src + '" ' : '') +
|
||
(ci.height ? ' height="' + ci.height + '" ' : '') +
|
||
' style="' + (ci['floatStyle'] && ci['floatStyle'] != 'center' ? 'float:' + ci['floatStyle'] + ';' : '') +
|
||
(ci.border || '') + '" ' +
|
||
(ci.title ? ' title="' + ci.title + '"' : '') + ' /></p>';
|
||
html.push(str);
|
||
}
|
||
}
|
||
|
||
me.execCommand('insertHtml', html.join(''), true);
|
||
}
|
||
};
|
||
///import core
|
||
///commands 段落格式,居左,居右,居中,两端对齐
|
||
///commandsName JustifyLeft,JustifyCenter,JustifyRight,JustifyJustify
|
||
///commandsTitle 居左对齐,居中对齐,居右对齐,两端对齐
|
||
/**
|
||
* @description 居左右中
|
||
* @name UM.execCommand
|
||
* @param {String} cmdName justify执行对齐方式的命令
|
||
* @param {String} align 对齐方式:left居左,right居右,center居中,justify两端对齐
|
||
* @author zhanyi
|
||
*/
|
||
UM.plugins['justify']=function(){
|
||
var me = this;
|
||
$.each('justifyleft justifyright justifycenter justifyfull'.split(' '),function(i,cmdName){
|
||
me.commands[cmdName] = {
|
||
execCommand:function (cmdName) {
|
||
return this.document.execCommand(cmdName);
|
||
},
|
||
queryCommandValue: function (cmdName) {
|
||
var val = this.document.queryCommandValue(cmdName);
|
||
return val === true || val === 'true' ? cmdName.replace(/justify/,'') : '';
|
||
},
|
||
queryCommandState: function (cmdName) {
|
||
return this.document.queryCommandState(cmdName) ? 1 : 0
|
||
}
|
||
};
|
||
})
|
||
};
|
||
|
||
///import core
|
||
///import plugins\removeformat.js
|
||
///commands 字体颜色,背景色,字号,字体,下划线,删除线
|
||
///commandsName ForeColor,BackColor,FontSize,FontFamily,Underline,StrikeThrough
|
||
///commandsTitle 字体颜色,背景色,字号,字体,下划线,删除线
|
||
/**
|
||
* @description 字体
|
||
* @name UM.execCommand
|
||
* @param {String} cmdName 执行的功能名称
|
||
* @param {String} value 传入的值
|
||
*/
|
||
UM.plugins['font'] = function () {
|
||
var me = this,
|
||
fonts = {
|
||
'forecolor': 'forecolor',
|
||
'backcolor': 'backcolor',
|
||
'fontsize': 'fontsize',
|
||
'fontfamily': 'fontname'
|
||
},
|
||
cmdNameToStyle = {
|
||
'forecolor': 'color',
|
||
'backcolor': 'background-color',
|
||
'fontsize': 'font-size',
|
||
'fontfamily': 'font-family'
|
||
},
|
||
cmdNameToAttr = {
|
||
'forecolor': 'color',
|
||
'fontsize': 'size',
|
||
'fontfamily': 'face'
|
||
};
|
||
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'},
|
||
{ name: 'sans-serif',val:'sans-serif'}
|
||
],
|
||
'fontsize': [10, 12, 16, 18,24, 32,48]
|
||
});
|
||
|
||
me.addOutputRule(function (root) {
|
||
utils.each(root.getNodesByTagName('font'), function (node) {
|
||
if (node.tagName == 'font') {
|
||
var cssStyle = [];
|
||
for (var p in node.attrs) {
|
||
switch (p) {
|
||
case 'size':
|
||
var val = node.attrs[p];
|
||
$.each({
|
||
'10':'1',
|
||
'12':'2',
|
||
'16':'3',
|
||
'18':'4',
|
||
'24':'5',
|
||
'32':'6',
|
||
'48':'7'
|
||
},function(k,v){
|
||
if(v == val){
|
||
val = k;
|
||
return false;
|
||
}
|
||
});
|
||
cssStyle.push('font-size:' + val + '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(';')
|
||
};
|
||
}
|
||
node.tagName = 'span';
|
||
if(node.parentNode.tagName == 'span' && node.parentNode.children.length == 1){
|
||
$.each(node.attrs,function(k,v){
|
||
|
||
node.parentNode.attrs[k] = k == 'style' ? node.parentNode.attrs[k] + v : v;
|
||
})
|
||
node.parentNode.removeChild(node,true);
|
||
}
|
||
});
|
||
});
|
||
for(var p in fonts){
|
||
(function (cmd) {
|
||
me.commands[cmd] = {
|
||
execCommand: function (cmdName,value) {
|
||
if(value == 'transparent'){
|
||
return;
|
||
}
|
||
var rng = this.selection.getRange();
|
||
if(rng.collapsed){
|
||
var span = $('<span></span>').css(cmdNameToStyle[cmdName],value)[0];
|
||
rng.insertNode(span).setStart(span,0).setCursor();
|
||
}else{
|
||
if(cmdName == 'fontsize'){
|
||
value = {
|
||
'10':'1',
|
||
'12':'2',
|
||
'16':'3',
|
||
'18':'4',
|
||
'24':'5',
|
||
'32':'6',
|
||
'48':'7'
|
||
}[(value+"").replace(/px/,'')]
|
||
}
|
||
this.document.execCommand(fonts[cmdName],false, value);
|
||
if(browser.gecko){
|
||
$.each(this.$body.find('a'),function(i,a){
|
||
var parent = a.parentNode;
|
||
if(parent.lastChild === parent.firstChild && /FONT|SPAN/.test(parent.tagName)){
|
||
var cloneNode = parent.cloneNode(false);
|
||
cloneNode.innerHTML = a.innerHTML;
|
||
$(a).html('').append(cloneNode).insertBefore(parent);
|
||
|
||
$(parent).remove();
|
||
}
|
||
});
|
||
}
|
||
if(!browser.ie){
|
||
var nativeRange = this.selection.getNative().getRangeAt(0);
|
||
var common = nativeRange.commonAncestorContainer;
|
||
var rng = this.selection.getRange(),
|
||
bk = rng.createBookmark(true);
|
||
|
||
$(common).find('a').each(function(i,n){
|
||
var parent = n.parentNode;
|
||
if(parent.nodeName == 'FONT'){
|
||
var font = parent.cloneNode(false);
|
||
font.innerHTML = n.innerHTML;
|
||
$(n).html('').append(font);
|
||
}
|
||
});
|
||
rng.moveToBookmark(bk).select()
|
||
}
|
||
return true
|
||
}
|
||
|
||
},
|
||
queryCommandValue: function (cmdName) {
|
||
var start = me.selection.getStart();
|
||
var val = $(start).css(cmdNameToStyle[cmdName]);
|
||
if(val === undefined){
|
||
val = $(start).attr(cmdNameToAttr[cmdName])
|
||
}
|
||
return val ? utils.fixColor(cmdName,val).replace(/px/,'') : '';
|
||
},
|
||
queryCommandState: function (cmdName) {
|
||
return this.queryCommandValue(cmdName)
|
||
}
|
||
};
|
||
})(p);
|
||
}
|
||
};
|
||
///import core
|
||
///commands 超链接,取消链接
|
||
///commandsName Link,Unlink
|
||
///commandsTitle 超链接,取消链接
|
||
///commandsDialog dialogs\link
|
||
/**
|
||
* 超链接
|
||
* @function
|
||
* @name UM.execCommand
|
||
* @param {String} cmdName link插入超链接
|
||
* @param {Object} options url地址,title标题,target是否打开新页
|
||
* @author zhanyi
|
||
*/
|
||
/**
|
||
* 取消链接
|
||
* @function
|
||
* @name UM.execCommand
|
||
* @param {String} cmdName unlink取消链接
|
||
* @author zhanyi
|
||
*/
|
||
|
||
UM.plugins['link'] = function(){
|
||
var me = this;
|
||
|
||
me.setOpt('autourldetectinie',false);
|
||
//在ie下禁用autolink
|
||
if(browser.ie && this.options.autourldetectinie === false){
|
||
this.addListener('keyup',function(cmd,evt){
|
||
var me = this,keyCode = evt.keyCode;
|
||
if(keyCode == 13 || keyCode == 32){
|
||
var rng = me.selection.getRange();
|
||
var start = rng.startContainer;
|
||
if(keyCode == 13){
|
||
if(start.nodeName == 'P'){
|
||
var pre = start.previousSibling;
|
||
if(pre && pre.nodeType == 1){
|
||
var pre = pre.lastChild;
|
||
if(pre && pre.nodeName == 'A' && !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);
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
|
||
});
|
||
}
|
||
|
||
this.addOutputRule(function(root){
|
||
$.each(root.getNodesByTagName('a'),function(i,a){
|
||
var _href = utils.html(a.getAttr('_href'));
|
||
if(!/^(ftp|https?|\/|file)/.test(_href)){
|
||
_href = 'http://' + _href;
|
||
}
|
||
a.setAttr('href', _href);
|
||
a.setAttr('_href')
|
||
if(a.getAttr('title')==''){
|
||
a.setAttr('title')
|
||
}
|
||
})
|
||
});
|
||
this.addInputRule(function(root){
|
||
$.each(root.getNodesByTagName('a'),function(i,a){
|
||
a.setAttr('_href', utils.html(a.getAttr('href')));
|
||
})
|
||
});
|
||
me.commands['link'] = {
|
||
execCommand : function( cmdName, opt ) {
|
||
|
||
var me = this;
|
||
var rng = me.selection.getRange();
|
||
if(rng.collapsed){
|
||
var start = rng.startContainer;
|
||
if(start = domUtils.findParentByTagName(start,'a',true)){
|
||
$(start).attr(opt);
|
||
rng.selectNode(start).select()
|
||
}else{
|
||
rng.insertNode($('<a>' +opt.href+'</a>').attr(opt)[0]).select()
|
||
|
||
}
|
||
|
||
}else{
|
||
me.document.execCommand('createlink',false,'_umeditor_link');
|
||
utils.each(domUtils.getElementsByTagName(me.body,'a',function(n){
|
||
|
||
return n.getAttribute('href') == '_umeditor_link'
|
||
}),function(l){
|
||
if($(l).text() == '_umeditor_link'){
|
||
$(l).text(opt.href);
|
||
}
|
||
domUtils.setAttributes(l,opt);
|
||
rng.selectNode(l).select()
|
||
})
|
||
}
|
||
|
||
},
|
||
queryCommandState:function(){
|
||
return this.queryCommandValue('link') ? 1 : 0;
|
||
},
|
||
queryCommandValue:function(){
|
||
var path = this.selection.getStartElementPath();
|
||
var result;
|
||
$.each(path,function(i,n){
|
||
if(n.nodeName == "A"){
|
||
result = n;
|
||
return false;
|
||
}
|
||
})
|
||
return result;
|
||
}
|
||
};
|
||
me.commands['unlink'] = {
|
||
execCommand : function() {
|
||
this.document.execCommand('unlink');
|
||
}
|
||
};
|
||
};
|
||
///import core
|
||
///commands 打印
|
||
///commandsName Print
|
||
///commandsTitle 打印
|
||
/**
|
||
* @description 打印
|
||
* @name baidu.editor.execCommand
|
||
* @param {String} cmdName print打印编辑器内容
|
||
* @author zhanyi
|
||
*/
|
||
UM.commands['print'] = {
|
||
execCommand : function(){
|
||
var me = this,
|
||
id = 'editor_print_' + +new Date();
|
||
|
||
$('<iframe src="" id="' + id + '" name="' + id + '" frameborder="0"></iframe>').attr('id', id)
|
||
.css({
|
||
width:'0px',
|
||
height:'0px',
|
||
'overflow':'hidden',
|
||
'float':'left',
|
||
'position':'absolute',
|
||
top:'-10000px',
|
||
left:'-10000px'
|
||
})
|
||
.appendTo(me.$container.find('.edui-dialog-container'));
|
||
|
||
var w = window.open('', id, ''),
|
||
d = w.document;
|
||
d.open();
|
||
d.write('<html><head></head><body><div>'+this.getContent(null,null,true)+'</div><script>' +
|
||
"setTimeout(function(){" +
|
||
"window.print();" +
|
||
"setTimeout(function(){" +
|
||
"window.parent.$('#" + id + "').remove();" +
|
||
"},100);" +
|
||
"},200);" +
|
||
'</script></body></html>');
|
||
d.close();
|
||
},
|
||
notNeedUndo : 1
|
||
};
|
||
///import core
|
||
///commands 格式
|
||
///commandsName Paragraph
|
||
///commandsTitle 段落格式
|
||
/**
|
||
* 段落样式
|
||
* @function
|
||
* @name UM.execCommand
|
||
* @param {String} cmdName paragraph插入段落执行命令
|
||
* @param {String} style 标签值为:'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
|
||
* @param {String} attrs 标签的属性
|
||
* @author zhanyi
|
||
*/
|
||
UM.plugins['paragraph'] = function() {
|
||
var me = this;
|
||
me.setOpt('paragraph',{'p':'', 'h1':'', 'h2':'', 'h3':'', 'h4':'', 'h5':'', 'h6':''});
|
||
me.commands['paragraph'] = {
|
||
execCommand : function( cmdName, style ) {
|
||
return this.document.execCommand('formatBlock',false,'<' + style + '>');
|
||
},
|
||
queryCommandValue : function() {
|
||
try{
|
||
var val = this.document.queryCommandValue('formatBlock')
|
||
}catch(e){
|
||
}
|
||
return val ;
|
||
}
|
||
};
|
||
};
|
||
|
||
///import core
|
||
///import plugins\inserthtml.js
|
||
///commands 分割线
|
||
///commandsName Horizontal
|
||
///commandsTitle 分隔线
|
||
/**
|
||
* 分割线
|
||
* @function
|
||
* @name UM.execCommand
|
||
* @param {String} cmdName horizontal插入分割线
|
||
*/
|
||
UM.plugins['horizontal'] = function(){
|
||
var me = this;
|
||
me.commands['horizontal'] = {
|
||
execCommand : function( ) {
|
||
this.document.execCommand('insertHorizontalRule');
|
||
var rng = me.selection.getRange().txtToElmBoundary(true),
|
||
start = rng.startContainer;
|
||
if(domUtils.isBody(rng.startContainer)){
|
||
var next = rng.startContainer.childNodes[rng.startOffset];
|
||
if(!next){
|
||
next = $('<p></p>').appendTo(rng.startContainer).html(browser.ie ? ' ' : '<br/>')[0]
|
||
}
|
||
rng.setStart(next,0).setCursor()
|
||
}else{
|
||
|
||
while(dtd.$inline[start.tagName] && start.lastChild === start.firstChild){
|
||
|
||
var parent = start.parentNode;
|
||
parent.appendChild(start.firstChild);
|
||
parent.removeChild(start);
|
||
start = parent;
|
||
}
|
||
while(dtd.$inline[start.tagName]){
|
||
start = start.parentNode;
|
||
}
|
||
if(start.childNodes.length == 1 && start.lastChild.nodeName == 'HR'){
|
||
var hr = start.lastChild;
|
||
$(hr).insertBefore(start);
|
||
rng.setStart(start,0).setCursor();
|
||
}else{
|
||
hr = $('hr',start)[0];
|
||
domUtils.breakParent(hr,start);
|
||
var pre = hr.previousSibling;
|
||
if(pre && domUtils.isEmptyBlock(pre)){
|
||
$(pre).remove()
|
||
}
|
||
rng.setStart(hr.nextSibling,0).setCursor();
|
||
}
|
||
|
||
}
|
||
}
|
||
};
|
||
|
||
};
|
||
|
||
|
||
///import core
|
||
///commands 清空文档
|
||
///commandsName ClearDoc
|
||
///commandsTitle 清空文档
|
||
/**
|
||
*
|
||
* 清空文档
|
||
* @function
|
||
* @name UM.execCommand
|
||
* @param {String} cmdName cleardoc清空文档
|
||
*/
|
||
|
||
UM.commands['cleardoc'] = {
|
||
execCommand : function() {
|
||
var me = this,
|
||
range = me.selection.getRange();
|
||
me.body.innerHTML = "<p>"+(ie ? "" : "<br/>")+"</p>";
|
||
range.setStart(me.body.firstChild,0).setCursor(false,true);
|
||
setTimeout(function(){
|
||
me.fireEvent("clearDoc");
|
||
},0);
|
||
|
||
}
|
||
};
|
||
|
||
|
||
///import core
|
||
///commands 撤销和重做
|
||
///commandsName Undo,Redo
|
||
///commandsTitle 撤销,重做
|
||
/**
|
||
* @description 回退
|
||
* @author zhanyi
|
||
*/
|
||
|
||
UM.plugins['undo'] = function () {
|
||
var saveSceneTimer;
|
||
var me = this,
|
||
maxUndoCount = me.options.maxUndoCount || 20,
|
||
maxInputCount = me.options.maxInputCount || 20,
|
||
fillchar = new RegExp(domUtils.fillChar + '|<\/hr>', 'gi');// ie会产生多余的</hr>
|
||
var noNeedFillCharTags = {
|
||
ol:1,ul:1,table:1,tbody:1,tr:1,body:1
|
||
};
|
||
var orgState = me.options.autoClearEmptyNode;
|
||
function compareAddr(indexA, indexB) {
|
||
if (indexA.length != indexB.length)
|
||
return 0;
|
||
for (var i = 0, l = indexA.length; i < l; i++) {
|
||
if (indexA[i] != indexB[i])
|
||
return 0
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
function compareRangeAddress(rngAddrA, rngAddrB) {
|
||
if (rngAddrA.collapsed != rngAddrB.collapsed) {
|
||
return 0;
|
||
}
|
||
if (!compareAddr(rngAddrA.startAddress, rngAddrB.startAddress) || !compareAddr(rngAddrA.endAddress, rngAddrB.endAddress)) {
|
||
return 0;
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
function UndoManager() {
|
||
this.list = [];
|
||
this.index = 0;
|
||
this.hasUndo = false;
|
||
this.hasRedo = false;
|
||
this.undo = function () {
|
||
if (this.hasUndo) {
|
||
if (!this.list[this.index - 1] && this.list.length == 1) {
|
||
this.reset();
|
||
return;
|
||
}
|
||
while (this.list[this.index].content == this.list[this.index - 1].content) {
|
||
this.index--;
|
||
if (this.index == 0) {
|
||
return this.restore(0);
|
||
}
|
||
}
|
||
this.restore(--this.index);
|
||
}
|
||
};
|
||
this.redo = function () {
|
||
if (this.hasRedo) {
|
||
while (this.list[this.index].content == this.list[this.index + 1].content) {
|
||
this.index++;
|
||
if (this.index == this.list.length - 1) {
|
||
return this.restore(this.index);
|
||
}
|
||
}
|
||
this.restore(++this.index);
|
||
}
|
||
};
|
||
|
||
this.restore = function () {
|
||
var me = this.editor;
|
||
var scene = this.list[this.index];
|
||
var root = UM.htmlparser(scene.content.replace(fillchar, ''));
|
||
me.options.autoClearEmptyNode = false;
|
||
me.filterInputRule(root,true);
|
||
me.options.autoClearEmptyNode = orgState;
|
||
//trace:873
|
||
//去掉展位符
|
||
me.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,me.body).moveToAddress(scene.address);
|
||
if(browser.ie && rng.collapsed && rng.startContainer.nodeType == 1){
|
||
var tmpNode = rng.startContainer.childNodes[rng.startOffset];
|
||
if( !tmpNode || tmpNode.nodeType == 1 && dtd.$empty[tmpNode]){
|
||
rng.insertNode(me.document.createTextNode(' ')).collapse(true);
|
||
}
|
||
}
|
||
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 = UM.htmlparser(me.body.innerHTML,true);
|
||
me.options.autoClearEmptyNode = false;
|
||
me.filterOutputRule(root,true);
|
||
me.options.autoClearEmptyNode = orgState;
|
||
var cont = root.toHtml();
|
||
browser.ie && (cont = cont.replace(/> </g, '><').replace(/\s*</g, '<').replace(/>\s*/g, '>'));
|
||
me.fireEvent('aftergetscene');
|
||
return {
|
||
address:rngAddress,
|
||
content:cont
|
||
}
|
||
};
|
||
this.save = function (notCompareRange,notSetCursor) {
|
||
clearTimeout(saveSceneTimer);
|
||
var currentScene = this.getScene(notSetCursor),
|
||
lastScene = this.list[this.index];
|
||
//内容相同位置相同不存
|
||
if (lastScene && lastScene.content == currentScene.content &&
|
||
( 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 () {
|
||
$(this.body).on('compositionstart', function () {
|
||
inputType = true;
|
||
}).on('compositionend', function () {
|
||
inputType = false;
|
||
})
|
||
});
|
||
//快捷键
|
||
me.addshortcutkey({
|
||
"Undo":"ctrl+90", //undo
|
||
"Redo":"ctrl+89,shift+ctrl+z" //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){
|
||
|
||
if (cont.selection.getRange().collapsed)
|
||
cont.fireEvent('contentchange');
|
||
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;
|
||
}
|
||
}
|
||
});
|
||
|
||
};
|
||
|
||
///import core
|
||
///import plugins/inserthtml.js
|
||
///import plugins/undo.js
|
||
///import plugins/serialize.js
|
||
///commands 粘贴
|
||
///commandsName PastePlain
|
||
///commandsTitle 纯文本粘贴模式
|
||
/**
|
||
* @description 粘贴
|
||
* @author zhanyi
|
||
*/
|
||
UM.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));
|
||
this.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:" +
|
||
//要在现在光标平行的位置加入,否则会出现跳动的问题
|
||
$(bk.start).position().top + '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;
|
||
|
||
|
||
function filter(div) {
|
||
var html;
|
||
if (div.firstChild) {
|
||
//去掉cut中添加的边界值
|
||
var nodes = domUtils.getElementsByTagName(div, 'span');
|
||
for (var i = 0, ni; ni = nodes[i++];) {
|
||
if (ni.id == '_baidu_cut_start' || ni.id == '_baidu_cut_end') {
|
||
domUtils.remove(ni);
|
||
}
|
||
}
|
||
|
||
if (browser.webkit) {
|
||
|
||
var brs = div.querySelectorAll('div br');
|
||
for (var i = 0, bi; bi = brs[i++];) {
|
||
var pN = bi.parentNode;
|
||
if (pN.tagName == 'DIV' && pN.childNodes.length == 1) {
|
||
pN.innerHTML = '<p><br/></p>';
|
||
domUtils.remove(pN);
|
||
}
|
||
}
|
||
var divs = div.querySelectorAll('#baidu_pastebin');
|
||
for (var i = 0, di; di = divs[i++];) {
|
||
var tmpP = me.document.createElement('p');
|
||
di.parentNode.insertBefore(tmpP, di);
|
||
while (di.firstChild) {
|
||
tmpP.appendChild(di.firstChild);
|
||
}
|
||
domUtils.remove(di);
|
||
}
|
||
|
||
var metas = div.querySelectorAll('meta');
|
||
for (var i = 0, ci; ci = metas[i++];) {
|
||
domUtils.remove(ci);
|
||
}
|
||
|
||
var brs = div.querySelectorAll('br');
|
||
for (i = 0; ci = brs[i++];) {
|
||
if (/^apple-/i.test(ci.className)) {
|
||
domUtils.remove(ci);
|
||
}
|
||
}
|
||
}
|
||
if (browser.gecko) {
|
||
var dirtyNodes = div.querySelectorAll('[_moz_dirty]');
|
||
for (i = 0; ci = dirtyNodes[i++];) {
|
||
ci.removeAttribute('_moz_dirty');
|
||
}
|
||
}
|
||
if (!browser.ie) {
|
||
var spans = div.querySelectorAll('span.Apple-style-span');
|
||
for (var i = 0, ci; ci = spans[i++];) {
|
||
domUtils.remove(ci, true);
|
||
}
|
||
}
|
||
|
||
//ie下使用innerHTML会产生多余的\r\n字符,也会产生 这里过滤掉
|
||
html = div.innerHTML;//.replace(/>(?:(\s| )*?)</g,'><');
|
||
|
||
//过滤word粘贴过来的冗余属性
|
||
html = UM.filterWord(html);
|
||
//取消了忽略空白的第二个参数,粘贴过来的有些是有空白的,会被套上相关的标签
|
||
var root = UM.htmlparser(html);
|
||
//如果给了过滤规则就先进行过滤
|
||
if (me.options.filterRules) {
|
||
UM.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)
|
||
}
|
||
})
|
||
}
|
||
html = {'html': root.toHtml()};
|
||
me.fireEvent('beforepaste', html, root);
|
||
//抢了默认的粘贴,那后边的内容就不执行了,比如表格粘贴
|
||
if(!html.html){
|
||
return;
|
||
}
|
||
|
||
me.execCommand('insertHtml', html.html, true);
|
||
me.fireEvent("afterpaste", html);
|
||
}
|
||
}
|
||
|
||
|
||
me.addListener('ready', function () {
|
||
$(me.body).on( 'cut', function () {
|
||
var range = me.selection.getRange();
|
||
if (!range.collapsed && me.undoManger) {
|
||
me.undoManger.save();
|
||
}
|
||
}).on(browser.ie || browser.opera ? 'keydown' : 'paste', function (e) {
|
||
//ie下beforepaste在点击右键时也会触发,所以用监控键盘才处理
|
||
if ((browser.ie || browser.opera) && ((!e.ctrlKey && !e.metaKey) || e.keyCode != '86')) {
|
||
return;
|
||
}
|
||
getClipboardData.call(me, function (div) {
|
||
filter(div);
|
||
});
|
||
});
|
||
|
||
});
|
||
};
|
||
|
||
|
||
///import core
|
||
///commands 有序列表,无序列表
|
||
///commandsName InsertOrderedList,InsertUnorderedList
|
||
///commandsTitle 有序列表,无序列表
|
||
/**
|
||
* 有序列表
|
||
* @function
|
||
* @name UM.execCommand
|
||
* @param {String} cmdName insertorderlist插入有序列表
|
||
* @param {String} style 值为:decimal,lower-alpha,lower-roman,upper-alpha,upper-roman
|
||
* @author zhanyi
|
||
*/
|
||
/**
|
||
* 无序链接
|
||
* @function
|
||
* @name UM.execCommand
|
||
* @param {String} cmdName insertunorderlist插入无序列表
|
||
* * @param {String} style 值为:circle,disc,square
|
||
* @author zhanyi
|
||
*/
|
||
|
||
UM.plugins['list'] = function () {
|
||
var me = this;
|
||
|
||
me.setOpt( {
|
||
'insertorderedlist':{
|
||
'decimal':'',
|
||
'lower-alpha':'',
|
||
'lower-roman':'',
|
||
'upper-alpha':'',
|
||
'upper-roman':''
|
||
},
|
||
'insertunorderedlist':{
|
||
'circle':'',
|
||
'disc':'',
|
||
'square':''
|
||
}
|
||
} );
|
||
|
||
this.addInputRule(function(root){
|
||
utils.each(root.getNodesByTagName('li'), function (node) {
|
||
if(node.children.length == 0){
|
||
node.parentNode.removeChild(node);
|
||
}
|
||
})
|
||
});
|
||
me.commands['insertorderedlist'] =
|
||
me.commands['insertunorderedlist'] = {
|
||
execCommand:function (cmdName) {
|
||
this.document.execCommand(cmdName);
|
||
var rng = this.selection.getRange(),
|
||
bk = rng.createBookmark(true);
|
||
|
||
this.$body.find('ol,ul').each(function(i,n){
|
||
var parent = n.parentNode;
|
||
if(parent.tagName == 'P' && parent.lastChild === parent.firstChild){
|
||
$(n).children().each(function(j,li){
|
||
var p = parent.cloneNode(false);
|
||
$(p).append(li.innerHTML);
|
||
$(li).html('').append(p);
|
||
});
|
||
$(n).insertBefore(parent);
|
||
$(parent).remove();
|
||
}
|
||
|
||
if(dtd.$inline[parent.tagName]){
|
||
if(parent.tagName == 'SPAN'){
|
||
|
||
$(n).children().each(function(k,li){
|
||
var span = parent.cloneNode(false);
|
||
if(li.firstChild.nodeName != 'P'){
|
||
|
||
while(li.firstChild){
|
||
span.appendChild(li.firstChild)
|
||
};
|
||
$('<p></p>').appendTo(li).append(span);
|
||
}else{
|
||
while(li.firstChild){
|
||
span.appendChild(li.firstChild)
|
||
};
|
||
$(li.firstChild).append(span);
|
||
}
|
||
})
|
||
|
||
}
|
||
domUtils.remove(parent,true)
|
||
}
|
||
});
|
||
|
||
|
||
|
||
|
||
rng.moveToBookmark(bk).select();
|
||
return true;
|
||
},
|
||
queryCommandState:function (cmdName) {
|
||
return this.document.queryCommandState(cmdName);
|
||
}
|
||
};
|
||
};
|
||
|
||
|
||
///import core
|
||
///import plugins/serialize.js
|
||
///import plugins/undo.js
|
||
///commands 查看源码
|
||
///commandsName Source
|
||
///commandsTitle 查看源码
|
||
(function (){
|
||
var sourceEditors = {
|
||
textarea: function (editor, holder){
|
||
var textarea = holder.ownerDocument.createElement('textarea');
|
||
textarea.style.cssText = 'resize:none;border:0;padding:0;margin:0;overflow-y:auto;outline:0';
|
||
// 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 {
|
||
container : textarea,
|
||
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;
|
||
}
|
||
};
|
||
}
|
||
};
|
||
|
||
UM.plugins['source'] = function (){
|
||
var me = this;
|
||
var opt = this.options;
|
||
var sourceMode = false;
|
||
var sourceEditor;
|
||
|
||
opt.sourceEditor = 'textarea';
|
||
|
||
me.setOpt({
|
||
sourceEditorFirst:false
|
||
});
|
||
function createSourceEditor(holder){
|
||
return sourceEditors.textarea(me, holder);
|
||
}
|
||
|
||
var bakCssText;
|
||
//解决在源码模式下getContent不能得到最新的内容问题
|
||
var oldGetContent = me.getContent,
|
||
bakAddress;
|
||
|
||
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.body.style.cssText;
|
||
me.body.style.cssText += ';position:absolute;left:-32768px;top:-32768px;';
|
||
|
||
|
||
me.fireEvent('beforegetcontent');
|
||
var root = UM.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.body.parentNode);
|
||
|
||
sourceEditor.setContent(content);
|
||
|
||
var getStyleValue=function(attr){
|
||
return parseInt($(me.body).css(attr));
|
||
};
|
||
$(sourceEditor.container).width($(me.body).width()+getStyleValue("padding-left")+getStyleValue("padding-right"))
|
||
.height($(me.body).height());
|
||
setTimeout(function (){
|
||
sourceEditor.select();
|
||
});
|
||
//重置getContent,源码模式下取值也能是最新的数据
|
||
me.getContent = function (){
|
||
return sourceEditor.getContent() || '<p>' + (browser.ie ? '' : '<br/>')+'</p>';
|
||
};
|
||
} else {
|
||
me.$body.css({
|
||
'position':'',
|
||
'left':'',
|
||
'top':''
|
||
});
|
||
// me.body.style.cssText = bakCssText;
|
||
var cont = sourceEditor.getContent() || '<p>' + (browser.ie ? '' : '<br/>')+'</p>';
|
||
//处理掉block节点前后的空格,有可能会误命中,暂时不考虑
|
||
cont = cont.replace(new RegExp('[\\r\\t\\n ]*<\/?(\\w+)\\s*(?:[^>]*)>','g'), function(a,b){
|
||
if(b && !dtd.$inlineWithA[b.toLowerCase()]){
|
||
return a.replace(/(^[\n\r\t ]*)|([\n\r\t ]*$)/g,'');
|
||
}
|
||
return a.replace(/(^[\n\r\t]*)|([\n\r\t]*$)/g,'')
|
||
});
|
||
me.setContent(cont);
|
||
sourceEditor.dispose();
|
||
sourceEditor = null;
|
||
//还原getContent方法
|
||
me.getContent = oldGetContent;
|
||
var first = me.body.firstChild;
|
||
//trace:1106 都删除空了,下边会报错,所以补充一个p占位
|
||
if(!first){
|
||
me.body.innerHTML = '<p>'+(browser.ie?'':'<br/>')+'</p>';
|
||
}
|
||
//要在ifm为显示时ff才能取到selection,否则报错
|
||
//这里不能比较位置了
|
||
me.undoManger && me.undoManger.save(true);
|
||
if(browser.gecko){
|
||
me.body.contentEditable = true;
|
||
}
|
||
try{
|
||
me.selection.getRange().moveToAddress(bakAddress).select();
|
||
}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
|
||
} ? oldQueryCommandState.apply(this, arguments) : -1
|
||
}
|
||
return oldQueryCommandState.apply(this, arguments);
|
||
};
|
||
|
||
};
|
||
|
||
})();
|
||
///import core
|
||
///import plugins/undo.js
|
||
///commands 设置回车标签p或br
|
||
///commandsName EnterKey
|
||
///commandsTitle 设置回车标签p或br
|
||
/**
|
||
* @description 处理回车
|
||
* @author zhanyi
|
||
*/
|
||
UM.plugins['enterkey'] = function() {
|
||
var hTag,
|
||
me = this,
|
||
tag = me.options.enterTag;
|
||
me.addListener('keyup', function(type, evt) {
|
||
|
||
var keyCode = evt.keyCode || evt.which;
|
||
if (keyCode == 13) {
|
||
var range = me.selection.getRange(),
|
||
start = range.startContainer,
|
||
doSave;
|
||
|
||
//修正在h1-h6里边回车后不能嵌套p的问题
|
||
if (!browser.ie) {
|
||
|
||
if (/h\d/i.test(hTag)) {
|
||
if (browser.gecko) {
|
||
var h = domUtils.findParentByTagName(start, [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6','blockquote','caption','table'], true);
|
||
if (!h) {
|
||
me.document.execCommand('formatBlock', false, '<p>');
|
||
doSave = 1;
|
||
}
|
||
} else {
|
||
//chrome remove div
|
||
if (start.nodeType == 1) {
|
||
var tmp = me.document.createTextNode(''),div;
|
||
range.insertNode(tmp);
|
||
div = domUtils.findParentByTagName(tmp, 'div', true);
|
||
if (div) {
|
||
var p = me.document.createElement('p');
|
||
while (div.firstChild) {
|
||
p.appendChild(div.firstChild);
|
||
}
|
||
div.parentNode.insertBefore(p, div);
|
||
domUtils.remove(div);
|
||
range.setStartBefore(tmp).setCursor();
|
||
doSave = 1;
|
||
}
|
||
domUtils.remove(tmp);
|
||
|
||
}
|
||
}
|
||
|
||
if (me.undoManger && doSave) {
|
||
me.undoManger.save();
|
||
}
|
||
}
|
||
//没有站位符,会出现多行的问题
|
||
browser.opera && range.select();
|
||
}else{
|
||
me.fireEvent('saveScene',true,true)
|
||
}
|
||
}
|
||
});
|
||
|
||
me.addListener('keydown', function(type, evt) {
|
||
var keyCode = evt.keyCode || evt.which;
|
||
if (keyCode == 13) {//回车
|
||
if(me.fireEvent('beforeenterkeydown')){
|
||
domUtils.preventDefault(evt);
|
||
return;
|
||
}
|
||
me.fireEvent('saveScene',true,true);
|
||
hTag = '';
|
||
|
||
|
||
var range = me.selection.getRange();
|
||
|
||
if (!range.collapsed) {
|
||
//跨td不能删
|
||
var start = range.startContainer,
|
||
end = range.endContainer,
|
||
startTd = domUtils.findParentByTagName(start, 'td', true),
|
||
endTd = domUtils.findParentByTagName(end, 'td', true);
|
||
if (startTd && endTd && startTd !== endTd || !startTd && endTd || startTd && !endTd) {
|
||
evt.preventDefault ? evt.preventDefault() : ( evt.returnValue = false);
|
||
return;
|
||
}
|
||
}
|
||
if (tag == 'p') {
|
||
|
||
|
||
if (!browser.ie) {
|
||
|
||
start = domUtils.findParentByTagName(range.startContainer, ['ol','ul','p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6','blockquote','caption'], true);
|
||
|
||
//opera下执行formatblock会在table的场景下有问题,回车在opera原生支持很好,所以暂时在opera去掉调用这个原生的command
|
||
//trace:2431
|
||
if (!start && !browser.opera) {
|
||
|
||
me.document.execCommand('formatBlock', false, '<p>');
|
||
|
||
if (browser.gecko) {
|
||
range = me.selection.getRange();
|
||
start = domUtils.findParentByTagName(range.startContainer, 'p', true);
|
||
start && domUtils.removeDirtyAttr(start);
|
||
}
|
||
|
||
|
||
} else {
|
||
hTag = start.tagName;
|
||
start.tagName.toLowerCase() == 'p' && browser.gecko && domUtils.removeDirtyAttr(start);
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
});
|
||
|
||
browser.ie && me.addListener('setDisabled',function(){
|
||
$(me.body).find('p').each(function(i,p){
|
||
if(domUtils.isEmptyBlock(p)){
|
||
p.innerHTML = ' '
|
||
}
|
||
})
|
||
})
|
||
};
|
||
|
||
///import core
|
||
///commands 预览
|
||
///commandsName Preview
|
||
///commandsTitle 预览
|
||
/**
|
||
* 预览
|
||
* @function
|
||
* @name UM.execCommand
|
||
* @param {String} cmdName preview预览编辑器内容
|
||
*/
|
||
UM.commands['preview'] = {
|
||
execCommand : function(){
|
||
var w = window.open('', '_blank', ''),
|
||
d = w.document,
|
||
c = this.getContent(null,null,true),
|
||
path = this.getOpt('UMEDITOR_HOME_URL'),
|
||
formula = c.indexOf('mathquill-embedded-latex')!=-1 ?
|
||
'<link rel="stylesheet" href="' + path + 'third-party/mathquill/mathquill.css"/>' +
|
||
'<script src="' + path + 'third-party/jquery.min.js"></script>' +
|
||
'<script src="' + path + 'third-party/mathquill/mathquill.min.js"></script>':'';
|
||
d.open();
|
||
d.write('<html><head>' + formula + '</head><body><div>'+c+'</div></body></html>');
|
||
d.close();
|
||
},
|
||
notNeedUndo : 1
|
||
};
|
||
|
||
///import core
|
||
///commands 加粗,斜体,上标,下标
|
||
///commandsName Bold,Italic,Subscript,Superscript
|
||
///commandsTitle 加粗,加斜,下标,上标
|
||
/**
|
||
* b u i等基础功能实现
|
||
* @function
|
||
* @name UM.execCommands
|
||
* @param {String} cmdName bold加粗。italic斜体。subscript上标。superscript下标。
|
||
*/
|
||
UM.plugins['basestyle'] = function(){
|
||
var basestyles = ['bold','underline','superscript','subscript','italic','strikethrough'],
|
||
me = this;
|
||
//添加快捷键
|
||
me.addshortcutkey({
|
||
"Bold" : "ctrl+66",//^B
|
||
"Italic" : "ctrl+73", //^I
|
||
"Underline" : "ctrl+shift+85",//^U
|
||
"strikeThrough" : 'ctrl+shift+83' //^s
|
||
});
|
||
//过滤最后的产出数据
|
||
me.addOutputRule(function(root){
|
||
$.each(root.getNodesByTagName('b i u strike s'),function(i,node){
|
||
switch (node.tagName){
|
||
case 'b':
|
||
node.tagName = 'strong';
|
||
break;
|
||
case 'i':
|
||
node.tagName = 'em';
|
||
break;
|
||
case 'u':
|
||
node.tagName = 'span';
|
||
node.setStyle('text-decoration','underline');
|
||
break;
|
||
case 's':
|
||
case 'strike':
|
||
node.tagName = 'span';
|
||
node.setStyle('text-decoration','line-through')
|
||
}
|
||
});
|
||
});
|
||
$.each(basestyles,function(i,cmd){
|
||
me.commands[cmd] = {
|
||
execCommand : function( cmdName ) {
|
||
var rng = this.selection.getRange();
|
||
if(rng.collapsed && this.queryCommandState(cmdName) != 1){
|
||
var node = this.document.createElement({
|
||
'bold':'strong',
|
||
'underline':'u',
|
||
'superscript':'sup',
|
||
'subscript':'sub',
|
||
'italic':'em',
|
||
'strikethrough':'strike'
|
||
}[cmdName]);
|
||
rng.insertNode(node).setStart(node,0).setCursor(false);
|
||
return true;
|
||
}else{
|
||
return this.document.execCommand(cmdName)
|
||
}
|
||
|
||
},
|
||
queryCommandState : function(cmdName) {
|
||
if(browser.gecko){
|
||
return this.document.queryCommandState(cmdName)
|
||
}
|
||
var path = this.selection.getStartElementPath(),result = false;
|
||
$.each(path,function(i,n){
|
||
switch (cmdName){
|
||
case 'bold':
|
||
if(n.nodeName == 'STRONG' || n.nodeName == 'B'){
|
||
result = 1;
|
||
return false;
|
||
}
|
||
break;
|
||
case 'underline':
|
||
if(n.nodeName == 'U' || n.nodeName == 'SPAN' && $(n).css('text-decoration') == 'underline'){
|
||
result = 1;
|
||
return false;
|
||
}
|
||
break;
|
||
case 'superscript':
|
||
if(n.nodeName == 'SUP'){
|
||
result = 1;
|
||
return false;
|
||
}
|
||
break;
|
||
case 'subscript':
|
||
if(n.nodeName == 'SUB'){
|
||
result = 1;
|
||
return false;
|
||
}
|
||
break;
|
||
case 'italic':
|
||
if(n.nodeName == 'EM' || n.nodeName == 'I'){
|
||
result = 1;
|
||
return false;
|
||
}
|
||
break;
|
||
case 'strikethrough':
|
||
if(n.nodeName == 'S' || n.nodeName == 'STRIKE' || n.nodeName == 'SPAN' && $(n).css('text-decoration') == 'line-through'){
|
||
result = 1;
|
||
return false;
|
||
}
|
||
break;
|
||
}
|
||
})
|
||
return result
|
||
}
|
||
};
|
||
})
|
||
};
|
||
|
||
|
||
///import core
|
||
///import plugins/inserthtml.js
|
||
///commands 视频
|
||
///commandsName InsertVideo
|
||
///commandsTitle 插入视频
|
||
///commandsDialog dialogs\video
|
||
UM.plugins['video'] = function (){
|
||
var me =this,
|
||
div;
|
||
|
||
/**
|
||
* 创建插入视频字符窜
|
||
* @param url 视频地址
|
||
* @param width 视频宽度
|
||
* @param height 视频高度
|
||
* @param align 视频对齐
|
||
* @param toEmbed 是否以flash代替显示
|
||
* @param addParagraph 是否需要添加P 标签
|
||
*/
|
||
function creatInsertStr(url,width,height,id,align,toEmbed){
|
||
return !toEmbed ?
|
||
|
||
'<img ' + (id ? 'id="' + id+'"' : '') + ' width="'+ width +'" height="' + height + '" _url="'+url+'" class="edui-faked-video"' +
|
||
' src="' + me.options.UMEDITOR_HOME_URL+'themes/default/images/spacer.gif" style="background:url('+me.options.UMEDITOR_HOME_URL+'themes/default/images/videologo.gif) no-repeat center center; border:1px solid gray;'+(align ? 'float:' + align + ';': '')+'" />'
|
||
|
||
:
|
||
'<embed type="application/x-shockwave-flash" class="edui-faked-video" pluginspage="http://www.macromedia.com/go/getflashplayer"' +
|
||
' src="' + url + '" width="' + width + '" height="' + height + '"' + (align ? ' style="float:' + align + '"': '') +
|
||
' wmode="transparent" play="true" loop="false" menu="false" allowscriptaccess="never" allowfullscreen="true" >';
|
||
}
|
||
|
||
function switchImgAndEmbed(root,img2embed){
|
||
utils.each(root.getNodesByTagName(img2embed ? 'img' : 'embed'),function(node){
|
||
if(node.getAttr('class') == 'edui-faked-video'){
|
||
|
||
var html = creatInsertStr( img2embed ? node.getAttr('_url') : node.getAttr('src'),node.getAttr('width'),node.getAttr('height'),null,node.getStyle('float') || '',img2embed);
|
||
node.parentNode.replaceChild(UM.uNode.createElement(html),node)
|
||
}
|
||
})
|
||
}
|
||
|
||
me.addOutputRule(function(root){
|
||
switchImgAndEmbed(root,true)
|
||
});
|
||
me.addInputRule(function(root){
|
||
switchImgAndEmbed(root)
|
||
});
|
||
|
||
me.commands["insertvideo"] = {
|
||
execCommand: function (cmd, videoObjs){
|
||
videoObjs = utils.isArray(videoObjs)?videoObjs:[videoObjs];
|
||
var html = [],id = 'tmpVedio';
|
||
for(var i=0,vi,len = videoObjs.length;i<len;i++){
|
||
vi = videoObjs[i];
|
||
html.push(creatInsertStr( vi.url, vi.width || 420, vi.height || 280, id + i,vi.align,false));
|
||
}
|
||
me.execCommand("inserthtml",html.join(""),true);
|
||
|
||
},
|
||
queryCommandState : function(){
|
||
var img = me.selection.getRange().getClosedNode(),
|
||
flag = img && (img.className == "edui-faked-video");
|
||
return flag ? 1 : 0;
|
||
}
|
||
};
|
||
};
|
||
///import core
|
||
///commands 全选
|
||
///commandsName SelectAll
|
||
///commandsTitle 全选
|
||
/**
|
||
* 选中所有
|
||
* @function
|
||
* @name UM.execCommand
|
||
* @param {String} cmdName selectall选中编辑器里的所有内容
|
||
* @author zhanyi
|
||
*/
|
||
UM.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"
|
||
});
|
||
};
|
||
|
||
//UM.plugins['removeformat'] = function () {
|
||
// var me = this;
|
||
// me.commands['removeformat'] = {
|
||
// execCommand: function () {
|
||
// me.document.execCommand('removeformat');
|
||
//
|
||
// /* 处理ie8和firefox选区有链接时,清除格式的bug */
|
||
// if (browser.gecko || browser.ie8 || browser.webkit) {
|
||
// var nativeRange = this.selection.getNative().getRangeAt(0),
|
||
// common = nativeRange.commonAncestorContainer,
|
||
// rng = me.selection.getRange(),
|
||
// bk = rng.createBookmark();
|
||
//
|
||
// function isEleInBookmark(node, bk){
|
||
// if ( (domUtils.getPosition(node, bk.start) & domUtils.POSITION_FOLLOWING) &&
|
||
// (domUtils.getPosition(bk.end, node) & domUtils.POSITION_FOLLOWING) ) {
|
||
// return true;
|
||
// } else if ( (domUtils.getPosition(node, bk.start) & domUtils.POSITION_CONTAINS) ||
|
||
// (domUtils.getPosition(node, bk.end) & domUtils.POSITION_CONTAINS) ) {
|
||
// return true;
|
||
// }
|
||
// return false;
|
||
// }
|
||
//
|
||
// $(common).find('a').each(function (k, a) {
|
||
// if ( isEleInBookmark(a, bk) ) {
|
||
// a.removeAttribute('style');
|
||
// }
|
||
// });
|
||
//
|
||
// }
|
||
// }
|
||
// };
|
||
//
|
||
//};
|
||
//
|
||
|
||
|
||
UM.plugins['removeformat'] = function(){
|
||
var me = this;
|
||
me.setOpt({
|
||
'removeFormatTags': 'b,big,code,del,dfn,em,font,i,ins,kbd,q,samp,small,span,strike,strong,sub,sup,tt,u,var',
|
||
'removeFormatAttributes':'class,style,lang,width,height,align,hspace,valign'
|
||
});
|
||
me.commands['removeformat'] = {
|
||
execCommand : function( cmdName, tags, style, attrs,notIncludeA ) {
|
||
|
||
var tagReg = new RegExp( '^(?:' + (tags || this.options.removeFormatTags).replace( /,/g, '|' ) + ')$', 'i' ) ,
|
||
removeFormatAttributes = style ? [] : (attrs || this.options.removeFormatAttributes).split( ',' ),
|
||
range = new dom.Range( this.document ),
|
||
bookmark,node,parent,
|
||
filter = function( node ) {
|
||
return node.nodeType == 1;
|
||
};
|
||
|
||
function isRedundantSpan (node) {
|
||
if (node.nodeType == 3 || node.tagName.toLowerCase() != 'span'){
|
||
return 0;
|
||
}
|
||
if (browser.ie) {
|
||
//ie 下判断实效,所以只能简单用style来判断
|
||
//return node.style.cssText == '' ? 1 : 0;
|
||
var attrs = node.attributes;
|
||
if ( attrs.length ) {
|
||
for ( var i = 0,l = attrs.length; i<l; i++ ) {
|
||
if ( attrs[i].specified ) {
|
||
return 0;
|
||
}
|
||
}
|
||
return 1;
|
||
}
|
||
}
|
||
return !node.attributes.length;
|
||
}
|
||
function doRemove( range ) {
|
||
|
||
var bookmark1 = range.createBookmark();
|
||
if ( range.collapsed ) {
|
||
range.enlarge( true );
|
||
}
|
||
|
||
//不能把a标签切了
|
||
if(!notIncludeA){
|
||
var aNode = domUtils.findParentByTagName(range.startContainer,'a',true);
|
||
if(aNode){
|
||
range.setStartBefore(aNode);
|
||
}
|
||
|
||
aNode = domUtils.findParentByTagName(range.endContainer,'a',true);
|
||
if(aNode){
|
||
range.setEndAfter(aNode);
|
||
}
|
||
|
||
}
|
||
|
||
|
||
bookmark = range.createBookmark();
|
||
|
||
node = bookmark.start;
|
||
|
||
//切开始
|
||
while ( (parent = node.parentNode) && !domUtils.isBlockElm( parent ) ) {
|
||
domUtils.breakParent( node, parent );
|
||
domUtils.clearEmptySibling( node );
|
||
}
|
||
if ( bookmark.end ) {
|
||
//切结束
|
||
node = bookmark.end;
|
||
while ( (parent = node.parentNode) && !domUtils.isBlockElm( parent ) ) {
|
||
domUtils.breakParent( node, parent );
|
||
domUtils.clearEmptySibling( node );
|
||
}
|
||
|
||
//开始去除样式
|
||
var current = domUtils.getNextDomNode( bookmark.start, false, filter ),
|
||
next;
|
||
while ( current ) {
|
||
if ( current == bookmark.end ) {
|
||
break;
|
||
}
|
||
|
||
next = domUtils.getNextDomNode( current, true, filter );
|
||
|
||
if ( !dtd.$empty[current.tagName.toLowerCase()] && !domUtils.isBookmarkNode( current ) ) {
|
||
if ( tagReg.test( current.tagName ) ) {
|
||
if ( style ) {
|
||
domUtils.removeStyle( current, style );
|
||
if ( isRedundantSpan( current ) && style != 'text-decoration'){
|
||
domUtils.remove( current, true );
|
||
}
|
||
} else {
|
||
domUtils.remove( current, true );
|
||
}
|
||
} else {
|
||
//trace:939 不能把list上的样式去掉
|
||
if(!dtd.$tableContent[current.tagName] && !dtd.$list[current.tagName]){
|
||
domUtils.removeAttributes( current, removeFormatAttributes );
|
||
if ( isRedundantSpan( current ) ){
|
||
domUtils.remove( current, true );
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|
||
current = next;
|
||
}
|
||
}
|
||
//trace:1035
|
||
//trace:1096 不能把td上的样式去掉,比如边框
|
||
var pN = bookmark.start.parentNode;
|
||
if(domUtils.isBlockElm(pN) && !dtd.$tableContent[pN.tagName] && !dtd.$list[pN.tagName]){
|
||
domUtils.removeAttributes( pN,removeFormatAttributes );
|
||
}
|
||
pN = bookmark.end.parentNode;
|
||
if(bookmark.end && domUtils.isBlockElm(pN) && !dtd.$tableContent[pN.tagName]&& !dtd.$list[pN.tagName]){
|
||
domUtils.removeAttributes( pN,removeFormatAttributes );
|
||
}
|
||
range.moveToBookmark( bookmark ).moveToBookmark(bookmark1);
|
||
//清除冗余的代码 <b><bookmark></b>
|
||
var node = range.startContainer,
|
||
tmp,
|
||
collapsed = range.collapsed;
|
||
while(node.nodeType == 1 && domUtils.isEmptyNode(node) && dtd.$removeEmpty[node.tagName]){
|
||
tmp = node.parentNode;
|
||
range.setStartBefore(node);
|
||
//trace:937
|
||
//更新结束边界
|
||
if(range.startContainer === range.endContainer){
|
||
range.endOffset--;
|
||
}
|
||
domUtils.remove(node);
|
||
node = tmp;
|
||
}
|
||
|
||
if(!collapsed){
|
||
node = range.endContainer;
|
||
while(node.nodeType == 1 && domUtils.isEmptyNode(node) && dtd.$removeEmpty[node.tagName]){
|
||
tmp = node.parentNode;
|
||
range.setEndBefore(node);
|
||
domUtils.remove(node);
|
||
|
||
node = tmp;
|
||
}
|
||
|
||
|
||
}
|
||
}
|
||
|
||
|
||
|
||
range = this.selection.getRange();
|
||
if(!range.collapsed) {
|
||
doRemove( range );
|
||
range.select();
|
||
}
|
||
|
||
}
|
||
|
||
};
|
||
|
||
};
|
||
/*
|
||
* 处理特殊键的兼容性问题
|
||
*/
|
||
UM.plugins['keystrokes'] = function() {
|
||
var me = this;
|
||
var collapsed = true;
|
||
me.addListener('keydown', function(type, evt) {
|
||
var keyCode = evt.keyCode || evt.which,
|
||
rng = me.selection.getRange();
|
||
|
||
//处理全选的情况
|
||
if(!rng.collapsed && !(evt.ctrlKey || evt.shiftKey || evt.altKey || evt.metaKey) && (keyCode >= 65 && keyCode <=90
|
||
|| keyCode >= 48 && keyCode <= 57 ||
|
||
keyCode >= 96 && keyCode <= 111 || {
|
||
13:1,
|
||
8:1,
|
||
46:1
|
||
}[keyCode])
|
||
){
|
||
|
||
var tmpNode = rng.startContainer;
|
||
if(domUtils.isFillChar(tmpNode)){
|
||
rng.setStartBefore(tmpNode)
|
||
}
|
||
tmpNode = rng.endContainer;
|
||
if(domUtils.isFillChar(tmpNode)){
|
||
rng.setEndAfter(tmpNode)
|
||
}
|
||
rng.txtToElmBoundary();
|
||
//结束边界可能放到了br的前边,要把br包含进来
|
||
// x[xxx]<br/>
|
||
if(rng.endContainer && rng.endContainer.nodeType == 1){
|
||
tmpNode = rng.endContainer.childNodes[rng.endOffset];
|
||
if(tmpNode && domUtils.isBr(tmpNode)){
|
||
rng.setEndAfter(tmpNode);
|
||
}
|
||
}
|
||
if(rng.startOffset == 0){
|
||
tmpNode = rng.startContainer;
|
||
if(domUtils.isBoundaryNode(tmpNode,'firstChild') ){
|
||
tmpNode = rng.endContainer;
|
||
if(rng.endOffset == (tmpNode.nodeType == 3 ? tmpNode.nodeValue.length : tmpNode.childNodes.length) && domUtils.isBoundaryNode(tmpNode,'lastChild')){
|
||
me.fireEvent('saveScene');
|
||
me.body.innerHTML = '<p>'+(browser.ie ? '' : '<br/>')+'</p>';
|
||
rng.setStart(me.body.firstChild,0).setCursor(false,true);
|
||
me._selectionChange();
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//处理backspace
|
||
if (keyCode == 8) {
|
||
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;
|
||
}
|
||
}
|
||
start = rng.startContainer;
|
||
if(rng.collapsed && start.nodeType == 1){
|
||
var currentNode = start.childNodes[rng.startOffset-1];
|
||
if(currentNode && currentNode.nodeType == 1 && currentNode.tagName == 'BR'){
|
||
me.fireEvent('saveScene');
|
||
rng.setStartBefore(currentNode).collapse(true);
|
||
domUtils.remove(currentNode);
|
||
rng.select();
|
||
me.fireEvent('saveScene');
|
||
}
|
||
}
|
||
|
||
//trace:3613
|
||
if(browser.chrome){
|
||
if(rng.collapsed){
|
||
|
||
while(rng.startOffset == 0 && !domUtils.isEmptyBlock(rng.startContainer)){
|
||
rng.setStartBefore(rng.startContainer)
|
||
}
|
||
var pre = rng.startContainer.childNodes[rng.startOffset-1];
|
||
if(pre && pre.nodeName == 'BR'){
|
||
rng.setStartBefore(pre);
|
||
me.fireEvent('saveScene');
|
||
$(pre).remove();
|
||
rng.setCursor();
|
||
me.fireEvent('saveScene');
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|
||
//trace:1634
|
||
//ff的del键在容器空的时候,也会删除
|
||
if(browser.gecko && keyCode == 46){
|
||
var 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 == 8){
|
||
if(me.fireEvent('delkeyup')){
|
||
return;
|
||
}
|
||
rng = me.selection.getRange();
|
||
if(rng.collapsed){
|
||
var tmpNode,
|
||
autoClearTagName = ['h1','h2','h3','h4','h5','h6'];
|
||
if(tmpNode = domUtils.findParentByTagName(rng.startContainer,autoClearTagName,true)){
|
||
if(domUtils.isEmptyBlock(tmpNode)){
|
||
var pre = tmpNode.previousSibling;
|
||
if(pre && pre.nodeName != 'TABLE'){
|
||
domUtils.remove(tmpNode);
|
||
rng.setStartAtLast(pre).setCursor(false,true);
|
||
return;
|
||
}else{
|
||
var next = tmpNode.nextSibling;
|
||
if(next && next.nodeName != 'TABLE'){
|
||
domUtils.remove(tmpNode);
|
||
rng.setStartAtFirst(next).setCursor(false,true);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
//处理当删除到body时,要重新给p标签展位
|
||
if(domUtils.isBody(rng.startContainer)){
|
||
var tmpNode = domUtils.createElement(me.document,'p',{
|
||
'innerHTML' : browser.ie ? domUtils.fillChar : '<br/>'
|
||
});
|
||
rng.insertNode(tmpNode).setStart(tmpNode,0).setCursor(false,true);
|
||
}
|
||
}
|
||
|
||
|
||
//chrome下如果删除了inline标签,浏览器会有记忆,在输入文字还是会套上刚才删除的标签,所以这里再选一次就不会了
|
||
if( !collapsed && (rng.startContainer.nodeType == 3 || rng.startContainer.nodeType == 1 && domUtils.isEmptyBlock(rng.startContainer))){
|
||
if(browser.ie){
|
||
var span = rng.document.createElement('span');
|
||
rng.insertNode(span).setStartBefore(span).collapse(true);
|
||
rng.select();
|
||
domUtils.remove(span)
|
||
}else{
|
||
rng.select()
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
})
|
||
};
|
||
/**
|
||
* 自动保存草稿
|
||
*/
|
||
UM.plugins['autosave'] = function() {
|
||
|
||
|
||
var me = this,
|
||
//无限循环保护
|
||
lastSaveTime = new Date(),
|
||
//最小保存间隔时间
|
||
MIN_TIME = 20,
|
||
//auto save key
|
||
saveKey = null;
|
||
|
||
|
||
//默认间隔时间
|
||
me.setOpt('saveInterval', 500);
|
||
|
||
//存储媒介封装
|
||
var LocalStorage = UM.LocalStorage = ( function () {
|
||
|
||
var storage = window.localStorage || getUserData() || null,
|
||
LOCAL_FILE = "localStorage";
|
||
|
||
return {
|
||
|
||
saveLocalData: function ( key, data ) {
|
||
|
||
if ( storage && data) {
|
||
storage.setItem( key, data );
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
|
||
},
|
||
|
||
getLocalData: function ( key ) {
|
||
|
||
if ( storage ) {
|
||
return storage.getItem( key );
|
||
}
|
||
|
||
return null;
|
||
|
||
},
|
||
|
||
removeItem: function ( key ) {
|
||
|
||
storage && storage.removeItem( key );
|
||
|
||
}
|
||
|
||
};
|
||
|
||
function getUserData () {
|
||
|
||
var container = document.createElement( "div" );
|
||
container.style.display = "none";
|
||
|
||
if( !container.addBehavior ) {
|
||
return null;
|
||
}
|
||
|
||
container.addBehavior("#default#userdata");
|
||
|
||
return {
|
||
|
||
getItem: function ( key ) {
|
||
|
||
var result = null;
|
||
|
||
try {
|
||
document.body.appendChild( container );
|
||
container.load( LOCAL_FILE );
|
||
result = container.getAttribute( key );
|
||
document.body.removeChild( container );
|
||
} catch ( e ) {
|
||
}
|
||
|
||
return result;
|
||
|
||
},
|
||
|
||
setItem: function ( key, value ) {
|
||
|
||
document.body.appendChild( container );
|
||
container.setAttribute( key, value );
|
||
container.save( LOCAL_FILE );
|
||
document.body.removeChild( container );
|
||
|
||
},
|
||
// 暂时没有用到
|
||
// clear: function () {
|
||
//
|
||
// var expiresTime = new Date();
|
||
// expiresTime.setFullYear( expiresTime.getFullYear() - 1 );
|
||
// document.body.appendChild( container );
|
||
// container.expires = expiresTime.toUTCString();
|
||
// container.save( LOCAL_FILE );
|
||
// document.body.removeChild( container );
|
||
//
|
||
// },
|
||
|
||
removeItem: function ( key ) {
|
||
|
||
document.body.appendChild( container );
|
||
container.removeAttribute( key );
|
||
container.save( LOCAL_FILE );
|
||
document.body.removeChild( container );
|
||
|
||
}
|
||
|
||
};
|
||
|
||
}
|
||
|
||
} )();
|
||
|
||
function save ( editor ) {
|
||
|
||
var saveData = null;
|
||
|
||
if ( new Date() - lastSaveTime < MIN_TIME ) {
|
||
return;
|
||
}
|
||
|
||
if ( !editor.hasContents() ) {
|
||
//这里不能调用命令来删除, 会造成事件死循环
|
||
saveKey && LocalStorage.removeItem( saveKey );
|
||
return;
|
||
}
|
||
|
||
lastSaveTime = new Date();
|
||
|
||
editor._saveFlag = null;
|
||
|
||
saveData = me.body.innerHTML;
|
||
|
||
if ( editor.fireEvent( "beforeautosave", {
|
||
content: saveData
|
||
} ) === false ) {
|
||
return;
|
||
}
|
||
|
||
LocalStorage.saveLocalData( saveKey, saveData );
|
||
|
||
editor.fireEvent( "afterautosave", {
|
||
content: saveData
|
||
} );
|
||
|
||
}
|
||
|
||
me.addListener('ready', function(){
|
||
var _suffix = "-drafts-data",
|
||
key = null;
|
||
|
||
if ( me.key ) {
|
||
key = me.key + _suffix;
|
||
} else {
|
||
key = ( me.container.parentNode.id || 'ue-common' ) + _suffix;
|
||
}
|
||
|
||
//页面地址+编辑器ID 保持唯一
|
||
saveKey = ( location.protocol + location.host + location.pathname ).replace( /[.:\/]/g, '_' ) + key;
|
||
});
|
||
|
||
me.addListener('contentchange', function(){
|
||
|
||
if ( !saveKey ) {
|
||
return;
|
||
}
|
||
|
||
if ( me._saveFlag ) {
|
||
window.clearTimeout( me._saveFlag );
|
||
}
|
||
|
||
if ( me.options.saveInterval > 0 ) {
|
||
|
||
me._saveFlag = window.setTimeout( function () {
|
||
|
||
save( me );
|
||
|
||
}, me.options.saveInterval );
|
||
|
||
} else {
|
||
|
||
save(me);
|
||
|
||
}
|
||
|
||
})
|
||
|
||
|
||
me.commands['clearlocaldata'] = {
|
||
execCommand:function (cmd, name) {
|
||
if ( saveKey && LocalStorage.getLocalData( saveKey ) ) {
|
||
LocalStorage.removeItem( saveKey )
|
||
}
|
||
},
|
||
notNeedUndo: true,
|
||
ignoreContentChange:true
|
||
};
|
||
|
||
me.commands['getlocaldata'] = {
|
||
execCommand:function (cmd, name) {
|
||
return saveKey ? LocalStorage.getLocalData( saveKey ) || '' : '';
|
||
},
|
||
notNeedUndo: true,
|
||
ignoreContentChange:true
|
||
};
|
||
|
||
me.commands['drafts'] = {
|
||
execCommand:function (cmd, name) {
|
||
if ( saveKey ) {
|
||
me.body.innerHTML = LocalStorage.getLocalData( saveKey ) || '<p>'+(browser.ie ? ' ' : '<br/>')+'</p>';
|
||
me.focus(true);
|
||
}
|
||
},
|
||
queryCommandState: function () {
|
||
return saveKey ? ( LocalStorage.getLocalData( saveKey ) === null ? -1 : 0 ) : -1;
|
||
},
|
||
notNeedUndo: true,
|
||
ignoreContentChange:true
|
||
}
|
||
|
||
};
|
||
|
||
/**
|
||
* @description
|
||
* 1.拖放文件到编辑区域,自动上传并插入到选区
|
||
* 2.插入粘贴板的图片,自动上传并插入到选区
|
||
* @author Jinqn
|
||
* @date 2013-10-14
|
||
*/
|
||
UM.plugins['autoupload'] = function () {
|
||
|
||
var me = this;
|
||
|
||
me.setOpt('pasteImageEnabled', true);
|
||
me.setOpt('dropFileEnabled', true);
|
||
var sendAndInsertImage = function (file, editor) {
|
||
//模拟数据
|
||
var fd = new FormData();
|
||
fd.append(editor.options.imageFieldName || 'upfile', file, file.name || ('blob.' + file.type.substr('image/'.length)));
|
||
fd.append('type', 'ajax');
|
||
var xhr = new XMLHttpRequest();
|
||
xhr.open("post", me.options.imageUrl, true);
|
||
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
||
xhr.addEventListener('load', function (e) {
|
||
try {
|
||
var json = eval('('+e.target.response+')'),
|
||
link = json.url,
|
||
picLink = me.options.imagePath + link;
|
||
editor.execCommand('insertimage', {
|
||
src: picLink,
|
||
_src: picLink
|
||
});
|
||
} catch (er) {
|
||
}
|
||
});
|
||
xhr.send(fd);
|
||
};
|
||
|
||
function getPasteImage(e) {
|
||
return e.clipboardData && e.clipboardData.items && e.clipboardData.items.length == 1 && /^image\//.test(e.clipboardData.items[0].type) ? e.clipboardData.items : null;
|
||
}
|
||
|
||
function getDropImage(e) {
|
||
return e.dataTransfer && e.dataTransfer.files ? e.dataTransfer.files : null;
|
||
}
|
||
|
||
me.addListener('ready', function () {
|
||
if (window.FormData && window.FileReader) {
|
||
var autoUploadHandler = function (e) {
|
||
var hasImg = false,
|
||
items;
|
||
//获取粘贴板文件列表或者拖放文件列表
|
||
items = e.type == 'paste' ? getPasteImage(e.originalEvent) : getDropImage(e.originalEvent);
|
||
if (items) {
|
||
var len = items.length,
|
||
file;
|
||
while (len--) {
|
||
file = items[len];
|
||
if (file.getAsFile) file = file.getAsFile();
|
||
if (file && file.size > 0 && /image\/\w+/i.test(file.type)) {
|
||
sendAndInsertImage(file, me);
|
||
hasImg = true;
|
||
}
|
||
}
|
||
if (hasImg) return false;
|
||
}
|
||
|
||
};
|
||
me.getOpt('pasteImageEnabled') && me.$body.on('paste', autoUploadHandler);
|
||
me.getOpt('dropFileEnabled') && me.$body.on('drop', autoUploadHandler);
|
||
|
||
//取消拖放图片时出现的文字光标位置提示
|
||
me.$body.on('dragover', function (e) {
|
||
if (e.originalEvent.dataTransfer.types[0] == 'Files') {
|
||
return false;
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
};
|
||
/**
|
||
* 公式插件
|
||
*/
|
||
UM.plugins['formula'] = function () {
|
||
var me = this;
|
||
|
||
function getActiveIframe() {
|
||
return me.$body.find('iframe.edui-formula-active')[0] || null;
|
||
}
|
||
|
||
function blurActiveIframe(){
|
||
var iframe = getActiveIframe();
|
||
iframe && iframe.contentWindow.formula.blur();
|
||
}
|
||
|
||
me.addInputRule(function (root) {
|
||
$.each(root.getNodesByTagName('span'), function (i, node) {
|
||
if (node.hasClass('mathquill-embedded-latex')) {
|
||
var firstChild, latex = '';
|
||
while(firstChild = node.firstChild()){
|
||
latex += firstChild.data;
|
||
node.removeChild(firstChild);
|
||
}
|
||
node.tagName = 'iframe';
|
||
node.setAttr({
|
||
'frameborder': '0',
|
||
'src': me.getOpt('UMEDITOR_HOME_URL') + 'dialogs/formula/formula.html',
|
||
'data-latex': utils.unhtml(latex)
|
||
});
|
||
}
|
||
});
|
||
});
|
||
me.addOutputRule(function (root) {
|
||
$.each(root.getNodesByTagName('iframe'), function (i, node) {
|
||
if (node.hasClass('mathquill-embedded-latex')) {
|
||
node.tagName = 'span';
|
||
node.appendChild(UM.uNode.createText(node.getAttr('data-latex')));
|
||
node.setAttr({
|
||
'frameborder': '',
|
||
'src': '',
|
||
'data-latex': ''
|
||
});
|
||
}
|
||
});
|
||
});
|
||
me.addListener('click', function(){
|
||
blurActiveIframe();
|
||
});
|
||
me.addListener('afterexeccommand', function(type, cmd){
|
||
if(cmd != 'formula') {
|
||
blurActiveIframe();
|
||
}
|
||
});
|
||
|
||
me.commands['formula'] = {
|
||
execCommand: function (cmd, latex) {
|
||
var iframe = getActiveIframe();
|
||
if (iframe) {
|
||
iframe.contentWindow.formula.insertLatex(latex);
|
||
} else {
|
||
me.execCommand('inserthtml', '<span class="mathquill-embedded-latex">' + latex + '</span>');
|
||
browser.ie && browser.ie9below && setTimeout(function(){
|
||
var rng = me.selection.getRange(),
|
||
startContainer = rng.startContainer;
|
||
if(startContainer.nodeType == 1 && !startContainer.childNodes[rng.startOffset]){
|
||
rng.insertNode(me.document.createTextNode(' '));
|
||
rng.setCursor()
|
||
}
|
||
},100)
|
||
}
|
||
},
|
||
queryCommandState: function (cmd) {
|
||
return 0;
|
||
},
|
||
queryCommandValue: function (cmd) {
|
||
var iframe = getActiveIframe();
|
||
return iframe && iframe.contentWindow.formula.getLatex();
|
||
}
|
||
}
|
||
|
||
};
|
||
|
||
(function ($) {
|
||
//对jquery的扩展
|
||
$.parseTmpl = function parse(str, data) {
|
||
var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + 'with(obj||{}){__p.push(\'' + str.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/<%=([\s\S]+?)%>/g,function (match, code) {
|
||
return "'," + code.replace(/\\'/g, "'") + ",'";
|
||
}).replace(/<%([\s\S]+?)%>/g,function (match, code) {
|
||
return "');" + code.replace(/\\'/g, "'").replace(/[\r\n\t]/g, ' ') + "__p.push('";
|
||
}).replace(/\r/g, '\\r').replace(/\n/g, '\\n').replace(/\t/g, '\\t') + "');}return __p.join('');";
|
||
var func = new Function('obj', tmpl);
|
||
return data ? func(data) : func;
|
||
};
|
||
$.extend2 = function (t, s) {
|
||
var a = arguments,
|
||
notCover = $.type(a[a.length - 1]) == 'boolean' ? a[a.length - 1] : false,
|
||
len = $.type(a[a.length - 1]) == 'boolean' ? a.length - 1 : a.length;
|
||
for (var i = 1; i < len; i++) {
|
||
var x = a[i];
|
||
for (var k in x) {
|
||
if (!notCover || !t.hasOwnProperty(k)) {
|
||
t[k] = x[k];
|
||
}
|
||
}
|
||
}
|
||
return t;
|
||
};
|
||
|
||
$.IE6 = !!window.ActiveXObject && parseFloat(navigator.userAgent.match(/msie (\d+)/i)[1]) == 6;
|
||
|
||
//所有ui的基类
|
||
var _eventHandler = [];
|
||
var _widget = function () {
|
||
};
|
||
var _prefix = 'edui';
|
||
_widget.prototype = {
|
||
on: function (ev, cb) {
|
||
this.root().on(ev, $.proxy(cb, this));
|
||
return this;
|
||
},
|
||
off: function (ev, cb) {
|
||
this.root().off(ev, $.proxy(cb, this));
|
||
return this;
|
||
},
|
||
trigger: function (ev, data) {
|
||
return this.root().trigger(ev, data) === false ? false : this;
|
||
},
|
||
root: function ($el) {
|
||
return this._$el || (this._$el = $el);
|
||
},
|
||
destroy: function () {
|
||
|
||
},
|
||
data: function (key, val) {
|
||
if (val !== undefined) {
|
||
this.root().data(_prefix + key, val);
|
||
return this;
|
||
} else {
|
||
return this.root().data(_prefix + key)
|
||
}
|
||
},
|
||
register: function (eventName, $el, fn) {
|
||
_eventHandler.push({
|
||
'evtname': eventName,
|
||
'$els': $.isArray($el) ? $el : [$el],
|
||
handler: $.proxy(fn, $el)
|
||
})
|
||
}
|
||
};
|
||
|
||
//从jq实例上拿到绑定的widget实例
|
||
$.fn.edui = function (obj) {
|
||
return obj ? this.data('eduiwidget', obj) : this.data('eduiwidget');
|
||
};
|
||
|
||
function _createClass(ClassObj, properties, supperClass) {
|
||
ClassObj.prototype = $.extend2(
|
||
$.extend({}, properties),
|
||
(UM.ui[supperClass] || _widget).prototype,
|
||
true
|
||
);
|
||
ClassObj.prototype.supper = (UM.ui[supperClass] || _widget).prototype;
|
||
//父class的defaultOpt 合并
|
||
if( UM.ui[supperClass] && UM.ui[supperClass].prototype.defaultOpt ) {
|
||
|
||
var parentDefaultOptions = UM.ui[supperClass].prototype.defaultOpt,
|
||
subDefaultOptions = ClassObj.prototype.defaultOpt;
|
||
|
||
ClassObj.prototype.defaultOpt = $.extend( {}, parentDefaultOptions, subDefaultOptions || {} );
|
||
|
||
}
|
||
return ClassObj
|
||
}
|
||
|
||
var _guid = 1;
|
||
|
||
function mergeToJQ(ClassObj, className) {
|
||
$[_prefix + className] = ClassObj;
|
||
$.fn[_prefix + className] = function (opt) {
|
||
var result, args = Array.prototype.slice.call(arguments, 1);
|
||
|
||
this.each(function (i, el) {
|
||
var $this = $(el);
|
||
var obj = $this.edui();
|
||
if (!obj) {
|
||
ClassObj(!opt || !$.isPlainObject(opt) ? {} : opt, $this);
|
||
$this.edui(obj)
|
||
}
|
||
if ($.type(opt) == 'string') {
|
||
if (opt == 'this') {
|
||
result = obj;
|
||
} else {
|
||
result = obj[opt].apply(obj, args);
|
||
if (result !== obj && result !== undefined) {
|
||
return false;
|
||
}
|
||
result = null;
|
||
}
|
||
|
||
}
|
||
});
|
||
|
||
return result !== null ? result : this;
|
||
}
|
||
}
|
||
|
||
UM.ui = {
|
||
define: function (className, properties, supperClass) {
|
||
var ClassObj = UM.ui[className] = _createClass(function (options, $el) {
|
||
var _obj = function () {
|
||
};
|
||
$.extend(_obj.prototype, ClassObj.prototype, {
|
||
guid: className + _guid++,
|
||
widgetName: className
|
||
}
|
||
);
|
||
var obj = new _obj;
|
||
if ($.type(options) == 'string') {
|
||
obj.init && obj.init({});
|
||
obj.root().edui(obj);
|
||
obj.root().find('a').click(function (evt) {
|
||
evt.preventDefault()
|
||
});
|
||
return obj.root()[_prefix + className].apply(obj.root(), arguments)
|
||
} else {
|
||
$el && obj.root($el);
|
||
obj.init && obj.init(!options || $.isPlainObject(options) ? $.extend2(options || {}, obj.defaultOpt || {}, true) : options);
|
||
try{
|
||
obj.root().find('a').click(function (evt) {
|
||
evt.preventDefault()
|
||
});
|
||
}catch(e){
|
||
}
|
||
|
||
return obj.root().edui(obj);
|
||
}
|
||
|
||
},properties, supperClass);
|
||
|
||
mergeToJQ(ClassObj, className);
|
||
}
|
||
};
|
||
|
||
$(function () {
|
||
$(document).on('click mouseup mousedown dblclick mouseover', function (evt) {
|
||
$.each(_eventHandler, function (i, obj) {
|
||
if (obj.evtname == evt.type) {
|
||
$.each(obj.$els, function (i, $el) {
|
||
if ($el[0] !== evt.target && !$.contains($el[0], evt.target)) {
|
||
obj.handler(evt);
|
||
}
|
||
})
|
||
}
|
||
})
|
||
})
|
||
})
|
||
})(jQuery);
|
||
//button 类
|
||
UM.ui.define('button', {
|
||
tpl: '<<%if(!texttype){%>div class="edui-btn edui-btn-<%=icon%> <%if(name){%>edui-btn-name-<%=name%><%}%>" unselectable="on" onmousedown="return false" <%}else{%>a class="edui-text-btn"<%}%><% if(title) {%> data-original-title="<%=title%>" <%};%>> ' +
|
||
'<% if(icon) {%><div unselectable="on" class="edui-icon-<%=icon%> edui-icon"></div><% }; %><%if(text) {%><span unselectable="on" onmousedown="return false" class="edui-button-label"><%=text%></span><%}%>' +
|
||
'<%if(caret && text){%><span class="edui-button-spacing"></span><%}%>' +
|
||
'<% if(caret) {%><span unselectable="on" onmousedown="return false" class="edui-caret"></span><% };%></<%if(!texttype){%>div<%}else{%>a<%}%>>',
|
||
defaultOpt: {
|
||
text: '',
|
||
title: '',
|
||
icon: '',
|
||
width: '',
|
||
caret: false,
|
||
texttype: false,
|
||
click: function () {
|
||
}
|
||
},
|
||
init: function (options) {
|
||
var me = this;
|
||
|
||
me.root($($.parseTmpl(me.tpl, options)))
|
||
.click(function (evt) {
|
||
me.wrapclick(options.click, evt)
|
||
});
|
||
|
||
me.root().hover(function () {
|
||
if(!me.root().hasClass("edui-disabled")){
|
||
me.root().toggleClass('edui-hover')
|
||
}
|
||
})
|
||
|
||
return me;
|
||
},
|
||
wrapclick: function (fn, evt) {
|
||
if (!this.disabled()) {
|
||
this.root().trigger('wrapclick');
|
||
$.proxy(fn, this, evt)()
|
||
}
|
||
return this;
|
||
},
|
||
label: function (text) {
|
||
if (text === undefined) {
|
||
return this.root().find('.edui-button-label').text();
|
||
} else {
|
||
this.root().find('.edui-button-label').text(text);
|
||
return this;
|
||
}
|
||
},
|
||
disabled: function (state) {
|
||
if (state === undefined) {
|
||
return this.root().hasClass('edui-disabled')
|
||
}
|
||
this.root().toggleClass('edui-disabled', state);
|
||
if(this.root().hasClass('edui-disabled')){
|
||
this.root().removeClass('edui-hover')
|
||
}
|
||
return this;
|
||
},
|
||
active: function (state) {
|
||
if (state === undefined) {
|
||
return this.root().hasClass('edui-active')
|
||
}
|
||
this.root().toggleClass('edui-active', state)
|
||
|
||
return this;
|
||
},
|
||
mergeWith: function ($obj) {
|
||
var me = this;
|
||
me.data('$mergeObj', $obj);
|
||
$obj.edui().data('$mergeObj', me.root());
|
||
if (!$.contains(document.body, $obj[0])) {
|
||
$obj.appendTo(me.root());
|
||
}
|
||
me.on('click',function () {
|
||
me.wrapclick(function () {
|
||
$obj.edui().show();
|
||
})
|
||
}).register('click', me.root(), function (evt) {
|
||
$obj.hide()
|
||
});
|
||
}
|
||
});
|
||
//toolbar 类
|
||
(function () {
|
||
UM.ui.define('toolbar', {
|
||
tpl: '<div class="edui-toolbar" ><div class="edui-btn-toolbar" unselectable="on" onmousedown="return false" ></div></div>'
|
||
,
|
||
init: function () {
|
||
var $root = this.root($(this.tpl));
|
||
this.data('$btnToolbar', $root.find('.edui-btn-toolbar'))
|
||
},
|
||
appendToBtnmenu : function(data){
|
||
var $cont = this.data('$btnToolbar');
|
||
data = $.isArray(data) ? data : [data];
|
||
$.each(data,function(i,$item){
|
||
$cont.append($item)
|
||
})
|
||
}
|
||
});
|
||
})();
|
||
|
||
//menu 类
|
||
UM.ui.define('menu',{
|
||
show : function($obj,dir,fnname,topOffset,leftOffset){
|
||
|
||
fnname = fnname || 'position';
|
||
if(this.trigger('beforeshow') === false){
|
||
return;
|
||
}else{
|
||
this.root().css($.extend({display:'block'},$obj ? {
|
||
top : $obj[fnname]().top + ( dir == 'right' ? 0 : $obj.outerHeight()) - (topOffset || 0),
|
||
left : $obj[fnname]().left + (dir == 'right' ? $obj.outerWidth() : 0) - (leftOffset || 0)
|
||
}:{}))
|
||
this.trigger('aftershow');
|
||
}
|
||
},
|
||
hide : function(all){
|
||
var $parentmenu;
|
||
if(this.trigger('beforehide') === false){
|
||
return;
|
||
} else {
|
||
|
||
if($parentmenu = this.root().data('parentmenu')){
|
||
if($parentmenu.data('parentmenu')|| all)
|
||
$parentmenu.edui().hide();
|
||
}
|
||
this.root().css('display','none');
|
||
this.trigger('afterhide');
|
||
}
|
||
},
|
||
attachTo : function($obj){
|
||
var me = this;
|
||
if(!$obj.data('$mergeObj')){
|
||
$obj.data('$mergeObj',me.root());
|
||
$obj.on('wrapclick',function(evt){
|
||
me.show()
|
||
});
|
||
me.register('click',$obj,function(evt){
|
||
me.hide()
|
||
});
|
||
me.data('$mergeObj',$obj)
|
||
}
|
||
}
|
||
});
|
||
//dropmenu 类
|
||
UM.ui.define('dropmenu', {
|
||
tmpl: '<ul class="edui-dropdown-menu" aria-labelledby="dropdownMenu" >' +
|
||
'<%for(var i=0,ci;ci=data[i++];){%>' +
|
||
'<%if(ci.divider){%><li class="edui-divider"></li><%}else{%>' +
|
||
'<li <%if(ci.active||ci.disabled){%>class="<%= ci.active|| \'\' %> <%=ci.disabled||\'\' %>" <%}%> data-value="<%= ci.value%>">' +
|
||
'<a href="#" tabindex="-1"><em class="edui-dropmenu-checkbox"><i class="edui-icon-ok"></i></em><%= ci.label%></a>' +
|
||
'</li><%}%>' +
|
||
'<%}%>' +
|
||
'</ul>',
|
||
defaultOpt: {
|
||
data: [],
|
||
click: function () {
|
||
|
||
}
|
||
},
|
||
init: function (options) {
|
||
var me = this;
|
||
var eventName = {
|
||
click: 1,
|
||
mouseover: 1,
|
||
mouseout: 1
|
||
};
|
||
|
||
this.root($($.parseTmpl(this.tmpl, options))).on('click', 'li[class!="edui-disabled edui-divider edui-dropdown-submenu"]',function (evt) {
|
||
$.proxy(options.click, me, evt, $(this).data('value'), $(this))()
|
||
}).find('li').each(function (i, el) {
|
||
var $this = $(this);
|
||
if (!$this.hasClass("edui-disabled edui-divider edui-dropdown-submenu")) {
|
||
var data = options.data[i];
|
||
$.each(eventName, function (k) {
|
||
data[k] && $this[k](function (evt) {
|
||
$.proxy(data[k], el)(evt, data, me.root)
|
||
})
|
||
})
|
||
}
|
||
})
|
||
|
||
},
|
||
disabled: function (cb) {
|
||
$('li[class!=edui-divider]', this.root()).each(function () {
|
||
var $el = $(this);
|
||
if (cb === true) {
|
||
$el.addClass('edui-disabled')
|
||
} else if ($.isFunction(cb)) {
|
||
$el.toggleClass('edui-disabled', cb(li))
|
||
} else {
|
||
$el.removeClass('edui-disabled')
|
||
}
|
||
|
||
});
|
||
},
|
||
val: function (val) {
|
||
var currentVal;
|
||
$('li[class!="edui-divider edui-disabled edui-dropdown-submenu"]', this.root()).each(function () {
|
||
var $el = $(this);
|
||
if (val === undefined) {
|
||
if ($el.find('em.edui-dropmenu-checked').length) {
|
||
currentVal = $el.data('value');
|
||
return false
|
||
}
|
||
} else {
|
||
$el.find('em').toggleClass('edui-dropmenu-checked', $el.data('value') == val)
|
||
}
|
||
});
|
||
if (val === undefined) {
|
||
return currentVal
|
||
}
|
||
},
|
||
addSubmenu: function (label, menu, index) {
|
||
index = index || 0;
|
||
|
||
var $list = $('li[class!=edui-divider]', this.root());
|
||
var $node = $('<li class="edui-dropdown-submenu"><a tabindex="-1" href="#">' + label + '</a></li>').append(menu);
|
||
|
||
if (index >= 0 && index < $list.length) {
|
||
$node.insertBefore($list[index]);
|
||
} else if (index < 0) {
|
||
$node.insertBefore($list[0]);
|
||
} else if (index >= $list.length) {
|
||
$node.appendTo($list);
|
||
}
|
||
}
|
||
}, 'menu');
|
||
//splitbutton 类
|
||
///import button
|
||
UM.ui.define('splitbutton',{
|
||
tpl :'<div class="edui-splitbutton <%if (name){%>edui-splitbutton-<%= name %><%}%>" unselectable="on" <%if(title){%>data-original-title="<%=title%>"<%}%>><div class="edui-btn" unselectable="on" ><%if(icon){%><div unselectable="on" class="edui-icon-<%=icon%> edui-icon"></div><%}%><%if(text){%><%=text%><%}%></div>'+
|
||
'<div unselectable="on" class="edui-btn edui-dropdown-toggle" >'+
|
||
'<div unselectable="on" class="edui-caret"><\/div>'+
|
||
'</div>'+
|
||
'</div>',
|
||
defaultOpt:{
|
||
text:'',
|
||
title:'',
|
||
click:function(){}
|
||
},
|
||
init : function(options){
|
||
var me = this;
|
||
me.root( $($.parseTmpl(me.tpl,options)));
|
||
me.root().find('.edui-btn:first').click(function(evt){
|
||
if(!me.disabled()){
|
||
$.proxy(options.click,me)();
|
||
}
|
||
});
|
||
me.root().find('.edui-dropdown-toggle').click(function(){
|
||
if(!me.disabled()){
|
||
me.trigger('arrowclick')
|
||
}
|
||
});
|
||
me.root().hover(function () {
|
||
if(!me.root().hasClass("edui-disabled")){
|
||
me.root().toggleClass('edui-hover')
|
||
}
|
||
});
|
||
|
||
return me;
|
||
},
|
||
wrapclick:function(fn,evt){
|
||
if(!this.disabled()){
|
||
$.proxy(fn,this,evt)()
|
||
}
|
||
return this;
|
||
},
|
||
disabled : function(state){
|
||
if(state === undefined){
|
||
return this.root().hasClass('edui-disabled')
|
||
}
|
||
this.root().toggleClass('edui-disabled',state).find('.edui-btn').toggleClass('edui-disabled',state);
|
||
return this;
|
||
},
|
||
active:function(state){
|
||
if(state === undefined){
|
||
return this.root().hasClass('edui-active')
|
||
}
|
||
this.root().toggleClass('edui-active',state).find('.edui-btn:first').toggleClass('edui-active',state);
|
||
return this;
|
||
},
|
||
mergeWith:function($obj){
|
||
var me = this;
|
||
me.data('$mergeObj',$obj);
|
||
$obj.edui().data('$mergeObj',me.root());
|
||
if(!$.contains(document.body,$obj[0])){
|
||
$obj.appendTo(me.root());
|
||
}
|
||
me.root().delegate('.edui-dropdown-toggle','click',function(){
|
||
me.wrapclick(function(){
|
||
$obj.edui().show();
|
||
})
|
||
});
|
||
me.register('click',me.root().find('.edui-dropdown-toggle'),function(evt){
|
||
$obj.hide()
|
||
});
|
||
}
|
||
});
|
||
/**
|
||
* Created with JetBrains PhpStorm.
|
||
* User: hn
|
||
* Date: 13-7-10
|
||
* Time: 下午3:07
|
||
* To change this template use File | Settings | File Templates.
|
||
*/
|
||
UM.ui.define('colorsplitbutton',{
|
||
|
||
tpl : '<div class="edui-splitbutton <%if (name){%>edui-splitbutton-<%= name %><%}%>" unselectable="on" <%if(title){%>data-original-title="<%=title%>"<%}%>><div class="edui-btn" unselectable="on" ><%if(icon){%><div unselectable="on" class="edui-icon-<%=icon%> edui-icon"></div><%}%><div class="edui-splitbutton-color-label" <%if (color) {%>style="background: <%=color%>"<%}%>></div><%if(text){%><%=text%><%}%></div>'+
|
||
'<div unselectable="on" class="edui-btn edui-dropdown-toggle" >'+
|
||
'<div unselectable="on" class="edui-caret"><\/div>'+
|
||
'</div>'+
|
||
'</div>',
|
||
defaultOpt: {
|
||
color: ''
|
||
},
|
||
init: function( options ){
|
||
|
||
var me = this;
|
||
|
||
me.supper.init.call( me, options );
|
||
|
||
},
|
||
colorLabel: function(){
|
||
return this.root().find('.edui-splitbutton-color-label');
|
||
}
|
||
|
||
}, 'splitbutton');
|
||
//popup 类
|
||
UM.ui.define('popup', {
|
||
tpl: '<div class="edui-dropdown-menu edui-popup"'+
|
||
'<%if(!<%=stopprop%>){%>onmousedown="return false"<%}%>'+
|
||
'><div class="edui-popup-body" unselectable="on" onmousedown="return false"><%=subtpl%></div>' +
|
||
'<div class="edui-popup-caret"></div>' +
|
||
'</div>',
|
||
defaultOpt: {
|
||
stopprop:false,
|
||
subtpl: '',
|
||
width: '',
|
||
height: ''
|
||
},
|
||
init: function (options) {
|
||
this.root($($.parseTmpl(this.tpl, options)));
|
||
return this;
|
||
},
|
||
mergeTpl: function (data) {
|
||
return $.parseTmpl(this.tpl, {subtpl: data});
|
||
},
|
||
show: function ($obj, posObj) {
|
||
if (!posObj) posObj = {};
|
||
|
||
var fnname = posObj.fnname || 'position';
|
||
if (this.trigger('beforeshow') === false) {
|
||
return;
|
||
} else {
|
||
this.root().css($.extend({display: 'block'}, $obj ? {
|
||
top: $obj[fnname]().top + ( posObj.dir == 'right' ? 0 : $obj.outerHeight()) - (posObj.offsetTop || 0),
|
||
left: $obj[fnname]().left + (posObj.dir == 'right' ? $obj.outerWidth() : 0) - (posObj.offsetLeft || 0),
|
||
position: 'absolute'
|
||
} : {}));
|
||
|
||
this.root().find('.edui-popup-caret').css({
|
||
top: posObj.caretTop || 0,
|
||
left: posObj.caretLeft || 0,
|
||
position: 'absolute'
|
||
}).addClass(posObj.caretDir || "up")
|
||
|
||
}
|
||
this.trigger("aftershow");
|
||
},
|
||
hide: function () {
|
||
this.root().css('display', 'none');
|
||
this.trigger('afterhide')
|
||
},
|
||
attachTo: function ($obj, posObj) {
|
||
var me = this
|
||
if (!$obj.data('$mergeObj')) {
|
||
$obj.data('$mergeObj', me.root());
|
||
$obj.on('wrapclick', function (evt) {
|
||
me.show($obj, posObj)
|
||
});
|
||
me.register('click', $obj, function (evt) {
|
||
me.hide()
|
||
});
|
||
me.data('$mergeObj', $obj)
|
||
}
|
||
},
|
||
getBodyContainer: function () {
|
||
return this.root().find(".edui-popup-body");
|
||
}
|
||
});
|
||
//scale 类
|
||
UM.ui.define('scale', {
|
||
tpl: '<div class="edui-scale" unselectable="on">' +
|
||
'<span class="edui-scale-hand0"></span>' +
|
||
'<span class="edui-scale-hand1"></span>' +
|
||
'<span class="edui-scale-hand2"></span>' +
|
||
'<span class="edui-scale-hand3"></span>' +
|
||
'<span class="edui-scale-hand4"></span>' +
|
||
'<span class="edui-scale-hand5"></span>' +
|
||
'<span class="edui-scale-hand6"></span>' +
|
||
'<span class="edui-scale-hand7"></span>' +
|
||
'</div>',
|
||
defaultOpt: {
|
||
$doc: $(document),
|
||
$wrap: $(document)
|
||
},
|
||
init: function (options) {
|
||
if(options.$doc) this.defaultOpt.$doc = options.$doc;
|
||
if(options.$wrap) this.defaultOpt.$wrap = options.$wrap;
|
||
this.root($($.parseTmpl(this.tpl, options)));
|
||
this.initStyle();
|
||
this.startPos = this.prePos = {x: 0, y: 0};
|
||
this.dragId = -1;
|
||
return this;
|
||
},
|
||
initStyle: function () {
|
||
utils.cssRule('edui-style-scale', '.edui-scale{display:none;position:absolute;border:1px solid #38B2CE;cursor:hand;}' +
|
||
'.edui-scale span{position:absolute;left:0;top:0;width:7px;height:7px;overflow:hidden;font-size:0px;display:block;background-color:#3C9DD0;}'
|
||
+ '.edui-scale .edui-scale-hand0{cursor:nw-resize;top:0;margin-top:-4px;left:0;margin-left:-4px;}'
|
||
+ '.edui-scale .edui-scale-hand1{cursor:n-resize;top:0;margin-top:-4px;left:50%;margin-left:-4px;}'
|
||
+ '.edui-scale .edui-scale-hand2{cursor:ne-resize;top:0;margin-top:-4px;left:100%;margin-left:-3px;}'
|
||
+ '.edui-scale .edui-scale-hand3{cursor:w-resize;top:50%;margin-top:-4px;left:0;margin-left:-4px;}'
|
||
+ '.edui-scale .edui-scale-hand4{cursor:e-resize;top:50%;margin-top:-4px;left:100%;margin-left:-3px;}'
|
||
+ '.edui-scale .edui-scale-hand5{cursor:sw-resize;top:100%;margin-top:-3px;left:0;margin-left:-4px;}'
|
||
+ '.edui-scale .edui-scale-hand6{cursor:s-resize;top:100%;margin-top:-3px;left:50%;margin-left:-4px;}'
|
||
+ '.edui-scale .edui-scale-hand7{cursor:se-resize;top:100%;margin-top:-3px;left:100%;margin-left:-3px;}');
|
||
},
|
||
_eventHandler: function (e) {
|
||
var me = this,
|
||
$doc = me.defaultOpt.$doc;
|
||
switch (e.type) {
|
||
case 'mousedown':
|
||
var hand = e.target || e.srcElement, hand;
|
||
if (hand.className.indexOf('edui-scale-hand') != -1) {
|
||
me.dragId = hand.className.slice(-1);
|
||
me.startPos.x = me.prePos.x = e.clientX;
|
||
me.startPos.y = me.prePos.y = e.clientY;
|
||
$doc.bind('mousemove', $.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;
|
||
me.updateTargetElement();
|
||
}
|
||
break;
|
||
case 'mouseup':
|
||
if (me.dragId != -1) {
|
||
me.dragId = -1;
|
||
me.updateTargetElement();
|
||
var $target = me.data('$scaleTarget');
|
||
if ($target.parent()) me.attachTo(me.data('$scaleTarget'));
|
||
}
|
||
$doc.unbind('mousemove', $.proxy(me._eventHandler, me));
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
},
|
||
updateTargetElement: function () {
|
||
var me = this,
|
||
$root = me.root(),
|
||
$target = me.data('$scaleTarget');
|
||
$target.css({width: $root.width(), height: $root.height()});
|
||
me.attachTo($target);
|
||
},
|
||
updateContainerStyle: function (dir, offset) {
|
||
var me = this,
|
||
$dom = me.root(),
|
||
tmp,
|
||
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]
|
||
];
|
||
|
||
if (rect[dir][0] != 0) {
|
||
tmp = parseInt($dom.offset().left) + offset.x;
|
||
$dom.css('left', me._validScaledProp('left', tmp));
|
||
}
|
||
if (rect[dir][1] != 0) {
|
||
tmp = parseInt($dom.offset().top) + offset.y;
|
||
$dom.css('top', me._validScaledProp('top', tmp));
|
||
}
|
||
if (rect[dir][2] != 0) {
|
||
tmp = $dom.width() + rect[dir][2] * offset.x;
|
||
$dom.css('width', me._validScaledProp('width', tmp));
|
||
}
|
||
if (rect[dir][3] != 0) {
|
||
tmp = $dom.height() + rect[dir][3] * offset.y;
|
||
$dom.css('height', me._validScaledProp('height', tmp));
|
||
}
|
||
},
|
||
_validScaledProp: function (prop, value) {
|
||
var $ele = this.root(),
|
||
$wrap = this.defaultOpt.$doc,
|
||
calc = function(val, a, b){
|
||
return (val + a) > b ? b - a : value;
|
||
};
|
||
|
||
value = isNaN(value) ? 0 : value;
|
||
switch (prop) {
|
||
case 'left':
|
||
return value < 0 ? 0 : calc(value, $ele.width(), $wrap.width());
|
||
case 'top':
|
||
return value < 0 ? 0 : calc(value, $ele.height(),$wrap.height());
|
||
case 'width':
|
||
return value <= 0 ? 1 : calc(value, $ele.offset().left, $wrap.width());
|
||
case 'height':
|
||
return value <= 0 ? 1 : calc(value, $ele.offset().top, $wrap.height());
|
||
}
|
||
},
|
||
show: function ($obj) {
|
||
var me = this;
|
||
if ($obj) me.attachTo($obj);
|
||
me.root().bind('mousedown', $.proxy(me._eventHandler, me));
|
||
me.defaultOpt.$doc.bind('mouseup', $.proxy(me._eventHandler, me));
|
||
me.root().show();
|
||
me.trigger("aftershow");
|
||
},
|
||
hide: function () {
|
||
var me = this;
|
||
me.root().unbind('mousedown', $.proxy(me._eventHandler, me));
|
||
me.defaultOpt.$doc.unbind('mouseup', $.proxy(me._eventHandler, me));
|
||
me.root().hide();
|
||
me.trigger('afterhide')
|
||
},
|
||
attachTo: function ($obj) {
|
||
var me = this,
|
||
imgPos = $obj.offset(),
|
||
$root = me.root(),
|
||
$wrap = me.defaultOpt.$wrap,
|
||
posObj = $wrap.offset();
|
||
|
||
me.data('$scaleTarget', $obj);
|
||
me.root().css({
|
||
position: 'absolute',
|
||
width: $obj.width(),
|
||
height: $obj.height(),
|
||
left: imgPos.left - posObj.left - parseInt($wrap.css('border-left-width')) - parseInt($root.css('border-left-width')),
|
||
top: imgPos.top - posObj.top - parseInt($wrap.css('border-top-width')) - parseInt($root.css('border-top-width'))
|
||
});
|
||
},
|
||
getScaleTarget: function () {
|
||
return this.data('$scaleTarget')[0];
|
||
}
|
||
});
|
||
//colorpicker 类
|
||
UM.ui.define('colorpicker', {
|
||
tpl: function (opt) {
|
||
var COLORS = (
|
||
'ffffff,000000,eeece1,1f497d,4f81bd,c0504d,9bbb59,8064a2,4bacc6,f79646,' +
|
||
'f2f2f2,7f7f7f,ddd9c3,c6d9f0,dbe5f1,f2dcdb,ebf1dd,e5e0ec,dbeef3,fdeada,' +
|
||
'd8d8d8,595959,c4bd97,8db3e2,b8cce4,e5b9b7,d7e3bc,ccc1d9,b7dde8,fbd5b5,' +
|
||
'bfbfbf,3f3f3f,938953,548dd4,95b3d7,d99694,c3d69b,b2a2c7,92cddc,fac08f,' +
|
||
'a5a5a5,262626,494429,17365d,366092,953734,76923c,5f497a,31859b,e36c09,' +
|
||
'7f7f7f,0c0c0c,1d1b10,0f243e,244061,632423,4f6128,3f3151,205867,974806,' +
|
||
'c00000,ff0000,ffc000,ffff00,92d050,00b050,00b0f0,0070c0,002060,7030a0,').split(',');
|
||
|
||
var html = '<div unselectable="on" onmousedown="return false" class="edui-colorpicker<%if (name){%> edui-colorpicker-<%=name%><%}%>" >' +
|
||
'<table unselectable="on" onmousedown="return false">' +
|
||
'<tr><td colspan="10">'+opt.lang_themeColor+'</td> </tr>' +
|
||
'<tr class="edui-colorpicker-firstrow" >';
|
||
|
||
for (var i = 0; i < COLORS.length; i++) {
|
||
if (i && i % 10 === 0) {
|
||
html += '</tr>' + (i == 60 ? '<tr><td colspan="10">'+opt.lang_standardColor+'</td></tr>' : '') + '<tr' + (i == 60 ? ' class="edui-colorpicker-firstrow"' : '') + '>';
|
||
}
|
||
html += i < 70 ? '<td><a unselectable="on" onmousedown="return false" title="' + COLORS[i] + '" class="edui-colorpicker-colorcell"' +
|
||
' data-color="#' + COLORS[i] + '"' +
|
||
' style="background-color:#' + COLORS[i] + ';border:solid #ccc;' +
|
||
(i < 10 || i >= 60 ? 'border-width:1px;' :
|
||
i >= 10 && i < 20 ? 'border-width:1px 1px 0 1px;' :
|
||
'border-width:0 1px 0 1px;') +
|
||
'"' +
|
||
'></a></td>' : '';
|
||
}
|
||
html += '</tr></table></div>';
|
||
return html;
|
||
},
|
||
init: function (options) {
|
||
var me = this;
|
||
me.root($($.parseTmpl(me.supper.mergeTpl(me.tpl(options)),options)));
|
||
|
||
me.root().on("click",function (e) {
|
||
me.trigger('pickcolor', $(e.target).data('color'));
|
||
});
|
||
}
|
||
}, 'popup');
|
||
/**
|
||
* Created with JetBrains PhpStorm.
|
||
* User: hn
|
||
* Date: 13-5-29
|
||
* Time: 下午8:01
|
||
* To change this template use File | Settings | File Templates.
|
||
*/
|
||
|
||
(function(){
|
||
|
||
var widgetName = 'combobox',
|
||
itemClassName = 'edui-combobox-item',
|
||
HOVER_CLASS = 'edui-combobox-item-hover',
|
||
ICON_CLASS = 'edui-combobox-checked-icon',
|
||
labelClassName = 'edui-combobox-item-label';
|
||
|
||
UM.ui.define( widgetName, ( function(){
|
||
|
||
return {
|
||
tpl: "<ul class=\"dropdown-menu edui-combobox-menu<%if (comboboxName!=='') {%> edui-combobox-<%=comboboxName%><%}%>\" unselectable=\"on\" onmousedown=\"return false\" role=\"menu\" aria-labelledby=\"dropdownMenu\">" +
|
||
"<%if(autoRecord) {%>" +
|
||
"<%for( var i=0, len = recordStack.length; i<len; i++ ) {%>" +
|
||
"<%var index = recordStack[i];%>" +
|
||
"<li class=\"<%=itemClassName%><%if( selected == index ) {%> edui-combobox-checked<%}%>\" data-item-index=\"<%=index%>\" unselectable=\"on\" onmousedown=\"return false\">" +
|
||
"<span class=\"edui-combobox-icon\" unselectable=\"on\" onmousedown=\"return false\"></span>" +
|
||
"<label class=\"<%=labelClassName%>\" style=\"<%=itemStyles[ index ]%>\" unselectable=\"on\" onmousedown=\"return false\"><%=items[index]%></label>" +
|
||
"</li>" +
|
||
"<%}%>" +
|
||
"<%if( i ) {%>" +
|
||
"<li class=\"edui-combobox-item-separator\"></li>" +
|
||
"<%}%>" +
|
||
"<%}%>" +
|
||
"<%for( var i=0, label; label = items[i]; i++ ) {%>" +
|
||
"<li class=\"<%=itemClassName%><%if( selected == i ) {%> edui-combobox-checked<%}%> edui-combobox-item-<%=i%>\" data-item-index=\"<%=i%>\" unselectable=\"on\" onmousedown=\"return false\">" +
|
||
"<span class=\"edui-combobox-icon\" unselectable=\"on\" onmousedown=\"return false\"></span>" +
|
||
"<label class=\"<%=labelClassName%>\" style=\"<%=itemStyles[ i ]%>\" unselectable=\"on\" onmousedown=\"return false\"><%=label%></label>" +
|
||
"</li>" +
|
||
"<%}%>" +
|
||
"</ul>",
|
||
defaultOpt: {
|
||
//记录栈初始列表
|
||
recordStack: [],
|
||
//可用项列表
|
||
items: [],
|
||
//item对应的值列表
|
||
value: [],
|
||
comboboxName: '',
|
||
selected: '',
|
||
//自动记录
|
||
autoRecord: true,
|
||
//最多记录条数
|
||
recordCount: 5
|
||
},
|
||
init: function( options ){
|
||
|
||
var me = this;
|
||
|
||
$.extend( me._optionAdaptation( options ), me._createItemMapping( options.recordStack, options.items ), {
|
||
itemClassName: itemClassName,
|
||
iconClass: ICON_CLASS,
|
||
labelClassName: labelClassName
|
||
} );
|
||
|
||
this._transStack( options );
|
||
|
||
me.root( $( $.parseTmpl( me.tpl, options ) ) );
|
||
|
||
this.data( 'options', options ).initEvent();
|
||
|
||
},
|
||
initEvent: function(){
|
||
|
||
var me = this;
|
||
|
||
me.initSelectItem();
|
||
|
||
this.initItemActive();
|
||
|
||
},
|
||
/**
|
||
* 初始化选择项
|
||
*/
|
||
initSelectItem: function(){
|
||
|
||
var me = this,
|
||
labelClass = "."+labelClassName;
|
||
|
||
me.root().delegate('.' + itemClassName, 'click', function(){
|
||
|
||
var $li = $(this),
|
||
index = $li.attr('data-item-index');
|
||
|
||
me.trigger('comboboxselect', {
|
||
index: index,
|
||
label: $li.find(labelClass).text(),
|
||
value: me.data('options').value[ index ]
|
||
}).select( index );
|
||
|
||
me.hide();
|
||
|
||
return false;
|
||
|
||
});
|
||
|
||
},
|
||
initItemActive: function(){
|
||
var fn = {
|
||
mouseenter: 'addClass',
|
||
mouseleave: 'removeClass'
|
||
};
|
||
if ($.IE6) {
|
||
this.root().delegate( '.'+itemClassName, 'mouseenter mouseleave', function( evt ){
|
||
$(this)[ fn[ evt.type ] ]( HOVER_CLASS );
|
||
}).one('afterhide', function(){
|
||
});
|
||
}
|
||
},
|
||
/**
|
||
* 选择给定索引的项
|
||
* @param index 项索引
|
||
* @returns {*} 如果存在对应索引的项,则返回该项;否则返回null
|
||
*/
|
||
select: function( index ){
|
||
|
||
var itemCount = this.data('options').itemCount,
|
||
items = this.data('options').autowidthitem;
|
||
|
||
if ( items && !items.length ) {
|
||
items = this.data('options').items;
|
||
}
|
||
|
||
if( itemCount == 0 ) {
|
||
return null;
|
||
}
|
||
|
||
if( index < 0 ) {
|
||
|
||
index = itemCount + index % itemCount;
|
||
|
||
} else if ( index >= itemCount ) {
|
||
|
||
index = itemCount-1;
|
||
|
||
}
|
||
|
||
this.trigger( 'changebefore', items[ index ] );
|
||
|
||
this._update( index );
|
||
|
||
this.trigger( 'changeafter', items[ index ] );
|
||
|
||
return null;
|
||
|
||
},
|
||
selectItemByLabel: function( label ){
|
||
|
||
var itemMapping = this.data('options').itemMapping,
|
||
me = this,
|
||
index = null;
|
||
|
||
!$.isArray( label ) && ( label = [ label ] );
|
||
|
||
$.each( label, function( i, item ){
|
||
|
||
index = itemMapping[ item ];
|
||
|
||
if( index !== undefined ) {
|
||
|
||
me.select( index );
|
||
return false;
|
||
|
||
}
|
||
|
||
} );
|
||
|
||
},
|
||
/**
|
||
* 转换记录栈
|
||
*/
|
||
_transStack: function( options ) {
|
||
|
||
var temp = [],
|
||
itemIndex = -1,
|
||
selected = -1;
|
||
|
||
$.each( options.recordStack, function( index, item ){
|
||
|
||
itemIndex = options.itemMapping[ item ];
|
||
|
||
if( $.isNumeric( itemIndex ) ) {
|
||
|
||
temp.push( itemIndex );
|
||
|
||
//selected的合法性检测
|
||
if( item == options.selected ) {
|
||
selected = itemIndex;
|
||
}
|
||
|
||
}
|
||
|
||
} );
|
||
|
||
options.recordStack = temp;
|
||
options.selected = selected;
|
||
temp = null;
|
||
|
||
},
|
||
_optionAdaptation: function( options ) {
|
||
|
||
if( !( 'itemStyles' in options ) ) {
|
||
|
||
options.itemStyles = [];
|
||
|
||
for( var i = 0, len = options.items.length; i < len; i++ ) {
|
||
options.itemStyles.push('');
|
||
}
|
||
|
||
}
|
||
|
||
options.autowidthitem = options.autowidthitem || options.items;
|
||
options.itemCount = options.items.length;
|
||
|
||
return options;
|
||
|
||
},
|
||
_createItemMapping: function( stackItem, items ){
|
||
|
||
var temp = {},
|
||
result = {
|
||
recordStack: [],
|
||
mapping: {}
|
||
};
|
||
|
||
$.each( items, function( index, item ){
|
||
temp[ item ] = index;
|
||
} );
|
||
|
||
result.itemMapping = temp;
|
||
|
||
$.each( stackItem, function( index, item ){
|
||
|
||
if( temp[ item ] !== undefined ) {
|
||
result.recordStack.push( temp[ item ] );
|
||
result.mapping[ item ] = temp[ item ];
|
||
}
|
||
|
||
} );
|
||
|
||
return result;
|
||
|
||
},
|
||
_update: function ( index ) {
|
||
|
||
var options = this.data("options"),
|
||
newStack = [],
|
||
newChilds = null;
|
||
|
||
$.each( options.recordStack, function( i, item ){
|
||
|
||
if( item != index ) {
|
||
newStack.push( item );
|
||
}
|
||
|
||
} );
|
||
|
||
//压入最新的记录
|
||
newStack.unshift( index );
|
||
|
||
if( newStack.length > options.recordCount ) {
|
||
newStack.length = options.recordCount;
|
||
}
|
||
|
||
options.recordStack = newStack;
|
||
options.selected = index;
|
||
|
||
newChilds = $( $.parseTmpl( this.tpl, options ) );
|
||
|
||
//重新渲染
|
||
this.root().html( newChilds.html() );
|
||
|
||
newChilds = null;
|
||
newStack = null;
|
||
|
||
}
|
||
};
|
||
|
||
} )(), 'menu' );
|
||
|
||
})();
|
||
|
||
/**
|
||
* Combox 抽象基类
|
||
* User: hn
|
||
* Date: 13-5-29
|
||
* Time: 下午8:01
|
||
* To change this template use File | Settings | File Templates.
|
||
*/
|
||
|
||
(function(){
|
||
|
||
var widgetName = 'buttoncombobox';
|
||
|
||
UM.ui.define( widgetName, ( function(){
|
||
|
||
return {
|
||
defaultOpt: {
|
||
//按钮初始文字
|
||
label: '',
|
||
title: ''
|
||
},
|
||
init: function( options ) {
|
||
|
||
var me = this;
|
||
|
||
var btnWidget = $.eduibutton({
|
||
caret: true,
|
||
name: options.comboboxName,
|
||
title: options.title,
|
||
text: options.label,
|
||
click: function(){
|
||
me.show( this.root() );
|
||
}
|
||
});
|
||
|
||
me.supper.init.call( me, options );
|
||
|
||
//监听change, 改变button显示内容
|
||
me.on('changebefore', function( e, label ){
|
||
btnWidget.eduibutton('label', label );
|
||
});
|
||
|
||
me.data( 'button', btnWidget );
|
||
|
||
me.attachTo(btnWidget)
|
||
|
||
},
|
||
button: function(){
|
||
return this.data( 'button' );
|
||
}
|
||
}
|
||
|
||
} )(), 'combobox' );
|
||
|
||
})();
|
||
|
||
/*modal 类*/
|
||
UM.ui.define('modal', {
|
||
tpl: '<div class="edui-modal" tabindex="-1" >' +
|
||
'<div class="edui-modal-header">' +
|
||
'<div class="edui-close" data-hide="modal"></div>' +
|
||
'<h3 class="edui-title"><%=title%></h3>' +
|
||
'</div>' +
|
||
'<div class="edui-modal-body" style="<%if(width){%>width:<%=width%>px;<%}%>' +
|
||
'<%if(height){%>height:<%=height%>px;<%}%>">' +
|
||
' </div>' +
|
||
'<% if(cancellabel || oklabel) {%>' +
|
||
'<div class="edui-modal-footer">' +
|
||
'<div class="edui-modal-tip"></div>' +
|
||
'<%if(oklabel){%><div class="edui-btn edui-btn-primary" data-ok="modal"><%=oklabel%></div><%}%>' +
|
||
'<%if(cancellabel){%><div class="edui-btn" data-hide="modal"><%=cancellabel%></div><%}%>' +
|
||
'</div>' +
|
||
'<%}%></div>',
|
||
defaultOpt: {
|
||
title: "",
|
||
cancellabel: "",
|
||
oklabel: "",
|
||
width: '',
|
||
height: '',
|
||
backdrop: true,
|
||
keyboard: true
|
||
},
|
||
init: function (options) {
|
||
var me = this;
|
||
|
||
me.root($($.parseTmpl(me.tpl, options || {})));
|
||
|
||
me.data("options", options);
|
||
if (options.okFn) {
|
||
me.on('ok', $.proxy(options.okFn, me))
|
||
}
|
||
if (options.cancelFn) {
|
||
me.on('beforehide', $.proxy(options.cancelFn, me))
|
||
}
|
||
|
||
me.root().delegate('[data-hide="modal"]', 'click', $.proxy(me.hide, me))
|
||
.delegate('[data-ok="modal"]', 'click', $.proxy(me.ok, me));
|
||
|
||
$('[data-hide="modal"],[data-ok="modal"]',me.root()).hover(function(){
|
||
$(this).toggleClass('edui-hover')
|
||
});
|
||
},
|
||
toggle: function () {
|
||
var me = this;
|
||
return me[!me.data("isShown") ? 'show' : 'hide']();
|
||
},
|
||
show: function () {
|
||
|
||
var me = this;
|
||
|
||
me.trigger("beforeshow");
|
||
|
||
if (me.data("isShown")) return;
|
||
|
||
me.data("isShown", true);
|
||
|
||
me.escape();
|
||
|
||
me.backdrop(function () {
|
||
me.autoCenter();
|
||
me.root()
|
||
.show()
|
||
.focus()
|
||
.trigger('aftershow');
|
||
})
|
||
},
|
||
showTip: function ( text ) {
|
||
$( '.edui-modal-tip', this.root() ).html( text ).fadeIn();
|
||
},
|
||
hideTip: function ( text ) {
|
||
$( '.edui-modal-tip', this.root() ).fadeOut( function (){
|
||
$(this).html('');
|
||
} );
|
||
},
|
||
autoCenter: function () {
|
||
//ie6下不用处理了
|
||
!$.IE6 && this.root().css("margin-left", -(this.root().width() / 2));
|
||
},
|
||
hide: function () {
|
||
var me = this;
|
||
|
||
me.trigger("beforehide");
|
||
|
||
if (!me.data("isShown")) return;
|
||
|
||
me.data("isShown", false);
|
||
|
||
me.escape();
|
||
|
||
me.hideModal();
|
||
},
|
||
escape: function () {
|
||
var me = this;
|
||
if (me.data("isShown") && me.data("options").keyboard) {
|
||
me.root().on('keyup', function (e) {
|
||
e.which == 27 && me.hide();
|
||
})
|
||
}
|
||
else if (!me.data("isShown")) {
|
||
me.root().off('keyup');
|
||
}
|
||
},
|
||
hideModal: function () {
|
||
var me = this;
|
||
me.root().hide();
|
||
me.backdrop(function () {
|
||
me.removeBackdrop();
|
||
me.trigger('afterhide');
|
||
})
|
||
},
|
||
removeBackdrop: function () {
|
||
this.$backdrop && this.$backdrop.remove();
|
||
this.$backdrop = null;
|
||
},
|
||
backdrop: function (callback) {
|
||
var me = this;
|
||
if (me.data("isShown") && me.data("options").backdrop) {
|
||
me.$backdrop = $('<div class="edui-modal-backdrop" />').click(
|
||
me.data("options").backdrop == 'static' ?
|
||
$.proxy(me.root()[0].focus, me.root()[0])
|
||
: $.proxy(me.hide, me)
|
||
)
|
||
}
|
||
me.trigger('afterbackdrop');
|
||
callback && callback();
|
||
|
||
},
|
||
attachTo: function ($obj) {
|
||
var me = this
|
||
if (!$obj.data('$mergeObj')) {
|
||
|
||
$obj.data('$mergeObj', me.root());
|
||
$obj.on('click', function () {
|
||
me.toggle($obj)
|
||
});
|
||
me.data('$mergeObj', $obj)
|
||
}
|
||
},
|
||
ok: function () {
|
||
var me = this;
|
||
me.trigger('beforeok');
|
||
if (me.trigger("ok", me) === false) {
|
||
return;
|
||
}
|
||
me.hide();
|
||
},
|
||
getBodyContainer: function () {
|
||
return this.root().find('.edui-modal-body')
|
||
}
|
||
});
|
||
|
||
|
||
/*tooltip 类*/
|
||
UM.ui.define('tooltip', {
|
||
tpl: '<div class="edui-tooltip" unselectable="on" onmousedown="return false">' +
|
||
'<div class="edui-tooltip-arrow" unselectable="on" onmousedown="return false"></div>' +
|
||
'<div class="edui-tooltip-inner" unselectable="on" onmousedown="return false"></div>' +
|
||
'</div>',
|
||
init: function (options) {
|
||
var me = this;
|
||
me.root($($.parseTmpl(me.tpl, options || {})));
|
||
},
|
||
content: function (e) {
|
||
var me = this,
|
||
title = $(e.currentTarget).attr("data-original-title");
|
||
|
||
me.root().find('.edui-tooltip-inner')['text'](title);
|
||
},
|
||
position: function (e) {
|
||
var me = this,
|
||
$obj = $(e.currentTarget);
|
||
|
||
me.root().css($.extend({display: 'block'}, $obj ? {
|
||
top: $obj.outerHeight(),
|
||
left: (($obj.outerWidth() - me.root().outerWidth()) / 2)
|
||
} : {}))
|
||
},
|
||
show: function (e) {
|
||
if ($(e.currentTarget).hasClass('edui-disabled')) return;
|
||
|
||
var me = this;
|
||
me.content(e);
|
||
me.root().appendTo($(e.currentTarget));
|
||
me.position(e);
|
||
me.root().css('display', 'block');
|
||
},
|
||
hide: function () {
|
||
var me = this;
|
||
me.root().css('display', 'none')
|
||
},
|
||
attachTo: function ($obj) {
|
||
var me = this;
|
||
|
||
function tmp($obj) {
|
||
var me = this;
|
||
|
||
if (!$.contains(document.body, me.root()[0])) {
|
||
me.root().appendTo($obj);
|
||
}
|
||
|
||
me.data('tooltip', me.root());
|
||
|
||
$obj.each(function () {
|
||
if ($(this).attr("data-original-title")) {
|
||
$(this).on('mouseenter', $.proxy(me.show, me))
|
||
.on('mouseleave click', $.proxy(me.hide, me))
|
||
|
||
}
|
||
});
|
||
|
||
}
|
||
|
||
if ($.type($obj) === "undefined") {
|
||
$("[data-original-title]").each(function (i, el) {
|
||
tmp.call(me, $(el));
|
||
})
|
||
|
||
} else {
|
||
if (!$obj.data('tooltip')) {
|
||
tmp.call(me, $obj);
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
/*tab 类*/
|
||
UM.ui.define('tab', {
|
||
init: function (options) {
|
||
var me = this,
|
||
slr = options.selector;
|
||
|
||
if ($.type(slr)) {
|
||
me.root($(slr, options.context));
|
||
me.data("context", options.context);
|
||
|
||
$(slr, me.data("context")).on('click', function (e) {
|
||
me.show(e);
|
||
});
|
||
}
|
||
},
|
||
show: function (e) {
|
||
|
||
var me = this,
|
||
$cur = $(e.target),
|
||
$ul = $cur.closest('ul'),
|
||
selector,
|
||
previous,
|
||
$target,
|
||
e;
|
||
|
||
selector = $cur.attr('data-context');
|
||
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '');
|
||
|
||
var $tmp = $cur.parent('li');
|
||
|
||
if (!$tmp.length || $tmp.hasClass('edui-active')) return;
|
||
|
||
previous = $ul.find('.edui-active:last a')[0];
|
||
|
||
e = $.Event('beforeshow', {
|
||
target: $cur[0],
|
||
relatedTarget: previous
|
||
});
|
||
|
||
me.trigger(e);
|
||
|
||
if (e.isDefaultPrevented()) return;
|
||
|
||
$target = $(selector, me.data("context"));
|
||
|
||
me.activate($cur.parent('li'), $ul);
|
||
me.activate($target, $target.parent(), function () {
|
||
me.trigger({
|
||
type: 'aftershow', relatedTarget: previous
|
||
})
|
||
});
|
||
},
|
||
activate: function (element, container, callback) {
|
||
if (element === undefined) {
|
||
return $(".edui-tab-item.edui-active",this.root()).index();
|
||
}
|
||
|
||
var $active = container.find('> .edui-active');
|
||
|
||
$active.removeClass('edui-active');
|
||
|
||
element.addClass('edui-active');
|
||
|
||
callback && callback();
|
||
}
|
||
});
|
||
|
||
|
||
//button 类
|
||
UM.ui.define('separator', {
|
||
tpl: '<div class="edui-separator" unselectable="on" onmousedown="return false" ></div>',
|
||
init: function (options) {
|
||
var me = this;
|
||
me.root($($.parseTmpl(me.tpl, options)));
|
||
return me;
|
||
}
|
||
});
|
||
/**
|
||
* @file adapter.js
|
||
* @desc adapt ui to editor
|
||
* @import core/Editor.js, core/utils.js
|
||
*/
|
||
|
||
(function () {
|
||
var _editorUI = {},
|
||
_editors = {},
|
||
_readyFn = [],
|
||
_activeWidget = null,
|
||
_widgetData = {},
|
||
_widgetCallBack = {},
|
||
_cacheUI = {},
|
||
_maxZIndex = null;
|
||
|
||
utils.extend(UM, {
|
||
defaultWidth : 500,
|
||
defaultHeight : 500,
|
||
registerUI: function (name, fn) {
|
||
utils.each(name.split(/\s+/), function (uiname) {
|
||
_editorUI[uiname] = fn;
|
||
})
|
||
},
|
||
|
||
setEditor : function(editor){
|
||
!_editors[editor.id] && (_editors[editor.id] = editor);
|
||
},
|
||
registerWidget : function(name,pro,cb){
|
||
_widgetData[name] = $.extend2(pro,{
|
||
$root : '',
|
||
_preventDefault:false,
|
||
root:function($el){
|
||
return this.$root || (this.$root = $el);
|
||
},
|
||
preventDefault:function(){
|
||
this._preventDefault = true;
|
||
},
|
||
clear:false
|
||
});
|
||
if(cb){
|
||
_widgetCallBack[name] = cb;
|
||
}
|
||
},
|
||
getWidgetData : function(name){
|
||
return _widgetData[name]
|
||
},
|
||
setWidgetBody : function(name,$widget,editor){
|
||
if(!editor._widgetData){
|
||
|
||
utils.extend(editor,{
|
||
_widgetData : {},
|
||
getWidgetData : function(name){
|
||
return this._widgetData[name];
|
||
},
|
||
getWidgetCallback : function(widgetName){
|
||
var me = this;
|
||
return function(){
|
||
return _widgetCallBack[widgetName].apply(me,[me,$widget].concat(Array.prototype.slice.call(arguments,0)))
|
||
}
|
||
}
|
||
})
|
||
|
||
}
|
||
var pro = _widgetData[name];
|
||
if(!pro){
|
||
return null;
|
||
}
|
||
pro = editor._widgetData[name];
|
||
if(!pro){
|
||
pro = _widgetData[name];
|
||
pro = editor._widgetData[name] = $.type(pro) == 'function' ? pro : utils.clone(pro);
|
||
}
|
||
|
||
pro.root($widget.edui().getBodyContainer());
|
||
|
||
pro.initContent(editor,$widget);
|
||
if(!pro._preventDefault){
|
||
pro.initEvent(editor,$widget);
|
||
}
|
||
|
||
pro.width && $widget.width(pro.width);
|
||
|
||
|
||
},
|
||
setActiveWidget : function($widget){
|
||
_activeWidget = $widget;
|
||
},
|
||
getEditor: function (id, options) {
|
||
var editor = _editors[id] || (_editors[id] = this.createEditor(id, options));
|
||
_maxZIndex = _maxZIndex ? Math.max(editor.getOpt('zIndex'), _maxZIndex):editor.getOpt('zIndex');
|
||
return editor;
|
||
},
|
||
setTopEditor: function(editor){
|
||
$.each(_editors, function(i, o){
|
||
if(editor == o) {
|
||
editor.$container && editor.$container.css('zIndex', _maxZIndex + 1);
|
||
} else {
|
||
o.$container && o.$container.css('zIndex', o.getOpt('zIndex'));
|
||
}
|
||
});
|
||
},
|
||
clearCache : function(id){
|
||
if ( _editors[id]) {
|
||
delete _editors[id]
|
||
}
|
||
},
|
||
delEditor: function (id) {
|
||
var editor;
|
||
if (editor = _editors[id]) {
|
||
editor.destroy();
|
||
}
|
||
},
|
||
ready: function( fn ){
|
||
_readyFn.push( fn );
|
||
},
|
||
createEditor: function (id, opt) {
|
||
var editor = new UM.Editor(opt);
|
||
var T = this;
|
||
|
||
editor.langIsReady ? $.proxy(renderUI,T)() : editor.addListener("langReady", $.proxy(renderUI,T));
|
||
function renderUI(){
|
||
|
||
|
||
var $container = this.createUI('#' + id, editor);
|
||
editor.key=id;
|
||
editor.ready(function(){
|
||
$.each( _readyFn, function( index, fn ){
|
||
$.proxy( fn, editor )();
|
||
} );
|
||
});
|
||
var options = editor.options;
|
||
if(options.initialFrameWidth){
|
||
options.minFrameWidth = options.initialFrameWidth
|
||
}else{
|
||
options.minFrameWidth = options.initialFrameWidth = editor.$body.width() || UM.defaultWidth;
|
||
}
|
||
|
||
$container.css({
|
||
width: options.initialFrameWidth,
|
||
zIndex:editor.getOpt('zIndex')
|
||
});
|
||
|
||
//ie6下缓存图片
|
||
UM.browser.ie && UM.browser.version === 6 && document.execCommand("BackgroundImageCache", false, true);
|
||
|
||
editor.render(id);
|
||
|
||
|
||
//添加tooltip;
|
||
$.eduitooltip && $.eduitooltip('attachTo', $("[data-original-title]",$container)).css('z-index',editor.getOpt('zIndex')+1);
|
||
|
||
$container.find('a').click(function(evt){
|
||
evt.preventDefault()
|
||
});
|
||
|
||
editor.fireEvent("afteruiready");
|
||
}
|
||
|
||
return editor;
|
||
|
||
},
|
||
createUI: function (id, editor) {
|
||
var $editorCont = $(id),
|
||
$container = $('<div class="edui-container"><div class="edui-editor-body"></div></div>').insertBefore($editorCont);
|
||
editor.$container = $container;
|
||
editor.container = $container[0];
|
||
|
||
editor.$body = $editorCont;
|
||
|
||
//修正在ie9+以上的版本中,自动长高收起时的,残影问题
|
||
if(browser.ie && browser.ie9above){
|
||
var $span = $('<span style="padding:0;margin:0;height:0;width:0"></span>');
|
||
$span.insertAfter($container);
|
||
}
|
||
//初始化注册的ui组件
|
||
$.each(_editorUI,function(n,v){
|
||
var widget = v.call(editor,n);
|
||
if(widget){
|
||
_cacheUI[n] = widget;
|
||
}
|
||
|
||
});
|
||
|
||
$container.find('.edui-editor-body').append($editorCont).before(this.createToolbar(editor.options, editor));
|
||
|
||
$container.find('.edui-toolbar').append($('<div class="edui-dialog-container"></div>'));
|
||
|
||
|
||
return $container;
|
||
},
|
||
createToolbar: function (options, editor) {
|
||
var $toolbar = $.eduitoolbar(), toolbar = $toolbar.edui();
|
||
//创建下来菜单列表
|
||
|
||
if (options.toolbar && options.toolbar.length) {
|
||
var btns = [];
|
||
$.each(options.toolbar,function(i,uiNames){
|
||
$.each(uiNames.split(/\s+/),function(index,name){
|
||
if(name == '|'){
|
||
$.eduiseparator && btns.push($.eduiseparator());
|
||
}else{
|
||
var ui = _cacheUI[name];
|
||
if(name=="fullscreen"){
|
||
ui&&btns.unshift(ui);
|
||
}else{
|
||
ui && btns.push(ui);
|
||
}
|
||
}
|
||
|
||
});
|
||
btns.length && toolbar.appendToBtnmenu(btns);
|
||
});
|
||
} else {
|
||
$toolbar.find('.edui-btn-toolbar').remove()
|
||
}
|
||
return $toolbar;
|
||
}
|
||
|
||
})
|
||
|
||
|
||
})();
|
||
|
||
|
||
|
||
UM.registerUI('bold italic redo undo underline strikethrough superscript subscript insertorderedlist insertunorderedlist ' +
|
||
'cleardoc selectall link unlink print preview justifyleft justifycenter justifyright justifyfull removeformat horizontal drafts',
|
||
function(name) {
|
||
var me = this;
|
||
var $btn = $.eduibutton({
|
||
icon : name,
|
||
click : function(){
|
||
me.execCommand(name);
|
||
},
|
||
title: this.getLang('labelMap')[name] || ''
|
||
});
|
||
|
||
this.addListener('selectionchange',function(){
|
||
var state = this.queryCommandState(name);
|
||
$btn.edui().disabled(state == -1).active(state == 1)
|
||
});
|
||
return $btn;
|
||
}
|
||
);
|
||
|
||
|
||
/**
|
||
* 全屏组件
|
||
*/
|
||
|
||
(function(){
|
||
|
||
//状态缓存
|
||
var STATUS_CACHE = {},
|
||
//状态值列表
|
||
STATUS_LIST = [ 'width', 'height', 'position', 'top', 'left', 'margin', 'padding', 'overflowX', 'overflowY' ],
|
||
CONTENT_AREA_STATUS = {},
|
||
//页面状态
|
||
DOCUMENT_STATUS = {},
|
||
DOCUMENT_ELEMENT_STATUS = {},
|
||
|
||
FULLSCREENS = {};
|
||
|
||
|
||
UM.registerUI('fullscreen', function( name ){
|
||
|
||
var me = this,
|
||
$button = $.eduibutton({
|
||
'icon': 'fullscreen',
|
||
'title': (me.options.labelMap && me.options.labelMap[name]) || me.getLang("labelMap." + name),
|
||
'click': function(){
|
||
//切换
|
||
me.execCommand( name );
|
||
UM.setTopEditor(me);
|
||
}
|
||
});
|
||
|
||
me.addListener( "selectionchange", function () {
|
||
|
||
var state = this.queryCommandState( name );
|
||
$button.edui().disabled( state == -1 ).active( state == 1 );
|
||
|
||
} );
|
||
|
||
//切换至全屏
|
||
me.addListener('ready', function(){
|
||
|
||
me.options.fullscreen && Fullscreen.getInstance( me ).toggle();
|
||
|
||
});
|
||
|
||
return $button;
|
||
|
||
});
|
||
|
||
UM.commands[ 'fullscreen' ] = {
|
||
|
||
execCommand: function (cmdName) {
|
||
|
||
Fullscreen.getInstance( this ).toggle();
|
||
|
||
},
|
||
queryCommandState: function (cmdName) {
|
||
|
||
return this._edui_fullscreen_status;
|
||
},
|
||
notNeedUndo: 1
|
||
|
||
};
|
||
|
||
function Fullscreen( editor ) {
|
||
|
||
var me = this;
|
||
|
||
if( !editor ) {
|
||
throw new Error('invalid params, notfound editor');
|
||
}
|
||
|
||
me.editor = editor;
|
||
|
||
//记录初始化的全屏组件
|
||
FULLSCREENS[ editor.uid ] = this;
|
||
|
||
editor.addListener('destroy', function(){
|
||
delete FULLSCREENS[ editor.uid ];
|
||
me.editor = null;
|
||
});
|
||
|
||
}
|
||
|
||
Fullscreen.prototype = {
|
||
|
||
/**
|
||
* 全屏状态切换
|
||
*/
|
||
toggle: function(){
|
||
|
||
var editor = this.editor,
|
||
//当前编辑器的缩放状态
|
||
_edui_fullscreen_status = this.isFullState();
|
||
editor.fireEvent('beforefullscreenchange', !_edui_fullscreen_status );
|
||
|
||
//更新状态
|
||
this.update( !_edui_fullscreen_status );
|
||
|
||
!_edui_fullscreen_status ? this.enlarge() : this.revert();
|
||
|
||
editor.fireEvent('afterfullscreenchange', !_edui_fullscreen_status );
|
||
if(editor.body.contentEditable === 'true'){
|
||
editor.fireEvent( 'fullscreenchanged', !_edui_fullscreen_status );
|
||
}
|
||
|
||
editor.fireEvent( 'selectionchange' );
|
||
|
||
},
|
||
/**
|
||
* 执行放大
|
||
*/
|
||
enlarge: function(){
|
||
|
||
this.saveSataus();
|
||
|
||
this.setDocumentStatus();
|
||
|
||
this.resize();
|
||
|
||
},
|
||
/**
|
||
* 全屏还原
|
||
*/
|
||
revert: function(){
|
||
|
||
//还原CSS表达式
|
||
var options = this.editor.options,
|
||
height = /%$/.test(options.initialFrameHeight) ? '100%' : (options.initialFrameHeight - this.getStyleValue("padding-top")- this.getStyleValue("padding-bottom") - this.getStyleValue('border-width'));
|
||
|
||
$.IE6 && this.getEditorHolder().style.setExpression('height', 'this.scrollHeight <= ' + height + ' ? "' + height + 'px" : "auto"');
|
||
|
||
//还原容器状态
|
||
this.revertContainerStatus();
|
||
|
||
this.revertContentAreaStatus();
|
||
|
||
this.revertDocumentStatus();
|
||
|
||
},
|
||
/**
|
||
* 更新状态
|
||
* @param isFull 当前状态是否是全屏状态
|
||
*/
|
||
update: function( isFull ) {
|
||
this.editor._edui_fullscreen_status = isFull;
|
||
},
|
||
/**
|
||
* 调整当前编辑器的大小, 如果当前编辑器不处于全屏状态, 则不做调整
|
||
*/
|
||
resize: function(){
|
||
|
||
var $win = null,
|
||
height = 0,
|
||
width = 0,
|
||
borderWidth = 0,
|
||
paddingWidth = 0,
|
||
editor = this.editor,
|
||
me = this,
|
||
bound = null,
|
||
editorBody = null;
|
||
|
||
if( !this.isFullState() ) {
|
||
return;
|
||
}
|
||
|
||
$win = $( window );
|
||
width = $win.width();
|
||
height = $win.height();
|
||
editorBody = this.getEditorHolder();
|
||
//文本编辑区border宽度
|
||
borderWidth = parseInt( domUtils.getComputedStyle( editorBody, 'border-width' ), 10 ) || 0;
|
||
//容器border宽度
|
||
borderWidth += parseInt( domUtils.getComputedStyle( editor.container, 'border-width' ), 10 ) || 0;
|
||
//容器padding
|
||
paddingWidth += parseInt( domUtils.getComputedStyle( editorBody, 'padding-left' ), 10 ) + parseInt( domUtils.getComputedStyle( editorBody, 'padding-right' ), 10 ) || 0;
|
||
|
||
//干掉css表达式
|
||
$.IE6 && editorBody.style.setExpression( 'height', null );
|
||
|
||
bound = this.getBound();
|
||
|
||
$( editor.container ).css( {
|
||
width: width + 'px',
|
||
height: height + 'px',
|
||
position: !$.IE6 ? 'fixed' : 'absolute',
|
||
top: bound.top,
|
||
left: bound.left,
|
||
margin: 0,
|
||
padding: 0,
|
||
overflowX: 'hidden',
|
||
overflowY: 'hidden'
|
||
} );
|
||
|
||
$( editorBody ).css({
|
||
width: width - 2*borderWidth - paddingWidth + 'px',
|
||
height: height - 2*borderWidth - ( editor.options.withoutToolbar ? 0 : $( '.edui-toolbar', editor.container ).outerHeight() ) - $( '.edui-bottombar', editor.container).outerHeight() + 'px',
|
||
overflowX: 'hidden',
|
||
overflowY: 'auto'
|
||
});
|
||
|
||
},
|
||
/**
|
||
* 保存状态
|
||
*/
|
||
saveSataus: function(){
|
||
|
||
var styles = this.editor.container.style,
|
||
tmp = null,
|
||
cache = {};
|
||
|
||
for( var i= 0, len = STATUS_LIST.length; i<len; i++ ) {
|
||
|
||
tmp = STATUS_LIST[ i ];
|
||
cache[ tmp ] = styles[ tmp ];
|
||
|
||
}
|
||
|
||
STATUS_CACHE[ this.editor.uid ] = cache;
|
||
|
||
this.saveContentAreaStatus();
|
||
this.saveDocumentStatus();
|
||
|
||
},
|
||
saveContentAreaStatus: function(){
|
||
|
||
var $holder = $(this.getEditorHolder());
|
||
|
||
CONTENT_AREA_STATUS[ this.editor.uid ] = {
|
||
width: $holder.css("width"),
|
||
overflowX: $holder.css("overflowX"),
|
||
overflowY: $holder.css("overflowY"),
|
||
height: $holder.css("height")
|
||
};
|
||
|
||
},
|
||
/**
|
||
* 保存与指定editor相关的页面的状态
|
||
*/
|
||
saveDocumentStatus: function(){
|
||
|
||
var $doc = $( this.getEditorDocumentBody() );
|
||
|
||
DOCUMENT_STATUS[ this.editor.uid ] = {
|
||
overflowX: $doc.css( 'overflowX' ),
|
||
overflowY: $doc.css( 'overflowY' )
|
||
};
|
||
DOCUMENT_ELEMENT_STATUS[ this.editor.uid ] = {
|
||
overflowX: $( this.getEditorDocumentElement() ).css( 'overflowX'),
|
||
overflowY: $( this.getEditorDocumentElement() ).css( 'overflowY' )
|
||
};
|
||
|
||
},
|
||
/**
|
||
* 恢复容器状态
|
||
*/
|
||
revertContainerStatus: function(){
|
||
$( this.editor.container ).css( this.getEditorStatus() );
|
||
},
|
||
/**
|
||
* 恢复编辑区状态
|
||
*/
|
||
revertContentAreaStatus: function(){
|
||
var holder = this.getEditorHolder(),
|
||
state = this.getContentAreaStatus();
|
||
|
||
if ( this.supportMin() ) {
|
||
delete state.height;
|
||
holder.style.height = null;
|
||
}
|
||
|
||
$( holder ).css( state );
|
||
},
|
||
/**
|
||
* 恢复页面状态
|
||
*/
|
||
revertDocumentStatus: function() {
|
||
|
||
var status = this.getDocumentStatus();
|
||
$( this.getEditorDocumentBody() ).css( 'overflowX', status.body.overflowX );
|
||
$( this.getEditorDocumentElement() ).css( 'overflowY', status.html.overflowY );
|
||
|
||
},
|
||
setDocumentStatus: function(){
|
||
$(this.getEditorDocumentBody()).css( {
|
||
overflowX: 'hidden',
|
||
overflowY: 'hidden'
|
||
} );
|
||
$(this.getEditorDocumentElement()).css( {
|
||
overflowX: 'hidden',
|
||
overflowY: 'hidden'
|
||
} );
|
||
},
|
||
/**
|
||
* 检测当前编辑器是否处于全屏状态全屏状态
|
||
* @returns {boolean} 是否处于全屏状态
|
||
*/
|
||
isFullState: function(){
|
||
return !!this.editor._edui_fullscreen_status;
|
||
},
|
||
/**
|
||
* 获取编辑器状态
|
||
*/
|
||
getEditorStatus: function(){
|
||
return STATUS_CACHE[ this.editor.uid ];
|
||
},
|
||
getContentAreaStatus: function(){
|
||
return CONTENT_AREA_STATUS[ this.editor.uid ];
|
||
},
|
||
getEditorDocumentElement: function(){
|
||
return this.editor.container.ownerDocument.documentElement;
|
||
},
|
||
getEditorDocumentBody: function(){
|
||
return this.editor.container.ownerDocument.body;
|
||
},
|
||
/**
|
||
* 获取编辑区包裹对象
|
||
*/
|
||
getEditorHolder: function(){
|
||
return this.editor.body;
|
||
},
|
||
/**
|
||
* 获取编辑器状态
|
||
* @returns {*}
|
||
*/
|
||
getDocumentStatus: function(){
|
||
return {
|
||
'body': DOCUMENT_STATUS[ this.editor.uid ],
|
||
'html': DOCUMENT_ELEMENT_STATUS[ this.editor.uid ]
|
||
};
|
||
},
|
||
supportMin: function () {
|
||
|
||
var node = null;
|
||
|
||
if ( !this._support ) {
|
||
|
||
node = document.createElement("div");
|
||
|
||
this._support = "minWidth" in node.style;
|
||
|
||
node = null;
|
||
|
||
}
|
||
|
||
return this._support;
|
||
|
||
},
|
||
getBound: function () {
|
||
|
||
var tags = {
|
||
html: true,
|
||
body: true
|
||
},
|
||
result = {
|
||
top: 0,
|
||
left: 0
|
||
},
|
||
offsetParent = null;
|
||
|
||
if ( !$.IE6 ) {
|
||
return result;
|
||
}
|
||
|
||
offsetParent = this.editor.container.offsetParent;
|
||
|
||
if( offsetParent && !tags[ offsetParent.nodeName.toLowerCase() ] ) {
|
||
tags = offsetParent.getBoundingClientRect();
|
||
result.top = -tags.top;
|
||
result.left = -tags.left;
|
||
}
|
||
|
||
return result;
|
||
|
||
},
|
||
getStyleValue: function (attr) {
|
||
return parseInt(domUtils.getComputedStyle( this.getEditorHolder() ,attr));
|
||
}
|
||
};
|
||
|
||
|
||
$.extend( Fullscreen, {
|
||
/**
|
||
* 监听resize
|
||
*/
|
||
listen: function(){
|
||
|
||
var timer = null;
|
||
|
||
if( Fullscreen._hasFullscreenListener ) {
|
||
return;
|
||
}
|
||
|
||
Fullscreen._hasFullscreenListener = true;
|
||
|
||
$( window ).on( 'resize', function(){
|
||
|
||
if( timer !== null ) {
|
||
window.clearTimeout( timer );
|
||
timer = null;
|
||
}
|
||
|
||
timer = window.setTimeout(function(){
|
||
|
||
for( var key in FULLSCREENS ) {
|
||
FULLSCREENS[ key ].resize();
|
||
}
|
||
|
||
timer = null;
|
||
|
||
}, 50);
|
||
|
||
} );
|
||
|
||
},
|
||
|
||
getInstance: function ( editor ) {
|
||
|
||
if ( !FULLSCREENS[editor.uid ] ) {
|
||
new Fullscreen( editor );
|
||
}
|
||
|
||
return FULLSCREENS[editor.uid ];
|
||
|
||
}
|
||
|
||
});
|
||
|
||
//开始监听
|
||
Fullscreen.listen();
|
||
|
||
})();
|
||
UM.registerUI('link image video map formula',function(name){
|
||
|
||
var me = this, currentRange, $dialog,
|
||
opt = {
|
||
title: (me.options.labelMap && me.options.labelMap[name]) || me.getLang("labelMap." + name),
|
||
url: me.options.UMEDITOR_HOME_URL + 'dialogs/' + name + '/' + name + '.js'
|
||
};
|
||
|
||
var $btn = $.eduibutton({
|
||
icon: name,
|
||
title: this.getLang('labelMap')[name] || ''
|
||
});
|
||
//加载模版数据
|
||
utils.loadFile(document,{
|
||
src: opt.url,
|
||
tag: "script",
|
||
type: "text/javascript",
|
||
defer: "defer"
|
||
},function(){
|
||
//调整数据
|
||
var data = UM.getWidgetData(name);
|
||
if(!data) return;
|
||
if(data.buttons){
|
||
var ok = data.buttons.ok;
|
||
if(ok){
|
||
opt.oklabel = ok.label || me.getLang('ok');
|
||
if(ok.exec){
|
||
opt.okFn = function(){
|
||
return $.proxy(ok.exec,null,me,$dialog)()
|
||
}
|
||
}
|
||
}
|
||
var cancel = data.buttons.cancel;
|
||
if(cancel){
|
||
opt.cancellabel = cancel.label || me.getLang('cancel');
|
||
if(cancel.exec){
|
||
opt.cancelFn = function(){
|
||
return $.proxy(cancel.exec,null,me,$dialog)()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
data.width && (opt.width = data.width);
|
||
data.height && (opt.height = data.height);
|
||
|
||
$dialog = $.eduimodal(opt);
|
||
|
||
$dialog.attr('id', 'edui-dialog-' + name).addClass('edui-dialog-' + name)
|
||
.find('.edui-modal-body').addClass('edui-dialog-' + name + '-body');
|
||
|
||
$dialog.edui().on('beforehide',function () {
|
||
var rng = me.selection.getRange();
|
||
if (rng.equals(currentRange)) {
|
||
rng.select()
|
||
}
|
||
}).on('beforeshow', function () {
|
||
var $root = this.root(),
|
||
win = null,
|
||
offset = null;
|
||
currentRange = me.selection.getRange();
|
||
if (!$root.parent()[0]) {
|
||
me.$container.find('.edui-dialog-container').append($root);
|
||
}
|
||
|
||
//IE6下 特殊处理, 通过计算进行定位
|
||
if( $.IE6 ) {
|
||
|
||
win = {
|
||
width: $( window ).width(),
|
||
height: $( window ).height()
|
||
};
|
||
offset = $root.parents(".edui-toolbar")[0].getBoundingClientRect();
|
||
$root.css({
|
||
position: 'absolute',
|
||
margin: 0,
|
||
left: ( win.width - $root.width() ) / 2 - offset.left,
|
||
top: 100 - offset.top
|
||
});
|
||
|
||
}
|
||
UM.setWidgetBody(name,$dialog,me);
|
||
UM.setTopEditor(me);
|
||
}).on('afterbackdrop',function(){
|
||
this.$backdrop.css('zIndex',me.getOpt('zIndex')+1).appendTo(me.$container.find('.edui-dialog-container'))
|
||
$dialog.css('zIndex',me.getOpt('zIndex')+2)
|
||
}).on('beforeok',function(){
|
||
try{
|
||
currentRange.select()
|
||
}catch(e){}
|
||
}).attachTo($btn)
|
||
});
|
||
|
||
|
||
|
||
|
||
me.addListener('selectionchange', function () {
|
||
var state = this.queryCommandState(name);
|
||
$btn.edui().disabled(state == -1).active(state == 1)
|
||
});
|
||
return $btn;
|
||
});
|
||
UM.registerUI( 'emotion formula', function( name ){
|
||
var me = this,
|
||
url = me.options.UMEDITOR_HOME_URL + 'dialogs/' +name+ '/'+name+'.js';
|
||
|
||
var $btn = $.eduibutton({
|
||
icon: name,
|
||
title: this.getLang('labelMap')[name] || ''
|
||
});
|
||
|
||
//加载模版数据
|
||
utils.loadFile(document,{
|
||
src: url,
|
||
tag: "script",
|
||
type: "text/javascript",
|
||
defer: "defer"
|
||
},function(){
|
||
var opt = {
|
||
url : url
|
||
};
|
||
//调整数据
|
||
var data = UM.getWidgetData(name);
|
||
|
||
data.width && (opt.width = data.width);
|
||
data.height && (opt.height = data.height);
|
||
|
||
$.eduipopup(opt).css('zIndex',me.options.zIndex + 1)
|
||
.addClass('edui-popup-' + name)
|
||
.edui()
|
||
.on('beforeshow',function(){
|
||
var $root = this.root();
|
||
if(!$root.parent().length){
|
||
me.$container.find('.edui-dialog-container').append($root);
|
||
}
|
||
UM.setWidgetBody(name,$root,me);
|
||
UM.setTopEditor(me);
|
||
}).attachTo($btn,{
|
||
offsetTop:-5,
|
||
offsetLeft:10,
|
||
caretLeft:11,
|
||
caretTop:-8
|
||
});
|
||
me.addListener('selectionchange', function () {
|
||
var state = this.queryCommandState(name);
|
||
$btn.edui().disabled(state == -1).active(state == 1);
|
||
});
|
||
});
|
||
return $btn;
|
||
|
||
} );
|
||
UM.registerUI('imagescale',function () {
|
||
var me = this,
|
||
$imagescale;
|
||
|
||
me.setOpt('imageScaleEnabled', true);
|
||
|
||
if (browser.webkit && me.getOpt('imageScaleEnabled')) {
|
||
|
||
me.addListener('click', function (type, e) {
|
||
var range = me.selection.getRange(),
|
||
img = range.getClosedNode(),
|
||
target = e.target;
|
||
|
||
/* 点击第一个图片的后面,八个角不消失 fix:3652 */
|
||
if (img && img.tagName == 'IMG' && target == img) {
|
||
|
||
if (!$imagescale) {
|
||
$imagescale = $.eduiscale({'$wrap':me.$container}).css('zIndex', me.options.zIndex);
|
||
me.$container.append($imagescale);
|
||
|
||
var _keyDownHandler = function () {
|
||
$imagescale.edui().hide();
|
||
}, _mouseDownHandler = function (e) {
|
||
var ele = e.target || e.srcElement;
|
||
if (ele && ele.className.indexOf('edui-scale') == -1) {
|
||
_keyDownHandler(e);
|
||
}
|
||
}, timer;
|
||
|
||
$imagescale.edui()
|
||
.on('aftershow', function () {
|
||
$(document).bind('keydown', _keyDownHandler);
|
||
$(document).bind('mousedown', _mouseDownHandler);
|
||
me.selection.getNative().removeAllRanges();
|
||
})
|
||
.on('afterhide', function () {
|
||
$(document).unbind('keydown', _keyDownHandler);
|
||
$(document).unbind('mousedown', _mouseDownHandler);
|
||
var target = $imagescale.edui().getScaleTarget();
|
||
if (target.parentNode) {
|
||
me.selection.getRange().selectNode(target).select();
|
||
}
|
||
})
|
||
.on('mousedown', function (e) {
|
||
me.selection.getNative().removeAllRanges();
|
||
var ele = e.target || e.srcElement;
|
||
if (ele && ele.className.indexOf('edui-scale-hand') == -1) {
|
||
timer = setTimeout(function() {
|
||
$imagescale.edui().hide();
|
||
}, 200);
|
||
}
|
||
})
|
||
.on('mouseup', function (e) {
|
||
var ele = e.target || e.srcElement;
|
||
if (ele && ele.className.indexOf('edui-scale-hand') == -1) {
|
||
clearTimeout(timer);
|
||
}
|
||
});
|
||
}
|
||
$imagescale.edui().show($(img));
|
||
|
||
} else {
|
||
if ($imagescale && $imagescale.css('display') != 'none') $imagescale.edui().hide();
|
||
|
||
}
|
||
});
|
||
|
||
me.addListener('click', function (type, e) {
|
||
if (e.target.tagName == 'IMG') {
|
||
var range = new dom.Range(me.document, me.body);
|
||
range.selectNode(e.target).select();
|
||
}
|
||
});
|
||
|
||
}
|
||
});
|
||
UM.registerUI('autofloat',function(){
|
||
var me = this,
|
||
lang = me.getLang();
|
||
me.setOpt({
|
||
autoFloatEnabled: true,
|
||
topOffset: 0
|
||
});
|
||
var optsAutoFloatEnabled = me.options.autoFloatEnabled !== false,
|
||
topOffset = me.options.topOffset;
|
||
|
||
|
||
//如果不固定toolbar的位置,则直接退出
|
||
if(!optsAutoFloatEnabled){
|
||
return;
|
||
}
|
||
me.ready(function(){
|
||
var LteIE6 = browser.ie && browser.version <= 6,
|
||
quirks = browser.quirks;
|
||
|
||
function checkHasUI(){
|
||
if(!UM.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=function(element){
|
||
var bcr;
|
||
//trace IE6下在控制编辑器显隐时可能会报错,catch一下
|
||
try{
|
||
bcr = element.getBoundingClientRect();
|
||
}catch(e){
|
||
bcr={left:0,top:0,height:0,width:0}
|
||
}
|
||
var rect = {
|
||
left: Math.round(bcr.left),
|
||
top: Math.round(bcr.top),
|
||
height: Math.round(bcr.bottom - bcr.top),
|
||
width: Math.round(bcr.right - bcr.left)
|
||
};
|
||
var doc;
|
||
while ((doc = element.ownerDocument) !== document &&
|
||
(element = domUtils.getWindow(doc).frameElement)) {
|
||
bcr = element.getBoundingClientRect();
|
||
rect.left += bcr.left;
|
||
rect.top += bcr.top;
|
||
}
|
||
rect.bottom = rect.top + rect.height;
|
||
rect.right = rect.left + rect.width;
|
||
return rect;
|
||
};
|
||
var isFullScreening = false;
|
||
function setFloating(){
|
||
if(isFullScreening){
|
||
return;
|
||
}
|
||
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(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(){
|
||
|
||
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(){
|
||
$(window).off('scroll resize',updateFloating);
|
||
me.removeListener('keydown', defer_updateFloating);
|
||
});
|
||
|
||
if(checkHasUI(me)){
|
||
toolbarBox = $('.edui-toolbar',me.container)[0];
|
||
me.addListener("afteruiready",function(){
|
||
setTimeout(function(){
|
||
orgTop = $(toolbarBox).offset().top;
|
||
},100);
|
||
});
|
||
bakCssText = toolbarBox.style.cssText;
|
||
placeHolder.style.height = toolbarBox.offsetHeight + 'px';
|
||
if(LteIE6){
|
||
fixIE6FixedPos();
|
||
}
|
||
|
||
$(window).on('scroll resize',updateFloating);
|
||
me.addListener('keydown', defer_updateFloating);
|
||
me.addListener('resize', function(){
|
||
unsetFloating();
|
||
placeHolder.style.height = toolbarBox.offsetHeight + 'px';
|
||
updateFloating();
|
||
});
|
||
|
||
me.addListener('beforefullscreenchange', function (t, enabled){
|
||
if (enabled) {
|
||
unsetFloating();
|
||
isFullScreening = enabled;
|
||
}
|
||
});
|
||
me.addListener('fullscreenchanged', function (t, enabled){
|
||
if (!enabled) {
|
||
updateFloating();
|
||
}
|
||
isFullScreening = enabled;
|
||
});
|
||
me.addListener('sourcemodechanged', function (t, enabled){
|
||
setTimeout(function (){
|
||
updateFloating();
|
||
},0);
|
||
});
|
||
me.addListener("clearDoc",function(){
|
||
setTimeout(function(){
|
||
updateFloating();
|
||
},0);
|
||
|
||
})
|
||
}
|
||
})
|
||
|
||
|
||
});
|
||
UM.registerUI('source',function(name){
|
||
var me = this;
|
||
me.addListener('fullscreenchanged',function(){
|
||
me.$container.find('textarea').width(me.$body.width() - 10).height(me.$body.height())
|
||
|
||
});
|
||
var $btn = $.eduibutton({
|
||
icon : name,
|
||
click : function(){
|
||
me.execCommand(name);
|
||
UM.setTopEditor(me);
|
||
},
|
||
title: this.getLang('labelMap')[name] || ''
|
||
});
|
||
|
||
this.addListener('selectionchange',function(){
|
||
var state = this.queryCommandState(name);
|
||
$btn.edui().disabled(state == -1).active(state == 1)
|
||
});
|
||
return $btn;
|
||
});
|
||
|
||
UM.registerUI('paragraph fontfamily fontsize', function( name ) {
|
||
|
||
var me = this,
|
||
label = (me.options.labelMap && me.options.labelMap[name]) || me.getLang("labelMap." + name),
|
||
options = {
|
||
label: label,
|
||
title: label,
|
||
comboboxName: name,
|
||
items: me.options[ name ] || [],
|
||
itemStyles: [],
|
||
value: [],
|
||
autowidthitem: []
|
||
},
|
||
$combox = null,
|
||
comboboxWidget = null;
|
||
if(options.items.length == 0){
|
||
return null;
|
||
}
|
||
switch ( name ) {
|
||
|
||
case 'paragraph':
|
||
options = transForParagraph( options );
|
||
break;
|
||
|
||
case 'fontfamily':
|
||
options = transForFontfamily( options );
|
||
break;
|
||
|
||
case 'fontsize':
|
||
options = transForFontsize( options );
|
||
break;
|
||
|
||
}
|
||
|
||
//实例化
|
||
$combox = $.eduibuttoncombobox(options).css('zIndex',me.getOpt('zIndex') + 1);
|
||
comboboxWidget = $combox.edui();
|
||
|
||
comboboxWidget.on('comboboxselect', function( evt, res ){
|
||
me.execCommand( name, res.value );
|
||
}).on("beforeshow", function(){
|
||
if( $combox.parent().length === 0 ) {
|
||
$combox.appendTo( me.$container.find('.edui-dialog-container') );
|
||
}
|
||
UM.setTopEditor(me);
|
||
});
|
||
|
||
|
||
//状态反射
|
||
this.addListener('selectionchange',function( evt ){
|
||
|
||
var state = this.queryCommandState( name ),
|
||
value = this.queryCommandValue( name );
|
||
|
||
//设置按钮状态
|
||
comboboxWidget.button().edui().disabled( state == -1 ).active( state == 1 );
|
||
if(value){
|
||
//设置label
|
||
value = value.replace(/['"]/g, '').toLowerCase().split(/['|"]?\s*,\s*[\1]?/);
|
||
|
||
comboboxWidget.selectItemByLabel( value );
|
||
}
|
||
|
||
|
||
});
|
||
|
||
return comboboxWidget.button().addClass('edui-combobox');
|
||
|
||
/**
|
||
* 宽度自适应工具函数
|
||
* @param word 单词内容
|
||
* @param hasSuffix 是否含有后缀
|
||
*/
|
||
function wordCountAdaptive ( word, hasSuffix ) {
|
||
|
||
var $tmpNode = $('<span>' ).html( word ).css( {
|
||
display: 'inline',
|
||
position: 'absolute',
|
||
top: -10000000,
|
||
left: -100000
|
||
} ).appendTo( document.body),
|
||
width = $tmpNode.width();
|
||
|
||
$tmpNode.remove();
|
||
$tmpNode = null;
|
||
|
||
if( width < 50 ) {
|
||
|
||
return word;
|
||
|
||
} else {
|
||
|
||
word = word.slice( 0, hasSuffix ? -4 : -1 );
|
||
|
||
if( !word.length ) {
|
||
return '...';
|
||
}
|
||
|
||
return wordCountAdaptive( word + '...', true );
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
//段落参数转换
|
||
function transForParagraph ( options ) {
|
||
|
||
var tempItems = [];
|
||
|
||
for( var key in options.items ) {
|
||
|
||
options.value.push( key );
|
||
tempItems.push( key );
|
||
options.autowidthitem.push( wordCountAdaptive( key ) );
|
||
|
||
}
|
||
|
||
options.items = tempItems;
|
||
options.autoRecord = false;
|
||
|
||
return options;
|
||
|
||
}
|
||
|
||
//字体参数转换
|
||
function transForFontfamily ( options ) {
|
||
|
||
var temp = null,
|
||
tempItems = [];
|
||
|
||
for( var i = 0, len = options.items.length; i < len; i++ ) {
|
||
|
||
temp = options.items[ i ].val;
|
||
tempItems.push( temp.split(/\s*,\s*/)[0] );
|
||
options.itemStyles.push('font-family: ' + temp);
|
||
options.value.push( temp );
|
||
options.autowidthitem.push( wordCountAdaptive( tempItems[ i ] ) );
|
||
|
||
}
|
||
|
||
options.items = tempItems;
|
||
|
||
return options;
|
||
|
||
}
|
||
|
||
//字体大小参数转换
|
||
function transForFontsize ( options ) {
|
||
|
||
var temp = null,
|
||
tempItems = [];
|
||
|
||
options.itemStyles = [];
|
||
options.value = [];
|
||
|
||
for( var i = 0, len = options.items.length; i < len; i++ ) {
|
||
|
||
temp = options.items[ i ];
|
||
tempItems.push( temp );
|
||
options.itemStyles.push('font-size: ' + temp +'px');
|
||
|
||
}
|
||
|
||
options.value = options.items;
|
||
options.items = tempItems;
|
||
options.autoRecord = false;
|
||
|
||
return options;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
|
||
UM.registerUI('forecolor backcolor', function( name ) {
|
||
function getCurrentColor() {
|
||
return domUtils.getComputedStyle( $colorLabel[0], 'background-color' );
|
||
}
|
||
|
||
var me = this,
|
||
$colorPickerWidget = null,
|
||
$colorLabel = null,
|
||
$btn = null;
|
||
|
||
//querycommand
|
||
this.addListener('selectionchange', function(){
|
||
|
||
var state = this.queryCommandState( name );
|
||
$btn.edui().disabled( state == -1 ).active( state == 1 );
|
||
|
||
});
|
||
|
||
$btn = $.eduicolorsplitbutton({
|
||
icon: name,
|
||
caret: true,
|
||
name: name,
|
||
title: me.getLang("labelMap")[name],
|
||
click: function() {
|
||
me.execCommand( name, getCurrentColor() );
|
||
}
|
||
});
|
||
|
||
$colorLabel = $btn.edui().colorLabel();
|
||
|
||
$colorPickerWidget = $.eduicolorpicker({
|
||
name: name,
|
||
lang_clearColor: me.getLang('clearColor') || '',
|
||
lang_themeColor: me.getLang('themeColor') || '',
|
||
lang_standardColor: me.getLang('standardColor') || ''
|
||
})
|
||
.on('pickcolor', function( evt, color ){
|
||
window.setTimeout( function(){
|
||
$colorLabel.css("backgroundColor", color);
|
||
me.execCommand( name, color );
|
||
}, 0 );
|
||
})
|
||
.on('show',function(){
|
||
UM.setActiveWidget( colorPickerWidget.root() );
|
||
}).css('zIndex',me.getOpt('zIndex') + 1);
|
||
|
||
$btn.edui().on('arrowclick',function(){
|
||
if(!$colorPickerWidget.parent().length){
|
||
me.$container.find('.edui-dialog-container').append($colorPickerWidget);
|
||
}
|
||
$colorPickerWidget.edui().show($btn,{
|
||
caretDir:"down",
|
||
offsetTop:-5,
|
||
offsetLeft:8,
|
||
caretLeft:11,
|
||
caretTop:-8
|
||
});
|
||
UM.setTopEditor(me);
|
||
}).register('click', $btn, function () {
|
||
$colorPickerWidget.edui().hide()
|
||
});
|
||
|
||
return $btn;
|
||
|
||
});
|
||
|
||
})(jQuery) |