From 0f49506bcf561bf63b0f122f84d0f6536806672e Mon Sep 17 00:00:00 2001 From: Jinqn <459452372@qq.com> Date: Wed, 21 Aug 2013 18:45:50 +0800 Subject: [PATCH 01/81] [fix]by Jinqn --- _src/core/EventBase.js | 102 +++++++++++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 23 deletions(-) diff --git a/_src/core/EventBase.js b/_src/core/EventBase.js index 35d3b1c74..5c0980cb0 100644 --- a/_src/core/EventBase.js +++ b/_src/core/EventBase.js @@ -1,19 +1,48 @@ /** + * UE采用的事件基类 * @file - * @name UE.EventBase - * @short EventBase - * @import editor.js,core/utils.js - * @desc UE采用的事件基类,继承此类的对应类将获取addListener,removeListener,fireEvent方法。 + * @module UE + * @class EventBase + * @since 1.2.6.1 + */ + +/** + * UEditor公用空间,UEditor所有的功能都挂载在该空间下 + * @unfile + * @module UE + */ + +/** + * UE采用的事件基类,继承此类的对应类将获取addListener,removeListener,fireEvent方法。 * 在UE中,Editor以及所有ui实例都继承了该类,故可以在对应的ui对象以及editor对象上使用上述方法。 + * @unfile + * @module UE + * @class EventBase */ -var EventBase = UE.EventBase = function () {}; + +/** + * 通过此构造器,子类可以继承EventBase获取事件监听的方法 + * @constructor + * @example + * ```javascript + * UE.EventBase.call(editor); + * ``` + */ + +var EventBase = UE.EventBase = function () { +}; EventBase.prototype = { + + /** * 注册事件监听器 - * @name addListener - * @grammar editor.addListener(types,fn) //types为事件名称,多个可用空格分隔 + * @method addListener + * @param { String } types 监听的事件名称,同时监听多个事件使用空格分隔 + * @param { Function } fn 监听的事件被触发时,会执行该回调函数 + * @waining 事件被触发时,监听的函数假如返回的值恒等于true,回调函数的队列中后面的函数将不执行 * @example + * ```javascript * editor.addListener('selectionchange',function(){ * console.log("选区已经变化!"); * }) @@ -25,35 +54,59 @@ EventBase.prototype = { * } * console.log(this.getContent) // this是注册的事件的编辑器实例 * }) + * ``` + * @see UE.EventBase:fireEvent(String) */ - addListener:function (types, listener) { + 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为事件名称,多个可用空格分隔 + * @method removeListener + * @param { String } types 移除的事件名称,同时移除多个事件使用空格分隔 + * @param { Function } fn 移除监听事件的函数引用 * @example + * ```javascript * //changeCallback为方法体 * editor.removeListener("selectionchange",changeCallback); + * ``` */ - removeListener:function (types, listener) { + removeListener: function (types, listener) { types = utils.trim(types).split(' '); for (var i = 0, ti; ti = types[i++];) { utils.removeItem(getListener(this, ti) || [], listener); } }, + + + /** + * 触发事件 + * @method fireEvent + * @param { String } types 触发的事件名称,同时触发多个事件使用空格分隔 + * @remind 该方法会触发addListener + * @return { * } 返回触发事件的队列中,最后执行的回调函数的返回值 + * @example + * ```javascript + * editor.fireEvent("selectionchange"); + * ``` + */ + /** * 触发事件 - * @name fireEvent - * @grammar editor.fireEvent(types) //types为事件名称,多个可用空格分隔 + * @method fireEvent + * @param { String } types 触发的事件名称,同时触发多个事件使用空格分隔 + * @param { *... } options 第二项开始的参数会传给触发的回调函数 * @example + * ```javascript * editor.fireEvent("selectionchange"); + * ``` */ - fireEvent:function () { + fireEvent: function () { var types = arguments[0]; types = utils.trim(types).split(' '); for (var i = 0, ti; ti = types[i++];) { @@ -62,9 +115,9 @@ EventBase.prototype = { if (listeners) { k = listeners.length; while (k--) { - if(!listeners[k])continue; + if (!listeners[k])continue; t = listeners[k].apply(this, arguments); - if(t === true){ + if (t === true) { return t; } if (t !== undefined) { @@ -79,19 +132,22 @@ EventBase.prototype = { return r; } }; + /** * 获得对象所拥有监听类型的所有监听器 + * @unfile + * @module UE + * @since 1.2.6.1 + * @method getListener * @public - * @function - * @param {Object} obj 查询监听器的对象 - * @param {String} type 事件类型 - * @param {Boolean} force 为true且当前所有type类型的侦听器不存在时,创建一个空监听器数组 - * @returns {Array} 监听器数组 + * @param { Object } obj 查询监听器的对象 + * @param { String } type 事件类型 + * @param { Boolean } force 为true且当前所有type类型的侦听器不存在时,创建一个空监听器数组 + * @return { Array } 监听器数组 */ function getListener(obj, type, force) { var allListeners; type = type.toLowerCase(); return ( ( allListeners = ( obj.__allListeners || force && ( obj.__allListeners = {} ) ) ) && ( allListeners[type] || force && ( allListeners[type] = [] ) ) ); -} - +} \ No newline at end of file From ea125ec7dc82db4b4fc1c1af1046c3fa3a9f8dab Mon Sep 17 00:00:00 2001 From: Jinqn <459452372@qq.com> Date: Thu, 22 Aug 2013 13:28:05 +0800 Subject: [PATCH 02/81] [fix]by Jinqn --- _src/core/Editor.js | 740 +++++++++++++++++++++++++++++++++----------- 1 file changed, 562 insertions(+), 178 deletions(-) diff --git a/_src/core/Editor.js b/_src/core/Editor.js index 2cc367d7e..f4f477e3f 100644 --- a/_src/core/Editor.js +++ b/_src/core/Editor.js @@ -1,18 +1,33 @@ /** + * 编辑器主类,包含编辑器提供的大部分公用接口 * @file - * @name UE.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 编辑器主类,包含编辑器提供的大部分公用接口 + * @module UE + * @class Editor + * @since 1.2.6.1 */ + +/** + * UEditor公用空间,UEditor所有的功能都挂载在该空间下 + * @unfile + * @module UE + */ + +/** + * UEditor的核心类,为用户提供与编辑器交互的接口。 + * @unfile + * @module UE + * @class Editor + */ + (function () { var uid = 0, _selectionChangeTimer; + /** + * 获取编辑器的html内容,赋值到编辑器所在表单的textarea文本域里面 * @private - * @ignore - * @param form 编辑器所在的form元素 - * @param editor 编辑器实例对象 + * @method setValue + * @param { Editor } editor 编辑器事例 */ function setValue(form, editor) { var textarea; @@ -41,7 +56,14 @@ (editor.options.allHtmlEnabled ? editor.getAllHtml() : editor.getContent(null, null, true)) : '' } - function loadPlugins(me){ + + /** + * 初始化插件 + * @private + * @method loadPlugins + * @param { Editor } editor 编辑器事例 + */ + function loadPlugins(me) { //初始化插件 for (var pi in UE.plugins) { UE.plugins[pi].call(me); @@ -50,21 +72,67 @@ me.fireEvent("langReady"); } - function checkCurLang(I18N){ - for(var lang in I18N){ + + /** + * 获取语言包里面的第一个 + * @private + * @method checkCurLang + * @param { PlainObject } I18N 编辑器事例 + */ + function checkCurLang(I18N) { + for (var lang in I18N) { return lang } } + + + /** + * 编辑器准备就绪后会触发该事件 + * @module UE + * @class Editor + * @event ready + * @example + * ```javascript + * editor.addListener( 'ready', function( editor ) { + * editor.execCommand( 'focus' ); + * } ); + * ``` + */ + /** - * UEditor编辑器类 - * @name Editor - * @desc 创建一个跟编辑器实例 - * - ***container*** 编辑器容器对象 - * - ***iframe*** 编辑区域所在的iframe对象 - * - ***window*** 编辑区域所在的window - * - ***document*** 编辑区域所在的document对象 - * - ***body*** 编辑区域所在的body对象 - * - ***selection*** 编辑区域的选区对象 + * 每当编辑器内部选区发生改变后, 将触发该事件 + * @event selectionchange + * @warning 该事件的触发非常频繁,不建议在该事件的处理过程中做重量级的处理 + * @example + * ```javascript + * editor.addListener( 'selectionchange', function( editor ) { + * + * editor.execCommand( 'bold' ); + * + * } + */ + + /** + * 以默认参数构建一个编辑器实例 + * @constructor + * @example + * ```javascript + * var editor = new UE.Editor(); + * editor.execCommand('blod'); + * ``` + * @see UE.Config + */ + + /** + * 以给定的参数集合创建一个编辑器对象,对于未指定的参数,将应用默认参数。 + * @constructor + * @param { PlainObject } setting 创建编辑器的参数 + * @example + * ```javascript + * var editor = new UE.Editor(); + * editor.execCommand('blod'); + * ``` + * @see UE.Config */ var Editor = UE.Editor = function (options) { var me = this; @@ -79,7 +147,7 @@ me.setOpt({ isShow: true, initialContent: '', - initialStyle:'', + initialStyle: '', autoClearinitialContent: false, iframeCssUrl: me.options.UEDITOR_HOME_URL + 'themes/iframe.css', textarea: 'editorValue', @@ -99,14 +167,14 @@ allHtmlEnabled: false, scaleEnabled: false, tableNativeEditInFF: false, - autoSyncData : true + autoSyncData: true }); - if(!utils.isEmptyObject(UE.I18N)){ + if (!utils.isEmptyObject(UE.I18N)) { //修改默认的语言类型 me.options.lang = checkCurLang(UE.I18N); loadPlugins(me) - }else{ + } else { utils.loadFile(document, { src: me.options.langPath + me.options.lang + "/" + me.options.lang + ".js", tag: "script", @@ -120,17 +188,22 @@ UE.instants['ueditorInstant' + me.uid] = me; }; Editor.prototype = { + + /** - * 当编辑器ready后执行传入的fn,如果编辑器已经完成ready,就马上执行fn,fn的中的this是编辑器实例。 - * 大部分的实例接口都需要放在该方法内部执行,否则在IE下可能会报错。 - * @name ready - * @grammar editor.ready(fn) fn是当编辑器渲染好后执行的function + * 编辑器对外提供的监听ready事件的接口, 通过调用该方法,达到的效果与监听ready事件是一致的 + * @method ready + * @param { Function } fn 编辑器ready之后所执行的回调, 如果在注册事件之前编辑器已经ready,将会 + * 立即触发该回调。 + * @see UE.Editor.event:ready * @example - * var editor = new UE.ui.Editor(); - * editor.render("myEditor"); - * editor.ready(function(){ - * editor.setContent("欢迎使用UEditor!"); - * }) + * ```javascript + * editor.ready( function( editor ) { + * + * editor.setContent('初始化完毕'); + * + * } ); + * ``` */ ready: function (fn) { var me = this; @@ -138,10 +211,31 @@ me.isReady ? fn.apply(me) : me.addListener('ready', fn); } }, + + + /** + * 以attributeName - attributeValue的方式设置编辑器的配置项,以覆盖编辑器的默认选项值 + * @method setOpt + * @warning 该方法仅供编辑器构造函数调用,其他任何方法不能调用。 + * @param { String } key 编辑器的可接受的选项名称 + * @param { * } val 该选项可接受的值 + * @example + * ```javascript + * editor.setOpt( 'initContent', '欢迎使用编辑器' ); + * ``` + */ + /** - * 为编辑器设置默认参数值。若用户配置为空,则以默认配置为准 - * @grammar editor.setOpt(key,value); //传入一个键、值对 - * @grammar editor.setOpt({ key:value}); //传入一个json对象 + * 以key-value集合的方式设置编辑器的配置项,以覆盖编辑器的默认选项值 + * @method setOpt + * @warning 该方法仅供编辑器构造函数调用,其他任何方法不能调用。 + * @param { PlainObject } settings 编辑器的可接受的选项的key-value集合 + * @example + * ```javascript + * editor.setOpt( { + * 'initContent': '欢迎使用编辑器' + * } ); + * ``` */ setOpt: function (key, val) { var obj = {}; @@ -152,10 +246,15 @@ } utils.extend(this.options, obj, true); }, + + /** * 销毁编辑器实例对象 - * @name destroy - * @grammar editor.destroy(); + * @method destroy + * @example + * ```javascript + * editor.destroy(); + * ``` */ destroy: function () { @@ -185,83 +284,92 @@ } UE.delEditor(key); }, + /** - * 渲染编辑器的DOM到指定容器,必须且只能调用一次 - * @name render - * @grammar editor.render(containerId); //可以指定一个容器ID - * @grammar editor.render(containerDom); //也可以直接指定容器对象 + * 渲染编辑器的DOM到指定容器 + * @method render + * @param { String } containerId 指定一个容器ID + * @warning 必须且只能调用一次 + */ + + /** + * 渲染编辑器的DOM到指定容器 + * @method render + * @param { Element } containerDom 直接指定容器对象 + * @warning 必须且只能调用一次 */ render: function (container) { var me = this, options = me.options, - getStyleValue=function(attr){ - return parseInt(domUtils.getComputedStyle(container,attr)); + getStyleValue = function (attr) { + return parseInt(domUtils.getComputedStyle(container, attr)); }; if (utils.isString(container)) { container = document.getElementById(container); } if (container) { - if(options.initialFrameWidth){ + if (options.initialFrameWidth) { options.minFrameWidth = options.initialFrameWidth - }else{ + } else { options.minFrameWidth = options.initialFrameWidth = container.offsetWidth; } - if(options.initialFrameHeight){ + if (options.initialFrameHeight) { options.minFrameHeight = options.initialFrameHeight - }else{ + } else { options.initialFrameHeight = options.minFrameHeight = container.offsetHeight; } - container.style.width = /%$/.test(options.initialFrameWidth) ? '100%' : options.initialFrameWidth- - getStyleValue("padding-left")- getStyleValue("padding-right") +'px'; - container.style.height = /%$/.test(options.initialFrameHeight) ? '100%' : options.initialFrameHeight - - getStyleValue("padding-top")- getStyleValue("padding-bottom") +'px'; + container.style.width = /%$/.test(options.initialFrameWidth) ? '100%' : options.initialFrameWidth - + getStyleValue("padding-left") - getStyleValue("padding-right") + 'px'; + container.style.height = /%$/.test(options.initialFrameHeight) ? '100%' : options.initialFrameHeight - + getStyleValue("padding-top") - getStyleValue("padding-bottom") + 'px'; container.style.zIndex = options.zIndex; - var html = ( ie && browser.version < 9 ? '' : '') + - '
' + - '' + - ( options.iframeCssUrl ? '' : '' ) + - (options.initialStyle ? '' : '') + - '' + - ''; + var html = ( ie && browser.version < 9 ? '' : '') + + '' + + '' + + ( options.iframeCssUrl ? '' : '' ) + + (options.initialStyle ? '' : '') + + '' + + ''; container.appendChild(domUtils.createElement(document, 'iframe', { id: 'ueditor_' + me.uid, width: "100%", height: "100%", frameborder: "0", - src: 'javascript:void(function(){document.open();' + (options.customDomain && document.domain != location.hostname ? 'document.domain="' + document.domain + '";' : '') + + src: 'javascript:void(function(){document.open();' + (options.customDomain && document.domain != location.hostname ? 'document.domain="' + document.domain + '";' : '') + 'document.write("' + html + '");document.close();}())' })); container.style.overflow = 'hidden'; //解决如果是给定的百分比,会导致高度算不对的问题 - setTimeout(function(){ - if( /%$/.test(options.initialFrameWidth)){ + setTimeout(function () { + if (/%$/.test(options.initialFrameWidth)) { options.minFrameWidth = options.initialFrameWidth = container.offsetWidth; container.style.width = options.initialFrameWidth + 'px'; } - if(/%$/.test(options.initialFrameHeight)){ + if (/%$/.test(options.initialFrameHeight)) { options.minFrameHeight = options.initialFrameHeight = container.offsetHeight; container.style.height = options.initialFrameHeight + 'px'; } }) } }, + /** * 编辑器初始化 + * @method _setup * @private - * @ignore - * @param {Element} doc 编辑器Iframe中的文档对象 + * @param { Element } doc 编辑器Iframe中的文档对象 */ _setup: function (doc) { @@ -291,11 +399,11 @@ for (var form = this.iframe.parentNode; !domUtils.isBody(form); form = form.parentNode) { if (form.tagName == 'FORM') { me.form = form; - if(me.options.autoSyncData){ - domUtils.on(me.window,'blur',function(){ - setValue(form,me); + if (me.options.autoSyncData) { + domUtils.on(me.window, 'blur', function () { + setValue(form, me); }); - }else{ + } else { domUtils.on(form, 'submit', function () { setValue(this, me); }); @@ -388,17 +496,25 @@ !options.isShow && me.setHide(); options.readonly && me.setDisabled(); }, + /** - * 同步编辑器的数据,为提交数据做准备,主要用于你是手动提交的情况 - * @name sync - * @grammar editor.sync(); //从编辑器的容器向上查找,如果找到就同步数据 - * @grammar editor.sync(formID); //formID制定一个要同步数据的form的id,编辑器的数据会同步到你指定form下 - * @desc - * 后台取得数据得键值使用你容器上得''name''属性,如果没有就使用参数传入的''textarea'' + * 同步编辑器的数据,为提交数据做准备,主要用于是手动提交的情况 + * 后台取得数据的键值使用你容器上的name属性,如果没有就使用参数传入的textarea属性 + * 获取要同步的表单,从编辑器的容器向上查找要同步数据的form,如果找到就同步数据 + * @method sync * @example + * ```javascript * editor.sync(); * form.sumbit(); //form变量已经指向了form元素 - * + * ``` + */ + + /** + * 同步编辑器的数据,为提交数据做准备,主要用于是手动提交的情况 + * 后台取得数据的键值使用你容器上的name属性,如果没有就使用参数传入的textarea属性 + * 根据传入表单id参数,获取要同步数据的form + * @method sync + * @param { String } formID 指定一个要同步数据的form的id,编辑器的数据会同步到你指定form下 */ sync: function (formId) { var me = this, @@ -408,12 +524,17 @@ }, true); form && setValue(form, me); }, + /** * 设置编辑器高度 - * @name setHeight - * @grammar editor.setHeight(number); //纯数值,不带单位 + * @method setHeight + * @param { Number } number 设置的高度值,纯数值,不带单位 + * @example + * ```javascript + * editor.setHeight(number); + * ``` */ - setHeight: function (height,notSetHeight) { + setHeight: function (height, notSetHeight) { if (height !== parseInt(this.iframe.parentNode.style.height)) { this.iframe.parentNode.style.height = height + 'px'; } @@ -422,6 +543,28 @@ this.body.style.height = height + 'px'; }, + /** + * 添加命令的快捷键 + * @method addshortcutkey + * @param { PlainObject } keyset 命令和快捷键的键值对对象,多个按钮的快捷键用“+”分隔 + * @example + * ```javascript + * editor.addshortcutkey({ + * "Bold" : "ctrl+66",//^B + * "Italic" : "ctrl+73", //^I + * }); + * ``` + */ + /** + * 添加命令的快捷键 + * @method addshortcutkey + * @param { String } cmd 触发快捷键时,响应的命令 + * @param { String } keys 快捷键的字符串,多个按钮用“+”分隔 + * @example + * ```javascript + * editor.addshortcutkey("Underline", "ctrl+85"); //^U + * ``` + */ addshortcutkey: function (cmd, keys) { var obj = {}; if (keys) { @@ -431,6 +574,12 @@ } utils.extend(this.shortcutkeys, obj) }, + + /** + * 对编辑器设置keydown事件监听,绑定快捷键和命令,当快捷键组合触发成功,会响应对应的命令 + * @method _bindshortcutKeys + * @private + */ _bindshortcutKeys: function () { var me = this, shortcutkeys = this.shortcutkeys; me.addListener('keydown', function (type, e) { @@ -447,7 +596,7 @@ ) || keyCode == RegExp.$1 ) { - if (me.queryCommandState(i,param) != -1) + if (me.queryCommandState(i, param) != -1) me.execCommand(i, param); domUtils.preventDefault(e); } @@ -457,19 +606,38 @@ } }); }, + + + /** + * 获取编辑器的内容 + * @method getContent + * @warning 该方法获取到的是经过编辑器内置的过滤规则进行过滤后得到的内容 + * @return { String } 编辑器的内容字符串, 如果编辑器的内容为空, 则返回空字符串 + * @example + * ```javascript + * var content = editor.getContent(); + * ``` + */ + /** - * 获取编辑器内容 - * @name getContent - * @grammar editor.getContent() => String //若编辑器中只包含字符"<p><br /></p/>"会返回空。 - * @grammar editor.getContent(fn) => String + * 获取编辑器的内容。 可以通过参数定义编辑器内置的判空规则 + * @method getContent + * @param { Function } fn 自定的判空规则, 要求该方法返回一个boolean类型的值, + * 代表当前编辑器的内容是否空, + * 如果返回true, 则该方法将直接返回空字符串;如果返回false,则编辑器将返回 + * 经过内置过滤规则处理后的内容。 + * @remind 该方法在处理包含有初始化内容的时候能起到很好的作用。 + * @warning 该方法获取到的是经过编辑器内置的过滤规则进行过滤后得到的内容 + * @return { String } 编辑器的内容字符串 * @example - * getContent默认是会现调用hasContents来判断编辑器是否为空,如果是,就直接返回空字符串 - * 你也可以传入一个fn来接替hasContents的工作,定制判断的规则 - * editor.getContent(function(){ - * return false //编辑器没有内容 ,getContent直接返回空 - * }) + * ```javascript + * // editor 是一个编辑器的实例 + * var content = editor.getContent( function ( editor ) { + * return editor.body.innerHTML === '欢迎使用UEditor'; + * } ); + * ``` */ - getContent: function (cmd, fn,notSetCursor,ignoreBlank,formatter) { + getContent: function (cmd, fn, notSetCursor, ignoreBlank, formatter) { var me = this; if (cmd && utils.isFunction(cmd)) { fn = cmd; @@ -479,15 +647,20 @@ return ''; } me.fireEvent('beforegetcontent'); - var root = UE.htmlparser(me.body.innerHTML,ignoreBlank); + var root = UE.htmlparser(me.body.innerHTML, ignoreBlank); me.filterOutputRule(root); me.fireEvent('aftergetcontent', cmd); return root.toHtml(formatter); }, + /** * 取得完整的html代码,可以直接显示成完整的html文档 - * @name getAllHtml - * @grammar editor.getAllHtml() => String + * @method getAllHtml + * @return { String } 编辑器的内容html文档字符串 + * @eaxmple + * ```javascript + * editor.getAllHtml(); + * ``` */ getAllHtml: function () { var me = this, @@ -508,10 +681,15 @@ + (headHtmlForIE9 || me.document.getElementsByTagName('head')[0].innerHTML) + headHtml.join('\n') + '' + '' + me.getContent(null, null, true) + '