1 /**
  2  * Editor For KISSY 1.3
  3  * @preserve thanks to CKSource's intelligent work on CKEditor
  4  * @author yiminghe@gmail.com
  5  */
  6 KISSY.add("editor", function (S, Editor, Utils, focusManager, Styles, zIndexManger, meta, clipboard, enterKey, htmlDataProcessor, selectionFix) {
  7     var TRUE = true,
  8 
  9         undefined = undefined,
 10 
 11         $ = S.all,
 12 
 13         FALSE = false,
 14 
 15         NULL = null,
 16 
 17         DOC = document,
 18 
 19         UA = S.UA,
 20 
 21         IS_IE = UA['ie'],
 22 
 23         DOM = S.DOM,
 24 
 25         Node = S.Node,
 26 
 27         Event = S.Event,
 28 
 29         DISPLAY = "display",
 30 
 31         WIDTH = "width",
 32 
 33         HEIGHT = "height",
 34 
 35         NONE = "none",
 36 
 37         tryThese = Utils.tryThese,
 38 
 39         HTML5_DTD = '<!doctype html>',
 40 
 41         KE_TEXTAREA_WRAP_CLASS = ".ks-editor-textarea-wrap",
 42 
 43         KE_TOOLBAR_CLASS = ".ks-editor-tools",
 44 
 45         KE_STATUSBAR_CLASS = ".ks-editor-status",
 46 
 47         IFRAME_HTML_TPL = HTML5_DTD + "<html>" +
 48             "<head>{doctype}" +
 49             "<title>{title}</title>" +
 50             "<link href='" + "{href}' rel='stylesheet' />" +
 51             "<style>" +
 52             "{style}" +
 53             "</style>" +
 54             "{links}" +
 55             "</head>" +
 56             "<body class='ks-editor'>" +
 57             "{data}" +
 58             "{script}" +
 59             "</body>" +
 60             "</html>",
 61 
 62         EMPTY_IFRAME_SRC = DOM.getEmptyIframeSrc(),
 63 
 64         IFRAME_TPL = '<iframe' +
 65             ' style="width:100%;height:100%;border:none;" ' +
 66             ' frameborder="0" ' +
 67             ' title="kissy-editor" ' +
 68             ' allowTransparency="true" ' +
 69             // With IE, the custom domain has to be taken care at first,
 70             // for other browsers, the 'src' attribute should be left empty to
 71             // trigger iframe's 'load' event.
 72             (EMPTY_IFRAME_SRC ? (' src="' + EMPTY_IFRAME_SRC + '"') : '') +
 73             '</iframe>' ,
 74 
 75         EDITOR_TPL = '<div class="' + KE_TOOLBAR_CLASS.substring(1) + '"></div>' +
 76             '<div class="' + KE_TEXTAREA_WRAP_CLASS.substring(1) + '">' +
 77             '</div>' +
 78             "<div class='" + KE_STATUSBAR_CLASS.substring(1) + "'></div>";
 79 
 80 
 81     S.mix(Editor,
 82         /**
 83          * @lends Editor
 84          */
 85         {
 86             SOURCE_MODE:0,
 87             WYSIWYG_MODE:1
 88         });
 89 
 90     var WYSIWYG_MODE = Editor.WYSIWYG_MODE;
 91 
 92     S.augment(Editor,
 93 
 94         /**
 95          * @lends Editor#
 96          */
 97         {
 98             createDom:function () {
 99                 var self = this,
100                     wrap,
101                     textarea = self.get("textarea"),
102                     editorEl;
103 
104                 if (!textarea) {
105                     self.set("textarea", textarea = $("<textarea class='ks-editor-textarea'></textarea>"));
106                 }
107 
108                 editorEl = self.get("el");
109 
110                 editorEl.html(EDITOR_TPL);
111 
112                 wrap = editorEl.one(KE_TEXTAREA_WRAP_CLASS);
113 
114                 self._UUID = S.guid();
115 
116                 self.set({
117                     toolBarEl:editorEl.one(KE_TOOLBAR_CLASS),
118                     statusBarEl:editorEl.one(KE_STATUSBAR_CLASS)
119                 }, {
120                     silent:1
121                 });
122 
123                 // 标准浏览器编辑器内焦点不失去,firefox?
124                 // 标准浏览器实际上不需要!range在iframe内保存着呢,选择高亮变灰而已
125                 // 2011-11-19 启用封装 preventFocus
126                 // 点击工具栏内任何东西都不会使得焦点转移
127                 // 支持 select 键盘 : 2012-03-16
128                 // Utils.preventFocus(self.toolBarEl);
129 
130                 textarea.css(WIDTH, "100%");
131                 textarea.css(DISPLAY, NONE);
132 
133                 wrap.append(textarea);
134 
135                 // 实例集中管理
136                 focusManager.register(self);
137             },
138             // 在插件运行前,运行核心兼容
139             renderUI:function () {
140                 var self = this;
141                 clipboard.init(self);
142                 enterKey.init(self);
143                 htmlDataProcessor.init(self);
144                 selectionFix.init(self);
145             },
146 
147             bindUI:function () {
148                 var self = this,
149                     form,
150                     prefixCls = self.get("prefixCls"),
151                     textarea = self.get("textarea");
152 
153                 if (self.get("attachForm") &&
154                     (form = textarea[0].form)) {
155                     DOM.on(form, "submit", self.sync, self);
156                     self.on("destroy", function () {
157                         DOM.detach(form, "submit", self.sync, self);
158                     });
159                 }
160 
161                 function docReady() {
162                     self.detach("docReady", docReady);
163                     // 是否自动focus
164                     if (self.get("focused")) {
165                         self.focus();
166                     }
167                     //否则清空选择区域
168                     else {
169                         var sel = self.getSelection();
170                         sel && sel.removeAllRanges();
171                     }
172                 }
173 
174                 self.on("docReady", docReady);
175 
176                 self.on("blur", function () {
177                     self.get("el").removeClass(prefixCls + "editor-focused");
178                 });
179 
180                 self.on("focus", function () {
181                     self.get("el").addClass(prefixCls + "editor-focused");
182                 });
183             },
184 
185             syncUI:function () {
186                 var self = this,
187                     h = self.get("height")
188                 if (h) {
189                     // 根据容器高度,设置内层高度
190                     self._uiSetHeight(h);
191                 }
192             },
193 
194             /**
195              * 高度不在 el 上设置,设置 iframeWrap 以及 textarea(for ie).
196              * width 依然在 el 上设置
197              */
198             _uiSetHeight:function (v) {
199                 var self = this,
200                     textareaEl = self.get("textarea"),
201                     toolBarEl = self.get("toolBarEl"),
202                     statusBarEl = self.get("statusBarEl");
203                 v = parseInt(v, 10);
204                 // 减去顶部和底部工具条高度
205                 v -= (toolBarEl && toolBarEl.outerHeight() || 0) +
206                     (statusBarEl && statusBarEl.outerHeight() || 0);
207                 textareaEl.parent().css(HEIGHT, v);
208                 textareaEl.css(HEIGHT, v);
209             },
210 
211             _uiSetMode:function (v) {
212                 var self = this,
213                     save,
214                     rendered = self.get("render"),
215                     iframe = self.get("iframe"),
216                     textarea = self.get("textarea");
217                 if (v == WYSIWYG_MODE) {
218                     // 初始化时不保存历史
219                     if (rendered) {
220                         self.execCommand("save");
221                     }
222                     // recreate iframe need load time
223                     self.on("docReady", save = function () {
224                         if (rendered) {
225                             self.execCommand("save");
226                         }
227                         self.detach("docReady", save);
228                     });
229                     self._setData(textarea.val());
230                     self.fire("wysiwygMode");
231                 } else {
232                     textarea.val(self._getData(1, WYSIWYG_MODE));
233                     textarea[0].focus();
234                     textarea.show();
235                     iframe.hide();
236                     self.fire("sourceMode");
237                 }
238             },
239 
240             // 覆盖 controller
241             _uiSetFocused:function (v) {
242                 // docReady 后才能调用
243                 if (v && this.__docReady) {
244                     this.focus();
245                 }
246             },
247 
248             destructor:function () {
249                 var self = this,
250                     doc = self.get("document")[0],
251                     win = self.get("window");
252 
253                 self.sync();
254 
255                 focusManager.remove(self);
256 
257                 Event.remove([doc, doc.documentElement, doc.body, win[0]]);
258 
259                 S.each(self.__controls, function (control) {
260                     if (control.destroy) {
261                         control.destroy();
262                     }
263                 });
264 
265                 self.__commands = {};
266                 self.__controls = {};
267             },
268 
269             /**
270              * Retrieve control by id.
271              */
272             getControl:function (id) {
273                 return this.__controls[id];
274             },
275 
276             /**
277              * Retrieve all controls.
278              * @return {*}
279              */
280             getControls:function () {
281                 return this.__controls;
282             },
283 
284             /**
285              * Register a control to editor by id.
286              * @private
287              */
288             addControl:function (id, control) {
289                 this.__controls[id] = control;
290             },
291 
292             /**
293              * Show dialog
294              * @param {String} name Dialog name
295              * @param args Arguments passed to show
296              */
297             showDialog:function (name, args) {
298                 name += "/dialog";
299                 var self = this,
300                     d = self.__controls[name];
301                 d.show(args);
302                 self.fire("dialogShow", {
303                     dialog:d.dialog,
304                     pluginDialog:d,
305                     dialogName:name
306                 });
307             },
308 
309             /**
310              * Add a command object to current editor.
311              * @param name {string} Command name.
312              * @param obj {Object} Command object.
313              */
314             addCommand:function (name, obj) {
315                 this.__commands[name] = obj;
316             },
317 
318             /**
319              * Whether current editor has specified command instance.
320              * @param name {string}
321              */
322             hasCommand:function (name) {
323                 return this.__commands[name];
324             },
325 
326             /**
327              * Whether current editor has specified command.
328              * Refer: https://developer.mozilla.org/en/Rich-Text_Editing_in_Mozilla
329              * @param name {string} Command name.
330              */
331             execCommand:function (name) {
332                 var self = this,
333                     cmd = self.__commands[name],
334                     args = S.makeArray(arguments);
335                 args.shift();
336                 args.unshift(self);
337                 if (cmd) {
338                     return cmd.exec.apply(cmd, args);
339                 } else {
340                     S.log(name + ": command not found");
341                 }
342             },
343 
344             queryCommandValue:function (name) {
345                 return this.execCommand(Utils.getQueryCmd(name));
346             },
347 
348             _getData:function (format, mode) {
349                 var self = this,
350                     htmlDataProcessor = self.htmlDataProcessor,
351                     html;
352                 if (mode == undefined) {
353                     mode = self.get("mode");
354                 }
355                 if (mode == WYSIWYG_MODE) {
356                     html = self.get("document")[0].body.innerHTML;
357                 } else {
358                     html = htmlDataProcessor.toDataFormat(self.get("textarea").val());
359                 }
360                 //如果不需要要格式化,例如提交数据给服务器
361                 if (format) {
362                     html = htmlDataProcessor.toHtml(html);
363                 } else {
364                     html = htmlDataProcessor.toServer(html);
365                 }
366                 html = S.trim(html);
367                 /*
368                  如果内容为空,对 parser 自动加的空行滤掉
369                  */
370                 if (/^(?:<(p)>)?(?:(?: )|\s)*(?:<\/\1>)?$/.test(html)) {
371                     html = "";
372                 }
373                 return html;
374             },
375 
376             _setData:function (data) {
377                 var self = this,
378                     htmlDataProcessor,
379                     afterData = data;
380                 if (self.get("mode") != WYSIWYG_MODE) {
381                     // 代码模式下不需过滤
382                     self.get("textarea").val(data);
383                     return;
384                 }
385                 if (htmlDataProcessor = self.htmlDataProcessor) {
386                     afterData = htmlDataProcessor.toDataFormat(data);
387                 }
388                 // https://github.com/kissyteam/kissy-editor/issues/17, 重建最保险
389                 clearIframeDocContent(self);
390                 createIframe(self, afterData);
391             },
392 
393             /**
394              * Synchronize textarea value with editor data.
395              */
396             sync:function () {
397                 var self = this;
398                 self.get("textarea").val(self.get("data"));
399             },
400 
401             /**
402              * Get full html content of editor 's iframe.
403              */
404             getDocHtml:function () {
405                 var self = this;
406                 return prepareIFrameHtml(0, self.get('customStyle'),
407                     self.get('customLink'), self.get("formatData"));
408             },
409 
410             /**
411              * Get selection instance of current editor.
412              */
413             getSelection:function () {
414                 return Editor.Selection.getSelection(this.get("document")[0]);
415             },
416 
417             /**
418              * Make current editor has focus
419              */
420             focus:function () {
421                 var self = this,
422                     doc = self.get("document")[0],
423                     win = DOM._getWin(doc);
424                 // firefox7 need this
425                 if (!UA['ie']) {
426                     // note : 2011-11-17 report by 石霸
427                     // ie 的 parent 不能 focus ,否则会使得 iframe 内的编辑器光标回到开头
428                     win && win.parent && win.parent.focus();
429                 }
430                 // yiminghe note:webkit need win.focus
431                 // firefox 7 needs also?
432                 win && win.focus();
433                 // ie and firefox need body focus
434                 doc.body.focus();
435                 self.notifySelectionChange();
436             },
437 
438             /**
439              * Make current editor lose focus
440              */
441             blur:function () {
442                 var self = this,
443                     win = DOM._getWin(self.get("document")[0]);
444                 win.blur();
445                 self.get("document")[0].body.blur();
446             },
447 
448             /**
449              * Add style text to current editor
450              * @param cssText {string}
451              */
452             addCustomStyle:function (cssText, id) {
453                 var self = this,
454                     customStyle = self.get("customStyle") || "";
455                 customStyle += "\n" + cssText;
456                 self.set("customStyle", customStyle);
457                 DOM.addStyleSheet(self.get("window"), customStyle, id);
458             },
459 
460             /**
461              * Remove style text with specified id from current editor
462              * @param id
463              */
464             removeCustomStyle:function (id) {
465                 DOM.remove(DOM.get("#" + id, this.get("window")[0]));
466             },
467 
468             /**
469              * Add css link to current editor
470              * @param {String} link
471              */
472             addCustomLink:function (link) {
473                 var self = this,
474                     customLink = self.get('customLink') || [],
475                     doc = self.get("document")[0];
476                 customLink.push(link);
477                 self.set("customLink", customLink);
478                 var elem = doc.createElement("link");
479                 elem.rel = "stylesheet";
480                 doc.getElementsByTagName("head")[0].appendChild(elem);
481                 elem.href = link;
482             },
483 
484             /**
485              * Remove css link from current editor.
486              * @param {String} link
487              */
488             removeCustomLink:function (link) {
489                 var self = this,
490                     doc = self.get("document")[0],
491                     links = DOM.query("link", doc);
492                 for (var i = 0; i < links.length; i++) {
493                     if (DOM.attr(links[i], "href") == link) {
494                         DOM.remove(links[i]);
495                     }
496                 }
497                 var cls = self.get('customLink') || [],
498                     ind = S.indexOf(link, cls);
499                 if (ind != -1) {
500                     cls.splice(ind, 1);
501                 }
502             },
503 
504             /**
505              * Add callback which will called when editor document is ready
506              * (fire when editor is renderer from textarea/source)
507              * @param {Function} func
508              */
509             docReady:function (func) {
510                 var self = this;
511                 self.on("docReady", func);
512                 if (self.__docReady) {
513                     func.call(self);
514                 }
515             },
516 
517             /**
518              * Check whether selection has changed since last check point.
519              */
520             checkSelectionChange:function () {
521                 var self = this;
522                 if (self.__checkSelectionChangeId) {
523                     clearTimeout(self.__checkSelectionChangeId);
524                 }
525 
526                 self.__checkSelectionChangeId = setTimeout(function () {
527                     var selection = self.getSelection();
528                     if (selection && !selection.isInvalid) {
529                         var startElement = selection.getStartElement(),
530                             currentPath = new Editor.ElementPath(startElement);
531                         if (!self.__previousPath ||
532                             !self.__previousPath.compare(currentPath)) {
533                             self.__previousPath = currentPath;
534                             self.fire("selectionChange",
535                                 {
536                                     selection:selection,
537                                     path:currentPath,
538                                     element:startElement
539                                 });
540                         }
541                     }
542                 }, 100);
543             },
544 
545             /**
546              * Fire selectionChange manually.
547              */
548             notifySelectionChange:function () {
549                 var self = this;
550                 self.__previousPath = NULL;
551                 self.checkSelectionChange();
552             },
553 
554             /**
555              * Insert a element into current editor.
556              * @param {NodeList} element
557              */
558             insertElement:function (element) {
559 
560                 var self = this;
561 
562                 if (self.get("mode") !== WYSIWYG_MODE) {
563                     return;
564                 }
565 
566                 self.focus();
567 
568                 var clone,
569                     elementName = element['nodeName'](),
570                     xhtml_dtd = Editor.XHTML_DTD,
571                     isBlock = xhtml_dtd['$block'][ elementName ],
572                     KER = Editor.RANGE,
573                     selection = self.getSelection(),
574                     ranges = selection && selection.getRanges(),
575                     range,
576                     notWhitespaceEval,
577                     i,
578                     next,
579                     nextName,
580                     lastElement;
581 
582                 if (!ranges || ranges.length == 0) {
583                     return;
584                 }
585 
586                 self.execCommand("save");
587 
588                 for (i = ranges.length - 1; i >= 0; i--) {
589                     range = ranges[ i ];
590                     // Remove the original contents.
591 
592                     clone = !i && element || element['clone'](TRUE);
593                     range.insertNodeByDtd(clone);
594                     // Save the last element reference so we can make the
595                     // selection later.
596                     if (!lastElement) {
597                         lastElement = clone;
598                     }
599                 }
600 
601                 if (!lastElement) {
602                     return;
603                 }
604 
605                 range.moveToPosition(lastElement, KER.POSITION_AFTER_END);
606                 // If we're inserting a block element immediately followed by
607                 // another block element, the selection must move there. (#3100,#5436)
608                 if (isBlock) {
609                     notWhitespaceEval = Editor.Walker.whitespaces(true);
610                     next = lastElement.next(notWhitespaceEval, 1);
611                     nextName = next && next[0].nodeType == DOM.ELEMENT_NODE
612                         && next.nodeName();
613                     // Check if it's a block element that accepts text.
614                     if (nextName &&
615                         xhtml_dtd.$block[ nextName ] &&
616                         xhtml_dtd[ nextName ]['#text']) {
617                         range.moveToElementEditablePosition(next);
618                     }
619                 }
620                 selection.selectRanges([ range ]);
621                 self.focus();
622                 // http://code.google.com/p/kissy/issues/detail?can=1&start=100&id=121
623                 // only tag can scroll
624                 if (clone && clone[0].nodeType == 1) {
625                     clone.scrollIntoView(undefined, false);
626                 }
627                 saveLater.call(self);
628                 return clone;
629             },
630 
631             /**
632              * Insert html string into current editor.
633              * @param data {string}
634              */
635             insertHtml:function (data, dataFilter) {
636                 var self = this,
637                     htmlDataProcessor,
638                     editorDoc = self.get("document")[0];
639 
640                 if (self.get("mode") !== WYSIWYG_MODE) {
641                     return;
642                 }
643 
644                 if (htmlDataProcessor = self.htmlDataProcessor) {
645                     data = htmlDataProcessor.toDataFormat(data, dataFilter);
646                 }
647 
648                 // webkit bug
649                 if (UA.webkit) {
650                     var nodes = new Node(data, null, editorDoc);
651                     nodes.each(function (node) {
652                         self.insertElement(node);
653                     });
654                     return;
655                 }
656 
657                 self.focus();
658                 self.execCommand("save");
659 
660                 // ie9 仍然需要这样!
661                 // ie9 标准 selection 有问题,连续插入不能定位光标到插入内容后面
662                 if (IS_IE) {
663                     var $sel = editorDoc.selection;
664                     if ($sel.type == 'Control') {
665                         $sel.clear();
666                     }
667                     try {
668                         $sel.createRange().pasteHTML(data);
669                     } catch (e) {
670                         S.log("insertHtml error in ie");
671                     }
672                 } else {
673                     // ie9 仍然没有
674                     // 1.webkit insert html 有问题!会把标签去掉,算了直接用 insertElement.
675                     // 10.0 修复??
676                     // firefox 初始编辑器无焦点报异常
677                     try {
678                         editorDoc.execCommand('inserthtml', FALSE, data);
679                     } catch (e) {
680                         setTimeout(function () {
681                             // still not ok in ff!
682                             // 手动选择 body 的第一个节点
683                             if (self.getSelection().getRanges().length == 0) {
684                                 var r = new Editor.Range(editorDoc),
685                                     node = DOM.first(editorDoc.body, function (el) {
686                                         return el.nodeType == 1 && DOM.nodeName(el) != "br";
687                                     });
688                                 if (!node) {
689                                     node = new Node(editorDoc.createElement("p"));
690                                     node._4e_appendBogus(undefined);
691                                     editorDoc.body.appendChild(node[0]);
692                                 }
693                                 r.setStartAt(node, Editor.RANGE.POSITION_AFTER_START);
694                                 r.select();
695                             }
696                             editorDoc.execCommand('inserthtml', FALSE, data);
697                         }, 50);
698                     }
699                 }
700                 // bug by zjw2004112@163.com :
701                 // 有的浏览器 : chrome , ie67 貌似不会自动滚动到粘贴后的位置
702                 setTimeout(function () {
703                     self.getSelection().scrollIntoView();
704                 }, 50);
705                 saveLater.call(self);
706             }
707         });
708 
709     /**
710      * 初始化iframe内容以及浏览器间兼容性处理,
711      * 必须等待iframe内的脚本向父窗口通知
712      *
713      * @param id {string}
714      */
715     Editor["_initIFrame"] = function (id) {
716 
717         var self = focusManager.getInstance(id),
718             doc = self.get("document")[0],
719         // Remove bootstrap script from the DOM.
720             script = doc.getElementById("ke_active_script");
721 
722         DOM.remove(script);
723 
724         fixByBindIframeDoc(self);
725 
726         var body = doc.body;
727 
728         /**
729          * from kissy editor 1.0
730          *
731          * // 注1:在 tinymce 里,designMode = "on" 放在 try catch 里。
732          //     原因是在 firefox 下,当iframe 在 display: none 的容器里,会导致错误。
733          //     但经过我测试,firefox 3+ 以上已无此现象。
734          // 注2: ie 用 contentEditable = true.
735          //     原因是在 ie 下,IE needs to use contentEditable or
736          // it will display non secure items for HTTPS
737          // Ref:
738          //   - Differences between designMode and contentEditable
739          //     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
740          */
741 
742         // 这里对主流浏览器全部使用 contenteditable
743         // 那么不同于 kissy editor 1.0
744         // 在body范围外右键,不会出现 复制,粘贴等菜单
745         // 因为这时右键作用在document而不是body
746         // 1.0 document.designMode='on' 是编辑模式
747         // 2.0 body.contentEditable=true body外不是编辑模式
748         if (IS_IE) {
749             // Don't display the focus border.
750             body['hideFocus'] = TRUE;
751             // Disable and re-enable the body to avoid IE from
752             // taking the editing focus at startup. (#141 / #523)
753             body.disabled = TRUE;
754             body['contentEditable'] = TRUE;
755             body.removeAttribute('disabled');
756         } else {
757             // Avoid opening design mode in a frame window thread,
758             // which will cause host page scrolling.(#4397)
759             setTimeout(function () {
760                 // Prefer 'contentEditable' instead of 'designMode'. (#3593)
761                 if (UA['gecko'] || UA['opera']) {
762                     body['contentEditable'] = TRUE;
763                 }
764                 else if (UA['webkit'])
765                     body.parentNode['contentEditable'] = TRUE;
766                 else
767                     doc['designMode'] = 'on';
768             }, 0);
769         }
770 
771         // IE standard compliant in editing frame doesn't focus the editor when
772         // clicking outside actual content, manually apply the focus. (#1659)
773 
774         if (
775         // ie6,7 点击滚动条失效
776         // IS_IE
777         // && doc.compatMode == 'CSS1Compat'
778         // wierd ,sometimes ie9 break
779         // ||
780         // 2012-01-11 ie 处理装移到 selection.js :
781         // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
782         // doc['documentMode']
783             UA['gecko']
784                 || UA['opera']) {
785             var htmlElement = doc.documentElement;
786             Event.on(htmlElement, 'mousedown', function (evt) {
787                 // Setting focus directly on editor doesn't work, we
788                 // have to use here a temporary element to 'redirect'
789                 // the focus.
790                 // firefox 不能直接设置,需要先失去焦点
791                 // return;
792                 // 左键激活
793                 var t = evt.target;
794                 if (t == htmlElement) {
795                     //S.log("click");
796                     //self.focus();
797                     //return;
798                     if (UA['gecko']) {
799                         blinkCursor(doc, FALSE);
800                     }
801                     //setTimeout(function() {
802                     //这种:html mousedown -> body beforedeactivate
803                     //    self.focus();
804                     //}, 30);
805                     //这种:body beforedeactivate -> html mousedown
806                     self.activateGecko();
807                 }
808             });
809         }
810 
811         // Adds the document body as a context menu target.
812         setTimeout(function () {
813             /*
814              * IE BUG: IE might have rendered the iframe with invisible contents.
815              * (#3623). Push some inconsequential CSS style changes to force IE to
816              * refresh it.
817              *
818              * Also, for some unknown reasons, short timeouts (e.g. 100ms) do not
819              * fix the problem. :(
820              */
821             if (IS_IE) {
822                 setTimeout(function () {
823                     if (doc) {
824                         body.runtimeStyle['marginBottom'] = '0px';
825                         body.runtimeStyle['marginBottom'] = '';
826                     }
827                 }, 1000);
828             }
829         }, 0);
830 
831 
832         setTimeout(function () {
833             self.__docReady = 1;
834             self.fire("docReady");
835             /*
836              some break for firefox ,不能立即设置
837              */
838             var disableObjectResizing = self.get('disableObjectResizing'),
839                 disableInlineTableEditing = self.get('disableInlineTableEditing');
840             if (disableObjectResizing || disableInlineTableEditing) {
841                 // IE, Opera and Safari may not support it and throw errors.
842                 try {
843                     doc.execCommand('enableObjectResizing', FALSE, !disableObjectResizing);
844                     doc.execCommand('enableInlineTableEditing', FALSE, !disableInlineTableEditing);
845                 }
846                 catch (e) {
847                     // 只能ie能用?,目前只有 firefox,ie 支持图片缩放
848                     // For browsers which don't support the above methods,
849                     // we can use the the resize event or resizestart for IE (#4208)
850                     Event.on(body, IS_IE ? 'resizestart' : 'resize', function (evt) {
851                         var t = new Node(evt.target);
852                         if (
853                             disableObjectResizing ||
854                                 (
855                                     t.nodeName() === 'table'
856                                         &&
857                                         disableInlineTableEditing )
858                             ) {
859                             evt.preventDefault();
860                         }
861                     });
862                 }
863             }
864         }, 10);
865     };
866 
867     // ---------------------------------------------------------------------- start private
868 
869 
870     function blinkCursor(doc, retry) {
871         var body = doc.body;
872         tryThese(
873             function () {
874                 doc['designMode'] = 'on';
875                 //异步引起时序问题,尽可能小间隔
876                 setTimeout(function () {
877                     doc['designMode'] = 'off';
878                     body.focus();
879                     // Try it again once..
880                     if (!arguments.callee.retry) {
881                         arguments.callee.retry = TRUE;
882                         //arguments.callee();
883                     }
884                 }, 50);
885             },
886             function () {
887                 // The above call is known to fail when parent DOM
888                 // tree layout changes may break design mode. (#5782)
889                 // Refresh the 'contentEditable' is a cue to this.
890                 doc['designMode'] = 'off';
891                 DOM.attr(body, 'contentEditable', FALSE);
892                 DOM.attr(body, 'contentEditable', TRUE);
893                 // Try it again once..
894                 !retry && blinkCursor(doc, 1);
895             }
896         );
897     }
898 
899     function fixByBindIframeDoc(self) {
900         var iframe = self.get("iframe"),
901             textarea = self.get("textarea")[0],
902             win = self.get("window")[0],
903             doc = self.get("document")[0];
904 
905         // Gecko need a key event to 'wake up' the editing
906         // ability when document is empty.(#3864)
907         // activateEditing 删掉,初始引起屏幕滚动了
908         // Webkit: avoid from editing form control elements content.
909         if (UA['webkit']) {
910             Event.on(doc, "click", function (ev) {
911                 var control = new Node(ev.target);
912                 if (S.inArray(control.nodeName(), ['input', 'select'])) {
913                     ev.preventDefault();
914                 }
915             });
916             // Prevent from editig textfield/textarea value.
917             Event.on(doc, "mouseup", function (ev) {
918                 var control = new Node(ev.target);
919                 if (S.inArray(control.nodeName(), ['input', 'textarea'])) {
920                     ev.preventDefault();
921                 }
922             });
923         }
924 
925 
926         // Create an invisible element to grab focus.
927         if (UA['gecko'] || IS_IE || UA['opera']) {
928             var focusGrabber;
929             focusGrabber = new Node(
930                 // Use 'span' instead of anything else to fly under the screen-reader radar. (#5049)
931                 '<span ' +
932                     'tabindex="-1" ' +
933                     'style="position:absolute; left:-10000"' +
934                     ' role="presentation"' +
935                     '></span>').insertAfter(textarea);
936             focusGrabber.on('focus', function () {
937                 self.focus();
938             });
939             self.activateGecko = function () {
940                 if (UA['gecko'] && self.__iframeFocus)
941                     focusGrabber[0].focus();
942             };
943             self.on('destroy', function () {
944                 focusGrabber.detach();
945                 focusGrabber.remove();
946             });
947         }
948 
949 
950         Event.on(win, 'focus', function () {
951             /**
952              * yiminghe特别注意:firefox光标丢失bug
953              * blink后光标出现在最后,这就需要实现保存range
954              * focus后再恢复range
955              */
956             if (UA['gecko']) {
957                 blinkCursor(doc, FALSE);
958             }
959             else if (UA['opera']) {
960                 doc.body.focus();
961             }
962             // focus 后强制刷新自己状态
963             self.notifySelectionChange();
964         });
965 
966 
967         if (UA['gecko']) {
968             /**
969              * firefox 焦点丢失后,再点编辑器区域焦点会移不过来,要点两下
970              */
971             Event.on(doc, "mousedown", function () {
972                 if (!self.__iframeFocus) {
973                     blinkCursor(doc, FALSE);
974                 }
975             });
976         }
977 
978         if (IS_IE) {
979             // Override keystrokes which should have deletion behavior
980             // on control types in IE . (#4047)
981             /**
982              * 选择img,出现缩放框后不能直接删除
983              */
984             Event.on(doc, 'keydown', function (evt) {
985                 var keyCode = evt.keyCode;
986                 // Backspace OR Delete.
987                 if (keyCode in { 8:1, 46:1 }) {
988                     var sel = self.getSelection(),
989                         control = sel.getSelectedElement();
990                     if (control) {
991                         // Make undo snapshot.
992                         self.execCommand('save');
993                         // Delete any element that 'hasLayout' (e.g. hr,table) in IE8 will
994                         // break up the selection, safely manage it here. (#4795)
995                         var bookmark = sel.getRanges()[ 0 ].createBookmark();
996                         // Remove the control manually.
997                         control.remove();
998                         sel.selectBookmarks([ bookmark ]);
999                         self.execCommand('save');
1000                         evt.preventDefault();
1001                     }
1002                 }
1003             });
1004 
1005             // PageUp/PageDown scrolling is broken in document
1006             // with standard doctype, manually fix it. (#4736)
1007             // ie8 主窗口滚动??
1008             if (doc.compatMode == 'CSS1Compat') {
1009                 var pageUpDownKeys = { 33:1, 34:1 };
1010                 Event.on(doc, 'keydown', function (evt) {
1011                     if (evt.keyCode in pageUpDownKeys) {
1012                         setTimeout(function () {
1013                             self.getSelection().scrollIntoView();
1014                         }, 0);
1015                     }
1016                 });
1017             }
1018         }
1019 
1020         // Gecko/Webkit need some help when selecting control type elements. (#3448)
1021         if (UA['webkit']) {
1022             Event.on(doc, "mousedown", function (ev) {
1023                 var control = new Node(ev.target);
1024                 if (S.inArray(control.nodeName(), ['img', 'hr', 'input', 'textarea', 'select'])) {
1025                     self.getSelection().selectElement(control);
1026                 }
1027             });
1028         }
1029 
1030 
1031         if (UA['gecko']) {
1032             Event.on(doc, "dragstart", function (ev) {
1033                 var control = new Node(ev.target);
1034                 if (control.nodeName() === 'img' && /ke_/.test(control[0].className)) {
1035                     // firefox禁止拖放
1036                     ev.preventDefault();
1037                 }
1038             });
1039         }
1040         //注意:必须放在这个位置,等iframe加载好再开始运行
1041         //加入焦点管理,和其他实例联系起来
1042         focusManager.add(self);
1043 
1044     }
1045 
1046     function prepareIFrameHtml(id, customStyle, customLink, data) {
1047         var links = "",
1048             i,
1049             innerCssFile = Utils.debugUrl("theme/editor-iframe.css");
1050 
1051         for (i = 0; i < customLink.length; i++) {
1052             links += S.substitute('<link href="' + '{href}" rel="stylesheet" />', {
1053                 href:customLink[i]
1054             });
1055         }
1056 
1057         return S.substitute(IFRAME_HTML_TPL, {
1058             // kissy-editor #12
1059             // IE8 doesn't support carets behind images(empty content after image's block)
1060             // setting ie7 compatible mode would force IE8+ to run in IE7 compat mode.
1061             doctype:DOC.documentMode === 8 ?
1062                 '<meta http-equiv="X-UA-Compatible" content="IE=7" />' :
1063                 "",
1064             title:"${title}",
1065             href:innerCssFile,
1066             style:customStyle,
1067             // firefox 必须里面有东西,否则编辑前不能删除!
1068             data:data || " ",
1069             script:id ?
1070                 // The script that launches the bootstrap logic on 'domReady', so the document
1071                 // is fully editable even before the editing iframe is fully loaded (#4455).
1072                 // 确保iframe确实载入成功,过早的话 document.domain 会出现无法访问
1073                 ('<script id="ke_active_script">' +
1074                     ( DOM.isCustomDomain() ? ( 'document.domain="' + DOC.domain + '";' ) : '' ) +
1075                     'parent.KISSY.Editor._initIFrame("' + id + '");' +
1076                     '</script>') :
1077                 ''
1078 
1079         });
1080     }
1081 
1082     var saveLater = S.buffer(function () {
1083         this.execCommand("save");
1084     }, 50);
1085 
1086     function setUpIFrame(self, data) {
1087         var iframe = self.get("iframe"),
1088             html = prepareIFrameHtml(self._UUID,
1089                 self.get('customStyle'),
1090                 self.get('customLink'), data),
1091             iframeDom = iframe[0],
1092             win = iframeDom.contentWindow,
1093             doc;
1094         iframe.__loaded = 1;
1095         try {
1096             // In IE, with custom document.domain, it may happen that
1097             // the iframe is not yet available, resulting in "Access
1098             // Denied" for the following property access.
1099             //ie 设置domain 有问题:yui也有
1100             //http://yuilibrary.com/projects/yui2/ticket/2052000
1101             //http://waelchatila.com/2007/10/31/1193851500000.html
1102             //http://nagoon97.wordpress.com/tag/designmode/
1103             doc = win.document;
1104         } catch (e) {
1105             // Trick to solve this issue, forcing the iframe to get ready
1106             // by simply setting its "src" property.
1107             //noinspection SillyAssignmentJS
1108             iframeDom.src = iframeDom.src;
1109             // In IE6 though, the above is not enough, so we must pause the
1110             // execution for a while, giving it time to think.
1111             if (IS_IE < 7) {
1112                 setTimeout(run, 10);
1113                 return;
1114             }
1115         }
1116         run();
1117         function run() {
1118             doc = win.document;
1119             self.__set("document", new Node(doc));
1120             self.__set("window", new Node(win));
1121             iframe.detach();
1122             // Don't leave any history log in IE. (#5657)
1123             doc['open']("text/html", "replace");
1124             doc.write(html);
1125             doc.close();
1126         }
1127     }
1128 
1129     function createIframe(self, afterData) {
1130         var iframe = new Node(IFRAME_TPL),
1131             textarea = self.get("textarea");
1132         if (textarea.hasAttr("tabindex")) {
1133             iframe.attr("tabIndex", UA['webkit'] ? -1 : textarea.attr("tabIndex"));
1134         }
1135         textarea.parent().prepend(iframe);
1136         self.set("iframe", iframe);
1137         self.__docReady = 0;
1138         // With FF, it's better to load the data on iframe.load. (#3894,#4058)
1139         if (UA['gecko'] && !iframe.__loaded) {
1140             iframe.on('load', function () {
1141                 setUpIFrame(self, afterData);
1142             }, self);
1143         } else {
1144             // webkit(chrome) load等不来!
1145             setUpIFrame(self, afterData);
1146         }
1147     }
1148 
1149     function clearIframeDocContent(self) {
1150         if (!self.get("iframe")) {
1151             return;
1152         }
1153         var iframe = self.get("iframe"),
1154             win = self.get("window")[0],
1155             doc = self.get("document")[0],
1156             documentElement = doc.documentElement,
1157             body = doc.body;
1158         Event.remove([doc, documentElement, body, win]);
1159         iframe.remove();
1160     }
1161 
1162     // ------------------------------------------------------------------- end private
1163 
1164     return Editor;
1165 }, {
1166     requires:[
1167         'editor/core/base',
1168         'editor/core/utils',
1169         'editor/core/focusManager',
1170         'editor/core/styles',
1171         'editor/core/zIndexManager',
1172         'editor/core/meta',
1173         'editor/core/clipboard',
1174         'editor/core/enterKey',
1175         'editor/core/htmlDataProcessor',
1176         'editor/core/selectionFix'
1177     ]
1178 });
1179 /**
1180  * 2012-03-05 重构 by yiminghe@gmail.com
1181  *  - core
1182  *  - plugins
1183  */