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 */