/** * @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 编辑器主类,包含编辑器提供的大部分公用接口 */ (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 == 'ueditor_textarea_' + editor.options.textarea) { textarea = ti; break; } } } else { textarea = editor.textarea; } } if (!textarea) { form.appendChild(textarea = domUtils.createElement(document, 'textarea', { 'name': editor.options.textarea, 'id': 'ueditor_textarea_' + editor.options.textarea, 'style': "display:none" })); //不要产生多个textarea editor.textarea = textarea; } textarea.value = editor.hasContents() ? (editor.options.allHtmlEnabled ? editor.getAllHtml() : editor.getContent(null, null, true)) : '' } function loadPlugins(me){ //初始化插件 for (var pi in UE.plugins) { UE.plugins[pi].call(me); } 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 = UE.Editor = function (options) { var me = this; me.uid = uid++; EventBase.call(me); me.commands = {}; me.options = utils.extend(utils.clone(options || {}), UEDITOR_CONFIG, true); me.shortcutkeys = {}; me.inputRules = []; me.outputRules = []; //设置默认的常用属性 me.setOpt({ isShow: true, initialContent: '', initialStyle:'', autoClearinitialContent: false, iframeCssUrl: me.options.UEDITOR_HOME_URL + 'themes/iframe.css', textarea: 'editorValue', focus: false, focusInEnd: true, autoClearEmptyNode: true, fullscreen: false, readonly: false, zIndex: 999, imagePopup: true, enterTag: 'p', customDomain: false, lang: 'zh-cn', langPath: me.options.UEDITOR_HOME_URL + 'lang/', theme: 'default', themePath: me.options.UEDITOR_HOME_URL + 'themes/', allHtmlEnabled: false, scaleEnabled: false, tableNativeEditInFF: false, autoSyncData : true }); if(!utils.isEmptyObject(UE.I18N)){ //修改默认的语言类型 me.options.lang = checkCurLang(UE.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) }); } UE.instants['ueditorInstant' + me.uid] = me; }; Editor.prototype = { /** * 当编辑器ready后执行传入的fn,如果编辑器已经完成ready,就马上执行fn,fn的中的this是编辑器实例。 * 大部分的实例接口都需要放在该方法内部执行,否则在IE下可能会报错。 * @name ready * @grammar editor.ready(fn) fn是当编辑器渲染好后执行的function * @example * var editor = new UE.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); }, /** * 销毁编辑器实例对象 * @name destroy * @grammar editor.destroy(); */ destroy: function () { var me = this; me.fireEvent('destroy'); var container = me.container.parentNode; var textarea = me.textarea; if (!textarea) { textarea = document.createElement('textarea'); container.parentNode.insertBefore(textarea, container); } else { textarea.style.display = '' } textarea.style.width = me.iframe.offsetWidth + 'px'; textarea.style.height = me.iframe.offsetHeight + 'px'; textarea.value = me.getContent(); textarea.id = me.key; container.innerHTML = ''; domUtils.remove(container); var key = me.key; //trace:2004 for (var p in me) { if (me.hasOwnProperty(p)) { delete this[p]; } } UE.delEditor(key); }, /** * 渲染编辑器的DOM到指定容器,必须且只能调用一次 * @name render * @grammar editor.render(containerId); //可以指定一个容器ID * @grammar editor.render(containerDom); //也可以直接指定容器对象 */ render: function (container,holder) { var me = this, options = me.options; if (utils.isString(container)) { container = document.getElementById(container); } if (container) { if(options.initialFrameWidth){ options.minFrameWidth = options.initialFrameWidth }else{ options.minFrameWidth = options.initialFrameWidth = container.offsetWidth; } if(options.initialFrameHeight){ options.minFrameHeight = options.initialFrameHeight }else{ options.initialFrameHeight = options.minFrameHeight = container.offsetHeight; } container.style.width = options.initialFrameWidth+ (/%$/.test(options.initialFrameWidth) ? '' : 'px'); container.style.height = options.initialFrameHeight + (/%$/.test(options.initialFrameHeight) ? '' : 'px'); container.style.zIndex = options.zIndex; var html = ( ie && browser.version < 9 ? '' : '') + '
' + '' + ( options.iframeCssUrl ? '' : '' ) + (options.initialStyle ? '' : '') + '' + ''; container.appendChild(domUtils.createElement(document, 'iframe', { id: 'ueditor_' + me.uid, width: "100%", height: "100%", frameborder: "0", 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'; } }, /** * 编辑器初始化 * @private * @ignore * @param {Element} doc 编辑器Iframe中的文档对象 */ _setup: function (doc) { var me = this, options = me.options; if (ie) { doc.body.disabled = true; doc.body.contentEditable = true; doc.body.disabled = false; } else { doc.body.contentEditable = true; } doc.body.spellcheck = false; me.document = doc; me.window = doc.defaultView || doc.parentWindow; me.iframe = me.window.frameElement; me.body = doc.body; me.selection = new dom.Selection(doc); //gecko初始化就能得到range,无法判断isFocus了 var geckoSel; if (browser.gecko && (geckoSel = this.selection.getNative())) { geckoSel.removeAllRanges(); } this._initEvents(); //为form提交提供一个隐藏的textarea for (var form = this.iframe.parentNode; !domUtils.isBody(form); form = form.parentNode) { if (form.tagName == 'FORM') { me.form = form; if(me.options.autoSyncData){ domUtils.on(me.window,'blur',function(){ setValue(form,me); }); }else{ domUtils.on(form, 'submit', function () { setValue(this, me); }); } break; } } if (options.initialContent) { if (options.autoClearinitialContent) { var oldExecCommand = me.execCommand; me.execCommand = function () { me.fireEvent('firstBeforeExecCommand'); return oldExecCommand.apply(me, arguments); }; this._setDefaultContent(options.initialContent); } else this.setContent(options.initialContent, false, true); } //编辑器不能为空内容 if (domUtils.isEmptyNode(me.body)) { me.body.innerHTML = '' + (browser.ie ? '' : '
') + '
' + (browser.ie ? ' ' : '
') + '
{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}
* @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.document.getElementsByTagName(ci).length > 0) {
return true;
}
}
}
if (!domUtils.isEmptyBlock(this.body)) {
return true
}
//随时添加,定义的特殊标签如果存在,不能认为是空
tags = ['div'];
for (i = 0; ci = tags[i++];) {
var nodes = domUtils.getElementsByTagName(this.document, ci);
for (var n = 0, cn; cn = nodes[n++];) {
if (domUtils.isCustomeNode(cn)) {
return true;
}
}
}
return false;
},
/**
* 重置编辑器,可用来做多个tab使用同一个编辑器实例
* @name reset
* @desc
* * 清空编辑器内容
* * 清空回退列表
* @grammar editor.reset()
*/
reset: function () {
this.fireEvent('reset');
},
setEnabled: function () {
var me = this, range;
if (me.body.contentEditable == 'false') {
me.body.contentEditable = true;
range = me.selection.getRange();
//有可能内容丢失了
try {
range.moveToBookmark(me.lastBk);
delete me.lastBk
} catch (e) {
range.setStartAtFirst(me.body).collapse(true)
}
range.select(true);
if (me.bkqueryCommandState) {
me.queryCommandState = me.bkqueryCommandState;
delete me.bkqueryCommandState;
}
me.fireEvent('selectionchange');
}
},
/**
* 设置当前编辑区域可以编辑
* @name enable
* @grammar editor.enable()
*/
enable: function () {
return this.setEnabled();
},
setDisabled: function (except) {
var me = this;
except = except ? utils.isArray(except) ? except : [except] : [];
if (me.body.contentEditable == 'true') {
if (!me.lastBk) {
me.lastBk = me.selection.getRange().createBookmark(true);
}
me.body.contentEditable = false;
me.bkqueryCommandState = me.queryCommandState;
me.queryCommandState = function (type) {
if (utils.indexOf(except, type) != -1) {
return me.bkqueryCommandState.apply(me, arguments);
}
return -1;
};
me.fireEvent('selectionchange');
}
},
/** 设置当前编辑区域不可编辑,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 = '' + (ie ? '' : '
') + '
' + cont + '
'; 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 = UE.I18N[this.options.lang]; if (!lang) { throw Error("not import language file"); } path = (path || "").split("."); for (var i = 0, ci; ci = path[i++];) { lang = lang[ci]; if (!lang)break; } return lang; }, /** * 计算编辑器当前内容的长度 * @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.document.getElementsByTagName(ci).length; } } return count; }, addInputRule: function (rule) { this.inputRules.push(rule); }, filterInputRule: function (root) { for (var i = 0, ci; ci = this.inputRules[i++];) { ci.call(this, root) } }, addOutputRule: function (rule) { this.outputRules.push(rule) }, filterOutputRule: function (root) { for (var i = 0, ci; ci = this.outputRules[i++];) { ci.call(this, root) } } /** * 得到dialog实例对象 * @name getDialog * @grammar editor.getDialog(dialogName) => Object * @example * var dialog = editor.getDialog("insertimage"); * dialog.open(); //打开dialog * dialog.close(); //关闭dialog */ }; utils.inherits(Editor, EventBase); })();