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