/** * @ignore * Editor For KISSY Based on CKEditor Core. * @author yiminghe@gmail.com */ KISSY.add('editor', function (S, Node, iframeContentTpl, Editor, Utils, focusManager, Styles, zIndexManger, clipboard, enterKey, htmlDataProcessor, selectionFix) { var TRUE = true, undefined = undefined, FALSE = false, NULL = null, logger = S.getLogger('s/editor'), window = S.Env.host, document = window.document, UA = S.UA, IS_IE = UA['ie'], NodeType = Node.NodeType, $ = Node.all, HEIGHT = 'height', tryThese = Utils.tryThese, IFRAME_TPL = '<iframe' + ' class="{prefixCls}editor-iframe"' + ' frameborder="0" ' + ' title="kissy-editor" ' + ' allowTransparency="true" ' + ' {iframeSrc} ' + '>' + '</iframe>' , EMPTY_CONTENT_REG = /^(?:<(p)>)?(?:(?: )|\s|<br[^>]*>)*(?:<\/\1>)?$/i; Editor.Mode = { SOURCE_MODE: 0, WYSIWYG_MODE: 1 }; var WYSIWYG_MODE = 1; Editor.addMembers({ initializer: function () { var self = this; self.__commands = {}; self.__controls = {}; // 实例集中管理 focusManager.register(self); }, // 在插件运行前,运行核心兼容 renderUI: function () { var self = this; clipboard.init(self); enterKey.init(self); htmlDataProcessor.init(self); selectionFix.init(self); }, bindUI: function () { var self = this, form, prefixCls = self.get('prefixCls'), textarea = self.get('textarea'); if (self.get('attachForm') && (form = textarea[0].form) && (form = $(form))) { form.on('submit', self.sync, self); } function docReady() { self.detach('docReady', docReady); // 是否自动focus if (self.get('focused')) { self.focus(); } //否则清空选择区域 else { var sel = self.getSelection(); sel && sel.removeAllRanges(); } } self.on('docReady', docReady); self.on('blur', function () { self.$el.removeClass(prefixCls + 'editor-focused'); }); self.on('focus', function () { self.$el.addClass(prefixCls + 'editor-focused'); }); }, // 高度不在 el 上设置,设置 iframeWrap 以及 textarea(for ie). width 依然在 el 上设置 _onSetHeight: function (v) { var self = this, textareaEl = self.get('textarea'), toolBarEl = self.get("toolBarEl"), statusBarEl = self.get("statusBarEl"); v = parseInt(v, 10); // 减去顶部和底部工具条高度 v -= (toolBarEl && toolBarEl.outerHeight() || 0) + (statusBarEl && statusBarEl.outerHeight() || 0); textareaEl.parent().css(HEIGHT, v); textareaEl.css(HEIGHT, v); }, _onSetMode: function (v) { var self = this, iframe = self.get('iframe'), textarea = self.get('textarea'); if (v == WYSIWYG_MODE) { self.setData(textarea.val()); textarea.hide(); self.fire("wysiwygMode"); } else { // 刚开始就配置 mode 为 sourcecode if (iframe) { textarea.val(self.getFormatData(WYSIWYG_MODE)); iframe.hide(); } textarea.show(); self.fire("sourceMode"); } }, // 覆盖 control _onSetFocused: function (v) { var self = this; // docReady 后才能调用 if (v && self.__docReady) { self.focus(); } }, destructor: function () { var self = this, form, textarea = self.get('textarea'), doc = self.get('document'); if (self.get('attachForm') && (form = textarea[0].form) && (form = $(form))) { form.detach("submit", self.sync, self); } if (doc) { var body = $(doc[0].body), documentElement = $(doc[0].documentElement), win = self.get('window'); focusManager.remove(self); doc.detach(); documentElement.detach(); body.detach(); win.detach(); } S.each(self.__controls, function (control) { if (control.destroy) { control.destroy(); } }); self.__commands = {}; self.__controls = {}; }, /** * Retrieve control by id. * @member KISSY.Editor */ getControl: function (id) { return this.__controls[id]; }, /** * Retrieve all controls. * @member KISSY.Editor */ getControls: function () { return this.__controls; }, /** * Register a control to editor by id. * @member KISSY.Editor * @private */ addControl: function (id, control) { this.__controls[id] = control; }, /** * Show dialog * @param {String} name Dialog name * @param args Arguments passed to show * @member KISSY.Editor */ showDialog: function (name, args) { name += '/dialog'; var self = this, d = self.__controls[name]; d.show(args); self.fire('dialogShow', { dialog: d.dialog, "pluginDialog": d, "dialogName": name }); }, /** * Add a command object to current editor. * @param name {string} Command name. * @param obj {Object} Command object. * @member KISSY.Editor */ addCommand: function (name, obj) { this.__commands[name] = obj; }, /** * Whether current editor has specified command instance. * @param name {string} * @member KISSY.Editor */ hasCommand: function (name) { return this.__commands[name]; }, /** * Whether current editor has specified command. * Refer: https://developer.mozilla.org/en/Rich-Text_Editing_in_Mozilla * @param name {string} Command name. * @member KISSY.Editor */ execCommand: function (name) { var self = this, cmd = self.__commands[name], args = S.makeArray(arguments); args.shift(); args.unshift(self); if (cmd) { return cmd.exec.apply(cmd, args); } else { logger.error(name + ': command not found'); return undefined; } }, /** * Return editor's value corresponding to command name. * @param {String} name Command name. * @member KISSY.Editor */ queryCommandValue: function (name) { return this.execCommand(Utils.getQueryCmd(name)); }, 'setData': function (data) { var self = this, htmlDataProcessor, afterData = data; if (self.get('mode') != WYSIWYG_MODE) { // 代码模式下不需过滤 self.get('textarea').val(data); return; } if (htmlDataProcessor = self.htmlDataProcessor) { afterData = htmlDataProcessor.toDataFormat(data); } // https://github.com/kissyteam/kissy-editor/issues/17, 重建最保险 clearIframeDocContent(self); createIframe(self, afterData); }, /** * get html content of editor body. * @member KISSY.Editor * @param {Boolean} format internal use * @param mode for internal use * @returns {String} html content of editor. */ getData: function (format, mode) { var self = this, htmlDataProcessor = self.htmlDataProcessor, html; if (mode == undefined) { mode = self.get('mode'); } if (mode == WYSIWYG_MODE && self.isDocReady()) { html = self.get('document')[0].body.innerHTML; } else { html = htmlDataProcessor.toDataFormat(self.get('textarea').val()); } //如果不需要要格式化,例如提交数据给服务器 if (format) { html = htmlDataProcessor.toHtml(html); } else { html = htmlDataProcessor.toServer(html); } html = S.trim(html); /* 如果内容为空,对 parser 自动加的空行滤掉 */ if (EMPTY_CONTENT_REG.test(html)) { html = ''; } return html; }, /** * get formatted html content of editor body. * @member KISSY.Editor * @param mode for internal use * @returns {String} html content of editor. */ getFormatData: function (mode) { return this.getData(1, mode); }, /** * Get full html content of editor 's iframe. * @member KISSY.Editor */ getDocHtml: function () { var self = this; return prepareIFrameHTML(0, self.get('customStyle'), self.get('customLink'), self.getFormatData()); }, /** * Get selection instance of current editor. * @member KISSY.Editor */ getSelection: function () { return Editor.Selection.getSelection(this.get('document')[0]); }, /** * Get selected html content of current editor * @member KISSY.Editor * @return {String} */ 'getSelectedHtml': function () { var self = this, range = self.getSelection().getRanges()[0], contents, html = ''; if (range) { contents = range.cloneContents(); html = self.get('document')[0].createElement('div'); html.appendChild(contents); html = html.innerHTML; } return html; }, /** * Make current editor has focus * @member KISSY.Editor */ focus: function () { var self = this, win = self.get('window'); // 刚开始就配置 mode 为 sourcecode if (!win) { return; } var doc = self.get('document')[0]; win = win[0]; // firefox7 need this if (!UA['ie']) { // note : 2011-11-17 report by 石霸 // ie 的 parent 不能 focus ,否则会使得 iframe 内的编辑器光标回到开头 win && win.parent && win.parent.focus(); } // yiminghe note:webkit need win.focus // firefox 7 needs also? win && win.focus(); // ie and firefox need body focus try { doc.body.focus(); } catch (e) { } self.notifySelectionChange(); }, /** * Make current editor lose focus * @member KISSY.Editor */ blur: function () { var self = this, win = self.get('window')[0]; win.blur(); self.get('document')[0].body.blur(); }, /** * Add style text to current editor * @param {String} cssText * @param {String} id style id * @member KISSY.Editor */ addCustomStyle: function (cssText, id) { var self = this, win = self.get('window'), customStyle = self.get('customStyle') || ''; customStyle += "\n" + cssText; self.set('customStyle', customStyle); if (win) { win.addStyleSheet(win, cssText, id); } }, /** * Remove style text with specified id from current editor * @param id style id * @member KISSY.Editor */ removeCustomStyle: function (id) { this.get('document').on('#' + id).remove(); }, /** * Add css link to current editor * @param {String} link * @member KISSY.Editor */ addCustomLink: function (link) { var self = this, customLink = self.get('customLink'), doc = self.get('document')[0]; customLink.push(link); self.set('customLink', customLink); var elem = doc.createElement('link'); elem.rel = 'stylesheet'; doc.getElementsByTagName('head')[0].appendChild(elem); elem.href = link; }, /** * Remove css link from current editor. * @param {String} link * @member KISSY.Editor */ removeCustomLink: function (link) { var self = this, doc = self.get('document'), links = doc.all('link'); links.each(function (l) { if (l.attr('href') == link) { l.remove(); } }); var cls = self.get('customLink'), ind = S.indexOf(link, cls); if (ind != -1) { cls.splice(ind, 1); } }, /** * Add callback which will called when editor document is ready * (fire when editor is renderer from textarea/source) * @param {Function} func * @member KISSY.Editor */ docReady: function (func) { var self = this; self.on('docReady', func); if (self.__docReady) { func.call(self); } }, /** * whether editor document is ready * @returns {number} * @member KISSY.Editor */ isDocReady: function () { return this.__docReady; }, /** * Check whether selection has changed since last check point. * @member KISSY.Editor * @private */ checkSelectionChange: function () { var self = this; if (self.__checkSelectionChangeId) { clearTimeout(self.__checkSelectionChangeId); } self.__checkSelectionChangeId = setTimeout(function () { var selection = self.getSelection(); if (selection && !selection.isInvalid) { var startElement = selection.getStartElement(), currentPath = new Editor.ElementPath(startElement); if (!self.__previousPath || !self.__previousPath.compare(currentPath)) { self.__previousPath = currentPath; self.fire('selectionChange', { selection: selection, path: currentPath, element: startElement }); } } }, 100); }, /** * Fire selectionChange manually. * @member KISSY.Editor * @private */ notifySelectionChange: function () { var self = this; self.__previousPath = NULL; self.checkSelectionChange(); }, /** * Insert a element into current editor. * @param {KISSY.NodeList} element * @member KISSY.Editor */ insertElement: function (element) { var self = this; if (self.get('mode') !== WYSIWYG_MODE) { return undefined; } self.focus(); var clone, elementName = element['nodeName'](), xhtml_dtd = Editor.XHTML_DTD, isBlock = xhtml_dtd['$block'][ elementName ], KER = Editor.RangeType, selection = self.getSelection(), ranges = selection && selection.getRanges(), range, notWhitespaceEval, i, next, nextName, lastElement; if (!ranges || ranges.length == 0) { return undefined; } self.execCommand('save'); for (i = ranges.length - 1; i >= 0; i--) { range = ranges[ i ]; // Remove the original contents. clone = !i && element || element['clone'](TRUE); range.insertNodeByDtd(clone); // Save the last element reference so we can make the // selection later. if (!lastElement) { lastElement = clone; } } if (!lastElement) { return undefined; } range.moveToPosition(lastElement, KER.POSITION_AFTER_END); // If we're inserting a block element immediately followed by // another block element, the selection must move there. (#3100,#5436) if (isBlock) { notWhitespaceEval = Editor.Walker.whitespaces(true); next = lastElement.next(notWhitespaceEval, 1); nextName = next && next[0].nodeType == NodeType.ELEMENT_NODE && next.nodeName(); // Check if it's a block element that accepts text. if (nextName && xhtml_dtd.$block[ nextName ] && xhtml_dtd[ nextName ]['#text']) { range.moveToElementEditablePosition(next); } } selection.selectRanges([ range ]); self.focus(); // http://code.google.com/p/kissy/issues/detail?can=1&start=100&id=121 // only tag can scroll if (clone && clone[0].nodeType == 1) { clone.scrollIntoView(undefined, { alignWithTop: false, allowHorizontalScroll: true, onlyScrollIfNeeded: true }); } saveLater.call(self); return clone; }, /** * insert html string into current editor. * @param {String} data * @param [dataFilter] internal usage * @member KISSY.Editor */ insertHtml: function (data, dataFilter) { var self = this, htmlDataProcessor, editorDoc = self.get('document')[0]; if (self.get('mode') !== WYSIWYG_MODE) { return; } if (htmlDataProcessor = self.htmlDataProcessor) { data = htmlDataProcessor.toDataFormat(data, dataFilter); } self.focus(); self.execCommand('save'); // ie9 仍然需要这样! // ie9 标准 selection 有问题,连续插入不能定位光标到插入内容后面 if (IS_IE) { var $sel = editorDoc.selection; if ($sel.type == 'Control') { $sel.clear(); } try { $sel.createRange().pasteHTML(data); } catch (e) { logger.error('insertHtml error in ie'); } } else { // ie9 仍然没有 // 1.webkit insert html 有问题!会把标签去掉,算了直接用 insertElement. // 10.0 修复?? // firefox 初始编辑器无焦点报异常 try { editorDoc.execCommand('inserthtml', FALSE, data); } catch (e) { setTimeout(function () { // still not ok in ff! // 手动选择 body 的第一个节点 if (self.getSelection().getRanges().length == 0) { var r = new Editor.Range(editorDoc), node = $(editorDoc.body).first(function (el) { return el.nodeType == 1 && el.nodeName.toLowerCase() != 'br'; }); if (!node) { node = $(editorDoc.createElement('p')); node._4e_appendBogus().appendTo(editorDoc.body); } r.setStartAt(node, Editor.RangeType.POSITION_AFTER_START); r.select(); } editorDoc.execCommand('inserthtml', FALSE, data); }, 50); } } // bug by zjw2004112@163.com : // 有的浏览器 : chrome , ie67 貌似不会自动滚动到粘贴后的位置 setTimeout(function () { self.getSelection().scrollIntoView(); }, 50); saveLater.call(self); } }); /** * create editor from textarea element * @member KISSY.Editor * @static * @param {HTMLTextAreaElement} textarea textarea to replaced by editor * @param cfg editor configuration * @returns {KISSY.Editor} editor instance */ Editor.decorate = function (textarea, cfg) { cfg = cfg || {}; textarea = $(textarea); var textareaAttrs = cfg.textareaAttrs = cfg.textareaAttrs || {}; var width = textarea.style('width'); var height = textarea.style('height'); var name = textarea.attr('name'); if (width) { cfg.width = cfg.width || width; } if (height) { cfg.height = cfg.height || height; } if (name) { textareaAttrs.name = name; } cfg.data = cfg.data || textarea.val(); cfg.elBefore = textarea; var editor = new Editor(cfg).render(); textarea.remove(); return editor; }; /* 初始化iframe内容以及浏览器间兼容性处理, 必须等待iframe内的脚本向父窗口通知 */ Editor["_initIframe"] = function (id) { var self = focusManager.getInstance(id), $doc = self.get('document'), doc = $doc[0], // Remove bootstrap script from the Dom. script = $doc.one('#ke_active_script'); script.remove(); fixByBindIframeDoc(self); var body = doc.body; var $body = $(body); /* from kissy editor 1.0 // 注1:在 tinymce 里,designMode = "on" 放在 try catch 里。 // 原因是在 firefox 下,当iframe 在 display: none 的容器里,会导致错误。 // 但经过我测试,firefox 3+ 以上已无此现象。 // 注2: ie 用 contentEditable = true. // 原因是在 ie 下,IE needs to use contentEditable or // it will display non secure items for HTTPS // Ref: // - Differences between designMode and contentEditable // http://74.125.153.132/search?q=cache:5LveNs1yHyMJ:nagoon97.wordpress.com/2008/04/20/differences-between-designmode-and-contenteditable/+ie+contentEditable+designMode+different&cd=6&hl=en&ct=clnk */ // 这里对主流浏览器全部使用 contenteditable // 那么不同于 kissy editor 1.0 // 在body范围外右键,不会出现 复制,粘贴等菜单 // 因为这时右键作用在document而不是body // 1.0 document.designMode='on' 是编辑模式 // 2.0 body.contentEditable=true body外不是编辑模式 if (IS_IE) { // Don't display the focus border. body['hideFocus'] = TRUE; // Disable and re-enable the body to avoid IE from // taking the editing focus at startup. (#141 / #523) body.disabled = TRUE; body['contentEditable'] = TRUE; body.removeAttribute('disabled'); } else { // Avoid opening design mode in a frame window thread, // which will cause host page scrolling.(#4397) setTimeout(function () { // Prefer 'contentEditable' instead of 'designMode'. (#3593) if (UA['gecko'] || UA['opera']) { body['contentEditable'] = TRUE; } else if (UA['webkit']) body.parentNode['contentEditable'] = TRUE; else doc['designMode'] = 'on'; }, 0); } // IE standard compliant in editing frame doesn't focus the editor when // clicking outside actual content, manually apply the focus. (#1659) if ( // ie6,7 点击滚动条失效 // IS_IE // && doc.compatMode == 'CSS1Compat' // wierd ,sometimes ie9 break // || // 2012-01-11 ie 处理装移到 selection.js : // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode // doc['documentMode'] UA['gecko'] || UA['opera'] ) { var htmlElement = doc.documentElement; $(htmlElement).on('mousedown', function (evt) { // Setting focus directly on editor doesn't work, we // have to use here a temporary element to 'redirect' // the focus. // firefox 不能直接设置,需要先失去焦点 // return; // 左键激活 var t = evt.target; if (t == htmlElement) { if (UA['gecko']) { blinkCursor(doc, FALSE); } //setTimeout(function() { //这种:html mousedown -> body beforedeactivate // self.focus(); //}, 30); //这种:body beforedeactivate -> html mousedown self.activateGecko(); } }); } // Adds the document body as a context menu target. setTimeout(function () { /* * IE BUG: IE might have rendered the iframe with invisible contents. * (#3623). Push some inconsequential CSS style changes to force IE to * refresh it. * * Also, for some unknown reasons, short timeouts (e.g. 100ms) do not * fix the problem. :( */ if (IS_IE) { setTimeout(function () { if (doc) { body.runtimeStyle['marginBottom'] = '0px'; body.runtimeStyle['marginBottom'] = ''; } }, 1000); } }, 0); setTimeout(function () { self.__docReady = 1; self.fire('docReady'); /* some break for firefox ,不能立即设置 */ var disableObjectResizing = self.get('disableObjectResizing'), disableInlineTableEditing = self.get('disableInlineTableEditing'); if (disableObjectResizing || disableInlineTableEditing) { // IE, Opera and Safari may not support it and throw errors. try { doc.execCommand('enableObjectResizing', FALSE, !disableObjectResizing); doc.execCommand('enableInlineTableEditing', FALSE, !disableInlineTableEditing); } catch (e) { // 只能ie能用?,目前只有 firefox,ie 支持图片缩放 // For browsers which don't support the above methods, // we can use the the resize event or resizestart for IE (#4208) $body.on(IS_IE ? 'resizestart' : 'resize', function (evt) { var t = new Node(evt.target); if (disableObjectResizing || (t.nodeName() === 'table' && disableInlineTableEditing)) { evt.preventDefault(); } }); } } }, 10); }; // ---------------------------------------------------------------------- start private function blinkCursor(doc, retry) { var body = doc.body; tryThese( function () { doc['designMode'] = 'on'; //异步引起时序问题,尽可能小间隔 setTimeout(function () { doc['designMode'] = 'off'; body.focus(); // Try it again once.. if (!arguments.callee.retry) { arguments.callee.retry = TRUE; //arguments.callee(); } }, 50); }, function () { doc['designMode'] = 'off'; body.setAttribute('contentEditable', false); body.setAttribute('contentEditable', true); // Try it again once.. !retry && blinkCursor(doc, 1); } ); } function fixByBindIframeDoc(self) { var iframe = self.get('iframe'), textarea = self.get('textarea')[0], $win = self.get('window'), $doc = self.get('document'), doc = $doc[0]; // Gecko need a key event to 'wake up' the editing // ability when document is empty.(#3864) // activateEditing 删掉,初始引起屏幕滚动了 // Webkit: avoid from editing form control elements content. if (UA['webkit']) { $doc.on('click', function (ev) { var control = new Node(ev.target); if (S.inArray(control.nodeName(), ['input', 'select'])) { ev.preventDefault(); } }); // Prevent from editing textfield/textarea value. $doc.on('mouseup', function (ev) { var control = new Node(ev.target); if (S.inArray(control.nodeName(), ['input', 'textarea'])) { ev.preventDefault(); } }); } // Create an invisible element to grab focus. if (UA['gecko'] || IS_IE || UA['opera']) { var focusGrabber; focusGrabber = new Node( // Use 'span' instead of anything else to fly under the screen-reader radar. (#5049) '<span ' + 'tabindex="-1" ' + 'style="position:absolute; left:-10000"' + ' role="presentation"' + '></span>').insertAfter(textarea); focusGrabber.on('focus', function () { self.focus(); }); self.activateGecko = function () { if (UA['gecko'] && self.__iframeFocus) focusGrabber[0].focus(); }; self.on('destroy', function () { focusGrabber.detach(); focusGrabber.remove(); }); } $win.on('focus', function () { /* 注意:firefox光标丢失bug blink后光标出现在最后,这就需要实现保存range focus后再恢复range */ if (UA['gecko']) { blinkCursor(doc, FALSE); } else if (UA['opera']) { doc.body.focus(); } // focus 后强制刷新自己状态 self.notifySelectionChange(); }); if (UA['gecko']) { /* firefox 焦点丢失后,再点编辑器区域焦点会移不过来,要点两下 */ $doc.on('mousedown', function () { if (!self.__iframeFocus) { blinkCursor(doc, FALSE); } }); } if (IS_IE) { // Override keystrokes which should have deletion behavior // on control types in IE . (#4047) /* 选择img,出现缩放框后不能直接删除 */ $doc.on('keydown', function (evt) { var keyCode = evt.keyCode; // Backspace OR Delete. if (keyCode in { 8: 1, 46: 1 }) { var sel = self.getSelection(), control = sel.getSelectedElement(); if (control) { // Make undo snapshot. self.execCommand('save'); // Delete any element that 'hasLayout' (e.g. hr,table) in IE8 will // break up the selection, safely manage it here. (#4795) var bookmark = sel.getRanges()[ 0 ].createBookmark(); // Remove the control manually. control.remove(); sel.selectBookmarks([ bookmark ]); self.execCommand('save'); evt.preventDefault(); } } }); // PageUp/PageDown scrolling is broken in document // with standard doctype, manually fix it. (#4736) // ie8 主窗口滚动?? if (doc.compatMode == 'CSS1Compat') { var pageUpDownKeys = { 33: 1, 34: 1 }; $doc.on('keydown', function (evt) { if (evt.keyCode in pageUpDownKeys) { setTimeout(function () { self.getSelection().scrollIntoView(); }, 0); } }); } } // Gecko/Webkit need some help when selecting control type elements. (#3448) if (UA['webkit']) { $doc.on('mousedown', function (ev) { var control = new Node(ev.target); if (S.inArray(control.nodeName(), ['img', 'hr', 'input', 'textarea', 'select'])) { self.getSelection().selectElement(control); } }); } if (UA['gecko']) { $doc.on('dragstart', function (ev) { var control = new Node(ev.target); if (control.nodeName() === 'img' && /ke_/.test(control[0].className)) { // firefox禁止拖放 ev.preventDefault(); } }); } //注意:必须放在这个位置,等iframe加载好再开始运行 //加入焦点管理,和其他实例联系起来 focusManager.add(self); } function prepareIFrameHTML(id, customStyle, customLink, data) { var links = '', i, innerCssFile = Utils.debugUrl('theme/editor-iframe.css'); customLink = customLink.concat([]); customLink.unshift(innerCssFile); for (i = 0; i < customLink.length; i++) { links += S.substitute('<link href="' + '{href}" rel="stylesheet" />', { href: customLink[i] }); } return S.substitute(iframeContentTpl,{ // kissy-editor #12 // IE8 doesn't support carets behind images(empty content after image's block) // setting ie7 compatible mode would force IE8+ to run in IE7 compat mode. doctype: document.documentMode === 8 ? '<meta http-equiv="X-UA-Compatible" content="IE=7" />' : '', title: '{title}', links: links, style: '<style>' + customStyle + '</style>', // firefox 必须里面有东西,否则编辑前不能删除! data: data || '', script: id ? // The script that launches the bootstrap logic on 'domReady', so the document // is fully editable even before the editing iframe is fully loaded (#4455). // 确保iframe确实载入成功,过早的话 document.domain 会出现无法访问 ('<script id="ke_active_script">' + // ie 特有,即使自己创建的空 iframe 也要设置 domain (如果外层设置了) // 否则下面的 parent.KISSY.Editor._initIframe 不能执行 ( $(window).isCustomDomain() ? ( 'document.domain="' + document.domain + '";' ) : '' ) + 'parent.KISSY.require("editor")._initIframe("' + id + '");' + '</script>') : '' }); } var saveLater = S.buffer(function () { this.execCommand('save'); }, 50); function setUpIFrame(self, data) { var iframe = self.get('iframe'), html = prepareIFrameHTML(self.get('id'), self.get('customStyle'), self.get('customLink'), data), iframeDom = iframe[0], win = iframeDom.contentWindow, doc; iframe.__loaded = 1; try { // In IE, with custom document.domain, it may happen that // the iframe is not yet available, resulting in "Access // Denied" for the following property access. //ie 设置domain 有问题:yui也有 //http://yuilibrary.com/projects/yui2/ticket/2052000 //http://waelchatila.com/2007/10/31/1193851500000.html //http://nagoon97.wordpress.com/tag/designmode/ doc = win.document; } catch (e) { // Trick to solve this issue, forcing the iframe to get ready // by simply setting its "src" property. //noinspection SillyAssignmentJS iframeDom.src = iframeDom.src; // In IE6 though, the above is not enough, so we must pause the // execution for a while, giving it time to think. if (IS_IE < 7) { setTimeout(run, 10); return; } } run(); function run() { doc = win.document; self.setInternal('document', new Node(doc)); self.setInternal('window', new Node(win)); iframe.detach(); // Don't leave any history log in IE. (#5657) doc['open']('text/html', 'replace'); doc.write(html); doc.close(); } } function createIframe(self, afterData) { // With IE, the custom domain has to be taken care at first, // for other browsers, the 'src' attribute should be left empty to // trigger iframe 's 'load' event. var iframeSrc = $(window).getEmptyIframeSrc() || ''; if (iframeSrc) { iframeSrc = ' src="' + iframeSrc + '" '; } var iframe = new Node(S.substitute(IFRAME_TPL, { iframeSrc: iframeSrc, prefixCls: self.get('prefixCls') })), textarea = self.get('textarea'); if (textarea.hasAttr('tabindex')) { iframe.attr('tabindex', UA['webkit'] ? -1 : textarea.attr('tabindex')); } textarea.parent().prepend(iframe); self.set('iframe', iframe); self.__docReady = 0; // With FF, it's better to load the data on iframe.load. (#3894,#4058) if (UA['gecko'] && !iframe.__loaded) { iframe.on('load', function () { setUpIFrame(self, afterData); }, self); } else { // webkit(chrome) load等不来! setUpIFrame(self, afterData); } } function clearIframeDocContent(self) { if (!self.get('iframe')) { return; } var iframe = self.get('iframe'), win = self.get('window'), doc = self.get('document'), domDoc = doc[0], documentElement = $(domDoc.documentElement), body = $(domDoc.body); S.each([doc, documentElement, body, win], function (el) { el.detach(); }); iframe.remove(); } // ------------------------------------------------------------------- end private return Editor; }, { requires: [ 'node', 'editor/iframe-content-tpl', 'editor/base', 'editor/utils', 'editor/focusManager', 'editor/styles', 'editor/z-index-manager', 'editor/clipboard', 'editor/enterKey', 'editor/htmlDataProcessor', 'editor/selectionFix', 'editor/plugin-meta' ] }); /** * @ignore * 2012-07-06 yiminghe@gmail.com note ie 的怪异: * * - 如果一开始主页面设置了 domain * * - 那么自己创建的 iframe src 要设置 getEmptyIframeSrc, * 否则 load 后取不到 iframe.contentWindow 的 document. * * - 自己创建的 iframe 里面 write 的内容要再次写 document.domain, * 否则 iframe 内的脚本不能通知外边编辑器控制层 ready. * * - 如果页面中途突然设置了 domain * * - iframe 内的 document 仍然还可以被外层 editor 控制层使用. * * - iframe 内的 window 的一些属性 (frameElement) 都不能访问了, 但是 focus 还是可以的. * * 因此 Dom.getEmptyIframeSrc 要用时再取不能缓存. * * ie 不能访问 window 的属性( ie 也不需要,还好 document 是可以的) * * * 2012-03-05 重构 by yiminghe@gmail.com * - core * - plugins * * refer * - http://html5.org/specs/dom-range.html */