1 /** 2 * Bitrix HTML Editor 3.0 3 * Date: 24.04.13 4 * Time: 4:23 5 */ 6 (function(window) { 7 var __BXHtmlEditorParserRules; 8 9 function BXEditor(config) 10 { 11 // Container contains links to dom elements 12 this.InitUtil(); 13 this.dom = { 14 // cont - 15 // iframeCont - 16 // textareaCont - 17 // iframe - 18 // textarea - 19 }; 20 21 this.bxTags = {}; 22 23 this.EMPTY_IMAGE_SRC = '/bitrix/images/1.gif'; 24 this.HTML5_TAGS = [ 25 "abbr", "article", "aside", "audio", "bdi", "canvas", "command", "datalist", "details", "figcaption", 26 "figure", "footer", "header", "hgroup", "keygen", "mark", "meter", "nav", "output", "progress", 27 "rp", "rt", "ruby", "svg", "section", "source", "summary", "time", "track", "video", "wbr" 28 ]; 29 this.BLOCK_TAGS = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "BLOCKQUOTE", "DIV", "SECTION", "PRE"]; 30 this.NESTED_BLOCK_TAGS = ["BLOCKQUOTE", "DIV"]; 31 this.TABLE_TAGS = ["TD", "TR", "TH", "TABLE", "TBODY", "CAPTION", "COL", "COLGROUP", "TFOOT", "THEAD"]; 32 this.BBCODE_TAGS = ['P', 'U', 'TABLE', 'TR', 'TD', 'TH', 'IMG', 'A', 'CENTER', 'LEFT', 'RIGHT', 'JUSTIFY']; 33 34 this.HTML_ENTITIES = ['¡','¢','£','¤','¥','¦','§','¨','©','ª','«','¬','®','¯','°','±','²','³','´','µ','¶','·','¸','¹','º','»','¼','½','¾','¿','À','Á','Â','Ã','Ä','Å','Æ','Ç','È','É','Ê','Ë','Ì','Í','Î','Ï','Ð','Ñ','Ò','Ó','Ô','Õ','Ö','×','Ø','Ù','Ú','Û','Ü','Ý','Þ','ß','à','á','â','ã','ä','å','æ','ç','è','é','ê','ë','ì','í','î','ï','ð','ñ','ò','ó','ô','õ','ö','÷','ø','ù','ú','û','ü','ý','þ','ÿ','Œ','œ','Š','š','Ÿ','ˆ','˜','–','—','‘','’','‚','“','”','„','†','‡','‰','‹','›','€','Α','Β','Γ','Δ','Ε','Ζ','Η','Θ','Ι','Κ','Λ','Μ','Ν','Ξ','Ο','Π','Ρ','Σ','Τ','Υ','Φ','Χ','Ψ','Ω','α','β','γ','δ','ε','ζ','η','θ','ι','κ','λ','μ','ν','ξ','ο','π','ρ','ς','σ','τ','υ','φ','χ','ψ','ω','•','…','′','″','‾','⁄','™','←','↑','→','↓','↔','∂','∑','−','√','∞','∫','≈','≠','≡','≤','≥','◊','♠','♣','♥']; 35 36 if(!BX.browser.IsIE()) 37 { 38 this.HTML_ENTITIES = this.HTML_ENTITIES.concat(['ϑ','ϒ','ϖ','℘','ℑ','ℜ','ℵ','↵','⇐','⇑','⇒','⇓','⇔','∀','∃','∅','∇','∈','∉','∋','∏','∗','∝','∠','∧','∨','∩','∪','∴','∼','≅','⊂','⊃','⊄','⊆','⊇','⊕','⊗','⊥','⋅','⌈','⌉','⌊','⌋','⟨','⟩','♦']); 39 } 40 41 this.SHORTCUTS = { 42 "66": "bold", // B 43 "73": "italic", // I 44 "85": "underline" // U 45 }; 46 47 this.KEY_CODES = { 48 'backspace': 8, 49 'enter': 13, 50 'escape': 27, 51 'space': 32, 52 'delete': 46, 53 'left': 37, 54 'right': 39, 55 'up': 38, 56 'down': 40, 57 'z': 90, 58 'y': 89, 59 'shift': 16, 60 'ctrl': 17, 61 'alt': 18, 62 'cmd': 91, // 93, 224, 17 Browser dependent 63 'cmdRight': 93, // 93, 224, 17 Browser dependent? 64 'pageUp': 33, 65 'pageDown': 34 66 }; 67 this.INVISIBLE_SPACE = "\uFEFF"; 68 this.INVISIBLE_CURSOR = "\u2060"; 69 this.NORMAL_WIDTH = 1020; 70 this.MIN_WIDTH = 700; 71 this.MIN_HEIGHT = 100; 72 73 this.MAX_HANDLED_FORMAT_LENGTH = 50000; // Max length of code which will be formated 74 this.MAX_HANDLED_FORMAT_TIME = 500; 75 this.iframeCssText = ''; // Here some controls can add additional css 76 77 this.InitConfig(this.CheckConfig(config)); 78 79 if (!config.lazyLoad) 80 { 81 this.Init(); 82 } 83 } 84 85 BXEditor.prototype = { 86 Init: function() 87 { 88 if (this.inited) 89 return; 90 91 // Parser 92 this.parser = new BXHtmlEditor.BXEditorParser(this); 93 94 this.On("OnEditorInitedBefore", [this]); 95 96 if (!this.BuildSceleton()) 97 return; 98 99 this.HTMLStyler = HTMLStyler; 100 101 // Textarea 102 this.dom.textarea = this.dom.textareaCont.appendChild(BX.create("TEXTAREA", {props: {className: "bxhtmled-textarea"}})); 103 this.dom.pValueInput = BX('bxed_' + this.id); 104 if (!this.dom.pValueInput) 105 { 106 this.dom.pValueInput = this.dom.cont.appendChild(BX.create("INPUT", {props: {type: "hidden", id: 'bxed_' + this.id, name: this.config.inputName}})); 107 } 108 this.dom.pValueInput.value = this.config.content; 109 110 this.dom.form = this.dom.textarea.form || false; 111 this.document = null; 112 113 // Protected iframe for wysiwyg 114 this.sandbox = this.CreateIframeSandBox(); 115 var iframe = this.sandbox.GetIframe(); 116 117 iframe.style.width = '100%'; 118 // TODO: height in pixels 119 iframe.style.height = '100%'; 120 121 // Views: 122 // 1. TextareaView 123 this.textareaView = new BXEditorTextareaView(this, this.dom.textarea, this.dom.textareaCont); 124 // 2. IframeView 125 this.iframeView = new BXEditorIframeView(this, this.dom.textarea, this.dom.iframeCont); 126 // 3. Syncronizer 127 this.synchro = new BXEditorViewsSynchro(this, this.textareaView, this.iframeView); 128 129 if (this.bbCode) 130 {
131 this.bbParser = new BXHtmlEditor.BXEditorBbCodeParser(this); 132 } 133 134 // Php parser 135 this.phpParser = new BXHtmlEditor.BXEditorPhpParser(this); 136 this.components = new BXHtmlEditor.BXEditorComponents(this); 137 138 this.styles = new BXStyles(this); 139 140 // Toolbar 141 this.overlay = new BXHtmlEditor.Overlay(this); 142 this.BuildToolbar(); 143 144 // Taskbars 145 if (this.showTaskbars) 146 { 147 this.taskbarManager = new BXHtmlEditor.TaskbarManager(this, true); 148 149 // Components 150 if (this.showComponents) 151 { 152 this.componentsTaskbar = new BXHtmlEditor.ComponentsControl(this); 153 this.taskbarManager.AddTaskbar(this.componentsTaskbar); 154 } 155 // Snippets 156 if (this.showSnippets) 157 { 158 this.snippets = new BXHtmlEditor.BXEditorSnippets(this); 159 this.snippetsTaskbar = new BXHtmlEditor.SnippetsControl(this, this.taskbarManager); 160 this.taskbarManager.AddTaskbar(this.snippetsTaskbar); 161 } 162 this.taskbarManager.ShowTaskbar(this.showComponents ? this.componentsTaskbar.GetId() : this.snippetsTaskbar.GetId()); 163 } 164 else 165 { 166 this.dom.taskbarCont.style.display = 'none'; 167 } 168 // Context menu 169 this.contextMenu = new BXHtmlEditor.ContextMenu(this); 170 171 if (this.config.showNodeNavi) 172 { 173 this.nodeNavi = new BXHtmlEditor.NodeNavigator(this); 174 this.nodeNavi.Show(); 175 } 176 177 this.pasteControl = new BXHtmlEditor.PasteControl(this); 178 179 this.InitEventHandlers(); 180 this.ResizeSceleton(); 181 182 // Restore taskbar mode from user settings 183 if (this.showTaskbars && this.config.taskbarShown) 184 { 185 this.taskbarManager.Show(false); 186 } 187 this.inited = true; 188 this.On("OnEditorInitedAfter", [this]); 189 190 if (!this.CheckBrowserCompatibility()) 191 { 192 this.dom.cont.parentNode.insertBefore(BX.create("DIV", {props: {className: "bxhtmled-warning"}, text: BX.message('BXEdInvalidBrowser')}), this.dom.cont); 193 } 194 195 this.InitImageUploader(); 196 this.Show(); 197 BX.onCustomEvent(BXHtmlEditor, 'OnEditorCreated', [this]); 198 }, 199 200 InitConfig: function(config) 201 { 202 this.config = config; 203 this.id = this.config.id; 204 this.dialogs = {}; 205 this.bbCode = !!this.config.bbCode; 206 this.config.splitVertical = !!this.config.splitVertical; 207 this.config.splitRatio = parseFloat(this.config.splitRatio); 208 this.config.view = this.config.view || 'wysiwyg'; 209 this.config.taskbarShown = !!this.config.taskbarShown; 210 this.config.taskbarWidth = parseInt(this.config.taskbarWidth); 211 this.config.showNodeNavi = this.config.showNodeNavi !== false; 212 this.config.setFocusAfterShow = this.config.setFocusAfterShow !== false; 213 this.cssCounter = 0; 214 this.iframeCssText = this.config.iframeCss; 215 this.fileDialogsLoaded = this.bbCode || this.config.useFileDialogs === false; 216 217 if (this.config.bbCodeTags && this.bbCode) 218 { 219 this.BBCODE_TAGS = this.config.bbCodeTags; 220 } 221 222 if (this.config.minBodyWidth) 223 this.MIN_WIDTH = parseInt(this.config.minBodyWidth); 224 if (this.config.minBodyHeight) 225 this.MIN_HEIGHT = parseInt(this.config.minBodyHeight); 226 227 if (this.config.normalBodyWidth) 228 this.NORMAL_WIDTH = parseInt(this.config.normalBodyWidth); 229 this.normalWidth = this.NORMAL_WIDTH; 230 231 if (!this.config.height) 232 this.config.height = this.MIN_HEIGHT; 233 if (!this.config.width) 234 this.config.width = this.MIN_WIDTH; 235 236 if (this.config.smiles) 237 { 238 this.smilesIndex = {}; 239 this.sortedSmiles = []; 240 241 var i, smile, j, arCodes; 242 for(i = 0; i < this.config.smiles.length; i++) 243 { 244 smile = this.config.smiles[i]; 245 if (!smile['codes'] || smile['codes'] == smile['code']) 246 { 247 this.smilesIndex[this.config.smiles[i].code] = smile; 248 this.sortedSmiles.push(smile); 249 } 250 else if(smile['codes'].length > 0) 251 { 252 arCodes = smile['codes'].split(' '); 253 for(j = 0; j < arCodes.length; j++) 254 { 255 this.smilesIndex[arCodes[j]] = smile; 256 this.sortedSmiles.push({name: smile.name, path: smile.path, code: arCodes[j]}); 257 } 258 } 259 } 260 261 this.sortedSmiles = this.sortedSmiles.sort(function(a, b){return b.code.length - a.code.length;}); 262 } 263 264 this.allowPhp = !!this.config.allowPhp; 265 // Limited Php Access - when user can only move or delete php code or change component params 266 this.lpa = !this.config.allowPhp && this.config.limitPhpAccess; 267 this.templateId = this.config.templateId; 268 this.componentFilter = this.config.componentFilter; 269 this.showSnippets = this.config.showSnippets !== false; 270 this.showComponents = this.config.showComponents !== false && (this.allowPhp || this.lpa); 271 this.showTaskbars = this.config.showTaskbars !== false && (this.showSnippets || this.showComponents); 272 273 this.templates = {}; 274 this.templates[this.templateId] = this.config.templateParams; 275 }, 276 277 InitEventHandlers: function() 278 { 279 var _this = this; 280 BX.bind(this.dom.cont, 'click', BX.proxy(this.OnClick, this)); 281 BX.bind(this.dom.cont, 'mousedown', BX.proxy(this.OnMousedown, this)); 282 283 BX.bind(window, 'resize', function(){_this.ResizeSceleton();}); 284 if (BX.adminMenu) 285 { 286 BX.addCustomEvent(BX.adminMenu, 'onAdminMenuResize', function(){_this.ResizeSceleton();}); 287 } 288 289 BX.addCustomEvent(this, "OnIframeFocus", function() 290 { 291 _this.bookmark = null; 292 if (_this.statusInterval) 293 { 294 clearInterval(_this.statusInterval); 295 } 296 _this.statusInterval = setInterval(BX.proxy(_this.CheckCurrentStatus, _this), 500); 297 }); 298 BX.addCustomEvent(this, "OnIframeBlur", function() 299 { 300 _this.bookmark = null; 301 if (_this.statusInterval) 302 { 303 clearInterval(_this.statusInterval); 304 } 305 _this.iframeView.stopBugusScroll = false;
306 }); 307 BX.addCustomEvent(this, "OnTextareaFocus", function() 308 { 309 _this.bookmark = null; 310 if (_this.statusInterval) 311 { 312 clearInterval(_this.statusInterval); 313 } 314 }); 315 316 // Surrogates 317 BX.addCustomEvent(this, "OnSurrogateDblClick", function(bxTag, origTag, target, e) 318 { 319 if (origTag) 320 { 321 switch (origTag.tag) 322 { 323 case 'php': 324 case 'javascript': 325 case 'htmlcomment': 326 case 'iframe': 327 case 'style': 328 _this.GetDialog('Source').Show(origTag); 329 break; 330 } 331 } 332 }); 333 334 if (this.dom.form) 335 { 336 BX.bind(this.dom.form, 'submit', BX.proxy(this.OnSubmit, this)); 337 338 // Autosave 339 if (this.config.initAutosave !== false) 340 { 341 setTimeout(function() 342 { 343 if (_this.dom.form.BXAUTOSAVE) 344 { 345 _this.InitAutosaveHandlers(); 346 } 347 }, 100); 348 } 349 } 350 351 BX.addCustomEvent(this, "OnSpecialcharInserted", function(entity) 352 { 353 var 354 lastChars = _this.GetLastSpecialchars(), 355 exInd = BX.util.array_search(entity, lastChars); 356 357 if (exInd !== -1) 358 { 359 lastChars = BX.util.deleteFromArray(lastChars, exInd); 360 lastChars.unshift(entity); 361 } 362 else 363 { 364 lastChars.unshift(entity); 365 lastChars.pop(); 366 } 367 368 _this.config.lastSpecialchars = lastChars; 369 _this.SaveOption('specialchars', lastChars.join('|')); 370 }); 371 372 this.parentDialog = BX.WindowManager.Get(); 373 if (this.parentDialog && this.parentDialog.DIV && BX.isNodeInDom(this.parentDialog.DIV) 374 && 375 BX.findParent(this.dom.cont, function(n){return n == _this.parentDialog.DIV;})) 376 { 377 BX.addCustomEvent(this.parentDialog, 'onWindowResizeExt', function(){_this.ResizeSceleton();}); 378 } 379 380 if (this.config.autoResize) 381 { 382 BX.addCustomEvent(this, "OnIframeKeyup", BX.proxy(this.AutoResizeSceleton, this)); 383 BX.addCustomEvent(this, "OnInsertHtml", BX.proxy(this.AutoResizeSceleton, this)); 384 BX.addCustomEvent(this, "OnIframeSetValue", BX.proxy(this.AutoResizeSceleton, this)); 385 BX.addCustomEvent(this, "OnFocus", BX.proxy(this.AutoResizeSceleton, this)); 386 BX.addCustomEvent(this, "OnSetViewAfter", BX.proxy(this.AutoResizeSceleton, this)); 387 } 388 389 BX.addCustomEvent(this, "OnIframeKeyup", BX.proxy(this.CheckBodyHeight, this)); 390 }, 391 392 BuildSceleton: function() 393 { 394 var result = false;
395 // Main container contain all editor parts 396 this.dom.cont = BX('bx-html-editor-' + this.id); 397 if (this.dom.cont && BX.isNodeInDom(this.dom.cont)) 398 { 399 this.dom.toolbarCont = BX('bx-html-editor-tlbr-cnt-' + this.id); 400 this.dom.toolbar = BX('bx-html-editor-tlbr-' + this.id); 401 this.dom.areaCont = BX('bx-html-editor-area-cnt-' + this.id); 402 403 // Container for content editable iframe 404 this.dom.iframeCont = BX('bx-html-editor-iframe-cnt-' + this.id); 405 this.dom.textareaCont = BX('bx-html-editor-ta-cnt-' + this.id); 406 407 this.dom.resizerOverlay = BX('bx-html-editor-res-over-' + this.id); 408 this.dom.splitResizer = BX('bx-html-editor-split-resizer-' + this.id); 409 this.dom.splitResizer.style.display = 'none'; 410 this.dom.splitResizer.className = this.config.splitVertical ? "bxhtmled-split-resizer-ver" : "bxhtmled-split-resizer-hor"; 411 BX.bind(this.dom.splitResizer, 'mousedown', BX.proxy(this.StartSplitResize, this)); 412 413 // Taskbars 414 this.dom.taskbarCont = BX('bx-html-editor-tskbr-cnt-' + this.id); 415 416 // Node navigation at the bottom 417 this.dom.navCont = BX('bx-html-editor-nav-cnt-' + this.id); 418 419 this.dom.fileDialogsWrap = BX('bx-html-editor-file-dialogs-' + this.id) 420 421 result = true; 422 } 423 return result; 424 }, 425 426 ResizeSceleton: function(width, height, params) 427 { 428 var _this = this; 429 if (this.expanded) 430 { 431 var innerSize = BX.GetWindowInnerSize(document); 432 width = this.config.width = innerSize.innerWidth; 433 height = this.config.height = innerSize.innerHeight; 434 } 435 436 if (!width) 437 { 438 width = this.config.width; 439 } 440 if (!height) 441 { 442 height = this.config.height; 443 } 444 445 this.dom.cont.style.minWidth = this.MIN_WIDTH + 'px'; 446 this.dom.cont.style.minHeight = this.MIN_HEIGHT + 'px'; 447 448 var styleW, styleH; 449 450 if (this.resizeTimeout) 451 { 452 clearTimeout(this.resizeTimeout); 453 this.resizeTimeout = null; 454 } 455 456 if (width.toString().indexOf('%') !== -1) 457 { 458 styleW = width; 459 width = this.dom.cont.offsetWidth; 460 461 if (!width) 462 { 463 this.resizeTimeout = setTimeout(function(){_this.ResizeSceleton(width, height, params);}, 500); 464 return; 465 } 466 } 467 else 468 { 469 if (width < this.MIN_WIDTH) 470 { 471 width = this.MIN_WIDTH; 472 } 473 styleW = width + 'px'; 474 } 475 476 this.dom.cont.style.width = styleW; 477 this.dom.toolbarCont.style.width = styleW; 478 479 if (height.toString().indexOf('%') !== -1) 480 { 481 styleH = height; 482 height = this.dom.cont.offsetHeight; 483 } 484 else 485 { 486 if (height < this.MIN_HEIGHT) 487 { 488 height = this.MIN_HEIGHT; 489 } 490 styleH = height + 'px'; 491 } 492 this.dom.cont.style.height = styleH; 493 494 var 495 w = Math.max(width, this.MIN_WIDTH), 496 h = Math.max(height, this.MIN_HEIGHT), 497 toolbarHeight = this.toolbar.GetHeight(), 498 taskbarWidth = this.showTaskbars ? (this.taskbarManager.GetWidth(true, w * 0.8)) : 0, 499 areaH = h - toolbarHeight - (this.config.showNodeNavi && this.nodeNavi ? this.nodeNavi.GetHeight() : 0), 500 areaW = w - taskbarWidth; 501 502 this.dom.areaCont.style.top = toolbarHeight ? toolbarHeight + 'px' : 0; 503 504 // Area 505 this.SetAreaContSize(areaW, areaH, params); 506 507 // Taskbars 508 this.dom.taskbarCont.style.height = areaH + 'px'; 509 this.dom.taskbarCont.style.width = taskbarWidth + 'px'; 510 if (this.showTaskbars) 511 { 512 this.taskbarManager.Resize(taskbarWidth, areaH); 513 } 514 this.toolbar.AdaptControls(width); 515 516 this.On('OnEditorResizedAfter', [{ 517 width: width, 518 height: height 519 }]); 520 }, 521 522 CheckBodyHeight: function() 523 { 524 if (this.iframeView.IsShown()) 525 { 526 var 527 padding = 15, 528 minHeight, 529 doc = this.GetIframeDoc(); 530 531 if (doc && doc.body) 532 { 533 minHeight = doc.body.parentNode.offsetHeight - padding * 2; 534 535 if (minHeight <= 20) 536 { 537 setTimeout(BX.proxy(this.CheckBodyHeight, this), 300); 538 } 539 else if (this.config.autoResize || minHeight > doc.body.offsetHeight) 540 { 541 setTimeout(function() 542 { 543 minHeight = doc.body.parentNode.offsetHeight - padding * 2; 544 doc.body.style.minHeight = minHeight + 'px'; 545 }, 300); 546 } 547 } 548 } 549 }, 550 551 GetSceletonSize: function() 552 { 553 return { 554 width: this.dom.cont.offsetWidth, 555 height: this.dom.cont.offsetHeight 556 }; 557 }, 558 559 AutoResizeSceleton: function() 560 { 561 if (this.expanded || !this.IsShown() || this.iframeView.IsEmpty()) 562 return; 563 564 var
565 maxHeight = parseInt(this.config.autoResizeMaxHeight || 0), 566 minHeight = parseInt(this.config.autoResizeMinHeight || 50), 567 areaHeight = this.dom.areaCont.offsetHeight, 568 newHeight, 569 _this = this; 570 571 if (this.autoResizeTimeout) 572 { 573 clearTimeout(this.autoResizeTimeout); 574 } 575 576 this.autoResizeTimeout = setTimeout(function() 577 { 578 newHeight = _this.GetHeightByContent(); 579 if (newHeight > areaHeight) 580 { 581 if (BX.browser.IsIOS()) 582 { 583 maxHeight = Infinity; 584 } 585 else if (!maxHeight || maxHeight < 10) 586 { 587 maxHeight = Math.round(BX.GetWindowInnerSize().innerHeight * 0.9); // 90% from screen height 588 } 589 590 newHeight = Math.min(newHeight, maxHeight); 591 newHeight = Math.max(newHeight, minHeight); 592 _this.SmoothResizeSceleton(newHeight); 593 } 594 }, 300); 595 }, 596 597 GetHeightByContent: function() 598 { 599 var 600 heightOffset = parseInt(this.config.autoResizeOffset || 80), 601 contentHeight; 602 603 if (this.GetViewMode() == 'wysiwyg') 604 { 605 var 606 body = this.GetIframeDoc().body, 607 node = body.lastChild, 608 offsetTop = false; 609 610 contentHeight = body.offsetHeight; 611 while (true) 612 { 613 if (!node) 614 { 615 break; 616 } 617 if (node.offsetTop) 618 { 619 offsetTop = node.offsetTop + (node.offsetHeight || 0); 620 contentHeight = offsetTop + heightOffset; 621 break; 622 } 623 else 624 { 625 node = node.previousSibling; 626 } 627 } 628 629 var oEdSize = BX.GetWindowSize(this.GetIframeDoc()); 630 if (oEdSize.scrollHeight - oEdSize.innerHeight > 5) 631 { 632 contentHeight = Math.max(oEdSize.scrollHeight + heightOffset, contentHeight); 633 } 634 } 635 else 636 { 637 contentHeight = (this.textareaView.element.value.split("\n").length /* rows count*/ + 5) * 17; 638 } 639 640 return contentHeight; 641 }, 642 643 SmoothResizeSceleton: function(height) 644 { 645 this.On('AutoResizeStarted'); 646 647 var 648 _this = this, 649 size = this.GetSceletonSize(), 650 curHeight = size.height, 651 count = 0, 652 bRise = height > curHeight, 653 timeInt = 50, 654 dy = 5; 655 656 if (!bRise) 657 return; 658 659 if (this.smoothResizeInt) 660 { 661 clearInterval(this.smoothResizeInt); 662 } 663 664 this.smoothResizeInt = setInterval(function() 665 { 666 curHeight += Math.round(dy * count); 667 var finished = curHeight >= height; 668 if (curHeight > height) 669 { 670 clearInterval(_this.smoothResizeInt); 671 if (curHeight > height) 672 { 673 curHeight = height; 674 } 675 } 676 _this.config.height = curHeight; 677 _this.ResizeSceleton(); 678 if (finished) 679 { 680 _this.On('AutoResizeFinished'); 681 } 682 count++; 683 }, 684 timeInt 685 ); 686 }, 687 688 SetAreaContSize: function(areaW, areaH, params) 689 { 690 areaW += 2; 691 this.dom.areaCont.style.width = areaW + 'px'; 692 this.dom.areaCont.style.height = areaH + 'px'; 693 694 if (params && params.areaContTop) 695 { 696 this.dom.areaCont.style.top = params.areaContTop + 'px'; 697 } 698 699 var WIDTH_DIF = 3; 700 701 if (this.currentViewName == 'split') 702 { 703 function getValue(value, min, max) 704 { 705 if (value < min) 706 { 707 value = min; 708 } 709 if (value > max) 710 { 711 value = max; 712 } 713 return value; 714 } 715 716 var MIN_SPLITTER_PAD = 10, delta, a, b; 717 718 if (this.config.splitVertical == true) 719 { 720 delta = params && params.deltaX ? params.deltaX : 0; 721 a = getValue((areaW * this.config.splitRatio / (1 + this.config.splitRatio)) - delta, MIN_SPLITTER_PAD, areaW - MIN_SPLITTER_PAD); 722 b = areaW - a; 723 724 this.dom.iframeCont.style.width = (a - WIDTH_DIF) + 'px'; 725 this.dom.iframeCont.style.height = areaH + 'px'; 726 this.dom.iframeCont.style.top = 0; 727 this.dom.iframeCont.style.left = 0; 728 729 this.dom.textareaCont.style.width = (b - WIDTH_DIF) + 'px'; 730 this.dom.textareaCont.style.height = areaH + 'px'; 731 this.dom.textareaCont.style.top = 0; 732 this.dom.textareaCont.style.left = a + 'px'; 733 734 this.dom.splitResizer.className = 'bxhtmled-split-resizer-ver'; 735 this.dom.splitResizer.style.top = 0; 736 this.dom.splitResizer.style.left = (a - 3) + 'px'; 737 738 this.dom.textareaCont.style.height = areaH + 'px'; 739 } 740 else 741 { 742 delta = params && params.deltaY ? params.deltaY : 0; 743 a = getValue((areaH * this.config.splitRatio / (1 + this.config.splitRatio)) - delta, MIN_SPLITTER_PAD, areaH - MIN_SPLITTER_PAD); 744 b = areaH - a; 745 746 this.dom.iframeCont.style.width = (areaW - WIDTH_DIF) + 'px'; 747 this.dom.iframeCont.style.height = a + 'px'; 748 this.dom.iframeCont.style.top = 0; 749 this.dom.iframeCont.style.left = 0; 750 751 this.dom.textareaCont.style.width = (areaW - WIDTH_DIF) + 'px'; 752 this.dom.textareaCont.style.height = b + 'px'; 753 this.dom.textareaCont.style.top = a + 'px'; 754 this.dom.textareaCont.style.left = 0; 755 756 this.dom.splitResizer.className = 'bxhtmled-split-resizer-hor'; 757 this.dom.splitResizer.style.top = (a - 3) + 'px'; 758 this.dom.splitResizer.style.left = 0; 759 } 760 761 if (params && params.updateSplitRatio) 762 { 763 this.config.splitRatio = a / b; 764 this.SaveOption('split_ratio', this.config.splitRatio); 765 } 766 } 767 else 768 {
769 // Set size and position of iframe container to the normal state 770 this.dom.iframeCont.style.width = (areaW - WIDTH_DIF) + 'px'; 771 this.dom.iframeCont.style.height = areaH + 'px'; 772 this.dom.iframeCont.style.top = 0; 773 this.dom.iframeCont.style.left = 0; 774 775 // Set size and position of textarea container to the normal state 776 this.dom.textareaCont.style.width = (areaW - WIDTH_DIF) + 'px'; 777 this.dom.textareaCont.style.height = areaH + 'px'; 778 this.dom.textareaCont.style.top = 0; 779 this.dom.textareaCont.style.left = 0; 780 } 781 }, 782 783 BuildToolbar: function() 784 { 785 this.toolbar = new BXHtmlEditor.Toolbar(this, this.GetTopControls()); 786 }, 787 788 GetTopControls: function() 789 { 790 this.On("GetTopButtons", [window.BXHtmlEditor.Controls]); 791 return window.BXHtmlEditor.Controls; 792 }, 793 794 CreateIframeSandBox: function() 795 { 796 return new Sandbox( 797 // Callback 798 BX.proxy(this.OnCreateIframe, this), 799 // Config 800 { 801 editor: this, 802 cont: this.dom.iframeCont 803 } 804 ); 805 }, 806 807 OnCreateIframe: function() 808 { 809 if (!document.body.contains(this.dom.iframeCont)) 810 { 811 //do not create frame if DOM doesn't contain editor's html structure (autocomposite). 812 return; 813 } 814 815 this.On('OnCreateIframeBefore'); 816 this.iframeView.OnCreateIframe(); 817 this.selection = new BXEditorSelection(this); 818 this.action = new BXEditorActions(this); 819 this.config.content = this.dom.pValueInput.value; 820 this.SetContent(this.config.content, true); 821 this.undoManager = new BXEditorUndoManager(this); 822 this.action.Exec("styleWithCSS", false, true); 823 this.iframeView.InitAutoLinking(); 824 // Simulate html5 placeholder attribute on contentEditable element 825// var placeholderText = typeof(this.config.placeholder) === "string" 826// ? this.config.placeholder 827// : this.textarea.element.getAttribute("placeholder"); 828// if (placeholderText) { 829// dom.simulatePlaceholder(this.parent, this, placeholderText); 830// } 831 832 if (this.config.view != 'wysiwyg') 833 { 834 var i, changeViewBut = false, switchCodeButton = false, controls = this.toolbar.GetControlsMap(); 835 836 // Mantis: 72063 837 if (this.config.view == 'split') 838 { 839 for (i = 0; i < controls.length; i++) 840 { 841 if (controls[i] && controls[i].id == 'ChangeView') 842 { 843 changeViewBut = true; 844 break; 845 } 846 } 847 if (!changeViewBut) 848 this.config.view = 'wysiwyg'; 849 } 850 851 // Mantis: 80663 852 if (this.config.view != 'wysiwyg') 853 { 854 for (i = 0; i < controls.length; i++) 855 {
856 if (controls[i] && (controls[i].id == 'BbCode' || controls[i].id == 'ChangeView')) 857 { 858 switchCodeButton = true; 859 break; 860 } 861 } 862 if (!switchCodeButton) 863 this.config.view = 'wysiwyg'; 864 } 865 } 866 867 this.SetView(this.config.view, false); 868 if (this.config.setFocusAfterShow !== false) 869 { 870 this.Focus(false); 871 } 872 873 this.sandbox.inited = true; 874 this.On('OnCreateIframeAfter', [this]); 875 }, 876 877 GetDialog: function(dialogName, params) 878 { 879 if (!this.dialogs[dialogName] && window.BXHtmlEditor.dialogs[dialogName]) 880 this.dialogs[dialogName] = new window.BXHtmlEditor.dialogs[dialogName](this, params); 881 882 return this.dialogs[dialogName] || null; 883 }, 884 885 Show: function() 886 { 887 this.dom.cont.style.display = ''; 888 }, 889 890 Hide: function() 891 { 892 this.dom.cont.style.display = 'none'; 893 }, 894 895 IsShown: function() 896 { 897 return this.inited && this.dom.cont.style.display !== 'none' && this.dom.cont.offsetWidth > 0 && BX.isNodeInDom(this.dom.cont); 898 }, 899 900 SetView: function(view, saveValue) 901 { 902 this.On('OnSetViewBefore'); 903 if (view == 'split' && this.bbCode) 904 view = 'wysiwyg'; 905 906 if (this.currentViewName != view) 907 { 908 if (view == 'wysiwyg') 909 { 910 this.iframeView.Show(); 911 this.textareaView.Hide(); 912 this.dom.splitResizer.style.display = 'none'; 913 this.CheckBodyHeight(); 914 } 915 else if (view == 'code') 916 { 917 this.iframeView.Hide(); 918 this.textareaView.Show(); 919 this.CheckCurrentStatus(false); 920 this.dom.splitResizer.style.display = 'none'; 921 } 922 else if (view == 'split') 923 { 924 this.textareaView.Show(); 925 this.iframeView.Show(); 926 this.dom.splitResizer.style.display = ''; 927 this.CheckBodyHeight(); 928 } 929 930 this.currentViewName = view; 931 } 932 933 if (saveValue !== false) 934 { 935 this.SaveOption('view', view); 936 } 937 938 this.ResizeSceleton(); 939 this.On('OnSetViewAfter'); 940 }, 941 942 GetViewMode: function() 943 { 944 return this.currentViewName; 945 }, 946 947 SetContent: function(value, bParse) 948 { 949 this.On('OnSetContentBefore'); 950 if (this.bbCode) 951 { 952 var htmlFromBbCode = this.bbParser.Parse(value); 953 this.iframeView.SetValue(htmlFromBbCode, bParse); 954 } 955 else 956 { 957 this.iframeView.SetValue(value, bParse); 958 } 959 960 this.textareaView.SetValue(value, false); 961 962 this.On('OnSetContentAfter'); 963 }, 964 965 Focus: function(setToEnd) 966 { 967 if (this.currentViewName == 'wysiwyg') 968 { 969 this.iframeView.Focus(setToEnd); 970 } 971 else if (this.currentViewName == 'code') 972 { 973 this.textareaView.Focus(setToEnd); 974 } 975 else if (this.currentViewName == 'split') 976 { 977 if (this.synchro.GetSplitMode() == 'wysiwyg') 978 { 979 this.iframeView.Focus(setToEnd); 980 } 981 else 982 { 983 this.textareaView.Focus(setToEnd); 984 } 985 } 986 this.On('OnFocus'); 987 return this; 988 }, 989 990 SaveContent: function() 991 { 992 if (this.currentViewName == 'wysiwyg' || 993 (this.currentViewName == 'split' && this.synchro.GetSplitMode() == 'wysiwyg')) 994 { 995 this.synchro.lastIframeValue = ''; 996 this.synchro.FromIframeToTextarea(true, true); 997 } 998 else 999 { 1000 this.textareaView.SaveValue(); 1001 } 1002 }, 1003 1004 GetContent: function() 1005 { 1006 this.SaveContent(); 1007 return this.textareaView.GetValue(); 1008 }, 1009 1010 IsExpanded: function() 1011 { 1012 return this.expanded; 1013 }, 1014 1015 Expand: function(bExpand) 1016 { 1017 if (bExpand == undefined) 1018 { 1019 bExpand = !this.expanded; 1020 } 1021 1022 var 1023 _this = this, 1024 innerSize = BX.GetWindowInnerSize(document), 1025 startWidth, startHeight, startTop, startLeft, 1026 endWidth, endHeight, endTop, endLeft; 1027 1028 if (bExpand) 1029 { 1030 var 1031 scrollPos = BX.GetWindowScrollPos(document), 1032 pos = BX.pos(this.dom.cont); 1033 1034 startWidth = this.dom.cont.offsetWidth; 1035 startHeight = this.dom.cont.offsetHeight; 1036 startTop = pos.top; 1037 startLeft = pos.left; 1038 endWidth = innerSize.innerWidth; 1039 endHeight = innerSize.innerHeight; 1040 endTop = scrollPos.scrollTop; 1041 endLeft = scrollPos.scrollLeft; 1042 1043 this.savedSize = { 1044 width: startWidth, 1045 height: startHeight, 1046 top: startTop, 1047 left: startLeft, 1048 scrollLeft: scrollPos.scrollLeft, 1049 scrollTop: scrollPos.scrollTop, 1050 configWidth: this.config.width, 1051 configHeight: this.config.height 1052 }; 1053 1054 this.config.width = endWidth; 1055 this.config.height = endHeight; 1056 1057 BX.addClass(this.dom.cont, 'bx-html-editor-absolute'); 1058 this._bodyOverflow = document.body.style.overflow; 1059 document.body.style.overflow = "hidden"; 1060 1061 // Create dummie div 1062 this.dummieDiv = BX.create('DIV'); 1063 this.dummieDiv.style.width = startWidth + 'px'; 1064 this.dummieDiv.style.height = startHeight + 'px'; 1065 this.dom.cont.parentNode.insertBefore(this.dummieDiv, this.dom.cont); 1066 document.body.appendChild(this.dom.cont); 1067 1068 BX.addCustomEvent(this, 'OnIframeKeydown', BX.proxy(this.CheckEscCollapse, this)); 1069 BX.bind(document.body, "keydown", BX.proxy(this.CheckEscCollapse, this)); 1070 BX.bind(window, "scroll", BX.proxy(this.PreventScroll, this)); 1071 1072 if (BX.ZIndexManager) 1073 { 1074 BX.ZIndexManager.register(this.dom.cont); 1075 BX.ZIndexManager.addStack(this.dom.cont); 1076 BX.ZIndexManager.bringToFront(this.dom.cont); 1077 } 1078 } 1079 else 1080 {
1081 startWidth = this.dom.cont.offsetWidth; 1082 startHeight = this.dom.cont.offsetHeight; 1083 startTop = this.savedSize.scrollTop; 1084 startLeft = this.savedSize.scrollLeft; 1085 endWidth = this.savedSize.width; 1086 endHeight = this.savedSize.height; 1087 endTop = this.savedSize.top; 1088 endLeft = this.savedSize.left; 1089 1090 BX.removeCustomEvent(this, 'OnIframeKeydown', BX.proxy(this.CheckEscCollapse, this)); 1091 BX.unbind(document.body, "keydown", BX.proxy(this.CheckEscCollapse, this)); 1092 BX.unbind(window, "scroll", BX.proxy(this.PreventScroll, this)); 1093 } 1094 1095 this.dom.cont.style.width = startWidth + 'px'; 1096 this.dom.cont.style.height = startHeight + 'px'; 1097 this.dom.cont.style.top = startTop + 'px'; 1098 this.dom.cont.style.left = startLeft + 'px'; 1099 1100 var content = this.content; 1101 1102 this.expandAnimation = new BX.easing({ 1103 duration : 300, 1104 start : { 1105 height: startHeight, 1106 width: startWidth, 1107 top: startTop, 1108 left: startLeft 1109 }, 1110 finish : { 1111 height: endHeight, 1112 width: endWidth, 1113 top: endTop, 1114 left: endLeft 1115 }, 1116 transition : BX.easing.makeEaseOut(BX.easing.transitions.quart), 1117 step : function(state) 1118 { 1119 _this.dom.cont.style.width = state.width + 'px'; 1120 _this.dom.cont.style.height = state.height + 'px'; 1121 _this.dom.cont.style.top = state.top + 'px'; 1122 _this.dom.cont.style.left = state.left + 'px'; 1123 _this.ResizeSceleton(state.width.toString(), state.height.toString()); 1124 }, 1125 complete : function() 1126 { 1127 _this.expandAnimation = null; 1128 if (!bExpand) 1129 { 1130 _this.util.ReplaceNode(_this.dummieDiv, _this.dom.cont); 1131 _this.dummieDiv = null; 1132 _this.dom.cont.style.width = ''; 1133 _this.dom.cont.style.height = ''; 1134 _this.dom.cont.style.top = ''; 1135 _this.dom.cont.style.left = ''; 1136 BX.removeClass(_this.dom.cont, 'bx-html-editor-absolute'); 1137 if (BX.ZIndexManager) 1138 { 1139 BX.ZIndexManager.unregister(_this.dom.cont); 1140 } 1141 _this.dom.cont.style.removeProperty('z-index'); 1142 document.body.style.overflow = _this._bodyOverflow; 1143 _this.config.width = _this.savedSize.configWidth; 1144 _this.config.height = _this.savedSize.configHeight; 1145 _this.ResizeSceleton(); 1146 } 1147 setTimeout(function(){_this.CheckAndReInit(content)}, 10); 1148 } 1149 }); 1150 1151 this.expandAnimation.animate(); 1152 this.expanded = bExpand; 1153 }, 1154 1155 CheckEscCollapse: function(e, keyCode, command, selectedNode) 1156 { 1157 if (!keyCode) 1158 { 1159 keyCode = e.keyCode; 1160 } 1161 1162 if ( 1163 this.IsExpanded() && 1164 keyCode == this.KEY_CODES['escape'] && 1165 !this.IsPopupsOpened() 1166 ) 1167 { 1168 this.Expand(false); 1169 return BX.PreventDefault(e); 1170 } 1171 }, 1172 1173 PreventScroll: function(e) 1174 { 1175 window.scrollTo(this.savedSize.scrollLeft, this.savedSize.scrollTop); 1176 return BX.PreventDefault(e); 1177 }, 1178 1179 IsPopupsOpened: function() 1180 { 1181 return !!(this.dialogShown || 1182 this.popupShown || 1183 this.contextMenuShown || 1184 this.overlay.bShown); 1185 }, 1186 1187 ReInitIframe: function() 1188 { 1189 this.sandbox.InitIframe(); 1190 this.iframeView.OnCreateIframe(); 1191 this.synchro.StopSync(); 1192 this.synchro.lastTextareaValue = ''; 1193 this.synchro.FromTextareaToIframe(true); 1194 this.synchro.StartSync(); 1195 this.iframeView.ReInit(); 1196 this.selection.lastCheckedRange = null; 1197 if (this.config.setFocusAfterShow !== false) 1198 { 1199 this.Focus(); 1200 } 1201 }, 1202 1203 CheckAndReInit: function(content) 1204 { 1205 if (this.sandbox.inited) 1206 { 1207 var win = this.sandbox.GetWindow(); 1208 if (win) 1209 { 1210 var doc = this.sandbox.GetDocument(); 1211 if (doc !== this.iframeView.document || !doc.head || doc.head.innerHTML == '') 1212 { 1213 this.iframeView.document = doc; 1214 this.iframeView.element = doc.body; 1215 this.ReInitIframe(); 1216 } 1217 else if(doc.body) 1218 { 1219 doc.body.style.minHeight = ''; 1220 } 1221 } 1222 else 1223 { 1224 throw new Error("HtmlEditor: CheckAndReInit error iframe isn't in the DOM"); 1225 } 1226 } 1227 1228 if (content !== undefined) 1229 { 1230 this.SetContent(content, true); 1231 this.CheckBodyHeight(); 1232 } 1233 }, 1234 1235 Disable: function() 1236 { 1237 }, 1238 1239 Enable: function() 1240 { 1241 }, 1242 1243 CheckConfig: function(config) 1244 { 1245 if (config.content === undefined) 1246 { 1247 config.content = ''; 1248 } 1249 1250 return config; 1251 }, 1252 1253 GetInnerHtml: function(el) 1254 { 1255 var 1256 TILDA = "%7E", 1257 AMP = "&", 1258 innerHTML = el.innerHTML; 1259 1260 if (innerHTML.indexOf(AMP) !== -1 || innerHTML.indexOf(TILDA) !== -1) 1261 { 1262 innerHTML = innerHTML.replace(/(?:href|src)\s*=\s*("|')([\s\S]*?)(\1)/ig, function(s) 1263 { 1264 // See https://bugzilla.mozilla.org/show_bug.cgi?id=664398 1265 s = s.replace(/%7E/ig, '~'); 1266 s = s.replace(/&/ig, '&'); 1267 return s; 1268 }); 1269 } 1270 1271 innerHTML = innerHTML.replace(/(?:title|alt)\s*=\s*("|')([\s\S]*?)(\1)/ig, function(s) 1272 { 1273 s = s.replace(/</g, '<'); 1274 s = s.replace(/>/g, '>'); 1275 return s; 1276 }); 1277 1278 if (this.bbCode) 1279 {
1280 innerHTML = innerHTML.replace(/[\s\n\r]*?<!--[\s\S]*?-->[\s\n\r]*?/ig, ""); 1281 } 1282 1283 return innerHTML; 1284 }, 1285 1286 InitUtil: function() 1287 { 1288 var _this = this; 1289 1290 this.util = {}; 1291 if ("textContent" in document.documentElement) 1292 { 1293 this.util.SetTextContent = function(element, text){element.textContent = text;}; 1294 this.util.GetTextContent = function(element){return element.textContent;}; 1295 } 1296 else if ("innerText" in document.documentElement) 1297 { 1298 this.util.SetTextContent = function(element, text){element.innerText = text;}; 1299 this.util.GetTextContent = function(element){return element.innerText;}; 1300 } 1301 else 1302 { 1303 this.util.SetTextContent = function(element, text){element.nodeValue = text;}; 1304 this.util.GetTextContent = function(element){return element.nodeValue;}; 1305 } 1306 1307 this.util.AutoCloseTagSupported = function() 1308 { 1309 var 1310 element = document.createElement("div"), 1311 result, 1312 innerHTML; 1313 1314 element.innerHTML = "<p><div></div>"; 1315 innerHTML = element.innerHTML.toLowerCase(); 1316 result = innerHTML === "<p></p><div></div>" || innerHTML === "<p><div></div></p>"; 1317 1318 _this.util.AutoCloseTagSupported = function(){return result;}; 1319 return result; 1320 }; 1321 1322 this.util.FirstLetterSupported = function() 1323 { 1324 var result = !BX.browser.IsChrome() && !BX.browser.IsSafari(); 1325 _this.util.FirstLetterSupported = function(){return result;}; 1326 return result; 1327 }; 1328 1329 // IE sometimes gives wrong results for hasAttribute/getAttribute 1330 this.util.CheckGetAttributeTruth = function() 1331 { 1332 var 1333 td = document.createElement("td"), 1334 result = td.getAttribute("rowspan") != "1"; 1335 1336 _this.util.CheckGetAttributeTruth = function(){return result;}; 1337 return result; 1338 }; 1339 1340 // Check if browser supports HTML5 1341 this.util.CheckHTML5Support = function(doc) 1342 { 1343 if (!doc) 1344 { 1345 doc = document; 1346 } 1347 1348 var 1349 result = false, 1350 html = "<article>bitrix</article>", 1351 el = doc.createElement("div"); 1352 1353 el.innerHTML = html; 1354 result = el.innerHTML.toLowerCase() === html; 1355 1356 _this.util.CheckHTML5Support = function(){return result;}; 1357 return result; 1358 }; 1359 1360 // Check if browser supports HTML5 (checks all tags) 1361 this.util.CheckHTML5FullSupport = function(doc) 1362 { 1363 if (!doc) 1364 { 1365 doc = document; 1366 } 1367 1368 var 1369 html, 1370 tags = _this.GetHTML5Tags(), 1371 result = false, 1372 el = doc.createElement("div"); 1373 1374 for (var i = 0; i < tags.length; i++) 1375 { 1376 html = "<" + tags[i] + ">bitrix</" + tags[i] + ">"; 1377 el.innerHTML = html; 1378 result = el.innerHTML.toLowerCase() === html; 1379 if (!result) 1380 { 1381 break; 1382 } 1383 } 1384 1385 _this.util.CheckHTML5FullSupport = function(){return result;}; 1386 return result; 1387 }; 1388 1389 this.util.GetEmptyImage = function() 1390 { 1391 return _this.EMPTY_IMAGE_SRC; 1392 }; 1393 1394 this.util.CheckDataTransferSupport = function() 1395 { 1396 var result = false;
1397 try { 1398 result = !!(window.Clipboard || window.DataTransfer).prototype.getData; 1399 } catch(e) {} 1400 _this.util.CheckDataTransferSupport = function(){return result;}; 1401 return result; 1402 }; 1403 1404 this.util.CheckImageSelectSupport = function() 1405 { 1406 var result = !(BX.browser.IsChrome() || BX.browser.IsSafari()); 1407 _this.util.CheckImageSelectSupport = function(){return result;}; 1408 return result; 1409 }; 1410 1411 this.util.CheckPreCursorSupport = function() 1412 { 1413 var result = !(BX.browser.IsIE() || BX.browser.IsIE10() || BX.browser.IsIE11()); 1414 _this.util.CheckPreCursorSupport = function(){return result;}; 1415 return result; 1416 }; 1417 1418 // Following hack is needed for firefox to make sure that image resize handles are properly removed 1419 this.util.Refresh = function(element) 1420 { 1421 if (element && element.parentNode) 1422 { 1423 var cn = "bx-editor-refresh"; 1424 1425 BX.addClass(element, cn); 1426 BX.removeClass(element, cn); 1427 1428 // Hack for firefox 1429 if (BX.browser.IsFirefox()) 1430 { 1431 try { 1432 var 1433 i, 1434 doc = element.ownerDocument, 1435 italics = doc.getElementsByTagName('I'), 1436 italicLen = italics.length; 1437 1438 for (i = 0; i < italics.length; i++) 1439 { 1440 italics[i].setAttribute('data-bx-orgig-i', true); 1441 } 1442 1443 doc.execCommand("italic", false, null); 1444 doc.execCommand("italic", false, null); 1445 1446 var italicsNew = doc.getElementsByTagName('I'); 1447 if (italicsNew.length !== italicLen) 1448 { 1449 for (i = 0; i < italicsNew.length; i++) 1450 { 1451 if (italicsNew[i].getAttribute('data-bx-orgig-i')) 1452 { 1453 italicsNew[i].removeAttribute('data-bx-orgig-i'); 1454 } 1455 else 1456 { 1457 _this.util.ReplaceWithOwnChildren(italicsNew[i]); 1458 } 1459 } 1460 } 1461 } catch(e) {} 1462 } 1463 } 1464 }; 1465 1466 this.util.addslashes = function(str) 1467 { 1468 str = str.replace(/\\/g,'\\\\'); 1469 str = str.replace(/"/g,'\\"'); 1470 return str; 1471 }; 1472 1473 this.util.stripslashes = function(str) 1474 { 1475 str = str.replace(/\\"/g,'"'); 1476 str = str.replace(/\\\\/g,'\\'); 1477 return str; 1478 }; 1479 1480 this.util.ReplaceNode = function(node, newNode) 1481 { 1482 node.parentNode.insertBefore(newNode, node); 1483 node.parentNode.removeChild(node); 1484 return newNode; 1485 }; 1486 1487 this.util.spaceUrlEncode = function(str) 1488 { 1489 str = str.replace(/ /g,'%20'); 1490 return str; 1491 }; 1492 1493 this.util.spaceUrlDecode = function(str) 1494 { 1495 str = str.replace(/%20/g,' '); 1496 return str; 1497 }; 1498 1499 // Fast way to check whether an element with a specific tag name is in the given document 1500 this.util.DocumentHasTag = function(doc, tag) 1501 { 1502 var 1503 LIVE_CACHE = {}, 1504 key = _this.id + ":" + tag, 1505 cacheEntry = LIVE_CACHE[key]; 1506 1507 if (!cacheEntry) 1508 cacheEntry = LIVE_CACHE[key] = doc.getElementsByTagName(tag); 1509 1510 return cacheEntry.length > 0; 1511 }; 1512 1513 this.util.IsSplitPoint = function(node, offset) 1514 { 1515 var res = offset > 0 && offset < node.childNodes.length; 1516 if (rangy.dom.isCharacterDataNode(node)) 1517 { 1518 if (offset == 0) 1519 res = !!node.previousSibling; 1520 else if (offset == node.length) 1521 res = !!node.nextSibling; 1522 else 1523 res = true; 1524 } 1525 return res; 1526 }; 1527 1528 this.util.SplitNodeAt = function(node, descendantNode, descendantOffset) 1529 { 1530 var newNode; 1531 if (rangy.dom.isCharacterDataNode(descendantNode)) 1532 { 1533 if (descendantOffset == 0) 1534 { 1535 descendantOffset = rangy.dom.getNodeIndex(descendantNode); 1536 descendantNode = descendantNode.parentNode; 1537 } 1538 else if (descendantOffset == descendantNode.length) 1539 { 1540 descendantOffset = rangy.dom.getNodeIndex(descendantNode) + 1; 1541 descendantNode = descendantNode.parentNode; 1542 } 1543 else 1544 { 1545 newNode = rangy.dom.splitDataNode(descendantNode, descendantOffset); 1546 } 1547 } 1548 1549 if (!newNode) 1550 { 1551 newNode = descendantNode.cloneNode(false); 1552 if (newNode.id) 1553 { 1554 newNode.removeAttribute("id"); 1555 } 1556 1557 var child; 1558 while ((child = descendantNode.childNodes[descendantOffset])) 1559 { 1560 newNode.appendChild(child); 1561 } 1562 1563 rangy.dom.insertAfter(newNode, descendantNode); 1564 } 1565 1566 if (descendantNode && descendantNode.nodeName == "BODY") 1567 { 1568 return newNode; 1569 } 1570 1571 return (descendantNode == node) ? newNode : _this.util.SplitNodeAt(node, newNode.parentNode, rangy.dom.getNodeIndex(newNode)); 1572 }; 1573 1574 this.util.ReplaceWithOwnChildren = function (el) 1575 { 1576 var parent = el.parentNode; 1577 while (el.firstChild) 1578 { 1579 parent.insertBefore(el.firstChild, el); 1580 } 1581 parent.removeChild(el); 1582 }; 1583 1584 this.util.IsBlockElement = function (node) 1585 { 1586 var styleDisplay = BX.style(node, 'display'); 1587 return styleDisplay && BX.type.isString(styleDisplay) && styleDisplay.toLowerCase() === "block"; 1588 }; 1589 1590 this.util.IsBlockNode = function (node) 1591 { 1592 return node && node.nodeType == 1 && BX.util.in_array(node.nodeName, _this.GetBlockTags()); 1593 }; 1594 1595 this.util.CopyAttributes = function(attributes, from, to) 1596 { 1597 if (from && to) 1598 { 1599 var 1600 attribute, 1601 i, 1602 length = attributes.length; 1603 1604 for (i = 0; i < length; i++) 1605 { 1606 attribute = attributes[i]; 1607 if (from[attribute]) 1608 to[attribute] = from[attribute]; 1609 } 1610 } 1611 }; 1612 1613 this.util.RenameNode = function(node, newNodeName) 1614 { 1615 var 1616 newNode = node.ownerDocument.createElement(newNodeName), 1617 firstChild; 1618 1619 while (firstChild = node.firstChild) 1620 newNode.appendChild(firstChild); 1621 1622 _this.util.CopyAttributes(["align", "className"], node, newNode); 1623 1624 if (node.style.cssText != '') 1625 { 1626 newNode.style.cssText = node.style.cssText; 1627 } 1628 1629 node.parentNode.replaceChild(newNode, node); 1630 return newNode; 1631 }; 1632 1633 this.util.GetInvisibleTextNode = function() 1634 { 1635 return _this.iframeView.document.createTextNode(_this.INVISIBLE_SPACE); 1636 }; 1637 1638 this.util.IsEmptyNode = function(node, bCheckNewLine, bCheckSpaces) 1639 { 1640 var res; 1641 if (node.nodeType == 3) 1642 { 1643 res = node.data === "" || node.data === _this.INVISIBLE_SPACE || (node.data === '\n' && bCheckNewLine); 1644 if (!res && bCheckSpaces && node.data.toString().match(/^[\s\n\r\t]+$/ig)) 1645 { 1646 res = true; 1647 } 1648 } 1649 else if(node.nodeType == 1) 1650 { 1651 res = node.innerHTML === "" || node.innerHTML === _this.INVISIBLE_SPACE; 1652 if (!res && bCheckSpaces && node.innerHTML.toString().match(/^[\s\n\r\t]+$/ig)) 1653 { 1654 res = true; 1655 } 1656 } 1657 return res; 1658 }; 1659 1660 var documentElement = document.documentElement; 1661 if ("textContent" in documentElement) 1662 { 1663 this.util.SetTextContent = function(node, text) 1664 { 1665 node.textContent = text; 1666 }; 1667 1668 this.util.GetTextContent = function(node) 1669 { 1670 return node.textContent; 1671 }; 1672 } 1673 else if ("innerText" in documentElement) 1674 { 1675 this.util.SetTextContent = function(node, text) 1676 { 1677 node.innerText = text; 1678 }; 1679 1680 this.util.GetTextContent = function(node) 1681 { 1682 return node.innerText; 1683 }; 1684 } 1685 else 1686 {
1687 this.util.SetTextContent = function(node, text) 1688 { 1689 node.nodeValue = text; 1690 }; 1691 1692 this.util.GetTextContent = function(node) 1693 { 1694 return node.nodeValue; 1695 }; 1696 } 1697 1698 this.util.GetTextContentEx = function(node) 1699 { 1700 var 1701 i, html, linkMap = [], 1702 clone = node.cloneNode(true), 1703 scripts = clone.getElementsByTagName('SCRIPT'), 1704 links = clone.getElementsByTagName('A'); 1705 1706 for (i = scripts.length - 1; i >= 0 ; i--) 1707 { 1708 BX.remove(scripts[i]); 1709 } 1710 1711 // mantis:64329, mantis:70550 1712 for (i = links.length - 1; i >= 0 ; i--) 1713 { 1714 var href = links[i].href; 1715 if (href.toLowerCase().indexOf('javascript:') !== -1) 1716 { 1717 _this.util.ReplaceNode(links[i], links[i].ownerDocument.createTextNode(_this.util.GetTextContent(links[i]))); 1718 } 1719 else 1720 { 1721 linkMap.push('<a href="' + links[i].href + '">' + _this.util.GetTextContent(links[i]) + '</a>'); 1722 _this.util.ReplaceNode(links[i], links[i].ownerDocument.createTextNode('#BX~TMP~LINK' + (linkMap.length - 1) + '#')); 1723 } 1724 } 1725 1726 html = _this.util.GetTextContent(clone); 1727 1728 if (linkMap.length > 0) 1729 { 1730 html = html.replace(/#BX~TMP~LINK(\d+)#/ig, function(s, num) 1731 { 1732 return linkMap[num] || ''; 1733 }); 1734 } 1735 return html; 1736 }; 1737 1738 this.util.RgbToHex = function(str) 1739 { 1740 if (!str) 1741 str = ''; 1742 1743 if (str.search("rgb") !== -1) 1744 { 1745 function hex(x) 1746 { 1747 return ("0" + parseInt(x).toString(16)).slice(-2); 1748 } 1749 1750 str = str.replace(/rgba\(0,\s*0,\s*0,\s*0\)/ig, 'transparent'); 1751 str = str.replace(/rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*([\d\.]{1,3}))?\)/ig, 1752 function(s, s1, s2, s3, s4) 1753 { 1754 return "#" + hex(s1) + hex(s2) + hex(s3); 1755 } 1756 ); 1757 } 1758 1759 return str; 1760 }; 1761 1762 this.util.CheckCss = function(node, arCss, bMatch) 1763 { 1764 var res = true, i; 1765 for (i in arCss) 1766 { 1767 if (arCss.hasOwnProperty(i)) 1768 { 1769 if (node.style[i] != '') 1770 { 1771 res = res && (bMatch ? node.style[i] == arCss[i] : true); 1772 } 1773 else 1774 { 1775 res = false; 1776 } 1777 } 1778 } 1779 return res; 1780 }; 1781 1782 this.util.SetCss = function(n, arCss) 1783 { 1784 if (n && arCss && typeof arCss == 'object') 1785 { 1786 for (var i in arCss) 1787 { 1788 if (arCss.hasOwnProperty(i)) 1789 { 1790 n.style[i] = arCss[i]; 1791 } 1792 } 1793 } 1794 }; 1795 1796 this.util.InsertAfter = function(node, precedingNode) 1797 { 1798 return rangy.dom.insertAfter(node, precedingNode); 1799 }; 1800 1801 this.util.GetNodeDomOffset = function(node) 1802 { 1803 var i = 0; 1804 while (node.parentNode && node.parentNode.nodeName !== 'BODY') 1805 { 1806 node = node.parentNode; 1807 i++; 1808 } 1809 return i; 1810 }; 1811 1812 this.util.CheckSurrogateNode = function(node) 1813 { 1814 return _this.phpParser.CheckParentSurrogate(node); 1815 }; 1816 1817 this.util.CheckSurrogateDd = function(node) 1818 { 1819 return _this.phpParser.CheckSurrogateDd(node); 1820 }; 1821 1822 this.util.GetPreviousNotEmptySibling = function(node) 1823 { 1824 var prev = node.previousSibling; 1825 while (prev && prev.nodeType == 3 && _this.util.IsEmptyNode(prev, true, true)) 1826 { 1827 prev = prev.previousSibling; 1828 } 1829 return prev; 1830 }; 1831 1832 this.util.GetNextNotEmptySibling = function(node) 1833 { 1834 var next = node.nextSibling; 1835 while (next && next.nodeType == 3 && _this.util.IsEmptyNode(next, true, true)) 1836 { 1837 next = next.nextSibling; 1838 } 1839 return next; 1840 }; 1841 1842 this.util.IsEmptyLi = function(li) 1843 { 1844 if (li && li.nodeName == 'LI') 1845 { 1846 return _this.util.IsEmptyNode(li, true, true) || li.innerHTML.toLowerCase() == '<br>'; 1847 } 1848 return false; 1849 }; 1850 1851 this.util.FindParentEx = function(obj, params, maxParent) 1852 { 1853 if (BX.checkNode && BX.checkNode(obj, params)) 1854 return obj; 1855 return BX.findParent(obj, params, maxParent); 1856 }; 1857 1858 this.util.IsEmptyObject = function(obj) 1859 { 1860 for (var i in obj) 1861 { 1862 if (obj.hasOwnProperty(i)) 1863 { 1864 return false; 1865 } 1866 } 1867 return true; 1868 }; 1869 1870 this.util.GetNextSibling = function(node) 1871 { 1872 var res = node.nextSibling; 1873 while (res && res.nodeType == 3 && res.nodeValue == '\ufeff' && res.nextSibling) 1874 { 1875 res = res.nextSibling; 1876 } 1877 return res || null; 1878 }; 1879 }, 1880 1881 Parse: function(content, bParseBxNodes, bFormat) 1882 { 1883 bParseBxNodes = !!bParseBxNodes; 1884 this.content = content; 1885 this.On("OnParse", [bParseBxNodes]); 1886 1887 if (bParseBxNodes) 1888 { 1889 this.content = this.parser.Parse(this.content, this.GetParseRules(), this.GetIframeDoc(), true, bParseBxNodes); 1890 if ((bFormat === true || this.textareaView.IsShown()) && !this.bbCode) 1891 { 1892 this.content = this.FormatHtml(this.content); 1893 } 1894 1895 this.content = this.phpParser.ParseBxNodes(this.content); 1896 } 1897 else 1898 {
1899 this.content = this.phpParser.ParsePhp(this.content); 1900 this.content = this.parser.Parse(this.content, this.GetParseRules(), this.GetIframeDoc(), true, bParseBxNodes); 1901 } 1902 1903 this.On("OnAfterParse", [bParseBxNodes]); 1904 1905 return this.content; 1906 }, 1907 1908 On: function(eventName, arEventParams) 1909 { 1910 BX.onCustomEvent(this, eventName, arEventParams || []); 1911 }, 1912 1913 GetIframeDoc: function() 1914 { 1915 if (!this.document) 1916 { 1917 this.document = this.sandbox.GetDocument(); 1918 BX.addCustomEvent(this, 'OnIframeReInit', BX.proxy(function(){this.document = this.sandbox.GetDocument();}, this)); 1919 } 1920 return this.document; 1921 }, 1922 1923 GetParseRules: function() 1924 { 1925 this.rules = __BXHtmlEditorParserRules; 1926 // Here we can make changes to this.rules 1927 this.On("OnGetParseRules"); 1928 var _this = this; 1929 this.GetParseRules = function(){return _this.rules;}; 1930 return this.rules; 1931 }, 1932 1933 GetHTML5Tags: function() 1934 { 1935 return this.HTML5_TAGS; 1936 }, 1937 1938 GetBlockTags: function() 1939 { 1940 return this.BLOCK_TAGS; 1941 }, 1942 1943 SetBxTag: function(el, params) 1944 { 1945 var id; 1946 if (params.id || el && el.id) 1947 id = params.id || el.id; 1948 1949 if (!id) 1950 { 1951 id = 'bxid' + Math.round(Math.random() * 1000000000); 1952 } 1953 else 1954 { 1955 if (this.bxTags[id]) 1956 { 1957 if (!params.tag) 1958 params.tag = this.bxTags[id].tag; 1959 } 1960 } 1961 1962 params.id = id; 1963 if (el) 1964 el.id = params.id; 1965 1966 this.bxTags[params.id] = params; 1967 return params.id; 1968 }, 1969 1970 GetBxTag: function(element) 1971 { 1972 var id; 1973 if (typeof element == 'object' && element && element.id) 1974 id = element.id; 1975 else 1976 id = element; 1977 1978 if (id) 1979 { 1980 if (typeof id != "string" && id.id) 1981 id = id.id; 1982 1983 if (id && id.length > 0 && this.bxTags[id] && this.bxTags[id].tag) 1984 { 1985 this.bxTags[id].tag = this.bxTags[id].tag.toLowerCase(); 1986 return this.bxTags[id]; 1987 } 1988 } 1989 1990 return {tag: false}; 1991 }, 1992 1993 OnMousedown: function(e) 1994 { 1995 var 1996 node = e.target || e.srcElement; 1997 1998 if (node && (node.getAttribute || node.parentNode)) 1999 { 2000 var 2001 _this = this, 2002 bxType = node.getAttribute('data-bx-type'); 2003 2004 if (!bxType) 2005 { 2006 node = BX.findParent(node, function(n) 2007 { 2008 return n == _this.dom.cont || (n.getAttribute && n.getAttribute('data-bx-type')); 2009 }, this.dom.cont); 2010 2011 bxType = (node && node.getAttribute) ? node.getAttribute('data-bx-type') : null; 2012 } 2013 2014 if (bxType == 'action') // Any type of button or element which runs action 2015 { 2016 return BX.PreventDefault(e); 2017 } 2018 } 2019 return true; 2020 }, 2021 2022 OnClick: function(e) 2023 { 2024 var 2025 target = e.target || e.srcElement, 2026 bxType = (target && target.getAttribute) ? target.getAttribute('data-bx-type') : false; 2027 2028 this.On("OnClickBefore", [{e: e, target: target, bxType: bxType}]); 2029 this.CheckCommand(target); 2030 }, 2031 2032 CheckCommand: function(node) 2033 { 2034 if (node && (node.getAttribute || node.parentNode)) 2035 { 2036 var 2037 _this = this, 2038 bxType = node.getAttribute('data-bx-type'); 2039 2040 if (!bxType) 2041 { 2042 node = BX.findParent(node, function(n) 2043 { 2044 return n == _this.dom.cont || (n.getAttribute && n.getAttribute('data-bx-type')); 2045 }, this.dom.cont); 2046 2047 bxType = (node && node.getAttribute) ? node.getAttribute('data-bx-type') : null; 2048 } 2049 2050 if (bxType == 'action') // Any type of button or element which runs action 2051 { 2052 var 2053 action = node.getAttribute('data-bx-action'), 2054 value = node.getAttribute('data-bx-value'); 2055 2056 if (this.action.IsSupported(action)) 2057 { 2058 this.action.Exec(action, value); 2059 } 2060 } 2061 } 2062 }, 2063 2064 SetSplitMode: function(vertical, saveValue) 2065 { 2066 this.config.splitVertical = !!vertical; 2067 2068 if (saveValue !== false) 2069 { 2070 this.SaveOption('split_vertical', this.config.splitVertical ? 1 : 0); 2071 } 2072 2073 this.SetView('split', saveValue); 2074 }, 2075 2076 GetSplitMode: function() 2077 { 2078 return this.config.splitVertical; 2079 }, 2080 2081 StartSplitResize: function(e) 2082 { 2083 this.dom.resizerOverlay.style.display = 'block'; 2084 var 2085 dX = 0, dY = 0, 2086 windowScroll = BX.GetWindowScrollPos(), 2087 startX = e.clientX + windowScroll.scrollLeft, 2088 startY = e.clientY + windowScroll.scrollTop, 2089 _this = this; 2090 2091 function moveResizer(e, bFinish) 2092 { 2093 var 2094 x = e.clientX + windowScroll.scrollLeft, 2095 y = e.clientY + windowScroll.scrollTop; 2096 2097 if(startX == x && startY == y) 2098 { 2099 return; 2100 } 2101 2102 dX = startX - x; 2103 dY = startY - y; 2104 2105 _this.ResizeSceleton(0, 0, {deltaX: dX, deltaY: dY, updateSplitRatio: bFinish}); 2106 } 2107 2108 function finishResizing(e) 2109 { 2110 moveResizer(e, true); 2111 BX.unbind(document, 'mousemove', moveResizer); 2112 BX.unbind(document, 'mouseup', finishResizing); 2113 _this.dom.resizerOverlay.style.display = 'none'; 2114 } 2115 2116 BX.bind(document, 'mousemove', moveResizer); 2117 BX.bind(document, 'mouseup', finishResizing); 2118 }, 2119 2120 Request: function(P) 2121 { 2122 if (!P.url) 2123 P.url = this.config.actionUrl; 2124 if (P.bIter !== false) 2125 P.bIter = true; 2126 2127 if (!P.postData && !P.getData) 2128 P.getData = this.GetReqData(); 2129 2130// var errorText; 2131// if (!P.errorText) 2132// errorText = false;
2133 2134 var reqId = P.getData ? P.getData.reqId : P.postData.reqId; 2135 2136 var _this = this, iter = 0; 2137 var handler = function(result) 2138 { 2139 function handleRes() 2140 { 2141 //_this.CloseWaitWindow(); 2142// var erInd = result.toLowerCase().indexOf('bx_event_calendar_action_error'); 2143// if (!result || result.length <= 0 || erInd != -1) 2144// { 2145// var errorText = ''; 2146// if (erInd >= 0) 2147// { 2148// var 2149// ind1 = erInd + 'BX_EVENT_CALENDAR_ACTION_ERROR:'.length, 2150// ind2 = result.indexOf('-->', ind1); 2151// errorText = result.substr(ind1, ind2 - ind1); 2152// } 2153// if (P.onerror && typeof P.onerror == 'function') 2154// P.onerror(); 2155// 2156// return _this.DisplayError(errorText || P.errorText || ''); 2157// } 2158 2159 var res = P.handler(_this.GetRequestRes(reqId), result); 2160 if(res === false && ++iter < 20 && P.bIter) 2161 setTimeout(handleRes, 5); 2162 else 2163 _this.ClearRequestRes(reqId); 2164 } 2165 setTimeout(handleRes, 50); 2166 }; 2167 //this.ShowWaitWindow(); 2168 2169 if (P.postData) 2170 BX.ajax.post(P.url, P.postData, handler); 2171 else 2172 BX.ajax.get(P.url, P.getData, handler); 2173 }, 2174 2175 GetRequestRes: function(key) 2176 { 2177 if (top.BXHtmlEditorAjaxResponse[key] != undefined) 2178 return top.BXHtmlEditorAjaxResponse[key]; 2179 2180 return {}; 2181 }, 2182 2183 ClearRequestRes: function(key) 2184 { 2185 if (top.BXHtmlEditorAjaxResponse) 2186 { 2187 top.BXHtmlEditorAjaxResponse[key] = null; 2188 delete top.BXHtmlEditorAjaxResponse[key]; 2189 } 2190 }, 2191 2192 GetReqData: function(action, O) 2193 { 2194 if (!O) 2195 O = {}; 2196 if (action) 2197 O.action = action; 2198 O.sessid = BX.bitrix_sessid(); 2199 O.bx_html_editor_request = 'Y'; 2200 O.reqId = Math.round(Math.random() * 1000000); 2201 return O; 2202 }, 2203 2204 GetTemplateId: function() 2205 { 2206 return this.templateId; 2207 }, 2208 2209 GetSiteId: function() 2210 { 2211 return this.config.siteId; 2212 }, 2213 2214 GetComponentFilter: function() 2215 { 2216 return this.componentFilter; 2217 }, 2218 2219 GetTemplateParams: function() 2220 { 2221 return this.templates[this.templateId]; 2222 }, 2223 2224 GetTemplateStyles: function() 2225 { 2226 var params = this.templates[this.templateId] || {}; 2227 return params.STYLES || ''; 2228 }, 2229 2230 ApplyTemplate: function(templateId) 2231 { 2232 if (this.templateId !== templateId) 2233 { 2234 if (this.templates[templateId]) 2235 { 2236 this.templateId = templateId; 2237 var 2238 templ = this.templates[templateId], 2239 i, 2240 doc = this.sandbox.GetDocument(), 2241 head = doc.head || doc.getElementsByTagName('HEAD')[0], 2242 styles = head.getElementsByTagName('STYLE'), 2243 links = head.getElementsByTagName('LINK'); 2244 2245 // Clean old template styles 2246 for (i = 0; i < styles.length; i++) 2247 { 2248 if (styles[i].getAttribute('data-bx-template-style') == 'Y') 2249 BX.cleanNode(styles[i], true); 2250 } 2251 2252 // Clean links with template styles 2253 i = 0; 2254 while (i < links.length) 2255 { 2256 if (links[i].getAttribute('data-bx-template-style') == 'Y') 2257 { 2258 BX.remove(links[i], true); 2259 } 2260 else 2261 { 2262 i++; 2263 } 2264 } 2265 2266 // Add new node in the iframe head 2267 if (templ['STYLES']) 2268 { 2269 head.appendChild(BX.create('STYLE', {props: {type: 'text/css'}, text: templ['STYLES']}, doc)).setAttribute('data-bx-template-style', 'Y'); 2270 } 2271 2272 if (templ && templ['EDITOR_STYLES']) 2273 { 2274 for (i = 0; i < templ['EDITOR_STYLES'].length; i++) 2275 { 2276 head.appendChild(BX.create('link', {props: {rel: 'stylesheet', href: templ['EDITOR_STYLES'][i] + '_' + this.cssCounter++}}, doc)).setAttribute('data-bx-template-style', 'Y'); 2277 } 2278 } 2279 2280 this.On("OnApplySiteTemplate", [templateId]); 2281 } 2282 else 2283 { 2284 var _this = this; 2285 this.Request({ 2286 getData: this.GetReqData('load_site_template', 2287 { 2288 site_template: templateId 2289 } 2290 ), 2291 handler: function(res) 2292 { 2293 _this.templates[templateId] = res; 2294 _this.ApplyTemplate(templateId); 2295 } 2296 }); 2297 } 2298 } 2299 }, 2300 2301 FormatHtml: function(html, bForceFormating) 2302 { 2303 if (html.length < this.MAX_HANDLED_FORMAT_LENGTH || bForceFormating === true) 2304 { 2305 if (!this.formatter) 2306 this.formatter = new window.BXHtmlEditor.BXCodeFormatter(this); 2307 2308 var time1 = new Date().getTime(); 2309 html = this.formatter.Format(html); 2310 var time2 = new Date().getTime(); 2311 2312 if (time2 - time1 > this.MAX_HANDLED_FORMAT_TIME) 2313 this.MAX_HANDLED_FORMAT_LENGTH -= 5000; 2314 } 2315 2316 return html; 2317 }, 2318 2319 GetFontFamilyList: function() 2320 { 2321 if (!this.fontFamilyList) 2322 { 2323 this.fontFamilyList = [ 2324 {value: ['Times New Roman', 'Times'], name: 'Times New Roman'}, 2325 {value: ['Courier New'], name: 'Courier New'}, 2326 {value: ['Arial', 'Helvetica'], name: 'Arial / Helvetica'}, 2327 {value: ['Arial Black', 'Gadget'], name: 'Arial Black'}, 2328 {value: ['Tahoma','Geneva'], name: 'Tahoma / Geneva'}, 2329 {value: 'Verdana', name: 'Verdana'}, 2330 {value: ['Georgia', 'serif'], name: 'Georgia'}, 2331 {value: 'monospace', name: 'monospace'} 2332 ]; 2333 this.On("GetFontFamilyList", [this.fontFamilyList]); 2334 } 2335 return this.fontFamilyList; 2336 }, 2337 2338 CheckCurrentStatus: function(status) 2339 { 2340 var 2341 arAction, action, actionState, value, 2342 actionList = this.GetActiveActions(); 2343 2344 if (status === false) 2345 { 2346 for (action in actionList) 2347 { 2348 if (actionList.hasOwnProperty(action) && this.action.IsSupported(action)) 2349 { 2350 arAction = actionList[action]; 2351 arAction.control.SetValue(false, null, action); 2352 } 2353 } 2354 } 2355 2356 if (!this.iframeView.IsFocused()) 2357 return this.On("OnIframeBlur"); 2358 2359 var range = this.selection.GetRange(); 2360 2361 if (!range || !range.isValid()) 2362 return this.On("OnIframeBlur"); 2363 2364 for (action in actionList) 2365 { 2366 if (actionList.hasOwnProperty(action) && this.action.IsSupported(action)) 2367 { 2368 arAction = actionList[action]; 2369 actionState = this.action.CheckState(action, arAction.value); 2370 value = arAction.control.GetValue(); 2371 2372 if (actionState) 2373 { 2374 arAction.control.SetValue(true, actionState, action); 2375 } 2376 else 2377 { 2378 arAction.control.SetValue(false, null, action); 2379 } 2380 } 2381 } 2382 }, 2383 2384 RegisterCheckableAction: function(action, params) 2385 { 2386 if (!this.checkedActionList) 2387 this.checkedActionList = {}; 2388 2389 this.checkedActionList[action] = params; 2390 }, 2391 2392 GetActiveActions: function() 2393 { 2394 return this.checkedActionList; 2395 }, 2396 2397 SaveOption: function(name, value) 2398 { 2399 BX.userOptions.save('html_editor', this.config.settingsKey, name, value); 2400 }, 2401
2402 GetCurrentCssClasses: function(filterTag) 2403 { 2404 return this.styles.GetCSS(this.templateId, this.templates[this.templateId].STYLES, this.templates[this.templateId].PATH || '', filterTag || false); 2405 }, 2406 2407 GetStylesDescription: function(templateId) 2408 { 2409 if (!templateId) 2410 templateId = this.templateId; 2411 var res = {}; 2412 if (templateId && this.templates[templateId]) 2413 { 2414 res = this.templates[templateId].STYLES_TITLE || {}; 2415 } 2416 return res; 2417 }, 2418 2419 IsInited: function() 2420 { 2421 return !!this.inited; 2422 }, 2423 2424 IsContentChanged: function() 2425 { 2426 var 2427 cont1 = this.config.content.replace(/[\s\n\r\t]+/ig, ''), 2428 cont2 = this.GetContent().replace(/[\s\n\r\t]+/ig, ''); 2429 2430 return cont1 != cont2; 2431 }, 2432 2433 IsSubmited: function() 2434 { 2435 return this.isSubmited; 2436 }, 2437 2438 OnSubmit: function() 2439 { 2440 if (!this.isSubmited && this.dom.cont.style.display !== 'none') 2441 { 2442 this.RemoveCursorNode(); 2443 this.isSubmited = true; 2444 2445 if (this.iframeView.IsFocused()) 2446 this.On("OnIframeBlur"); 2447 2448 this.On('OnSubmit'); 2449 this.SaveContent(); 2450 } 2451 }, 2452 2453 AllowBeforeUnloadHandler: function() 2454 { 2455 this.beforeUnloadHandlerAllowed = true; 2456 }, 2457 2458 DenyBeforeUnloadHandler: function() 2459 { 2460 this.beforeUnloadHandlerAllowed = false; 2461 }, 2462 2463 Destroy: function() 2464 { 2465 if (this.sandbox) 2466 this.sandbox.Destroy(); 2467 2468 if (this.Check()) 2469 BX.remove(this.dom.cont); 2470 }, 2471 2472 Check: function() 2473 { 2474 return this.dom.cont && BX.isNodeInDom(this.dom.cont); 2475 }, 2476 2477 IsVisible: function() 2478 { 2479 return this.Check() && this.dom.cont.offsetWidth > 0; 2480 }, 2481 2482 GetLastSpecialchars: function() 2483 { 2484 var def = ['¢', '§', '€', '£','¥','©', '®', '«', '»', '°', '±', '¶', '…','′','″', '™', '≈', '≠', '<', '>']; 2485 2486 if (this.config.lastSpecialchars && typeof this.config.lastSpecialchars == 'object' && this.config.lastSpecialchars.length > 1) 2487 { 2488 return this.config.lastSpecialchars; 2489 } 2490 else 2491 { 2492 return def; 2493 } 2494 }, 2495 2496 GetIframeElement: function(id) 2497 { 2498 var doc = this.GetIframeDoc(); 2499 return doc ? doc.getElementById(id) : null; 2500 }, 2501 2502 RegisterDialog: function(id, dialog) 2503 { 2504 window.BXHtmlEditor.dialogs[id] = dialog; 2505 }, 2506 2507 SetConfigHeight: function(height) 2508 { 2509 this.config.height = height; 2510 if (this.IsExpanded()) 2511 { 2512 this.savedSize.configHeight = height; 2513 this.savedSize.height = height; 2514 } 2515 }, 2516 2517 CheckBrowserCompatibility: function() 2518 { 2519 return !(BX.browser.IsOpera() || BX.browser.IsIE8() || BX.browser.IsIE7() || BX.browser.IsIE6() || !document.querySelectorAll); 2520 }, 2521 2522 GetCursorHtml: function() 2523 { 2524 return '<span id="bx-cursor-node"> </span>'; 2525 }, 2526 2527 SetCursorNode: function(range) 2528 { 2529 if (!range) 2530 range = this.selection.GetRange(); 2531 this.RemoveCursorNode(); 2532 this.selection.InsertHTML(this.GetCursorHtml(), range); 2533 }, 2534 2535 RestoreCursor: function() 2536 { 2537 var cursor = this.GetIframeElement('bx-cursor-node'); 2538 if (cursor) 2539 { 2540 this.selection.SetAfter(cursor); 2541 BX.remove(cursor); 2542 } 2543 }, 2544 2545 RemoveCursorNode: function() 2546 { 2547 if (this.synchro.IsFocusedOnTextarea()) 2548 { 2549 2550 } 2551 else 2552 { 2553 var cursor = this.GetIframeElement('bx-cursor-node'); 2554 if (cursor) 2555 { 2556 this.selection.SetAfter(cursor); 2557 BX.remove(cursor); 2558 } 2559 } 2560 }, 2561 2562 AddButton: function(params) 2563 { 2564 if (params.compact == undefined) 2565 params.compact = false; 2566 if (params.toolbarSort == undefined) 2567 params.toolbarSort = 301; 2568 if (params.hidden == undefined) 2569 params.hidden = false; 2570 2571 // 1. Create Button 2572 var but = function(editor, wrap) 2573 { 2574 // Call parrent constructor 2575 but.superclass.constructor.apply(this, arguments); 2576 this.id = params.id; 2577 this.title = params.name; 2578 if (params.iconClassName) 2579 this.className += ' ' + params.iconClassName; 2580 if (params.action) 2581 this.action = params.action; 2582 2583 if (params.disabledForTextarea !== undefined) 2584 this.disabledForTextarea = params.disabledForTextarea; 2585 2586 this.Create(); 2587 2588 if (params.src) 2589 this.pCont.firstChild.style.background = 'url("' + params.src + '") no-repeat scroll 0 0'; 2590 2591 if (wrap) 2592 wrap.appendChild(this.GetCont()); 2593 }; 2594 2595 BX.extend(but, window.BXHtmlEditor.Button); 2596 if (params.handler) 2597 but.prototype.OnClick = params.handler; 2598 2599 window.BXHtmlEditor.Controls[params.id] = but; 2600 2601 // 2. Add button to controls map 2602 BX.addCustomEvent(this, "GetControlsMap", function(controlsMap) 2603 { 2604 controlsMap.push({ 2605 id: params.id, 2606 compact: params.compact, 2607 hidden: params.hidden, 2608 sort: params.toolbarSort, 2609 checkWidth: params.checkWidth == undefined ? true : !!params.checkWidth, 2610 offsetWidth: (params.offsetWidth || 32) 2611 }); 2612 }); 2613 }, 2614 2615 AddCustomParser: function(parser) 2616 { 2617 if (this.phpParser && this.phpParser.AddCustomParser) 2618 { 2619 this.phpParser.AddCustomParser(parser); 2620 } 2621 else 2622 {
2623 var _this = this; 2624 BX.addCustomEvent("OnEditorInitedAfter", function(){_this.phpParser.AddCustomParser(parser);}); 2625 } 2626 }, 2627 2628 AddParser: function(parser) 2629 { 2630 if (parser && parser.name && typeof parser.obj == 'object') 2631 { 2632 this.parser.specialParsers[parser.name] = parser.obj; 2633 } 2634 }, 2635 2636 InsertHtml: function(html, range) 2637 { 2638 if (!this.synchro.IsFocusedOnTextarea()) 2639 { 2640 this.Focus(); 2641 2642 if (this.selection.lastCheckedRange && 2643 this.selection.lastCheckedRange.range && 2644 !range) 2645 { 2646 try 2647 { 2648 this.selection.SetSelection(this.selection.lastCheckedRange.range); 2649 } 2650 catch(e){} 2651 } 2652 2653 if (!range) 2654 { 2655 range = this.selection.GetRange(); 2656 } 2657 2658 if (!range && this.selection.lastRange) 2659 range = this.selection.lastRange; 2660 2661 if (range) 2662 { 2663 if (!range.collapsed && range.startContainer == range.endContainer && range.startContainer.nodeName !== 'BODY') 2664 { 2665 var surNode = this.util.CheckSurrogateNode(range.startContainer); 2666 if (surNode) 2667 { 2668 this.selection.SetAfter(surNode); 2669 } 2670 } 2671 2672 this.selection.InsertHTML(html, range); 2673 this.selection.ScrollIntoView(); 2674 } 2675 } 2676 }, 2677 2678 ParseContentFromBbCode: function(content) 2679 { 2680 if (this.bbCode) 2681 { 2682 content = this.bbParser.Parse(content); 2683 content = this.Parse(content, true, true); 2684 } 2685 return content; 2686 }, 2687 2688 LoadFileDialogs: function(callback) 2689 { 2690 var _this = this; 2691 2692 this.Request({ 2693 getData: this.GetReqData('load_file_dialogs', 2694 { 2695 editor_id: this.id 2696 } 2697 ), 2698 handler: function(res, html) 2699 { 2700 html = BX.util.trim(html); 2701 _this.dom.fileDialogsWrap.innerHTML = html; 2702 _this.fileDialogsLoaded = true; 2703 setTimeout(callback, 100); 2704 } 2705 }); 2706 }, 2707 2708 InitAutosaveHandlers: function() 2709 { 2710 var 2711 editor = this, 2712 form = this.dom.form; 2713 2714 try{ 2715// BX.addCustomEvent(this, 'OnSubmit', function(){form.BXAUTOSAVE.Init();}); // to prevent save ticker after form submit, OnContentChanged is enough 2716 BX.addCustomEvent(this, 'OnContentChanged', function(){form.BXAUTOSAVE.Init();}); 2717 2718 BX.addCustomEvent(form, 'onAutoSave', function (ob, data) 2719 { 2720 if (editor.IsShown() && !editor.IsSubmited()) 2721 { 2722 // Get it from textarea and put to form_data to saving 2723 data[editor.config.inputName] = editor.GetContent(); 2724 } 2725 }); 2726 2727 BX.addCustomEvent(form, 'onAutoSaveRestore', function (ob, data) 2728 { 2729 if (editor.IsShown()) 2730 { 2731 editor.SetContent(data[editor.config.inputName], true); 2732 } 2733 }); 2734 }catch(e){} 2735 }, 2736 2737 InitImageUploader: function() 2738 { 2739 if (this.config.uploadImagesFromClipboard !== false) 2740 { 2741 var uploadUrl = this.config.actionUrl; 2742 uploadUrl += (uploadUrl.indexOf('?') !== -1 ? "&" : "?") + 'action=uploadfile'; 2743 this.imageUploader = BX.Uploader.getInstance({ 2744 id: this.CID, 2745 streams: 1, 2746 allowUpload: "A", 2747 uploadFileUrl: uploadUrl, 2748 uploadMethod: "immediate", 2749 showImage: false, 2750 sortItems: false, 2751 input: null, 2752 placeHolder: null, 2753 uploadFormData: "N" 2754 }); 2755 2756 var _this = this; 2757 2758 BX.addCustomEvent(this, "OnImageDataUriHandle", function (editor, imageBase64) 2759 { 2760 var blob = BX.UploaderUtils.dataURLToBlob(imageBase64.src); 2761 if (blob && blob.size > 0 && blob.type.indexOf("image/") == 0) 2762 { 2763 blob.name = (blob.name || imageBase64.title || (this.GetDefaultImageName() + "." + blob.type.substr(6))); 2764 blob.uniqId = imageBase64.uniqId; 2765 blob.editorBase64Src = imageBase64.src; 2766 _this.imageUploader.onChange([blob]); 2767 } 2768 }); 2769 2770 BX.addCustomEvent(this.imageUploader, "onFileIsCreated", function (id, item) 2771 { 2772 BX.addCustomEvent(item, "onUploadDone", function (item, result) 2773 { 2774 _this.HandleImageDataUriCaughtUploadedCallback({ 2775 src: item.file.editorBase64Src, uniqId: item.file.uniqId 2776 }, { 2777 src: result.file.uploadedPath 2778 }); 2779 }); 2780 2781 //BX.addCustomEvent(item, "onUploadError", function(item, result){}); 2782 }); 2783 } 2784 }, 2785 2786 GetDefaultImageName: function() 2787 { 2788 var imageName = {value: false}; 2789 this.On("OnGetDefaultUploadImageName", [imageName]); 2790 if (!imageName.value) 2791 { 2792 imageName.value = 'content-img'; 2793 } 2794 2795 return imageName.value; 2796 }, 2797 2798 InitClipboardHandler: function() 2799 { 2800 var 2801 _this = this; 2802 2803 this.base64Images = []; 2804 2805 function checkImages(images) 2806 { 2807 _this.pasteCheckItteration++; 2808 var i; 2809 for (i = 0; i < images.length; i++) 2810 { 2811 if (!images[i].getAttribute('data-bx-paste-check')) 2812 { 2813 if (images[i].complete) 2814 _this.CheckImage(images[i], false); 2815 else 2816 BX.bind(images[i], 'load', BX.proxy(_this.CheckImage, _this)); 2817 images[i].setAttribute('data-bx-paste-check', 'Y'); 2818 } 2819 } 2820 2821 if (_this.pasteCheckItteration == 1) 2822 setTimeout(function(){checkImages(images);}, 500); 2823 else if (_this.pasteCheckItteration < 15) 2824 setTimeout(function(){checkImages(images);}, 1000); 2825 } 2826 2827 BX.bind(this.iframeView.element, 'paste', function (e) 2828 { 2829 var 2830 chromeVerion = 0, 2831 imageHandled = false, 2832 clipboard = e.clipboardData; 2833 2834 if (BX.browser.IsChrome() || BX.browser.IsSafari()) 2835 { 2836 var ua = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); 2837 chromeVerion = ua ? parseInt(ua[2], 10) : Infinity; 2838 } 2839 2840 // For firefox works wrong (see mantis:88928, mantis:122421) 2841 if (clipboard && clipboard.items 2842 && !BX.browser.IsFirefox()
2843 && (!chromeVerion || chromeVerion < 83) 2844 ) 2845 { 2846 var item = clipboard.items[0]; 2847 if (item && item.type.indexOf('image/') > -1) 2848 { 2849 var blob = item.getAsFile(); 2850 if (blob) 2851 { 2852 2853 var reader = new FileReader(); 2854 reader.readAsDataURL(blob); 2855 reader.onload = function (event) 2856 { 2857 imageHandled = true; 2858 var img = new Image(); 2859 img.src = event.target.result; 2860 setTimeout(function() 2861 { 2862 _this.selection.InsertNode(img); 2863 _this.HandleImageDataUri(img); 2864 }, 100); 2865 } 2866 } 2867 } 2868 } 2869 2870 if (!imageHandled) 2871 { 2872 _this.pasteCheckItteration = 0; 2873 checkImages(_this.GetIframeDoc().body.getElementsByTagName('IMG')); 2874 } 2875 }); 2876 2877 BX.removeCustomEvent(this, 'OnImageDataUriCaughtUploaded', BX.proxy(this.HandleImageDataUriCaughtUploadedCallback, this)); 2878 BX.addCustomEvent(this, 'OnImageDataUriCaughtUploaded', BX.proxy(this.HandleImageDataUriCaughtUploadedCallback, this)); 2879 }, 2880 2881 GetBase64Image: function(base64source) 2882 { 2883 var i, result = false;
2884 2885 for (i = 0; i < this.base64Images.length; i++) 2886 { 2887 if (this.base64Images[i].source == base64source) 2888 { 2889 result = this.base64Images[i]; 2890 } 2891 } 2892 return result; 2893 }, 2894 2895 RegisterBase64Image: function(base64source, status) 2896 { 2897 this.base64Images.push({ 2898 source: base64source, 2899 status: status, 2900 index: this.base64Images.length 2901 }); 2902 }, 2903 2904 CheckImage: function(image, unbind) 2905 { 2906 if (image && image.getAttribute) 2907 { 2908 var src = image.getAttribute('src'); 2909 if (src.indexOf('data:image/') !== -1) 2910 { 2911 this.HandleImageDataUri(image); 2912 } 2913 2914 if (unbind !== false) 2915 { 2916 BX.unbind(image, 'load', BX.proxy(this.CheckImage, this)); 2917 } 2918 } 2919 }, 2920 2921 HandleImageDataUri: function(image) 2922 { 2923 if (!image.getAttribute('data-bx-unique-id')) 2924 { 2925 this.skipPasteControl = true; 2926 if (this.pasteControl.isOpened) 2927 { 2928 this.pasteControl.Hide(); 2929 } 2930 2931 var base64Image = this.GetBase64Image(image.src); 2932 2933 if (base64Image === false) 2934 { 2935 this.RegisterBase64Image(image.src, 'requested'); 2936 2937 var uniqId = 'bx_base64_id_' + Math.round(Math.random() * 1000000000); 2938 image.setAttribute('data-bx-unique-id', uniqId); 2939 image.removeAttribute('data-bx-orig-src'); 2940 2941 this.On('OnImageDataUriHandle', [this, 2942 { 2943 src: image.src, 2944 title: image.title || '', 2945 uniqId: uniqId 2946 }]); 2947 } 2948 else 2949 { 2950 if (base64Image.status == 'uploaded') 2951 { 2952 this.HandleImageDataUriCaughtUploadedCallback( 2953 { 2954 src: image.src, 2955 title: image.title || '', 2956 uniqId: base64Image.uniqId 2957 }, 2958 { 2959 src: base64Image.fileSrc 2960 }, 2961 base64Image.htmlForInsert || null 2962 ); 2963 } 2964 } 2965 } 2966 }, 2967 2968 HandleImageDataUriCaughtUploadedCallback: function(imageReferer, file, htmlForInsert) 2969 { 2970 if (imageReferer && imageReferer.uniqId && file && file.src) 2971 { 2972 var base64Image = this.GetBase64Image(imageReferer.src); 2973 if (base64Image && !base64Image.fileSrc) 2974 { 2975 base64Image.status = 'uploaded'; 2976 base64Image.uniqId = imageReferer.uniqId; 2977 base64Image.fileSrc = file.src; 2978 base64Image.htmlForInsert = htmlForInsert; 2979 } 2980 2981 var 2982 i,image, 2983 images = this.GetIframeDoc().body.getElementsByTagName('IMG'); 2984 2985 for (i = 0; i < images.length; i++) 2986 { 2987 image = images[i]; 2988 if (image.getAttribute('data-bx-unique-id') == imageReferer.uniqId 2989 || 2990 image.getAttribute('src') == imageReferer.src 2991 ) 2992 { 2993 if (htmlForInsert && htmlForInsert.replacement) 2994 { 2995 this.selection.SetAfter(image); 2996 this.selection.InsertHTML(htmlForInsert.replacement); 2997 2998 BX.remove(image); 2999 if (htmlForInsert.callback) 3000 { 3001 setTimeout(htmlForInsert.callback, 300); 3002 } 3003 } 3004 else 3005 { 3006 image.src = file.src; 3007 image.setAttribute('src', file.src); 3008 image.setAttribute('data-bx-orig-src', file.src); 3009 image.removeAttribute('data-bx-paste-check'); 3010 image.removeAttribute('data-bx-unique-id'); 3011 } 3012 } 3013 } 3014 } 3015 this.skipPasteControl = false; 3016 } 3017 }; 3018 3019 window.BXEditor = BXEditor; 3020 3021 function Sandbox(callBack, config) 3022 { 3023 this.callback = callBack || BX.DoNothing; 3024 this.config = config || {}; 3025 this.editor = this.config.editor; 3026 this.iframe = this.CreateIframe(); 3027 this.bSandbox = false; 3028 3029 // Properties to unset/protect on the window object 3030 this.windowProperties = ["parent", "top", "opener", "frameElement", "frames", 3031 "localStorage", "globalStorage", "sessionStorage", "indexedDB"]; 3032 //Properties on the window object which are set to an empty function 3033 this.windowProperties2 = ["open", "close", "openDialog", "showModalDialog", "alert", "confirm", "prompt", "openDatabase", "postMessage", "XMLHttpRequest", "XDomainRequest"]; 3034 //Properties to unset/protect on the document object 3035 this.documentProperties = ["referrer", "write", "open", "close"]; 3036 } 3037 3038 Sandbox.prototype = 3039 { 3040 GetIframe: function() 3041 { 3042 return this.iframe; 3043 }, 3044 3045 GetWindow: function() 3046 { 3047 this._readyError(); 3048 }, 3049 3050 GetDocument: function() 3051 { 3052 this._readyError(); 3053 }, 3054 3055 Destroy: function() 3056 { 3057 var iframe = this.GetIframe(); 3058 iframe.parentNode.removeChild(iframe); 3059 }, 3060 3061 _readyError: function() 3062 { 3063 throw new Error("Sandbox: Sandbox iframe isn't loaded yet"); 3064 }, 3065 3066 CreateIframe: function() 3067 { 3068 var 3069 _this = this, 3070 iframe = BX.create("IFRAME", { 3071 props: { 3072 className: "bx-editor-iframe", 3073 frameborder: 0, 3074 allowtransparency: "true", 3075 width: 0, 3076 height: 0, 3077 marginwidth: 0, 3078 marginheight: 0 3079 //scrolling: 'no' 3080 } 3081 }); 3082 3083 iframe.onload = function() 3084 { 3085 iframe.onreadystatechange = iframe.onload = null; 3086 _this.OnLoadIframe(iframe); 3087 }; 3088 3089 iframe.onreadystatechange = function() 3090 { 3091 if (/loaded|complete/.test(iframe.readyState)) 3092 { 3093 iframe.onreadystatechange = iframe.onload = null; 3094 _this.OnLoadIframe(iframe); 3095 } 3096 }; 3097 3098 // Append iframe to ext container 3099 this.config.cont.appendChild(iframe); 3100 3101 return iframe; 3102 }, 3103 3104 OnLoadIframe: function(iframe) 3105 { 3106 if (BX.isNodeInDom(iframe)) 3107 { 3108 var 3109 _this = this, 3110 iframeWindow = iframe.contentWindow, 3111 iframeDocument = iframeWindow.document; 3112 3113 this.InitIframe(iframe); 3114 3115 iframeWindow.onerror = function(errorMessage, fileName, lineNumber) { 3116 throw new Error("Sandbox: " + errorMessage, fileName, lineNumber); 3117 }; 3118 3119 if (this.bSandbox) 3120 { 3121 var i, length; 3122 for (i = 0, length = this.windowProperties.length; i < length; i++) 3123 { 3124 this._unset(iframeWindow, this.windowProperties[i]); 3125 } 3126 3127 for (i=0, length = this.windowProperties2.length; i < length; i++) 3128 { 3129 this._unset(iframeWindow, this.windowProperties2[i], BX.DoNothing()); 3130 } 3131 3132 for (i = 0, length = this.documentProperties.length; i < length; i++) 3133 { 3134 this._unset(iframeDocument, this.documentProperties[i]); 3135 } 3136 3137 this._unset(iframeDocument, "cookie", "", true); 3138 } 3139 3140 this.loaded = true; 3141 // Trigger the callback 3142 setTimeout(function() 3143 { 3144 _this.callback(_this); 3145 }, 0); 3146 } 3147 }, 3148 3149 InitIframe: function(iframe) 3150 { 3151 iframe = this.iframe || iframe; 3152 var 3153 iframeDocument = iframe.contentWindow.document, 3154 iframeHtml = this.GetHtml(this.config.stylesheets, this.editor.GetTemplateStyles()); 3155 3156 // Create the basic dom tree including proper DOCTYPE and charset 3157 iframeDocument.open("text/html", "replace"); 3158 iframeDocument.write(iframeHtml); 3159 iframeDocument.close(); 3160 3161 this.GetWindow = function() 3162 { 3163 return iframe.contentWindow; 3164 }; 3165 this.GetDocument = function() 3166 { 3167 return iframe.contentWindow.document; 3168 }; 3169 3170 this.editor.On("OnIframeInit"); 3171 }, 3172 3173 GetHtml: function(css, cssText) 3174 { 3175 var 3176 bodyParams = '', 3177 headHtml = "", 3178 i; 3179 3180 if (this.editor.config.bodyClass || this.editor.IsExpanded()) 3181 { 3182 var bodyClass = this.editor.config.bodyClass || ''; 3183 if (this.editor.IsExpanded()) 3184 bodyClass += ' fullscreen'; 3185 bodyParams += ' class="' + BX.util.trim(bodyClass) + '"'; 3186 } 3187 if (this.editor.config.bodyId) 3188 { 3189 bodyParams += ' id="' + this.editor.config.bodyId + '"'; 3190 } 3191 3192 var templ = this.editor.GetTemplateParams(); 3193 if (templ && templ['EDITOR_STYLES']) 3194 { 3195 for (i = 0; i < templ['EDITOR_STYLES'].length; i++) 3196 { 3197 headHtml += '<link data-bx-template-style="Y" rel="stylesheet" href="' + templ['EDITOR_STYLES'][i] + '_' + this.editor.cssCounter++ + '">'; 3198 } 3199 } 3200 3201 css = typeof css === "string" ? [css] : css; 3202 if (css) 3203 { 3204 for (i = 0; i < css.length; i++) 3205 { 3206 headHtml += '<link rel="stylesheet" href="' + css[i] + '">'; 3207 } 3208 } 3209 3210 headHtml += '<link rel="stylesheet" href="' + this.editor.config.cssIframePath + '_' + this.editor.cssCounter++ + '">'; 3211 3212 if (typeof cssText === "string") 3213 { 3214 headHtml += '<style type="text/css" data-bx-template-style="Y">' + cssText + '</style>'; 3215 } 3216 3217 if (this.editor.iframeCssText && this.editor.iframeCssText.length > 0) 3218 { 3219 headHtml += '<style type="text/css">' + this.editor.iframeCssText + '</style>'; 3220 } 3221 3222 return '<!DOCTYPE html><html><head>' + headHtml + '</head><body' + bodyParams + '></body></html>'; 3223 }, 3224 3225 /** 3226 * Method to unset/override existing variables 3227 * @example 3228 * // Make cookie unreadable and unwritable
3229 * this._unset(document, "cookie", "", true); 3230 */ 3231 _unset: function(object, property, value, setter) 3232 { 3233 try { object[property] = value; } catch(e) {} 3234 3235 try { object.__defineGetter__(property, function() { return value; }); } catch(e) {} 3236 if (setter) { 3237 try { object.__defineSetter__(property, function() {}); } catch(e) {} 3238 } 3239 3240 if (!crashesWhenDefineProperty(property)) 3241 { 3242 try { 3243 var config = { 3244 get: function() { return value; } 3245 }; 3246 if (setter) { 3247 config.set = function() {}; 3248 } 3249 Object.defineProperty(object, property, config); 3250 } catch(e) {} 3251 } 3252 } 3253 }; 3254 3255 function BXEditorSelection(editor) 3256 { 3257 this.editor = editor; 3258 this.document = editor.sandbox.GetDocument(); 3259 BX.addCustomEvent(this.editor, 'OnIframeReInit', BX.proxy(function(){this.document = this.editor.sandbox.GetDocument();}, this)); 3260 // Make sure that our external range library is initialized 3261 window.rangy.init(); 3262 } 3263 3264 BXEditorSelection.prototype = 3265 { 3266 // Get the current selection as a bookmark to be able to later restore it 3267 GetBookmark: function() 3268 { 3269 if (!this.editor.synchro.IsFocusedOnTextarea()) 3270 { 3271 var range = this.GetRange(); 3272 return range && range.cloneRange(); 3273 } 3274 return false; 3275 }, 3276 3277 // Restore a selection 3278 SetBookmark: function(bookmark) 3279 { 3280 if (bookmark && this.editor.currentViewName !== 'code') 3281 { 3282 this.SetSelection(bookmark); 3283 } 3284 }, 3285 3286 // Save current selection 3287 SaveBookmark: function() 3288 { 3289 this.lastRange = this.GetBookmark(); 3290 return this.lastRange; 3291 }, 3292 3293 GetLastRange: function() 3294 { 3295 if (this.lastRange) 3296 return this.lastRange; 3297 }, 3298 3299 // Restore selection 3300 RestoreBookmark: function() 3301 { 3302 if (this.lastRange) 3303 { 3304 this.SetBookmark(this.lastRange); 3305 this.lastRange = false; 3306 } 3307 }, 3308 3309 /** 3310 * Set the caret in front of the given node 3311 * @param {Object} node The element or text node where to position the caret in front of 3312 */ 3313 SetBefore: function(node) 3314 { 3315 var range = rangy.createRange(this.document); 3316 range.setStartBefore(node); 3317 range.setEndBefore(node); 3318 return this.SetSelection(range); 3319 }, 3320 3321 /** 3322 * Set the caret after the given node 3323 * 3324 * @param {Object} node The element or text node where to position the caret in front of 3325 */ 3326 SetAfter: function(node) 3327 { 3328 var range = rangy.createRange(this.document); 3329 range.setStartAfter(node); 3330 range.setEndAfter(node); 3331 return this.SetSelection(range); 3332 }, 3333 3334 /** 3335 * Ability to select/mark nodes 3336 * 3337 * @param {Element} node The node/element to select 3338 */ 3339 SelectNode: function(node) 3340 { 3341 if (!node) 3342 return; 3343 3344 var 3345 range = rangy.createRange(this.document), 3346 isElement = node.nodeType === 1, 3347 canHaveHTML = "canHaveHTML" in node ? node.canHaveHTML : (node.nodeName !== "IMG"), 3348 content = isElement ? node.innerHTML : node.data, 3349 isEmpty = (content === "" || content === this.editor.INVISIBLE_SPACE), 3350 styleDisplay = BX.style(node, 'display'), 3351 bBlock = (styleDisplay === "block" || styleDisplay === "list-item"); 3352 3353 if ((BX.browser.IsIE() || BX.browser.IsIE10() || BX.browser.IsIE11()) && node && 3354 BX.util.in_array(node.nodeName.toUpperCase(), this.editor.TABLE_TAGS)) 3355 { 3356 //"TD", "TR", "TH", "TABLE", "TBODY", "CAPTION", "COL", "COLGROUP", "TFOOT", "THEAD"]; 3357 if (node.tagName == 'TABLE' || node.tagName == 'TBODY') 3358 { 3359 var 3360 firstRow = node.rows[0], 3361 lastRow = node.rows[node.rows.length - 1]; 3362 3363 range.setStartBefore(firstRow.cells[0]); 3364 range.setEndAfter(lastRow.cells[lastRow.cells.length - 1]); 3365 } 3366 else if (node.tagName == 'TR' || node.tagName == 'TH') 3367 { 3368 range.setStartBefore(node.cells[0]); 3369 range.setEndAfter(node.cells[node.cells.length - 1]); 3370 } 3371 else 3372 { 3373 range.setStartBefore(node); 3374 range.setEndAfter(node); 3375 } 3376 3377 this.SetSelection(range); 3378 return range; 3379 } 3380 3381 if (isEmpty && isElement && canHaveHTML) 3382 { 3383 // Make sure that caret is visible in node by inserting a zero width no breaking space 3384 try { 3385 node.innerHTML = this.editor.INVISIBLE_SPACE; 3386 } catch(e) {} 3387 } 3388 3389 if (canHaveHTML) 3390 range.selectNodeContents(node); 3391 else 3392 range.selectNode(node); 3393 3394 if (canHaveHTML && isEmpty && isElement) 3395 { 3396 range.collapse(bBlock); 3397 } 3398 else if (canHaveHTML && isEmpty) 3399 { 3400 range.setStartAfter(node); 3401 range.setEndAfter(node); 3402 } 3403 3404 try 3405 { 3406 this.SetSelection(range); 3407 } 3408 catch(e){} 3409 3410 return range; 3411 }, 3412 3413 /** 3414 * Get the node which contains the selection 3415 * 3416 * @param {Boolean} [controlRange] (only IE) Whether it should return the selected ControlRange element when the selection type is a "ControlRange" 3417 * @return {Object}
3417 The node that contains the caret 3418 */ 3419 GetSelectedNode: function(controlRange) 3420 { 3421 var 3422 res, 3423 selection, 3424 range; 3425 3426 if (controlRange && this.document.selection && this.document.selection.type === "Control") 3427 { 3428 range = this.document.selection.createRange(); 3429 if (range && range.length) 3430 { 3431 res = range.item(0); 3432 } 3433 } 3434 3435 if (!res) 3436 { 3437 selection = this.GetSelection(); 3438 if (selection.focusNode === selection.anchorNode) 3439 { 3440 res = selection.focusNode; 3441 } 3442 } 3443 3444 if (!res) 3445 { 3446 range = this.GetRange(); 3447 res = range ? range.commonparentContainer : this.document.body 3448 } 3449 3450 if (res && res.ownerDocument != this.editor.GetIframeDoc()) 3451 { 3452 res = this.document.body; 3453 } 3454 3455 return res; 3456 }, 3457 3458 ExecuteAndRestore: function(method, restoreScrollPosition) 3459 { 3460 var 3461 body = this.document.body, 3462 oldScrollTop = restoreScrollPosition && body.scrollTop, 3463 oldScrollLeft = restoreScrollPosition && body.scrollLeft, 3464 className = "_bx-editor-temp-placeholder", 3465 placeholderHTML = '<span class="' + className + '">' + this.editor.INVISIBLE_SPACE + '</span>', 3466 range = this.GetRange(), 3467 newRange; 3468 3469 // Nothing selected, execute and say goodbye 3470 if (!range) 3471 { 3472 method(body, body); 3473 return; 3474 } 3475 3476 var node = range.createContextualFragment(placeholderHTML); 3477 range.insertNode(node); 3478 3479 // Make sure that a potential error doesn't cause our placeholder element to be left as a placeholder 3480 try 3481 { 3482 method(range.startContainer, range.endContainer); 3483 } 3484 catch(e3) 3485 { 3486 setTimeout(function() { throw e3; }, 0); 3487 } 3488 3489 if (document.querySelector) 3490 { 3491 var caretPlaceholder = this.document.querySelector("." + className); 3492 if (caretPlaceholder) 3493 { 3494 newRange = rangy.createRange(this.document); 3495 newRange.selectNode(caretPlaceholder); 3496 newRange.deleteContents(); 3497 this.SetSelection(newRange); 3498 } 3499 else 3500 { 3501 // fallback for when all hell breaks loose 3502 body.focus(); 3503 } 3504 } 3505 3506 if (restoreScrollPosition) 3507 { 3508 body.scrollTop = oldScrollTop; 3509 body.scrollLeft = oldScrollLeft; 3510 } 3511 3512 // Remove it again, just to make sure that the placeholder is definitely out of the dom tree 3513 try { 3514 if (caretPlaceholder && caretPlaceholder.parentNode) 3515 caretPlaceholder.parentNode.removeChild(caretPlaceholder); 3516 } catch(e4) {} 3517 }, 3518 3519 /** 3520 * Different approach of preserving the selection (doesn't modify the dom) 3521 * Takes all text nodes in the selection and saves the selection position in the first and last one 3522 */ 3523 ExecuteAndRestoreSimple: function(method) 3524 { 3525 var 3526 range = this.GetRange(), 3527 body = this.document.body, 3528 newRange, 3529 firstNode, 3530 lastNode, 3531 textNodes, 3532 rangeBackup; 3533 3534 // Nothing selected, execute and say goodbye 3535 if (!range) 3536 { 3537 method(body, body); 3538 return; 3539 } 3540 3541 textNodes = range.getNodes([3]); 3542 firstNode = textNodes[0] || range.startContainer; 3543 lastNode = textNodes[textNodes.length - 1] || range.endContainer; 3544 3545 rangeBackup = { 3546 collapsed: range.collapsed, 3547 startContainer: firstNode, 3548 startOffset: firstNode === range.startContainer ? range.startOffset : 0, 3549 endContainer: lastNode, 3550 endOffset: lastNode === range.endContainer ? range.endOffset : lastNode.length 3551 }; 3552 3553 try 3554 { 3555 method(range.startContainer, range.endContainer); 3556 } 3557 catch(e) 3558 { 3559 setTimeout(function() { throw e; }, 0); 3560 } 3561 3562 newRange = rangy.createRange(this.document); 3563 try { newRange.setStart(rangeBackup.startContainer, rangeBackup.startOffset); } catch(e1) {} 3564 try { newRange.setEnd(rangeBackup.endContainer, rangeBackup.endOffset); } catch(e2) {} 3565 try { this.SetSelection(newRange); } catch(e3) {} 3566 }, 3567 3568 /** 3569 * Insert html at the caret position and move the cursor after the inserted html 3570 * 3571 * @param {String} html HTML string to insert 3572 */ 3573 InsertHTML: function(html, range) 3574 { 3575 var 3576 rng = rangy.createRangyRange(this.document), 3577 node = rng.createContextualFragment(html), 3578 lastChild = node.lastChild; 3579 3580 this.InsertNode(node, range); 3581 if (lastChild) 3582 { 3583 this.SetAfter(lastChild); 3584 } 3585 3586 this.editor.On('OnInsertHtml'); 3587 }, 3588 3589 /** 3590 * Insert a node at the caret position and move the cursor behind it 3591 * 3592 * @param {Object}
3592 node HTML string to insert 3593 */ 3594 InsertNode: function(node, range) 3595 { 3596 if (!range || !range.isValid || !range.isValid()) 3597 range = this.GetRange(); 3598 3599 if (range) 3600 { 3601 range.insertNode(node); 3602 } 3603 3604 this.editor.On('OnInsertHtml'); 3605 }, 3606 3607 RemoveNode: function(node) 3608 { 3609 this.editor.On('OnHtmlContentChangedByControl'); 3610 var 3611 parent = node.parentNode, 3612 cursorNode = node.nextSibling; 3613 BX.remove(node); 3614 this.editor.util.Refresh(parent); 3615 3616 if (cursorNode) 3617 { 3618 this.editor.selection.SetBefore(cursorNode); 3619 this.editor.Focus(); 3620 } 3621 this.editor.synchro.StartSync(100); 3622 }, 3623 3624 /** 3625 * Wraps current selection with the given node 3626 * 3627 * @param {Object} node The node to surround the selected elements with 3628 * @param {Object} range Current range 3629 */ 3630 Surround: function(node, range) 3631 { 3632 range = range || this.GetRange(); 3633 if (range) 3634 { 3635 try 3636 { 3637 // This only works when the range boundaries are not overlapping other elements 3638 range.surroundContents(node); 3639 this.SelectNode(node); 3640 } 3641 catch(e) 3642 { 3643 node.appendChild(range.extractContents()); 3644 range.insertNode(node); 3645 } 3646 } 3647 }, 3648 3649 ScrollIntoView: function() 3650 { 3651 var 3652 node, 3653 _this = this, 3654 doc = this.document, 3655 win = this.editor.sandbox.GetWindow(), 3656 bScrollBars = doc.documentElement.scrollHeight > doc.documentElement.offsetHeight; 3657 3658 if (bScrollBars && win) 3659 { 3660 var 3661 tempNode = doc.__scrollIntoViewElement = doc.__scrollIntoViewElement || (function() 3662 { 3663 return BX.create("SPAN", {html: _this.editor.INVISIBLE_SPACE}, doc); 3664 })(), 3665 top = 0; 3666 3667 this.InsertNode(tempNode); 3668 3669 if (tempNode.parentNode) 3670 { 3671 node = tempNode; 3672 do 3673 { 3674 top += node.offsetTop || 0; 3675 node = node.offsetParent; 3676 } while (node); 3677 tempNode.parentNode.removeChild(tempNode); 3678 } 3679 3680 var 3681 scrollPos = BX.GetWindowScrollPos(doc), 3682 innerSize = BX.GetWindowInnerSize(doc); 3683 3684 if (top > scrollPos.scrollTop + innerSize.innerHeight - 40) 3685 { 3686 win.scrollTo(scrollPos.scrollLeft, top); 3687 } 3688 } 3689 }, 3690 3691 /** 3692 * Select line where the caret is in 3693 */ 3694 SelectLine: function() 3695 { 3696 // See https://developer.mozilla.org/en/DOM/Selection/modify 3697 var bSelectionModify = "getSelection" in window && "modify" in window.getSelection(); 3698 if (bSelectionModify) 3699 { 3700 var 3701 win = this.document.defaultView, 3702 selection = win.getSelection(); 3703 selection.modify("move", "left", "lineboundary"); 3704 selection.modify("extend", "right", "lineboundary"); 3705 } 3706 else if (this.document.selection) // IE 3707 { 3708 var 3709 range = this.document.selection.createRange(), 3710 rangeTop = range.boundingTop, 3711 rangeHeight = range.boundingHeight, 3712 scrollWidth = this.document.body.scrollWidth, 3713 rangeBottom, 3714 rangeEnd, 3715 measureNode, 3716 i, 3717 j; 3718 3719 if (!range.moveToPoint) 3720 return; 3721 3722 if (rangeTop === 0) 3723 { 3724 // Don't know why, but when the selection ends at the end of a line 3725 // range.boundingTop is 0 3726 measureNode = this.document.createElement("span"); 3727 this.insertNode(measureNode); 3728 rangeTop = measureNode.offsetTop; 3729 measureNode.parentNode.removeChild(measureNode); 3730 } 3731 rangeTop += 1; 3732 3733 for (i =- 10; i < scrollWidth; i += 2) 3734 { 3735 try { 3736 range.moveToPoint(i, rangeTop); 3737 break; 3738 } catch(e1) {} 3739 } 3740 3741 // Investigate the following in order to handle multi line selections 3742 // rangeBottom = rangeTop + (rangeHeight ? (rangeHeight - 1) : 0); 3743 rangeBottom = rangeTop; 3744 rangeEnd = this.document.selection.createRange(); 3745 for (j = scrollWidth; j >= 0; j--) 3746 { 3747 try 3748 { 3749 rangeEnd.moveToPoint(j, rangeBottom); 3750 break; 3751 } catch(e2) {} 3752 } 3753 3754 range.setEndPoint("EndToEnd", rangeEnd); 3755 range.select(); 3756 } 3757 }, 3758 3759 GetText: function() 3760 { 3761 var selection = this.GetSelection(); 3762 return selection ? selection.toString() : ""; 3763 }, 3764 3765 GetNodes: function(nodeType, filter) 3766 { 3767 var range = this.GetRange(); 3768 if (range) 3769 return range.getNodes([nodeType], filter); 3770 else 3771 return []; 3772 }, 3773 3774 GetRange: function(selection, bSetFocus) 3775 { 3776 if (!selection) 3777 { 3778 if (!this.editor.iframeView.IsFocused() && bSetFocus !== false) 3779 { 3780 var 3781 doc = this.editor.GetIframeDoc(), 3782 originalScrollTop = doc.documentElement.scrollTop || doc.body.scrollTop, 3783 originalScrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft; 3784 3785 this.editor.iframeView.Focus(); 3786 3787 var 3788 newScrollTop = doc.documentElement.scrollTop || doc.body.scrollTop, 3789 newScrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft; 3790 3791 if (newScrollTop !== originalScrollTop || newScrollLeft !== originalScrollLeft) 3792 { 3793 var win = this.editor.sandbox.GetWindow(); 3794 if (win) 3795 win.scrollTo(originalScrollLeft, originalScrollTop); 3796 } 3797 } 3798 3799 selection = this.GetSelection(); 3800 } 3801 3802 var range = selection && selection.rangeCount && selection.getRangeAt(0); 3803 if (range.commonAncestorContainer && range.commonAncestorContainer.nodeName == '#document') 3804 { 3805 range.selectNodeContents(this.editor.GetIframeDoc().body); 3806 } 3807 3808 return range; 3809 }, 3810 3811 GetSelection: function(doc) 3812 { 3813 return rangy.getSelection(doc || this.document.defaultView || this.document.parentWindow); 3814 }, 3815 3816 SetSelection: function(range) 3817 { 3818 var 3819 win = this.document.defaultView || this.document.parentWindow, 3820 selection = rangy.getSelection(win); 3821 return selection.setSingleRange(range); 3822 }, 3823 3824 GetStructuralTags: function() 3825 { 3826 if (!this.structuralTags) 3827 { 3828 var tblRe = /^TABLE/i; 3829 this.structuralTags = { 3830 'LI': /^UL|OL|MENU/i, 3831 'DT': /^DL/i, 3832 'DD': /^DL/i, 3833 // table 3834 'TD': tblRe, 3835 'TR': tblRe, 3836 'TH': tblRe, 3837 'TBODY': tblRe, 3838 'TFOOT': tblRe, 3839 'THEAD': tblRe, 3840 'CAPTION': tblRe, 3841 'COL': tblRe, 3842 'COLGROUP': tblRe 3843 }; 3844 this.structuralTagsMatchRe = /^LI|DT|DD|TD|TR|TH|TBODY|CAPTION|COL|COLGROUP|TFOOT|THEAD/i; 3845 } 3846 return this.structuralTags; 3847 }, 3848 3849 SetCursorBeforeNode: function(e) 3850 { 3851 3852 }, 3853 3854 _GetNonTextLastChild: function(n) 3855 { 3856 var res = n.lastChild; 3857 while (res.nodeType != 1 && res.previousSibling) 3858 res = res.previousSibling; 3859 3860 return res.nodeType == 1 ? res : false;
3861 }, 3862 3863 _GetNonTextFirstChild: function(n) 3864 { 3865 var res = n.firstChild; 3866 while (res.nodeType != 1 && res.nextSibling) 3867 res = res.nextSibling; 3868 3869 return res.nodeType == 1 ? res : false; 3870 }, 3871 3872 _MoveCursorBeforeNode: function(node) 3873 { 3874 var 3875 _this = this, 3876 possibleParentRe, parNode, isFirstNode; 3877 3878 this.GetStructuralTags(); 3879 3880 // Check if it's child node which have special parent 3881 // We can't add text node and put carret <td> and <tr> or between <li>... 3882 // So we trying handle the only case when carret before beginning of the first child of our structural tags (UL, OL, MENU, DIR, TABLE, DL) 3883 if (node.nodeType == 1 && node.nodeName.match(this.structuralTagsMatchRe)) 3884 { 3885 isFirstNode = this._GetNonTextFirstChild(node.parentNode) === node; 3886 if (!isFirstNode) 3887 { 3888 return; 3889 } 3890 3891 possibleParentRe = this.structuralTags[node.nodeName]; 3892 if (possibleParentRe) 3893 { 3894 parNode = BX.findParent(node, function(n) 3895 { 3896 if (n.nodeName.match(possibleParentRe)) 3897 { 3898 return true; 3899 } 3900 3901 isFirstNode = isFirstNode && _this._GetNonTextFirstChild(n.parentNode) === n; 3902 return false; 3903 }, node.ownerDocument.BODY); 3904 3905 if (parNode && isFirstNode) 3906 { 3907 node = parNode; // Put carret before parrent tag 3908 } 3909 else 3910 { 3911 return; 3912 } 3913 } 3914 } 3915 3916 this.SetInvisibleTextBeforeNode(node); 3917 }, 3918 3919 _MoveCursorAfterNode: function(node) 3920 { 3921 var 3922 _this = this, 3923 possibleParentRe, parNode, isLastNode; 3924 3925 this.GetStructuralTags(); 3926 // Check if it's child node which have special parent 3927 // We can't add text node and put carret <td> and <tr> or between <li>... 3928 // So we trying handle the only case when carret in the end of the last child of last child of our structural tags (UL, OL, MENU, DIR, TABLE, DL) 3929 if (node.nodeType == 1 && node.nodeName.match(this.structuralTagsMatchRe)) 3930 { 3931 isLastNode = this._GetNonTextLastChild(node.parentNode) === no
3931de; 3932 if (!isLastNode) 3933 { 3934 return; 3935 } 3936 3937 possibleParentRe = this.structuralTags[node.nodeName]; 3938 if (possibleParentRe) 3939 { 3940 parNode = BX.findParent(node, function(n) 3941 { 3942 if (n.nodeName.match(possibleParentRe)) 3943 { 3944 return true; 3945 } 3946 3947 isLastNode = isLastNode && _this._GetNonTextLastChild(n.parentNode) === n; 3948 return false; 3949 }, node.ownerDocument.BODY); 3950 3951 if (parNode && isLastNode) 3952 { 3953 node = parNode; // Put carret after parrent tag 3954 } 3955 else 3956 { 3957 return; 3958 } 3959 } 3960 } 3961 3962 this.SetInvisibleTextAfterNode(node); 3963 }, 3964 3965 SaveRange: function(bSetFocus) 3966 { 3967 var range = this.GetRange(false, bSetFocus); 3968 if (range) 3969 { 3970 this.lastCheckedRange = {endOffset: range.endOffset, endContainer: range.endContainer, range: range}; 3971 } 3972 else 3973 { 3974 // Mantis: #66025 3975 setTimeout(BX.proxy(this.SaveRange, this), 0); 3976 } 3977 }, 3978 3979 CheckLastRange: function(range) 3980 { 3981 return this.lastCheckedRange && this.lastCheckedRange.endOffset == range.endOffset && this.lastCheckedRange.endContainer == range.endContainer; 3982 }, 3983 3984 SetInvisibleTextAfterNode: function(node, setCursorBefore) 3985 { 3986 var invis_text = this.editor.util.GetInvisibleTextNode(); 3987 if (node.nextSibling && node.nextSibling.nodeType == 3 && this.editor.util.IsEmptyNode(node.nextSibling)) 3988 { 3989 this.editor.util.ReplaceNode(node.nextSibling, invis_text); 3990 } 3991 else 3992 { 3993 this.editor.util.InsertAfter(invis_text, node); 3994 } 3995 3996 if (setCursorBefore) 3997 { 3998 this.SetBefore(invis_text); 3999 } 4000 else 4001 { 4002 this.SetAfter(invis_text); 4003 } 4004 4005 this.editor.Focus(); 4006 }, 4007 4008 SetInvisibleTextBeforeNode: function(node) 4009 { 4010 var invis_text = this.editor.util.GetInvisibleTextNode(); 4011 if (node.previousSibling && node.previousSibling.nodeType == 3 && this.editor.util.IsEmptyNode(node.previousSibling)) 4012 { 4013 this.editor.util.ReplaceNode(node.previousSibling, invis_text); 4014 } 4015 else 4016 { 4017 node.parentNode.insertBefore(invis_text, node); 4018 } 4019 4020 this.SetBefore(invis_text); 4021 this.editor.Focus(); 4022 }, 4023 4024 GetCommonAncestorForRange: function(range) 4025 { 4026 return range.collapsed ? 4027 range.startContainer : 4028 rangy.dom.getCommonAncestor(range.startContainer, range.endContainer); 4029 } 4030 }; 4031 4032 function NodeMerge(firstNode) 4033 { 4034 this.isElementMerge = (firstNode.nodeType == 1); 4035 this.firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode; 4036 this.firstNode = firstNode; 4037 this.textNodes = [this.firstTextNode]; 4038 } 4039 4040 NodeMerge.prototype = { 4041 DoMerge: function() 4042 { 4043 var 4044 i, len = this.textNodes.length, 4045 textBits = [], textNode, parent, text; 4046 4047 for (i = 0; i < len; ++i) 4048 { 4049 textNode = this.textNodes[i]; 4050 if (this.textNodes[i].nodeType !== 3) 4051 { 4052 return false; 4053 } 4054 4055 parent = textNode.parentNode; 4056 textBits[i] = textNode.data; 4057 if (i) 4058 { 4059 parent.removeChild(textNode); 4060 if (!parent.hasChildNodes()) 4061 parent.parentNode.removeChild(parent); 4062 } 4063 } 4064 this.firstTextNode.data = text = textBits.join(""); 4065 4066 return text; 4067 }, 4068 4069 GetLength: function() 4070 { 4071 var i = this.textNodes.length, len = 0; 4072 while (i--) 4073 len += this.textNodes[i].length; 4074 return len; 4075 } 4076 4077// ToString: function() 4078// { 4079// var textBits = []; 4080// 4081// for (var i = 0, len = this.textNodes.length; i < len; ++i) 4082// textBits[i] = "'" + this.textNodes[i].data + "'"; 4083// 4084// return "[Merge(" + textBits.join(",") + ")]"; 4085// } 4086 }; 4087 4088 function HTMLStyler(editor, tagNames, arStyle, cssClass, normalize) 4089 { 4090 this.editor = editor; 4091 this.document = editor.iframeView.document; 4092 this.tagNames = tagNames || [defaultTagName]; 4093 this.arStyle = arStyle || {}; 4094 this.cssClass = cssClass || ""; 4095 this.similarClassRegExp = null; 4096 this.normalize = normalize; 4097 this.applyToAnyTagName = false; 4098 } 4099 4100 HTMLStyler.prototype = 4101 { 4102 GetStyledParent: function(node, bMatchCss) 4103 { 4104 bMatchCss = bMatchCss !== false; 4105 var cssClassMatch, cssStyleMatch; 4106 while (node) 4107 { 4108 if (node.nodeType == 1) 4109 { 4110 cssStyleMatch = this.CheckCssStyle(node, bMatchCss); 4111 cssClassMatch = this.CheckCssClass(node); 4112 4113 if (BX.util.in_array(node.tagName.toLowerCase(), this.tagNames) && cssClassMatch && cssStyleMatch) 4114 return node; 4115 } 4116 4117 node = node.parentNode; 4118 } 4119 return false;
4120 }, 4121 4122 CheckCssStyle: function(node, bMatch) 4123 { 4124 return this.editor.util.CheckCss(node, this.arStyle, bMatch); 4125 }, 4126 4127 SimplifyNodesWithCss: function(node) 4128 { 4129 var i, parent = node.parentNode; 4130 if (parent.childNodes.length == 1) // container over our node 4131 { 4132 if (node.nodeName == parent.nodeName) 4133 { 4134 for (i in this.arStyle) 4135 { 4136 if (this.arStyle.hasOwnProperty(i) && node.style[i]) 4137 { 4138 parent.style[i] = node.style[i]; 4139 } 4140 } 4141 this.editor.util.ReplaceWithOwnChildren(node); 4142 } 4143 else 4144 { 4145 for (i in this.arStyle) 4146 { 4147 if (this.arStyle.hasOwnProperty(i) && parent.style[i] && node.style[i]) 4148 { 4149 parent.style[i] = ''; 4150 } 4151 } 4152 } 4153 } 4154 }, 4155 4156 CheckCssClass: function(node) 4157 { 4158 return !this.cssClass || (this.cssClass && BX.hasClass(node, this.cssClass)); 4159 }, 4160 4161 // Normalizes nodes after applying a CSS class to a Range. 4162 PostApply: function(textNodes, range) 4163 { 4164 var 4165 i, 4166 firstNode = textNodes[0], 4167 lastNode = textNodes[textNodes.length - 1], 4168 merges = [], 4169 currentMerge, 4170 rangeStartNode = firstNode, 4171 rangeEndNode = lastNode, 4172 rangeStartOffset = 0, 4173 rangeEndOffset = lastNode.length, 4174 textNode, precedingTextNode; 4175 4176 for (i = 0; i < textNodes.length; ++i) 4177 { 4178 textNode = textNodes[i]; 4179 precedingTextNode = this.GetAdjacentMergeableTextNode(textNode.parentNode, false); 4180 if (precedingTextNode) 4181 { 4182 if (!currentMerge) 4183 { 4184 currentMerge = new NodeMerge(precedingTextNode); 4185 merges.push(currentMerge); 4186 } 4187 currentMerge.textNodes.push(textNode); 4188 4189 if (textNode === firstNode) 4190 { 4191 rangeStartNode = currentMerge.firstTextNode; 4192 rangeStartOffset = rangeStartNode.length; 4193 } 4194 4195 if (textNode === lastNode) 4196 { 4197 rangeEndNode = currentMerge.firstTextNode; 4198 rangeEndOffset = currentMerge.GetLength(); 4199 } 4200 } 4201 else 4202 { 4203 currentMerge = null; 4204 } 4205 } 4206 4207 // Test whether the first node after the range needs merging 4208 var nextTextNode = this.GetAdjacentMergeableTextNode(lastNode.parentNode, true); 4209 if (nextTextNode) 4210 { 4211 if (!currentMerge) 4212 { 4213 currentMerge = new NodeMerge(lastNode); 4214 merges.push(currentMerge); 4215 } 4216 currentMerge.textNodes.push(nextTextNode); 4217 } 4218 4219 // Do the merges 4220 if (merges.length) 4221 { 4222 for (i = 0; i < merges.length; ++i) 4223 merges[i].DoMerge(); 4224 4225 // Set the range boundaries 4226 range.setStart(rangeStartNode, rangeStartOffset); 4227 range.setEnd(rangeEndNode, rangeEndOffset); 4228 } 4229 4230 // Simplify elements 4231 textNodes = range.getNodes([3]); 4232 for (i = 0; i < textNodes.length; ++i) 4233 { 4234 textNode = textNodes[i]; 4235 this.SimplifyNodesWithCss(textNode.parentNode); 4236 } 4237 }, 4238 4239 NormalizeNewNode: function(node, range) 4240 { 4241 var 4242 parent = node.parentNode; 4243 4244 if (parent && parent.nodeName !== 'BODY') 4245 { 4246 var 4247 childs = this.GetNonEmptyChilds(parent), 4248 cssStyleMatch = this.CheckCssStyle(parent, false), 4249 cssClassMatch = this.CheckCssClass(parent); 4250 4251 if (childs.length == 1 && parent.nodeName == node.nodeName && cssStyleMatch && cssClassMatch) 4252 { 4253 parent.parentNode.insertBefore(node, parent); 4254 BX.remove(parent); 4255 } 4256 } 4257 4258 return range; 4259 }, 4260 4261 GetNonEmptyChilds: function(node) 4262 { 4263 var 4264 i, 4265 childs = node.childNodes, 4266 res = []; 4267 4268 for (i = 0; i < childs.length; i++) 4269 { 4270 if (childs[i].nodeType == 1 || 4271 (childs[i].nodeType == 3 4272 && childs[i].nodeValue != "" 4273 && childs[i].nodeValue != this.editor.INVISIBLE_SPACE 4274 && !childs[i].nodeValue.match(/^[\s\n\r\t]+$/ig))) 4275 { 4276 res.push(childs[i]); 4277 } 4278 } 4279 return res; 4280 }, 4281 4282 GetAdjacentMergeableTextNode: function(node, forward) 4283 { 4284 var 4285 isTextNode = (node.nodeType == 3), 4286 el = isTextNode ? node.parentNode : node, 4287 adjacentNode, 4288 propName = forward ? "nextSibling" : "previousSibling"; 4289 4290 if (isTextNode) 4291 { 4292 // Can merge if the node's previous/next sibling is a text node 4293 adjacentNode = node[propName]; 4294 if (adjacentNode && adjacentNode.nodeType == 3) 4295 { 4296 return adjacentNode; 4297 } 4298 } 4299 else 4300 {
4301 // TODO: fix it. Now code fails on this example, try to make "22" italic 4302 // <i>11 </i>22<i><br> 4303 // 33 </i><br> 4304 /* 4305 // Compare element with its sibling 4306 adjacentNode = el[propName]; 4307 if (adjacentNode && this.AreElementsMergeable(node, adjacentNode)) 4308 { 4309 4310 return adjacentNode[forward ? "firstChild" : "lastChild"]; 4311 } 4312 */ 4313 } 4314 return null; 4315 }, 4316 4317 AreElementsMergeable: function(el1, el2) 4318 { 4319 return rangy.dom.arrayContains(this.tagNames, (el1.tagName || "").toLowerCase()) 4320 && rangy.dom.arrayContains(this.tagNames, (el2.tagName || "").toLowerCase()) 4321 && el1.className.replace(/\s+/g, " ") == el2.className.replace(/\s+/g, " ") 4322 && this.CompareNodeAttributes(el1, el2); 4323 }, 4324 4325 CompareNodeAttributes: function(el1, el2) 4326 { 4327 if (el1.attributes.length != el2.attributes.length) 4328 return false; 4329 4330 var i, len = el1.attributes.length, attr1, attr2, name; 4331 for (i = 0; i < len; i++) 4332 { 4333 attr1 = el1.attributes[i]; 4334 name = attr1.name; 4335 if (name != "class") 4336 { 4337 attr2 = el2.attributes.getNamedItem(name); 4338 if (attr1.specified != attr2.specified) 4339 return false; 4340 4341 if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) 4342 { 4343 return false; 4344 } 4345 } 4346 } 4347 return true; 4348 }, 4349 4350 CreateContainer: function() 4351 { 4352 var el = this.document.createElement(this.tagNames[0]); 4353 if (this.cssClass) 4354 { 4355 el.className = this.cssClass; 4356 } 4357 if (this.arStyle) 4358 { 4359 for (var i in this.arStyle) 4360 { 4361 if (this.arStyle.hasOwnProperty(i)) 4362 { 4363 el.style[i] = this.arStyle[i]; 4364 } 4365 } 4366 } 4367 return el; 4368 }, 4369 4370 ApplyToTextNode: function(textNode) 4371 { 4372 var parent = textNode.parentNode, i; 4373 4374 if (parent.childNodes.length == 1 && BX.util.in_array(parent.tagName.toLowerCase(), this.tagNames)) 4375 { 4376 if (this.cssClass) 4377 { 4378 BX.addClass(parent, this.cssClass); 4379 } 4380 4381 if (this.arStyle) 4382 { 4383 for (i in this.arStyle) 4384 { 4385 if (this.arStyle.hasOwnProperty(i)) 4386 { 4387 parent.style[i] = this.arStyle[i]; 4388 } 4389 } 4390 } 4391 } 4392 else 4393 { 4394 if (parent.childNodes.length == 1) // container over the text node 4395 { 4396 if (this.cssClass && BX.hasClass(parent, this.cssClass)) 4397 { 4398 BX.removeClass(parent, this.cssClass); 4399 } 4400 4401 if (this.arStyle) 4402 { 4403 for (i in this.arStyle) 4404 { 4405 if (this.arStyle.hasOwnProperty(i) && parent.style[i]) 4406 { 4407 parent.style[i] = ''; 4408 } 4409 } 4410 } 4411 } 4412 4413 var el = this.CreateContainer(); 4414 textNode.parentNode.insertBefore(el, textNode); 4415 el.appendChild(textNode); 4416 } 4417 }, 4418 4419 IsRemovable: function(el) 4420 { 4421 return rangy.dom.arrayContains(this.tagNames, el.tagName.toLowerCase()) && BX.util.trim(el.className) == this.cssClass; 4422 }, 4423 4424 IsAvailableTextNodeParent: function(node) 4425 { 4426 return node && node.nodeName && 4427 node.nodeName !== 'OL' && node.nodeName !== 'UL' && node.nodeName !== 'MENU' && // LLIST 4428 node.nodeName !== 'TBODY' && node.nodeName !== 'TFOOT' && node.nodeName !== 'THEAD' && node.nodeName !== 'TABLE' && // TABLE 4429 node.nodeName !== 'DL'; 4430 }, 4431 4432 UndoToTextNode: function(textNode, range, styledParent) 4433 { 4434 if (!range.containsNode(styledParent)) 4435 { 4436 // Split out the portion of the parent from which we can remove the CSS class 4437 var parentRange = range.cloneRange(); 4438 parentRange.selectNode(styledParent); 4439 4440 4441 BX.isParentForNode(styledParent, range.endContainer) 4442 if ( 4443 range.endContainer.nodeName !== 'BODY' && 4444 parentRange.isPointInRange(range.endContainer, range.endOffset) && 4445 this.editor.util.IsSplitPoint(range.endContainer, range.endOffset) && 4446 BX.isParentForNode(styledParent, range.endContainer) 4447 ) 4448 { 4449 this.editor.util.SplitNodeAt(styledParent, range.endContainer, range.endOffset); 4450 range.setEndAfter(styledParent); 4451 } 4452 4453 if ( 4454 range.startContainer.nodeName !== 'BODY' && 4455 parentRange.isPointInRange(range.startContainer, range.startOffset) && 4456 this.editor.util.IsSplitPoint(range.startContainer, range.startOffset) && 4457 BX.isParentForNode(styledParent, range.startContainer) 4458 ) 4459 { 4460 styledParent = this.editor.util.SplitNodeAt(styledParent, range.startContainer, range.startOffset); 4461 } 4462 } 4463 4464 if (styledParent && styledParent.nodeName != 'BODY' && this.IsRemovable(styledParent)) 4465 { 4466 if (this.arStyle) 4467 { 4468 for (var i in this.arStyle) 4469 { 4470 if (this.arStyle.hasOwnProperty(i) && styledParent.style[i]) 4471 { 4472 styledParent.style[i] = ''; 4473 } 4474 } 4475 } 4476 4477 if (!styledParent.style.cssText || BX.util.trim(styledParent.style.cssText) === '') 4478 { 4479 this.editor.util.ReplaceWithOwnChildren(styledParent); 4480 } 4481 else if (this.tagNames.length > 1 || this.tagNames[0] !== 'span') 4482 { 4483 this.editor.util.RenameNode(styledParent, "span"); 4484 } 4485 } 4486 }, 4487 4488 ApplyToRange: function(range) 4489 { 4490 var textNodes = range.getNodes([3]); 4491 4492 if (!textNodes.length) 4493 { 4494 try { 4495 var node = this.CreateContainer(); 4496 range.surroundContents(node); 4497 range = this.NormalizeNewNode(node, range); 4498 this.SelectNode(range, node); 4499 return range; 4500 } catch(e) {} 4501 } 4502 4503 range.splitBoundaries(); 4504 textNodes = range.getNodes([3]); 4505 4506 if (!textNodes.length && range.collapsed && range.startContainer == range.endContainer) 4507 { 4508 var inv = this.editor.util.GetInvisibleTextNode(); 4509 this.editor.selection.InsertNode(inv); 4510 textNodes = [inv]; 4511 } 4512 4513 if (textNodes.length) 4514 { 4515 var textNode; 4516 4517 for (var i = 0, len = textNodes.length; i < len; ++i) 4518 { 4519 textNode = textNodes[i]; 4520 if (!this.GetStyledParent(textNode) && this.IsAvailableTextNodeParent(textNode.parentNode)) 4521 {
4522 this.ApplyToTextNode(textNode); 4523 } 4524 } 4525 4526 range.setStart(textNodes[0], 0); 4527 textNode = textNodes[textNodes.length - 1]; 4528 range.setEnd(textNode, textNode.length); 4529 4530 if (this.normalize) 4531 { 4532 this.PostApply(textNodes, range); 4533 } 4534 } 4535 return range; 4536 }, 4537 4538 UndoToRange: function(range, bMatchCss) 4539 { 4540 var 4541 textNodes = range.getNodes([3]), 4542 textNode, 4543 styledParent; 4544 4545 bMatchCss = bMatchCss !== false; 4546 4547 if (textNodes.length) 4548 { 4549 range.splitBoundaries(); 4550 textNodes = range.getNodes([3]); 4551 } 4552 else 4553 { 4554 var node = this.editor.util.GetInvisibleTextNode(); 4555 range.insertNode(node); 4556 range.selectNode(node); 4557 textNodes = [node]; 4558 } 4559 4560 var i, len, sorted = []; 4561 for (i = 0, len = textNodes.length; i < len; i++) 4562 { 4563 sorted.push({node: textNodes[i], nesting: this.GetNodeNesting(textNodes[i])}); 4564 } 4565 4566 sorted = sorted.sort(function(a, b){return b.nesting - a.nesting}); 4567 4568 for (i = 0, len = sorted.length; i < len; i++) 4569 { 4570 textNode = sorted[i].node; 4571 styledParent = this.GetStyledParent(textNode, bMatchCss); 4572 if (styledParent) 4573 { 4574 this.UndoToTextNode(textNode, range, styledParent); 4575 range = this.editor.selection.GetRange(); 4576 } 4577 } 4578 4579 if (len == 1) 4580 { 4581 this.SelectNode(range, textNodes[0]); 4582 } 4583 else 4584 { 4585 range.setStart(textNodes[0], 0); 4586 range.setEnd(textNodes[textNodes.length - 1], textNodes[textNodes.length - 1].length); 4587 this.editor.selection.SetSelection(range); 4588 4589 if (this.normalize) 4590 { 4591 this.PostApply(textNodes, range); 4592 } 4593 } 4594 4595 return range; 4596 }, 4597 4598 // Node dom offset 4599 GetNodeNesting: function(node) 4600 { 4601 return this.editor.util.GetNodeDomOffset(node); 4602 }, 4603 4604 SelectNode: function(range, node) 4605 { 4606 var 4607 isElement = node.nodeType === 1, 4608 canHaveHTML = "canHaveHTML" in node ? node.canHaveHTML : true, 4609 content = isElement ? node.innerHTML : node.data, 4610 isEmpty = (content === "" || content === this.editor.INVISIBLE_SPACE); 4611 4612 if (isEmpty && isElement && canHaveHTML) 4613 { 4614 // Make sure that caret is visible in node by inserting a zero width no breaking space 4615 try {node.innerHTML = this.editor.INVISIBLE_SPACE;} catch(e) {} 4616 } 4617 4618 range.selectNodeContents(node); 4619 if (isEmpty && isElement) 4620 { 4621 range.collapse(false); 4622 } 4623 else if (isEmpty) 4624 { 4625 range.setStartAfter(node); 4626 range.setEndAfter(node); 4627 } 4628 }, 4629 4630 GetTextSelectedByRange: function(textNode, range) 4631 { 4632 var textRange = range.cloneRange(); 4633 textRange.selectNodeContents(textNode); 4634 4635 var intersectionRange = textRange.intersection(range); 4636 var text = intersectionRange ? intersectionRange.toString() : ""; 4637 textRange.detach(); 4638 4639 return text; 4640 }, 4641 4642 IsAppliedToRange: function(range, bMatchCss) 4643 { 4644 var 4645 parents = [], 4646 parent, 4647 textNodes = range.getNodes([3]); 4648 bMatchCss = bMatchCss !== false; 4649 4650 if (!textNodes.length) 4651 { 4652 parent = this.GetStyledParent(range.startContainer, bMatchCss); 4653 return parent ? [parent] : false; 4654 } 4655 4656 var i, selectedText; 4657 for (i = 0; i < textNodes.length; ++i) 4658 { 4659 selectedText = this.GetTextSelectedByRange(textNodes[i], range); 4660 parent = this.GetStyledParent(textNodes[i], bMatchCss); 4661 if (selectedText != "" && !parent) 4662 { 4663 return false; 4664 } 4665 else 4666 { 4667 parents.push(parent); 4668 } 4669 } 4670 return parents; 4671 }, 4672 4673 ToggleRange: function(range) 4674 { 4675 return this.IsAppliedToRange(range) ? this.UndoToRange(range) : this.ApplyToRange(range); 4676 } 4677 }; 4678 4679 function BXEditorUndoManager(editor) 4680 { 4681 this.editor = editor; 4682 this.history = [this.editor.iframeView.GetValue()]; 4683 this.position = 1; 4684 this.document = editor.sandbox.GetDocument(); 4685 this.historyLength = 30; 4686 BX.addCustomEvent(this.editor, 'OnIframeReInit', BX.proxy(function(){this.document = this.editor.sandbox.GetDocument();}, this)); 4687 this.Init(); 4688 } 4689 4690 BXEditorUndoManager.prototype = { 4691 Init: function() 4692 { 4693 var _this = this; 4694 BX.addCustomEvent(this.editor, "OnHtmlContentChangedByControl", BX.delegate(this.Transact, this)); 4695 BX.addCustomEvent(this.editor, "OnIframeNewWord", BX.delegate(this.Transact, this)); 4696 BX.addCustomEvent(this.editor, "OnIframeKeyup", BX.delegate(this.Transact, this)); 4697 BX.addCustomEvent(this.editor, "OnBeforeCommandExec", function(isContentAction) 4698 { 4699 if (isContentAction) 4700 { 4701 _this.Transact(); 4702 } 4703 }); 4704 4705 // CTRL+Z and CTRL+Y and handle DEL & BACKSPACE 4706 BX.addCustomEvent(this.editor, "OnIframeKeydown", BX.proxy(this.Keydown, this)); 4707 }, 4708 4709 Keydown: function(e, keyCode, command, selectedNode) 4710 { 4711 if ((e.ctrlKey || e.metaKey) && !e.altKey) 4712 { 4713 var 4714 isUndo = keyCode === this.editor.KEY_CODES['z'] && !e.shiftKey, 4715 isRedo = (keyCode === this.editor.KEY_CODES['z'] && e.shiftKey) || (keyCode === this.editor.KEY_CODES['y']); 4716 4717 if (isUndo) 4718 { 4719 this.Undo(); 4720 return BX.PreventDefault(e); 4721 } 4722 else if (isRedo) 4723 { 4724 this.Redo(); 4725 return BX.PreventDefault(e); 4726 } 4727 } 4728 4729 if (keyCode !== this.lastKey) 4730 { 4731 if (keyCode === this.editor.KEY_CODES['backspace'] || keyCode === this.editor.KEY_CODES['delete']) 4732 { 4733 this.Transact(); 4734 } 4735 this.lastKey = keyCode; 4736 } 4737 }, 4738 4739 Transact: function() 4740 { 4741 var 4742 previousHtml = this.history[this.position - 1], 4743 currentHtml = this.editor.iframeView.GetValue(); 4744 4745 if (currentHtml !== previousHtml) 4746 { 4747 var length = this.history.length = this.position; 4748 if (length > this.historyLength) 4749 { 4750 this.history.shift(); 4751 this.position--; 4752 } 4753 4754 this.position++; 4755 this.history.push(currentHtml); 4756 4757 this.CheckControls(); 4758 } 4759 }, 4760 4761 Undo: function() 4762 { 4763 if (this.position > 1) 4764 { 4765 this.Transact(); 4766 this.position--; 4767 this.Set(this.history[this.position - 1]); 4768 this.editor.On("OnUndo"); 4769 this.CheckControls(); 4770 } 4771 }, 4772 4773 Redo: function() 4774 { 4775 if (this.position < this.history.length) 4776 { 4777 this.position++; 4778 this.Set(this.history[this.position - 1]); 4779 this.editor.On("OnRedo"); 4780 this.CheckControls(); 4781 } 4782 }, 4783 4784 Set: function(html) 4785 { 4786 this.editor.iframeView.SetValue(html); 4787 this.editor.Focus(true); 4788 }, 4789 4790 CheckControls: function() 4791 { 4792 this.editor.On("OnEnableUndo", [this.position > 1]); 4793 this.editor.On("OnEnableRedo", [this.position < this.history.length]); 4794 } 4795 }; 4796 4797 function BXStyles(editor) 4798 { 4799 this.editor = editor; 4800 this.arStyles = {}; 4801 this.sStyles = ''; 4802 } 4803 4804 BXStyles.prototype = { 4805 GetIframe: function(styles) 4806 { 4807 if (!this.cssIframe) 4808 { 4809 this.cssIframe = this.CreateIframe(styles); 4810 } 4811 return this.cssIframe; 4812 }, 4813 4814 CreateIframe: function(styles) 4815 { 4816 this.cssIframe = document.body.appendChild(BX.create("IFRAME", {props: {className: "bx-editor-css-iframe"}})); 4817 this.iframeDocument = this.cssIframe.contentDocument || this.cssIframe.contentWindow.document; 4818 this.iframeDocument.open("text/html", "replace");
4819 this.iframeDocument.write('<!DOCTYPE html><html><head><style type="text/css" data-bx-template-style="Y">' + styles + '</style></head><body></body></html>'); 4820 this.iframeDocument.close(); 4821 return this.cssIframe; 4822 }, 4823 4824 GetCSS: function(templateId, styles, templatePath, filter) 4825 { 4826 if (!this.arStyles[templateId]) 4827 { 4828 if (!this.cssIframe) 4829 { 4830 this.cssIframe = this.CreateIframe(styles); 4831 } 4832 else 4833 { 4834 var 4835 i, 4836 doc = this.iframeDocument, 4837 head = doc.head || doc.getElementsByTagName('HEAD')[0], 4838 styleNodes = head.getElementsByTagName('STYLE'); 4839 4840 // Clean old template styles 4841 for (i = 0; i < styleNodes.length; i++) 4842 { 4843 if (styleNodes[i].getAttribute('data-bx-template-style') == 'Y') 4844 BX.cleanNode(styleNodes[i], true); 4845 } 4846 4847 // Add new node in the iframe head 4848 if (styles) 4849 { 4850 head.appendChild(BX.create('STYLE', {props: {type: 'text/css'}, text: styles}, doc)).setAttribute('data-bx-template-style', 'Y'); 4851 } 4852 } 4853 this.arStyles[templateId] = this.ParseCss(); 4854 } 4855 4856 var res = this.arStyles[templateId]; 4857 4858 if (filter) 4859 { 4860 var filteredRes = [], tag; 4861 if (typeof filter != 'object' ) 4862 { 4863 filter = [filter]; 4864 } 4865 filter.push('DEFAULT'); 4866 for (i = 0; i < filter.length; i++) 4867 { 4868 tag = filter[i]; 4869 if (res[tag] && typeof res[tag] == 'object') 4870 { 4871 filteredRes = filteredRes.concat(res[tag]); 4872 } 4873 } 4874 res = filteredRes; 4875 } 4876 4877 return res; 4878 }, 4879 4880 ParseCss: function() 4881 { 4882 var 4883 doc = this.iframeDocument, 4884 arAllSt = [], 4885 result = {}, 4886 rules, 4887 cssTag, arTags, i, j, k, 4888 t1, t2, l1, l2, l3; 4889 4890 if(!doc.styleSheets) 4891 { 4892 return result; 4893 } 4894 4895 var 4896 x1 = doc.styleSheets, 4897 stylesDescription = this.editor.GetStylesDescription(); 4898 4899 for(i = 0, l1 = x1.length; i < l1; i++) 4900 { 4901 rules = (x1[i].rules ? x1[i].rules : x1[i].cssRules); 4902 for(j = 0, l2 = rules.length; j < l2; j++) 4903 { 4904 if (rules[j].type != rules[j].STYLE_RULE) 4905 { 4906 continue; 4907 } 4908 4909 cssTag = rules[j].selectorText; 4910 arTags = cssTag.split(","); 4911 for(k = 0, l3 = arTags.length; k < l3; k++) 4912 { 4913 t1 = arTags[k].split(" "); 4914 t1 = t1[t1.length - 1].trim(); 4915 4916 if(t1.substr(0, 1) == '.') 4917 { 4918 t1 = t1.substr(1); 4919 t2 = 'DEFAULT'; 4920 } 4921 else 4922 { 4923 t2 = t1.split("."); 4924 t1 = (t2.length > 1) ? t2[1] : ''; 4925 t2 = t2[0].toUpperCase(); 4926 } 4927 4928 if(!arAllSt[t1]) 4929 { 4930 arAllSt[t1] = true; 4931 if(!result[t2]) 4932 { 4933 result[t2] = []; 4934 } 4935 4936 result[t2].push({ 4937 className: t1, 4938 classTitle: stylesDescription[t1] || null, 4939 original: arTags[k], 4940 cssText: rules[j].style.cssText 4941 }); 4942 } 4943 } 4944 } 4945 } 4946 return result; 4947 } 4948 }; 4949 4950 4951 // Parse rules 4952 /** 4953 * Full HTML5 compatibility rule set 4954 * These rules define which tags and css classes are supported and which tags should be specially treated. 4955 * 4956 * Examples based on this rule set: 4957 * 4958 * <a href="http://foobar.com">foo</a> 4959 * ... becomes ... 4960 * <a href="http://foobar.com" target="_blank" rel="nofollow">foo</a> 4961 * 4962 * <img align="left" src="http://foobar.com/image.png"> 4963 * ... becomes ... 4964 * <img class="wysiwyg-float-left" src="http://foobar.com/image.png" alt=""> 4965 * 4966 * <div>foo<script>alert(document.cookie)</script></div> 4967 * ... becomes ... 4968 * <div>foo</div> 4969 * 4970 * <marquee>foo</marquee> 4971 * ... becomes ... 4972 * <span>foo</marquee> 4973 * 4974 * foo <br clear="both"> bar 4975 * ... becomes ... 4976 * foo <br class="wysiwyg-clear-both"> bar 4977 * 4978 * <div>hello <iframe src="http://google.com"></iframe></div> 4979 * ... becomes ... 4980 * <div>hello </div> 4981 * 4982 * <center>hello</center> 4983 * ... becomes ... 4984 * <div class="wysiwyg-text-align-center">hello</div> 4985 */ 4986 __BXHtmlEditorParserRules = { 4987 /** 4988 * CSS Class white-list 4989 * Following css classes won't be removed when parsed by the parser 4990 */ 4991 classes: {}, 4992 4993 "tags": { 4994 "b": {clean_empty: true}, 4995 "strong": {clean_empty: true}, 4996 "i": {clean_empty: true}, 4997 "em": {clean_empty: true}, 4998 "u": {clean_empty: true}, 4999 "del": {clean_empty: true}, 5000 "s": {rename_tag: "del"}, 5001 "strike": {rename_tag: "del"}, 5002 5003 // headers 5004 "h1": {}, 5005 "h2": {}, 5006 "h3": {}, 5007 "h4": {}, 5008 "h5": {}, 5009 "h6": {}, 5010 5011 // popular tags 5012 "span": {clean_empty: true}, 5013 "p": {}, 5014 "br": {}, 5015 "div": {}, 5016 "hr": {}, 5017 "nobr": {}, 5018 "code": {}, 5019 "section": {}, 5020 "figure": {}, 5021 "figcaption": {}, 5022 "fieldset": {}, 5023 "legend": {}, 5024 5025 // Lists 5026 "menu": {rename_tag: "ul"}, // ?? 5027 "ol": {}, 5028 "ul": {}, 5029 "li": {}, 5030 "pre": {}, 5031 5032 // Table 5033 "table": {}, 5034 "tr": {}, 5035 "td": { 5036 "check_attributes": { 5037 "rowspan": "numbers", 5038 "colspan": "numbers" 5039 } 5040 }, 5041 "tbody": {}, 5042 "tfoot": {}, 5043 "thead": {}, 5044 "th": { 5045 "check_attributes": { 5046 "rowspan": "numbers", 5047 "colspan": "numbers" 5048 } 5049 }, 5050 "caption": {}, 5051 "col": {}, 5052 "colgroup": {}, 5053 5054 // Definitions // <dl>, <dt>, <dd> 5055 "dl": {rename_tag: ""}, 5056 "dd": {rename_tag: ""}, 5057 "dt": {rename_tag: ""}, 5058 5059 "iframe": {}, 5060 "noindex": {}, 5061 5062 "font": { 5063 rename_tag: "span", 5064 convert_attributes: { 5065 face: 'fontFamily', 5066 size: 'fontSize', 5067 color: 'color' 5068 }, 5069 replace_with_children: 1 5070 }, 5071 5072 "embed": {}, 5073 "noembed": {}, 5074 "object": {}, 5075 "param": {}, 5076 5077 "sup": {}, 5078 "sub": {}, 5079 5080 "address": {}, 5081 "nav": {}, 5082 "aside": {}, 5083 "article": {}, 5084 "main": {}, 5085 "acronym": {}, 5086 "abbr": {}, 5087 "label": {}, 5088 "time": {}, 5089 5090 "small": {}, 5091 "big": {}, 5092 5093 "details": {}, 5094 "summary": {}, 5095 "footer": {}, 5096 5097 "video": {}, 5098 "source": {}, 5099 "audio": {}, 5100 "nofollow": {}, 5101 5102 // tags to remove 5103 "title": {remove: 1}, 5104 "area": {remove: 1}, 5105 "command": {remove: 1}, 5106 "noframes": {remove: 1}, 5107 "bgsound": {remove: 1}, 5108 "basefont": {remove: 1}, 5109 "head": {remove: 1}, 5110 "track": {remove: 1}, 5111 "wbr": {remove: 1}, 5112 "noscript": {remove: 1}, 5113 "svg": {remove: 1}, 5114 "keygen": {remove: 1}, 5115 "meta": {remove: 1}, 5116 "isindex": {remove: 1}, 5117 "base": {remove: 1}, 5118 "canvas": {remove: 1}, 5119 "applet": {remove: 1}, 5120 "spacer": {remove: 1}, 5121 "frame": {remove: 1}, 5122 "style": {remove: 1}, 5123 "device": {remove: 1}, 5124 "xml": {remove: 1}, 5125 "nextid": {remove: 1}, 5126 "link": {remove: 1}, 5127 "script": {remove: 1}, 5128 "comment": {remove: 1}, 5129 "frameset": {remove: 1}, 5130 5131 // Tags to rename 5132 // to DIV 5133 "multicol": {rename_tag: "div"}, 5134 "map": {rename_tag: "div"}, 5135 "body": {rename_tag: "div"}, 5136 "html": {rename_tag: "div"}, 5137 "hgroup": {rename_tag: "div"}, 5138 "listing": {rename_tag: "div"}, 5139 "header": {rename_tag: "div"}, 5140 // to SPAN
5141 "rt": {rename_tag: "span"}, 5142 "xmp": {rename_tag: "span"}, 5143 "bdi": {rename_tag: "span"}, 5144 "progress": {rename_tag: "span"}, 5145 "dfn": {rename_tag: "span"}, 5146 "rb": {rename_tag: "span"}, 5147 "mark": {rename_tag: "span"}, 5148 "output": {rename_tag: "span"}, 5149 "marquee": {rename_tag: "span"}, 5150 "rp": {rename_tag: "span"}, 5151 5152 "var": {rename_tag: "span"}, 5153 "tt": {rename_tag: "span"}, 5154 "blink": {rename_tag: "span"}, 5155 "plaintext": {rename_tag: "span"}, 5156 "kbd": {rename_tag: "span"}, 5157 "meter": {rename_tag: "span"}, 5158 "datalist": {rename_tag: "span"}, 5159 "samp": {rename_tag: "span"}, 5160 "bdo": {rename_tag: "span"}, 5161 "ruby": {rename_tag: "span"}, 5162 "ins": {rename_tag: "span"}, 5163 "optgroup": {rename_tag: "span"}, 5164 5165 // Form elements 5166 "form": {}, 5167 "option": {}, 5168 "select": {}, 5169 "textarea": {}, 5170 "button": {}, 5171 "input": {}, 5172 5173 "dir": {rename_tag: "ul"}, 5174 "a": {}, 5175 "img": { 5176 "check_attributes": { 5177 "width": "numbers", 5178 "alt": "alt", 5179 "src": "url", 5180 "height": "numbers" 5181 }, 5182 "add_class": { 5183 "align": "align_img" 5184 } 5185 }, 5186 "q": { 5187 "check_attributes": { 5188 "cite": "url" 5189 } 5190 }, 5191 "blockquote": { 5192 "check_attributes": { 5193 "cite": "url" 5194 } 5195 }, 5196 "center": { 5197 rename_tag: "div", 5198 add_css: 5199 { 5200 textAlign : 'center' 5201 } 5202 }, 5203 "cite": {} 5204 } 5205 }; 5206 5207})(window);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.