Current Path : /var/www/www-root/data/www/www.monolith-realty.ru/bitrix/js/fileman/html_editor/ |
Current File : /var/www/www-root/data/www/www.monolith-realty.ru/bitrix/js/fileman/html_editor/html-editor.js |
/** * Bitrix HTML Editor 3.0 * Date: 24.04.13 * Time: 4:23 */ (function(window) { var __BXHtmlEditorParserRules; function BXEditor(config) { // Container contains links to dom elements this.InitUtil(); this.dom = { // cont - // iframeCont - // textareaCont - // iframe - // textarea - }; this.bxTags = {}; this.EMPTY_IMAGE_SRC = '/bitrix/images/1.gif'; this.HTML5_TAGS = [ "abbr", "article", "aside", "audio", "bdi", "canvas", "command", "datalist", "details", "figcaption", "figure", "footer", "header", "hgroup", "keygen", "mark", "meter", "nav", "output", "progress", "rp", "rt", "ruby", "svg", "section", "source", "summary", "time", "track", "video", "wbr" ]; this.BLOCK_TAGS = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "BLOCKQUOTE", "DIV", "SECTION", "PRE"]; this.NESTED_BLOCK_TAGS = ["BLOCKQUOTE", "DIV"]; this.TABLE_TAGS = ["TD", "TR", "TH", "TABLE", "TBODY", "CAPTION", "COL", "COLGROUP", "TFOOT", "THEAD"]; this.BBCODE_TAGS = ['P', 'U', 'TABLE', 'TR', 'TD', 'TH', 'IMG', 'A', 'CENTER', 'LEFT', 'RIGHT', 'JUSTIFY']; this.HTML_ENTITIES = ['¡','¢','£','¤','¥','¦','§','¨','©','ª','«','¬','®','¯','°','±','²','³','´','µ','¶','·','¸','¹','º','»','¼','½','¾','¿','À','Á','Â','Ã','Ä','Å','Æ','Ç','È','É','Ê','Ë','Ì','Í','Î','Ï','Ð','Ñ','Ò','Ó','Ô','Õ','Ö','×','Ø','Ù','Ú','Û','Ü','Ý','Þ','ß','à','á','â','ã','ä','å','æ','ç','è','é','ê','ë','ì','í','î','ï','ð','ñ','ò','ó','ô','õ','ö','÷','ø','ù','ú','û','ü','ý','þ','ÿ','Œ','œ','Š','š','Ÿ','ˆ','˜','–','—','‘','’','‚','“','”','„','†','‡','‰','‹','›','€','Α','Β','Γ','Δ','Ε','Ζ','Η','Θ','Ι','Κ','Λ','Μ','Ν','Ξ','Ο','Π','Ρ','Σ','Τ','Υ','Φ','Χ','Ψ','Ω','α','β','γ','δ','ε','ζ','η','θ','ι','κ','λ','μ','ν','ξ','ο','π','ρ','ς','σ','τ','υ','φ','χ','ψ','ω','•','…','′','″','‾','⁄','™','←','↑','→','↓','↔','∂','∑','−','√','∞','∫','≈','≠','≡','≤','≥','◊','♠','♣','♥']; if(!BX.browser.IsIE()) { this.HTML_ENTITIES = this.HTML_ENTITIES.concat(['ϑ','ϒ','ϖ','℘','ℑ','ℜ','ℵ','↵','⇐','⇑','⇒','⇓','⇔','∀','∃','∅','∇','∈','∉','∋','∏','∗','∝','∠','∧','∨','∩','∪','∴','∼','≅','⊂','⊃','⊄','⊆','⊇','⊕','⊗','⊥','⋅','⌈','⌉','⌊','⌋','⟨','⟩','♦']); } this.SHORTCUTS = { "66": "bold", // B "73": "italic", // I "85": "underline" // U }; this.KEY_CODES = { 'backspace': 8, 'enter': 13, 'escape': 27, 'space': 32, 'delete': 46, 'left': 37, 'right': 39, 'up': 38, 'down': 40, 'z': 90, 'y': 89, 'shift': 16, 'ctrl': 17, 'alt': 18, 'cmd': 91, // 93, 224, 17 Browser dependent 'cmdRight': 93, // 93, 224, 17 Browser dependent? 'pageUp': 33, 'pageDown': 34 }; this.INVISIBLE_SPACE = "\uFEFF"; this.INVISIBLE_CURSOR = "\u2060"; this.NORMAL_WIDTH = 1020; this.MIN_WIDTH = 700; this.MIN_HEIGHT = 100; this.MAX_HANDLED_FORMAT_LENGTH = 50000; // Max length of code which will be formated this.MAX_HANDLED_FORMAT_TIME = 500; this.iframeCssText = ''; // Here some controls can add additional css this.InitConfig(this.CheckConfig(config)); if (window.LHEPostForm) { const editorHandler = window.LHEPostForm.getHandler(this.config.id); if (editorHandler) { BX.addCustomEvent( editorHandler.eventNode, 'OnShowLHE', (show, setFocus, FCFormId) => { if (FCFormId) { this.iframeView.setCopilotContextParameters(FCFormId); } }, ); } } if (!config.lazyLoad) { this.Init(); } } BXEditor.prototype = { Init: function() { if (this.inited) return; // Parser this.parser = new BXHtmlEditor.BXEditorParser(this); this.On("OnEditorInitedBefore", [this]); if (!this.BuildSceleton()) return; this.HTMLStyler = HTMLStyler; // Textarea this.dom.textarea = this.dom.textareaCont.appendChild(BX.create("TEXTAREA", {props: {className: "bxhtmled-textarea"}})); this.dom.pValueInput = BX('bxed_' + this.id); if (!this.dom.pValueInput) { this.dom.pValueInput = this.dom.cont.appendChild(BX.create("INPUT", {props: {type: "hidden", id: 'bxed_' + this.id, name: this.config.inputName}})); } this.dom.pValueInput.value = this.config.content; this.dom.form = this.dom.textarea.form || false; this.document = null; // Protected iframe for wysiwyg this.sandbox = this.CreateIframeSandBox(); var iframe = this.sandbox.GetIframe(); iframe.style.width = '100%'; // TODO: height in pixels iframe.style.height = '100%'; // Views: if (this.config.content) { this.dom.textareaCont.style.opacity = 0; this.dom.iframeCont.style.opacity = 0; } // 1. TextareaView this.textareaView = new BXEditorTextareaView(this, this.dom.textarea, this.dom.textareaCont); // 2. IframeView this.iframeView = new BXEditorIframeView(this, this.dom.textarea, this.dom.iframeCont); // 3. Syncronizer this.synchro = new BXEditorViewsSynchro(this, this.textareaView, this.iframeView); if (this.bbCode) { this.bbParser = new BXHtmlEditor.BXEditorBbCodeParser(this); } // Php parser this.phpParser = new BXHtmlEditor.BXEditorPhpParser(this); this.components = new BXHtmlEditor.BXEditorComponents(this); this.styles = new BXStyles(this); // Toolbar this.overlay = new BXHtmlEditor.Overlay(this); this.BuildToolbar(); // Taskbars if (this.showTaskbars) { this.taskbarManager = new BXHtmlEditor.TaskbarManager(this, true); // Components if (this.showComponents) { this.componentsTaskbar = new BXHtmlEditor.ComponentsControl(this); this.taskbarManager.AddTaskbar(this.componentsTaskbar); } // Snippets if (this.showSnippets) { this.snippets = new BXHtmlEditor.BXEditorSnippets(this); this.snippetsTaskbar = new BXHtmlEditor.SnippetsControl(this, this.taskbarManager); this.taskbarManager.AddTaskbar(this.snippetsTaskbar); } this.taskbarManager.ShowTaskbar(this.showComponents ? this.componentsTaskbar.GetId() : this.snippetsTaskbar.GetId()); } else { this.dom.taskbarCont.style.display = 'none'; } // Context menu this.contextMenu = new BXHtmlEditor.ContextMenu(this); if (this.config.showNodeNavi) { this.nodeNavi = new BXHtmlEditor.NodeNavigator(this); this.nodeNavi.Show(); } this.pasteControl = new BXHtmlEditor.PasteControl(this); this.InitEventHandlers(); this.ResizeSceleton(); // Restore taskbar mode from user settings if (this.showTaskbars && this.config.taskbarShown) { this.taskbarManager.Show(false); } this.inited = true; this.On("OnEditorInitedAfter", [this]); if (!this.CheckBrowserCompatibility()) { this.dom.cont.parentNode.insertBefore(BX.create("DIV", {props: {className: "bxhtmled-warning"}, text: BX.message('BXEdInvalidBrowser')}), this.dom.cont); } this.InitImageUploader(); this.Show(); BX.onCustomEvent(BXHtmlEditor, 'OnEditorCreated', [this]); }, InitConfig: function(config) { this.config = config; this.id = this.config.id; this.dialogs = {}; this.bbCode = !!this.config.bbCode; this.config.splitVertical = !!this.config.splitVertical; this.config.splitRatio = parseFloat(this.config.splitRatio); this.config.view = this.config.view || 'wysiwyg'; this.config.taskbarShown = !!this.config.taskbarShown; this.config.taskbarWidth = parseInt(this.config.taskbarWidth); this.config.showNodeNavi = this.config.showNodeNavi !== false; this.config.setFocusAfterShow = this.config.setFocusAfterShow !== false; this.cssCounter = 0; this.iframeCssText = this.config.iframeCss; this.fileDialogsLoaded = this.bbCode || this.config.useFileDialogs === false; if (this.config.bbCodeTags && this.bbCode) { this.BBCODE_TAGS = this.config.bbCodeTags; } if (this.config.minBodyWidth) this.MIN_WIDTH = parseInt(this.config.minBodyWidth); if (this.config.minBodyHeight) this.MIN_HEIGHT = parseInt(this.config.minBodyHeight); if (this.config.normalBodyWidth) this.NORMAL_WIDTH = parseInt(this.config.normalBodyWidth); this.normalWidth = this.NORMAL_WIDTH; if (!this.config.height) this.config.height = this.MIN_HEIGHT; if (!this.config.width) this.config.width = this.MIN_WIDTH; if (this.config.smiles) { this.smilesIndex = {}; this.sortedSmiles = []; var i, smile, j, arCodes; for(i = 0; i < this.config.smiles.length; i++) { smile = this.config.smiles[i]; if (!smile['codes'] || smile['codes'] == smile['code']) { this.smilesIndex[this.config.smiles[i].code] = smile; this.sortedSmiles.push(smile); } else if(smile['codes'].length > 0) { arCodes = smile['codes'].split(' '); for(j = 0; j < arCodes.length; j++) { this.smilesIndex[arCodes[j]] = smile; this.sortedSmiles.push({name: smile.name, path: smile.path, code: arCodes[j]}); } } } this.sortedSmiles = this.sortedSmiles.sort(function(a, b){return b.code.length - a.code.length;}); } this.allowPhp = !!this.config.allowPhp; // Limited Php Access - when user can only move or delete php code or change component params this.lpa = !this.config.allowPhp && this.config.limitPhpAccess; this.templateId = this.config.templateId; this.componentFilter = this.config.componentFilter; this.showSnippets = this.config.showSnippets !== false; this.showComponents = this.config.showComponents !== false && (this.allowPhp || this.lpa); this.showTaskbars = this.config.showTaskbars !== false && (this.showSnippets || this.showComponents); this.templates = {}; this.templates[this.templateId] = this.config.templateParams; }, InitEventHandlers: function() { var _this = this; BX.bind(this.dom.cont, 'click', BX.proxy(this.OnClick, this)); BX.bind(this.dom.cont, 'mousedown', BX.proxy(this.OnMousedown, this)); BX.bind(window, 'resize', function(){_this.ResizeSceleton();}); if (BX.adminMenu) { BX.addCustomEvent(BX.adminMenu, 'onAdminMenuResize', function(){_this.ResizeSceleton();}); } BX.addCustomEvent(this, "OnIframeFocus", function() { _this.bookmark = null; if (_this.statusInterval) { clearInterval(_this.statusInterval); } _this.statusInterval = setInterval(BX.proxy(_this.CheckCurrentStatus, _this), 500); }); BX.addCustomEvent(this, "OnIframeBlur", function() { _this.bookmark = null; if (_this.statusInterval) { clearInterval(_this.statusInterval); } _this.iframeView.stopBugusScroll = false; }); BX.addCustomEvent(this, "OnTextareaFocus", function() { _this.bookmark = null; if (_this.statusInterval) { clearInterval(_this.statusInterval); } }); // Surrogates BX.addCustomEvent(this, "OnSurrogateDblClick", function(bxTag, origTag, target, e) { if (origTag) { switch (origTag.tag) { case 'php': case 'javascript': case 'htmlcomment': case 'iframe': case 'style': _this.GetDialog('Source').Show(origTag); break; } } }); if (this.dom.form) { BX.bind(this.dom.form, 'submit', BX.proxy(this.OnSubmit, this)); // Autosave if (this.config.initAutosave !== false) { setTimeout(function() { if (_this.dom.form.BXAUTOSAVE) { _this.InitAutosaveHandlers(); } }, 100); } } BX.addCustomEvent(this, "OnSpecialcharInserted", function(entity) { var lastChars = _this.GetLastSpecialchars(), exInd = BX.util.array_search(entity, lastChars); if (exInd !== -1) { lastChars = BX.util.deleteFromArray(lastChars, exInd); lastChars.unshift(entity); } else { lastChars.unshift(entity); lastChars.pop(); } _this.config.lastSpecialchars = lastChars; _this.SaveOption('specialchars', lastChars.join('|')); }); this.parentDialog = BX.WindowManager.Get(); if (this.parentDialog && this.parentDialog.DIV && BX.isNodeInDom(this.parentDialog.DIV) && BX.findParent(this.dom.cont, function(n){return n == _this.parentDialog.DIV;})) { BX.addCustomEvent(this.parentDialog, 'onWindowResizeExt', function(){_this.ResizeSceleton();}); } if (this.config.autoResize) { BX.addCustomEvent(this, "OnIframeKeyup", BX.proxy(this.AutoResizeSceleton, this)); BX.addCustomEvent(this, "OnInsertHtml", BX.proxy(this.AutoResizeSceleton, this)); BX.addCustomEvent(this, "OnIframeSetValue", BX.proxy(this.AutoResizeSceleton, this)); BX.addCustomEvent(this, "OnFocus", BX.proxy(this.AutoResizeSceleton, this)); BX.addCustomEvent(this, "OnSetViewAfter", BX.proxy(this.AutoResizeSceleton, this)); new ResizeObserver(() => { this.ResizeSceleton(this.dom.toolbarCont.offsetWidth); }).observe(this.dom.toolbarCont); } BX.addCustomEvent(this, "OnIframeKeyup", BX.proxy(this.CheckBodyHeight, this)); }, BuildSceleton: function() { var result = false; // Main container contain all editor parts this.dom.cont = BX('bx-html-editor-' + this.id); if (this.dom.cont && BX.isNodeInDom(this.dom.cont)) { this.dom.toolbarCont = BX('bx-html-editor-tlbr-cnt-' + this.id); this.dom.toolbar = BX('bx-html-editor-tlbr-' + this.id); this.dom.areaCont = BX('bx-html-editor-area-cnt-' + this.id); // Container for content editable iframe this.dom.iframeCont = BX('bx-html-editor-iframe-cnt-' + this.id); this.dom.textareaCont = BX('bx-html-editor-ta-cnt-' + this.id); this.dom.resizerOverlay = BX('bx-html-editor-res-over-' + this.id); this.dom.splitResizer = BX('bx-html-editor-split-resizer-' + this.id); this.dom.splitResizer.style.display = 'none'; this.dom.splitResizer.className = this.config.splitVertical ? "bxhtmled-split-resizer-ver" : "bxhtmled-split-resizer-hor"; BX.bind(this.dom.splitResizer, 'mousedown', BX.proxy(this.StartSplitResize, this)); // Taskbars this.dom.taskbarCont = BX('bx-html-editor-tskbr-cnt-' + this.id); // Node navigation at the bottom this.dom.navCont = BX('bx-html-editor-nav-cnt-' + this.id); this.dom.fileDialogsWrap = BX('bx-html-editor-file-dialogs-' + this.id) result = true; } return result; }, UpdateHeight: function() { const minHeight = parseInt(this.config.autoResizeMinHeight || 50); let maxHeight = parseInt(this.config.autoResizeMaxHeight || 0); if (!maxHeight || maxHeight < 10) { maxHeight = Math.round(BX.GetWindowInnerSize().innerHeight * 0.9); } const newHeight = Math.max(Math.min(this.GetHeightByContent(), maxHeight), minHeight); if (this.GetSceletonSize().height < newHeight) { this.config.height = newHeight; this.ResizeSceleton(); } }, ResizeSceleton: function(width, height, params) { if (this.config.autoResizeMaxHeight === 'Infinity' && this.sandbox.loaded && this.GetIframeDoc().body) { this.GetIframeDoc().body.style.minHeight = this.MIN_HEIGHT + 'px'; this.dom.cont.style.minHeight = this.MIN_HEIGHT + 'px'; const toolbarHeight = (this.toolbar.pCont.offsetHeight > 0) * this.toolbar.height; height = this.config.height = toolbarHeight + this.GetIframeDoc().body.scrollHeight; } var _this = this; if (this.expanded) { var innerSize = BX.GetWindowInnerSize(document); width = this.config.width = innerSize.innerWidth; height = this.config.height = innerSize.innerHeight; } if (!width) { width = this.config.width; } if (!height) { height = this.config.height; } this.dom.cont.style.minWidth = this.MIN_WIDTH + 'px'; this.dom.cont.style.minHeight = this.MIN_HEIGHT + 'px'; var styleW, styleH; if (this.resizeTimeout) { clearTimeout(this.resizeTimeout); this.resizeTimeout = null; } if (width.toString().indexOf('%') !== -1) { styleW = width; width = this.dom.cont.offsetWidth; if (!width) { this.resizeTimeout = setTimeout(function(){_this.ResizeSceleton(width, height, params);}, 500); return; } } else { if (width < this.MIN_WIDTH) { width = this.MIN_WIDTH; } styleW = width + 'px'; } this.dom.cont.style.width = styleW; this.dom.toolbarCont.style.width = styleW; if (height.toString().indexOf('%') !== -1) { styleH = height; height = this.dom.cont.offsetHeight; } else { if (height < this.MIN_HEIGHT) { height = this.MIN_HEIGHT; } styleH = height + 'px'; } this.dom.cont.style.height = styleH; var w = Math.max(width, this.MIN_WIDTH), h = Math.max(height, this.MIN_HEIGHT), toolbarHeight = this.toolbar.GetHeight(), taskbarWidth = this.showTaskbars ? (this.taskbarManager.GetWidth(true, w * 0.8)) : 0, areaH = h - toolbarHeight - (this.config.showNodeNavi && this.nodeNavi ? this.nodeNavi.GetHeight() : 0), areaW = w - taskbarWidth; this.dom.areaCont.style.top = toolbarHeight ? toolbarHeight + 'px' : 0; // Area this.SetAreaContSize(areaW, areaH, params); // Taskbars this.dom.taskbarCont.style.height = areaH + 'px'; this.dom.taskbarCont.style.width = taskbarWidth + 'px'; if (this.showTaskbars) { this.taskbarManager.Resize(taskbarWidth, areaH); } this.toolbar.AdaptControls(width); this.On('OnEditorResizedAfter', [{ width: width, height: height }]); }, CheckBodyHeight: function() { if (this.iframeView.IsShown()) { var padding = 15, minHeight, doc = this.GetIframeDoc(); if (doc && doc.body) { minHeight = doc.body.parentNode.offsetHeight - padding * 2; if (minHeight <= 20) { setTimeout(BX.proxy(this.CheckBodyHeight, this), 300); } else if (this.config.autoResize || minHeight > doc.body.offsetHeight) { setTimeout(function() { minHeight = doc.body.parentNode.offsetHeight - padding * 2; doc.body.style.minHeight = minHeight + 'px'; }, 300); } } } }, GetSceletonSize: function() { return { width: this.dom.cont.offsetWidth, height: this.dom.cont.offsetHeight }; }, AutoResizeSceleton: function() { if (this.expanded || !this.IsShown() || this.iframeView.IsEmpty()) return; if (this.config.autoResizeMaxHeight === 'Infinity') { this.ResizeSceleton(); return; } var maxHeight = parseInt(this.config.autoResizeMaxHeight || 0), minHeight = parseInt(this.config.autoResizeMinHeight || 50), areaHeight = this.dom.areaCont.offsetHeight, newHeight, _this = this; if (this.autoResizeTimeout) { clearTimeout(this.autoResizeTimeout); } this.autoResizeTimeout = setTimeout(function() { newHeight = _this.GetHeightByContent(); if (newHeight > areaHeight) { if (BX.browser.IsIOS()) { maxHeight = Infinity; } else if (!maxHeight || maxHeight < 10) { maxHeight = Math.round(BX.GetWindowInnerSize().innerHeight * 0.9); // 90% from screen height } newHeight = Math.min(newHeight, maxHeight); newHeight = Math.max(newHeight, minHeight); _this.SmoothResizeSceleton(newHeight); } }, 300); }, GetHeightByContent: function() { var heightOffset = parseInt(this.config.autoResizeOffset || 80), contentHeight; if (this.GetViewMode() == 'wysiwyg') { var body = this.GetIframeDoc().body, node = body.lastChild, offsetTop = false; contentHeight = body.offsetHeight; while (true) { if (!node) { break; } if (node.offsetTop) { offsetTop = node.offsetTop + (node.offsetHeight || 0); contentHeight = offsetTop + heightOffset; break; } else { node = node.previousSibling; } } var oEdSize = BX.GetWindowSize(this.GetIframeDoc()); if (oEdSize.scrollHeight - oEdSize.innerHeight > 5) { contentHeight = Math.max(oEdSize.scrollHeight + heightOffset, contentHeight); } } else { contentHeight = (this.textareaView.element.value.split("\n").length /* rows count*/ + 5) * 17; } return contentHeight; }, SmoothResizeSceleton: function(height) { this.On('AutoResizeStarted'); var _this = this, size = this.GetSceletonSize(), curHeight = size.height, count = 0, bRise = height > curHeight, timeInt = 50, dy = 5; if (!bRise) return; if (this.smoothResizeInt) { clearInterval(this.smoothResizeInt); } this.smoothResizeInt = setInterval(function() { curHeight += Math.round(dy * count); var finished = curHeight >= height; if (curHeight > height) { clearInterval(_this.smoothResizeInt); if (curHeight > height) { curHeight = height; } } _this.config.height = curHeight; _this.ResizeSceleton(); if (finished) { _this.On('AutoResizeFinished'); } count++; }, timeInt ); }, SetAreaContSize: function(areaW, areaH, params) { areaW += 2; this.dom.areaCont.style.width = areaW + 'px'; this.dom.areaCont.style.height = areaH + 'px'; if (params && params.areaContTop) { this.dom.areaCont.style.top = params.areaContTop + 'px'; } var WIDTH_DIF = 3; if (this.currentViewName == 'split') { function getValue(value, min, max) { if (value < min) { value = min; } if (value > max) { value = max; } return value; } var MIN_SPLITTER_PAD = 10, delta, a, b; if (this.config.splitVertical == true) { delta = params && params.deltaX ? params.deltaX : 0; a = getValue((areaW * this.config.splitRatio / (1 + this.config.splitRatio)) - delta, MIN_SPLITTER_PAD, areaW - MIN_SPLITTER_PAD); b = areaW - a; this.dom.iframeCont.style.width = (a - WIDTH_DIF) + 'px'; this.dom.iframeCont.style.height = areaH + 'px'; this.dom.iframeCont.style.top = 0; this.dom.iframeCont.style.left = 0; this.dom.textareaCont.style.width = (b - WIDTH_DIF) + 'px'; this.dom.textareaCont.style.height = areaH + 'px'; this.dom.textareaCont.style.top = 0; this.dom.textareaCont.style.left = a + 'px'; this.dom.splitResizer.className = 'bxhtmled-split-resizer-ver'; this.dom.splitResizer.style.top = 0; this.dom.splitResizer.style.left = (a - 3) + 'px'; this.dom.textareaCont.style.height = areaH + 'px'; } else { delta = params && params.deltaY ? params.deltaY : 0; a = getValue((areaH * this.config.splitRatio / (1 + this.config.splitRatio)) - delta, MIN_SPLITTER_PAD, areaH - MIN_SPLITTER_PAD); b = areaH - a; this.dom.iframeCont.style.width = (areaW - WIDTH_DIF) + 'px'; this.dom.iframeCont.style.height = a + 'px'; this.dom.iframeCont.style.top = 0; this.dom.iframeCont.style.left = 0; this.dom.textareaCont.style.width = (areaW - WIDTH_DIF) + 'px'; this.dom.textareaCont.style.height = b + 'px'; this.dom.textareaCont.style.top = a + 'px'; this.dom.textareaCont.style.left = 0; this.dom.splitResizer.className = 'bxhtmled-split-resizer-hor'; this.dom.splitResizer.style.top = (a - 3) + 'px'; this.dom.splitResizer.style.left = 0; } if (params && params.updateSplitRatio) { this.config.splitRatio = a / b; this.SaveOption('split_ratio', this.config.splitRatio); } } else { // Set size and position of iframe container to the normal state this.dom.iframeCont.style.width = (areaW - WIDTH_DIF) + 'px'; this.dom.iframeCont.style.height = areaH + 'px'; this.dom.iframeCont.style.top = 0; this.dom.iframeCont.style.left = 0; // Set size and position of textarea container to the normal state this.dom.textareaCont.style.width = (areaW - WIDTH_DIF) + 'px'; this.dom.textareaCont.style.height = areaH + 'px'; this.dom.textareaCont.style.top = 0; this.dom.textareaCont.style.left = 0; } }, ShowCopilotAtTheBottom: function() { if (!this.iframeView.isCopilotInitialized()) { return false; } this.iframeView.copilot.showAtTheBottom(); return true; }, BuildToolbar: function() { this.toolbar = new BXHtmlEditor.Toolbar(this, this.GetTopControls()); }, GetTopControls: function() { this.On("GetTopButtons", [window.BXHtmlEditor.Controls]); return window.BXHtmlEditor.Controls; }, CreateIframeSandBox: function() { return new Sandbox( // Callback BX.proxy(this.OnCreateIframe, this), // Config { editor: this, cont: this.dom.iframeCont } ); }, OnCreateIframe: function() { if (!document.body.contains(this.dom.iframeCont)) { //do not create frame if DOM doesn't contain editor's html structure (autocomposite). return; } this.On('OnCreateIframeBefore'); this.iframeView.OnCreateIframe(); this.selection = new BXEditorSelection(this); this.action = new BXEditorActions(this); this.config.content = this.dom.pValueInput.value; this.SetContent(this.config.content, true); this.undoManager = new BXEditorUndoManager(this); this.action.Exec("styleWithCSS", false, true); this.iframeView.InitAutoLinking(); // Simulate html5 placeholder attribute on contentEditable element // var placeholderText = typeof(this.config.placeholder) === "string" // ? this.config.placeholder // : this.textarea.element.getAttribute("placeholder"); // if (placeholderText) { // dom.simulatePlaceholder(this.parent, this, placeholderText); // } if (this.config.view != 'wysiwyg') { var i, changeViewBut = false, switchCodeButton = false, controls = this.toolbar.GetControlsMap(); // Mantis: 72063 if (this.config.view == 'split') { for (i = 0; i < controls.length; i++) { if (controls[i] && controls[i].id == 'ChangeView') { changeViewBut = true; break; } } if (!changeViewBut) this.config.view = 'wysiwyg'; } // Mantis: 80663 if (this.config.view != 'wysiwyg') { for (i = 0; i < controls.length; i++) { if (controls[i] && (controls[i].id == 'BbCode' || controls[i].id == 'ChangeView')) { switchCodeButton = true; break; } } if (!switchCodeButton) this.config.view = 'wysiwyg'; } } this.SetView(this.config.view, false); if (this.config.setFocusAfterShow !== false) { this.Focus(false); } this.sandbox.inited = true; this.On('OnCreateIframeAfter', [this]); }, GetDialog: function(dialogName, params) { if (!this.dialogs[dialogName] && window.BXHtmlEditor.dialogs[dialogName]) this.dialogs[dialogName] = new window.BXHtmlEditor.dialogs[dialogName](this, params); return this.dialogs[dialogName] || null; }, Show: function() { this.dom.cont.style.display = ''; }, Hide: function() { this.dom.cont.style.display = 'none'; }, IsShown: function() { return this.inited && this.dom.cont.style.display !== 'none' && this.dom.cont.offsetWidth > 0 && BX.isNodeInDom(this.dom.cont); }, SetView: function(view, saveValue) { this.On('OnSetViewBefore'); if (view == 'split' && this.bbCode) view = 'wysiwyg'; if (this.currentViewName != view) { this.toolbar.HideControl('ai-image-generator'); this.toolbar.HideControl('ai-text-generator'); if (view == 'wysiwyg') { this.iframeView.Show(); this.textareaView.Hide(); this.dom.splitResizer.style.display = 'none'; this.CheckBodyHeight(); this.toolbar.ShowControl('ai-image-generator'); this.toolbar.ShowControl('ai-text-generator'); } else if (view == 'code') { this.iframeView.Hide(); this.textareaView.Show(); this.CheckCurrentStatus(false); this.dom.splitResizer.style.display = 'none'; } else if (view == 'split') { this.textareaView.Show(); this.iframeView.Show(); this.dom.splitResizer.style.display = ''; this.CheckBodyHeight(); } this.currentViewName = view; } if (saveValue !== false) { this.SaveOption('view', view); } this.ResizeSceleton(); this.On('OnSetViewAfter'); }, GetViewMode: function() { return this.currentViewName; }, SetContent: function(value, bParse) { this.On('OnSetContentBefore'); if (this.bbCode) { var htmlFromBbCode = this.bbParser.Parse(value); this.iframeView.SetValue(htmlFromBbCode, bParse); if (htmlFromBbCode !== value || htmlFromBbCode.indexOf('[') < 0) { this.dom.textareaCont.style.opacity = 1; this.dom.iframeCont.style.opacity = 1; } } else { this.iframeView.SetValue(value, bParse); this.dom.textareaCont.style.opacity = 1; this.dom.iframeCont.style.opacity = 1; } this.textareaView.SetValue(value, false); this.On('OnSetContentAfter'); }, Focus: function(setToEnd) { if (this.currentViewName == 'wysiwyg') { this.iframeView.Focus(setToEnd); } else if (this.currentViewName == 'code') { this.textareaView.Focus(setToEnd); } else if (this.currentViewName == 'split') { if (this.synchro.GetSplitMode() == 'wysiwyg') { this.iframeView.Focus(setToEnd); } else { this.textareaView.Focus(setToEnd); } } this.On('OnFocus'); return this; }, SaveContent: function() { if (this.currentViewName == 'wysiwyg' || (this.currentViewName == 'split' && this.synchro.GetSplitMode() == 'wysiwyg')) { this.synchro.lastIframeValue = ''; this.synchro.FromIframeToTextarea(true, true); } else { this.textareaView.SaveValue(); } }, GetContent: function() { this.SaveContent(); return this.textareaView.GetValue(); }, IsExpanded: function() { return this.expanded; }, Expand: function(bExpand) { if (!bExpand) { bExpand = !this.expanded; } this.expanded = bExpand; this.On('OnFullscreenExpand', [this]); const innerSize = BX.GetWindowInnerSize(document); let startWidth, startHeight, startTop, startLeft, endWidth, endHeight, endTop, endLeft; if (bExpand) { const scrollPos = BX.GetWindowScrollPos(document); const pos = this.dom.cont.getBoundingClientRect(); startWidth = this.dom.cont.offsetWidth; startHeight = this.dom.cont.offsetHeight; startTop = pos.top; startLeft = pos.left; endWidth = innerSize.innerWidth; endHeight = innerSize.innerHeight; endTop = 0; endLeft = 0; this.savedSize = { width: startWidth, height: startHeight, top: startTop, left: startLeft, scrollLeft: scrollPos.scrollLeft, scrollTop: scrollPos.scrollTop, configWidth: this.config.width, configHeight: this.config.height }; this.savedStyle = { position: this.dom.cont.style.position, zIndex: this.dom.cont.style.zIndex }; this.config.width = endWidth; this.config.height = endHeight; //dummy element to keep place for the editor this.dummieDiv = BX.create('DIV'); this.dummieDiv.style.width = startWidth + 'px'; this.dummieDiv.style.height = startHeight + 'px'; this.dom.cont.parentNode.insertBefore(this.dummieDiv, this.dom.cont); //fix parent styles for correct positioning this.savedParentOpacity = []; this.savedParentPosition = []; let parent = this.dom.cont.parentNode; while (parent && parent !== document) { if (parseInt(window.getComputedStyle(parent).getPropertyValue('opacity')) < 1) { let opacity = ''; if (parseInt(parent.style.opacity) < 1) { opacity = parent.style.opacity; } this.savedParentOpacity.push({ parent: parent, opacity: opacity }) parent.style.opacity = '1'; } //zIndex of fixed elements cannot be greater than the relative parent element has if (window.getComputedStyle(parent).getPropertyValue('position') === 'relative' && parseInt(window.getComputedStyle(parent).getPropertyValue('z-index')) > 0) { let position = ''; if (parent.style.position === 'relative') { position = parent.style.position; } this.savedParentPosition.push({ parent: parent, position: position }) parent.style.position = 'static'; } //will-change property (it is experimental) breaks positioning, so we also disable this if (window.getComputedStyle(parent).getPropertyValue('will-change').includes('height')) { parent.style.willChange = 'unset'; } parent = parent.parentNode; } this.dom.cont.style.setProperty('background', 'white', 'important'); this.dom.cont.style.position = 'fixed'; this.dom.cont.style.zIndex = 999; BX.addCustomEvent(this, 'OnIframeKeydown', BX.proxy(this.CheckEscCollapse, this)); BX.bind(document.body, "keydown", BX.proxy(this.CheckEscCollapse, this)); BX.bind(window, "scroll", BX.proxy(this.PreventScroll, this)); } else { startWidth = this.dom.cont.offsetWidth; startHeight = this.dom.cont.offsetHeight; startTop = 0; startLeft = 0; endWidth = this.savedSize.width; endHeight = this.savedSize.height; endTop = this.savedSize.top; endLeft = this.savedSize.left; BX.removeCustomEvent(this, 'OnIframeKeydown', BX.proxy(this.CheckEscCollapse, this)); BX.unbind(document.body, "keydown", BX.proxy(this.CheckEscCollapse, this)); BX.unbind(window, "scroll", BX.proxy(this.PreventScroll, this)); } this.dom.cont.style.width = startWidth + 'px'; this.dom.cont.style.height = startHeight + 'px'; this.dom.cont.style.top = startTop + 'px'; this.dom.cont.style.left = startLeft + 'px'; var _this = this; this.expandAnimation = new BX.easing({ duration: 300, start : { height: startHeight, width: startWidth, top: startTop, left: startLeft }, finish : { height: endHeight, width: endWidth, top: endTop, left: endLeft }, transition : BX.easing.makeEaseOut(BX.easing.transitions.quart), step : function(state) { _this.dom.cont.style.width = state.width + 'px'; _this.dom.cont.style.height = state.height + 'px'; _this.dom.cont.style.top = state.top + 'px'; _this.dom.cont.style.left = state.left + 'px'; _this.ResizeSceleton(state.width, state.height); }, complete : function() { _this.dom.cont.style.width = endWidth + 'px'; _this.dom.cont.style.height = endHeight + 'px'; _this.dom.cont.style.top = endTop + 'px'; _this.dom.cont.style.left = endLeft + 'px'; if (!bExpand) { let parent = _this.dom.cont.parentNode; while (parent && parent !== document) { if (parent.style.willChange === 'unset') { parent.style.willChange = ''; } parent = parent.parentNode; } for (const parentOpacity of _this.savedParentOpacity) { parentOpacity.parent.style.opacity = parentOpacity.opacity; } for (const parentPosition of _this.savedParentPosition) { parentPosition.parent.style.position = parentPosition.position; } _this.dummieDiv.remove(); _this.dom.cont.style.position = _this.savedStyle.position; _this.dom.cont.style.zIndex = _this.savedStyle.zIndex; _this.dom.cont.style.width = ''; _this.dom.cont.style.height = ''; _this.dom.cont.style.top = ''; _this.dom.cont.style.left = ''; _this.config.width = _this.savedSize.configWidth; _this.config.height = _this.savedSize.configHeight; } _this.ResizeSceleton(); _this.CheckAndReInit(); } }); this.expandAnimation.animate(); }, CheckEscCollapse: function(e, keyCode, command, selectedNode) { if (!keyCode) { keyCode = e.keyCode; } if ( this.IsExpanded() && keyCode == this.KEY_CODES['escape'] && !this.IsPopupsOpened() ) { this.Expand(false); return BX.PreventDefault(e); } }, PreventScroll: function(e) { window.scrollTo(this.savedSize.scrollLeft, this.savedSize.scrollTop); return BX.PreventDefault(e); }, IsPopupsOpened: function() { return !!(this.dialogShown || this.popupShown || this.contextMenuShown || this.overlay.bShown); }, ReInitIframe: function(callback) { this.sandbox.InitIframe(null, () => { this.iframeView.OnCreateIframe(); this.synchro.StopSync(); this.synchro.lastTextareaValue = ''; this.synchro.FromTextareaToIframe(true); this.synchro.StartSync(); this.iframeView.ReInit(); this.selection.lastCheckedRange = null; if (this.config.setFocusAfterShow !== false) { this.Focus(); } if (typeof callback === 'function') { callback(); } this.On('OnAfterIframeInit', [this]); }); }, CheckAndReInit: function(content) { if (this.sandbox.inited) { var win = this.sandbox.GetWindow(); if (win) { var doc = this.sandbox.GetDocument(); if (doc !== this.iframeView.document || !doc.head || doc.head.innerHTML == '') { this.iframeView.document = doc; this.iframeView.element = doc.body; this.ReInitIframe(() => { if (content !== undefined) { this.SetContent(content, true); this.CheckBodyHeight(); } }); } else if(doc.body) { doc.body.style.minHeight = ''; } } else { throw new Error("HtmlEditor: CheckAndReInit error iframe isn't in the DOM"); } } if (content !== undefined) { this.SetContent(content, true); this.CheckBodyHeight(); } }, Disable: function() { }, Enable: function() { }, CheckConfig: function(config) { if (config.content === undefined) { config.content = ''; } return config; }, GetInnerHtml: function(el) { var TILDA = "%7E", AMP = "&", innerHTML = el.innerHTML; if (innerHTML.indexOf(AMP) !== -1 || innerHTML.indexOf(TILDA) !== -1) { innerHTML = innerHTML.replace(/(?:href|src)\s*=\s*("|')([\s\S]*?)(\1)/ig, function(s) { // See https://bugzilla.mozilla.org/show_bug.cgi?id=664398 s = s.replace(/%7E/ig, '~'); s = s.replace(/&/ig, '&'); return s; }); } innerHTML = innerHTML.replace(/(?:title|alt)\s*=\s*("|')([\s\S]*?)(\1)/ig, function(s) { s = s.replace(/</g, '<'); s = s.replace(/>/g, '>'); return s; }); if (this.bbCode) { innerHTML = innerHTML.replace(/[\s\n\r]*?<!--[\s\S]*?-->[\s\n\r]*?/ig, ""); } return innerHTML; }, InitUtil: function() { var _this = this; this.util = {}; if ("textContent" in document.documentElement) { this.util.SetTextContent = function(element, text){element.textContent = text;}; this.util.GetTextContent = function(element){return element.textContent;}; } else if ("innerText" in document.documentElement) { this.util.SetTextContent = function(element, text){element.innerText = text;}; this.util.GetTextContent = function(element){return element.innerText;}; } else { this.util.SetTextContent = function(element, text){element.nodeValue = text;}; this.util.GetTextContent = function(element){return element.nodeValue;}; } this.util.AutoCloseTagSupported = function() { var element = document.createElement("div"), result, innerHTML; element.innerHTML = "<p><div></div>"; innerHTML = element.innerHTML.toLowerCase(); result = innerHTML === "<p></p><div></div>" || innerHTML === "<p><div></div></p>"; _this.util.AutoCloseTagSupported = function(){return result;}; return result; }; this.util.FirstLetterSupported = function() { var result = !BX.browser.IsChrome() && !BX.browser.IsSafari(); _this.util.FirstLetterSupported = function(){return result;}; return result; }; // IE sometimes gives wrong results for hasAttribute/getAttribute this.util.CheckGetAttributeTruth = function() { var td = document.createElement("td"), result = td.getAttribute("rowspan") != "1"; _this.util.CheckGetAttributeTruth = function(){return result;}; return result; }; // Check if browser supports HTML5 this.util.CheckHTML5Support = function(doc) { if (!doc) { doc = document; } var result = false, html = "<article>bitrix</article>", el = doc.createElement("div"); el.innerHTML = html; result = el.innerHTML.toLowerCase() === html; _this.util.CheckHTML5Support = function(){return result;}; return result; }; // Check if browser supports HTML5 (checks all tags) this.util.CheckHTML5FullSupport = function(doc) { if (!doc) { doc = document; } var html, tags = _this.GetHTML5Tags(), result = false, el = doc.createElement("div"); for (var i = 0; i < tags.length; i++) { html = "<" + tags[i] + ">bitrix</" + tags[i] + ">"; el.innerHTML = html; result = el.innerHTML.toLowerCase() === html; if (!result) { break; } } _this.util.CheckHTML5FullSupport = function(){return result;}; return result; }; this.util.GetEmptyImage = function() { return _this.EMPTY_IMAGE_SRC; }; this.util.CheckDataTransferSupport = function() { var result = false; try { result = !!(window.Clipboard || window.DataTransfer).prototype.getData; } catch(e) {} _this.util.CheckDataTransferSupport = function(){return result;}; return result; }; this.util.CheckImageSelectSupport = function() { var result = !(BX.browser.IsChrome() || BX.browser.IsSafari()); _this.util.CheckImageSelectSupport = function(){return result;}; return result; }; this.util.CheckPreCursorSupport = function() { var result = !(BX.browser.IsIE() || BX.browser.IsIE10() || BX.browser.IsIE11()); _this.util.CheckPreCursorSupport = function(){return result;}; return result; }; // Following hack is needed for firefox to make sure that image resize handles are properly removed this.util.Refresh = function(element) { if (element && element.parentNode) { var cn = "bx-editor-refresh"; BX.addClass(element, cn); BX.removeClass(element, cn); // Hack for firefox if (BX.browser.IsFirefox()) { try { var i, doc = element.ownerDocument, italics = doc.getElementsByTagName('I'), italicLen = italics.length; for (i = 0; i < italics.length; i++) { italics[i].setAttribute('data-bx-orgig-i', true); } doc.execCommand("italic", false, null); doc.execCommand("italic", false, null); var italicsNew = doc.getElementsByTagName('I'); if (italicsNew.length !== italicLen) { for (i = 0; i < italicsNew.length; i++) { if (italicsNew[i].getAttribute('data-bx-orgig-i')) { italicsNew[i].removeAttribute('data-bx-orgig-i'); } else { _this.util.ReplaceWithOwnChildren(italicsNew[i]); } } } } catch(e) {} } } }; this.util.addslashes = function(str) { str = str.replace(/\\/g,'\\\\'); str = str.replace(/"/g,'\\"'); return str; }; this.util.stripslashes = function(str) { str = str.replace(/\\"/g,'"'); str = str.replace(/\\\\/g,'\\'); return str; }; this.util.ReplaceNode = function(node, newNode) { node.parentNode.insertBefore(newNode, node); node.parentNode.removeChild(node); return newNode; }; this.util.spaceUrlEncode = function(str) { str = str.replace(/ /g,'%20'); return str; }; this.util.spaceUrlDecode = function(str) { str = str.replace(/%20/g,' '); return str; }; // Fast way to check whether an element with a specific tag name is in the given document this.util.DocumentHasTag = function(doc, tag) { var LIVE_CACHE = {}, key = _this.id + ":" + tag, cacheEntry = LIVE_CACHE[key]; if (!cacheEntry) cacheEntry = LIVE_CACHE[key] = doc.getElementsByTagName(tag); return cacheEntry.length > 0; }; this.util.IsSplitPoint = function(node, offset) { var res = offset > 0 && offset < node.childNodes.length; if (rangy.dom.isCharacterDataNode(node)) { if (offset == 0) res = !!node.previousSibling; else if (offset == node.length) res = !!node.nextSibling; else res = true; } return res; }; this.util.SplitNodeAt = function(node, descendantNode, descendantOffset) { var newNode; if (rangy.dom.isCharacterDataNode(descendantNode)) { if (descendantOffset == 0) { descendantOffset = rangy.dom.getNodeIndex(descendantNode); descendantNode = descendantNode.parentNode; } else if (descendantOffset == descendantNode.length) { descendantOffset = rangy.dom.getNodeIndex(descendantNode) + 1; descendantNode = descendantNode.parentNode; } else { newNode = rangy.dom.splitDataNode(descendantNode, descendantOffset); } } if (!newNode) { newNode = descendantNode.cloneNode(false); if (newNode.id) { newNode.removeAttribute("id"); } var child; while ((child = descendantNode.childNodes[descendantOffset])) { newNode.appendChild(child); } rangy.dom.insertAfter(newNode, descendantNode); } if (descendantNode && descendantNode.nodeName == "BODY") { return newNode; } return (descendantNode == node) ? newNode : _this.util.SplitNodeAt(node, newNode.parentNode, rangy.dom.getNodeIndex(newNode)); }; this.util.ReplaceWithOwnChildren = function (el) { var parent = el.parentNode; while (el.firstChild) { parent.insertBefore(el.firstChild, el); } parent.removeChild(el); }; this.util.IsBlockElement = function (node) { var styleDisplay = BX.style(node, 'display'); return styleDisplay && BX.type.isString(styleDisplay) && styleDisplay.toLowerCase() === "block"; }; this.util.IsBlockNode = function (node) { return node && node.nodeType == 1 && BX.util.in_array(node.nodeName, _this.GetBlockTags()); }; this.util.CopyAttributes = function(attributes, from, to) { if (from && to) { var attribute, i, length = attributes.length; for (i = 0; i < length; i++) { attribute = attributes[i]; if (from[attribute]) to[attribute] = from[attribute]; } } }; this.util.RenameNode = function(node, newNodeName) { var newNode = node.ownerDocument.createElement(newNodeName), firstChild; while (firstChild = node.firstChild) newNode.appendChild(firstChild); _this.util.CopyAttributes(["align", "className"], node, newNode); if (node.style.cssText != '') { newNode.style.cssText = node.style.cssText; } node.parentNode.replaceChild(newNode, node); return newNode; }; this.util.GetInvisibleTextNode = function() { return _this.iframeView.document.createTextNode(_this.INVISIBLE_SPACE); }; this.util.IsEmptyNode = function(node, bCheckNewLine, bCheckSpaces) { var res; if (node.nodeType == 3) { res = node.data === "" || node.data === _this.INVISIBLE_SPACE || (node.data === '\n' && bCheckNewLine); if (!res && bCheckSpaces && node.data.toString().match(/^[\s\n\r\t]+$/ig)) { res = true; } } else if(node.nodeType == 1) { res = node.innerHTML === "" || node.innerHTML === _this.INVISIBLE_SPACE; if (!res && bCheckSpaces && node.innerHTML.toString().match(/^[\s\n\r\t]+$/ig)) { res = true; } } return res; }; var documentElement = document.documentElement; if ("textContent" in documentElement) { this.util.SetTextContent = function(node, text) { node.textContent = text; }; this.util.GetTextContent = function(node) { return node.textContent; }; } else if ("innerText" in documentElement) { this.util.SetTextContent = function(node, text) { node.innerText = text; }; this.util.GetTextContent = function(node) { return node.innerText; }; } else { this.util.SetTextContent = function(node, text) { node.nodeValue = text; }; this.util.GetTextContent = function(node) { return node.nodeValue; }; } this.util.GetTextContentEx = function(node) { var i, html, linkMap = [], clone = node.cloneNode(true), scripts = clone.getElementsByTagName('SCRIPT'), links = clone.getElementsByTagName('A'); for (i = scripts.length - 1; i >= 0 ; i--) { BX.remove(scripts[i]); } // mantis:64329, mantis:70550 for (i = links.length - 1; i >= 0 ; i--) { var href = links[i].href; if (href.toLowerCase().indexOf('javascript:') !== -1) { _this.util.ReplaceNode(links[i], links[i].ownerDocument.createTextNode(_this.util.GetTextContent(links[i]))); } else { linkMap.push('<a href="' + links[i].href + '">' + _this.util.GetTextContent(links[i]) + '</a>'); _this.util.ReplaceNode(links[i], links[i].ownerDocument.createTextNode('#BX~TMP~LINK' + (linkMap.length - 1) + '#')); } } html = _this.util.GetTextContent(clone); if (linkMap.length > 0) { html = html.replace(/#BX~TMP~LINK(\d+)#/ig, function(s, num) { return linkMap[num] || ''; }); } return html; }; this.util.RgbToHex = function(str) { if (!str) str = ''; if (str.search("rgb") !== -1) { function hex(x) { return ("0" + parseInt(x).toString(16)).slice(-2); } str = str.replace(/rgba\(0,\s*0,\s*0,\s*0\)/ig, 'transparent'); str = str.replace(/rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*([\d\.]{1,3}))?\)/ig, function(s, s1, s2, s3, s4) { return "#" + hex(s1) + hex(s2) + hex(s3); } ); } return str; }; this.util.CheckCss = function(node, arCss, bMatch) { var res = true, i; for (i in arCss) { if (arCss.hasOwnProperty(i)) { if (node.style[i] != '') { res = res && (bMatch ? node.style[i] == arCss[i] : true); } else { res = false; } } } return res; }; this.util.SetCss = function(n, arCss) { if (n && arCss && typeof arCss == 'object') { for (var i in arCss) { if (arCss.hasOwnProperty(i)) { n.style[i] = arCss[i]; } } } }; this.util.InsertAfter = function(node, precedingNode) { return rangy.dom.insertAfter(node, precedingNode); }; this.util.GetNodeDomOffset = function(node) { var i = 0; while (node.parentNode && node.parentNode.nodeName !== 'BODY') { node = node.parentNode; i++; } return i; }; this.util.CheckSurrogateNode = function(node) { return _this.phpParser.CheckParentSurrogate(node); }; this.util.CheckSurrogateDd = function(node) { return _this.phpParser.CheckSurrogateDd(node); }; this.util.GetPreviousNotEmptySibling = function(node) { var prev = node.previousSibling; while (prev && prev.nodeType == 3 && _this.util.IsEmptyNode(prev, true, true)) { prev = prev.previousSibling; } return prev; }; this.util.GetNextNotEmptySibling = function(node) { var next = node.nextSibling; while (next && next.nodeType == 3 && _this.util.IsEmptyNode(next, true, true)) { next = next.nextSibling; } return next; }; this.util.IsEmptyLi = function(li) { if (li && li.nodeName == 'LI') { return _this.util.IsEmptyNode(li, true, true) || li.innerHTML.toLowerCase() == '<br>'; } return false; }; this.util.FindParentEx = function(obj, params, maxParent) { if (BX.checkNode && BX.checkNode(obj, params)) return obj; return BX.findParent(obj, params, maxParent); }; this.util.IsEmptyObject = function(obj) { for (var i in obj) { if (obj.hasOwnProperty(i)) { return false; } } return true; }; this.util.GetNextSibling = function(node) { var res = node.nextSibling; while (res && res.nodeType == 3 && res.nodeValue == '\ufeff' && res.nextSibling) { res = res.nextSibling; } return res || null; }; }, Parse: function(content, bParseBxNodes, bFormat) { bParseBxNodes = !!bParseBxNodes; this.content = content; this.On("OnParse", [bParseBxNodes]); if (bParseBxNodes) { this.content = this.parser.Parse(this.content, this.GetParseRules(), this.GetIframeDoc(), true, bParseBxNodes); if ((bFormat === true || this.textareaView.IsShown()) && !this.bbCode) { this.content = this.FormatHtml(this.content); } this.content = this.phpParser.ParseBxNodes(this.content); } else { this.content = this.phpParser.ParsePhp(this.content); this.content = this.parser.Parse(this.content, this.GetParseRules(), this.GetIframeDoc(), true, bParseBxNodes); } this.On("OnAfterParse", [bParseBxNodes]); return this.content; }, On: function(eventName, arEventParams) { BX.onCustomEvent(this, eventName, arEventParams || []); }, GetIframeDoc: function() { if (!this.document) { this.document = this.sandbox.GetDocument(); BX.addCustomEvent(this, 'OnIframeReInit', BX.proxy(function(){this.document = this.sandbox.GetDocument();}, this)); } return this.document; }, GetParseRules: function() { this.rules = __BXHtmlEditorParserRules; // Here we can make changes to this.rules this.On("OnGetParseRules"); var _this = this; this.GetParseRules = function(){return _this.rules;}; return this.rules; }, GetHTML5Tags: function() { return this.HTML5_TAGS; }, GetBlockTags: function() { return this.BLOCK_TAGS; }, SetBxTag: function(el, params) { var id; if (params.id || el && el.id) id = params.id || el.id; if (!id) { id = 'bxid' + Math.round(Math.random() * 1000000000); } else { if (this.bxTags[id]) { if (!params.tag) params.tag = this.bxTags[id].tag; } } params.id = id; if (el) el.id = params.id; this.bxTags[params.id] = params; return params.id; }, GetBxTag: function(element) { var id; if (typeof element == 'object' && element && element.id) id = element.id; else id = element; if (id) { if (typeof id != "string" && id.id) id = id.id; if (id && id.length > 0 && this.bxTags[id] && this.bxTags[id].tag) { this.bxTags[id].tag = this.bxTags[id].tag.toLowerCase(); return this.bxTags[id]; } } return {tag: false}; }, OnMousedown: function(e) { var node = e.target || e.srcElement; if (node && (node.getAttribute || node.parentNode)) { var _this = this, bxType = node.getAttribute('data-bx-type'); if (!bxType) { node = BX.findParent(node, function(n) { return n == _this.dom.cont || (n.getAttribute && n.getAttribute('data-bx-type')); }, this.dom.cont); bxType = (node && node.getAttribute) ? node.getAttribute('data-bx-type') : null; } if (bxType == 'action') // Any type of button or element which runs action { return BX.PreventDefault(e); } } return true; }, OnClick: function(e) { var target = e.target || e.srcElement, bxType = (target && target.getAttribute) ? target.getAttribute('data-bx-type') : false; this.On("OnClickBefore", [{e: e, target: target, bxType: bxType}]); this.CheckCommand(target); }, CheckCommand: function(node) { if (node && (node.getAttribute || node.parentNode)) { var _this = this, bxType = node.getAttribute('data-bx-type'); if (!bxType) { node = BX.findParent(node, function(n) { return n == _this.dom.cont || (n.getAttribute && n.getAttribute('data-bx-type')); }, this.dom.cont); bxType = (node && node.getAttribute) ? node.getAttribute('data-bx-type') : null; } if (bxType == 'action') // Any type of button or element which runs action { var action = node.getAttribute('data-bx-action'), value = node.getAttribute('data-bx-value'); if (this.action.IsSupported(action)) { this.iframeView.copilot?.hideInvitationLine(); this.action.Exec(action, value); } } } }, SetSplitMode: function(vertical, saveValue) { this.config.splitVertical = !!vertical; if (saveValue !== false) { this.SaveOption('split_vertical', this.config.splitVertical ? 1 : 0); } this.SetView('split', saveValue); }, GetSplitMode: function() { return this.config.splitVertical; }, StartSplitResize: function(e) { this.dom.resizerOverlay.style.display = 'block'; var dX = 0, dY = 0, windowScroll = BX.GetWindowScrollPos(), startX = e.clientX + windowScroll.scrollLeft, startY = e.clientY + windowScroll.scrollTop, _this = this; function moveResizer(e, bFinish) { var x = e.clientX + windowScroll.scrollLeft, y = e.clientY + windowScroll.scrollTop; if(startX == x && startY == y) { return; } dX = startX - x; dY = startY - y; _this.ResizeSceleton(0, 0, {deltaX: dX, deltaY: dY, updateSplitRatio: bFinish}); } function finishResizing(e) { moveResizer(e, true); BX.unbind(document, 'mousemove', moveResizer); BX.unbind(document, 'mouseup', finishResizing); _this.dom.resizerOverlay.style.display = 'none'; } BX.bind(document, 'mousemove', moveResizer); BX.bind(document, 'mouseup', finishResizing); }, Request: function(P) { if (!P.url) P.url = this.config.actionUrl; if (P.bIter !== false) P.bIter = true; if (!P.postData && !P.getData) P.getData = this.GetReqData(); // var errorText; // if (!P.errorText) // errorText = false; var reqId = P.getData ? P.getData.reqId : P.postData.reqId; var _this = this, iter = 0; var handler = function(result) { function handleRes() { //_this.CloseWaitWindow(); // var erInd = result.toLowerCase().indexOf('bx_event_calendar_action_error'); // if (!result || result.length <= 0 || erInd != -1) // { // var errorText = ''; // if (erInd >= 0) // { // var // ind1 = erInd + 'BX_EVENT_CALENDAR_ACTION_ERROR:'.length, // ind2 = result.indexOf('-->', ind1); // errorText = result.substr(ind1, ind2 - ind1); // } // if (P.onerror && typeof P.onerror == 'function') // P.onerror(); // // return _this.DisplayError(errorText || P.errorText || ''); // } var res = P.handler(_this.GetRequestRes(reqId), result); if(res === false && ++iter < 20 && P.bIter) setTimeout(handleRes, 5); else _this.ClearRequestRes(reqId); } setTimeout(handleRes, 50); }; //this.ShowWaitWindow(); if (P.postData) BX.ajax.post(P.url, P.postData, handler); else BX.ajax.get(P.url, P.getData, handler); }, GetRequestRes: function(key) { if (top.BXHtmlEditorAjaxResponse[key] != undefined) return top.BXHtmlEditorAjaxResponse[key]; return {}; }, ClearRequestRes: function(key) { if (top.BXHtmlEditorAjaxResponse) { top.BXHtmlEditorAjaxResponse[key] = null; delete top.BXHtmlEditorAjaxResponse[key]; } }, GetReqData: function(action, O) { if (!O) O = {}; if (action) O.action = action; O.sessid = BX.bitrix_sessid(); O.bx_html_editor_request = 'Y'; O.reqId = Math.round(Math.random() * 1000000); return O; }, GetTemplateId: function() { return this.templateId; }, GetSiteId: function() { return this.config.siteId; }, GetComponentFilter: function() { return this.componentFilter; }, GetTemplateParams: function() { return this.templates[this.templateId]; }, GetTemplateStyles: function() { var params = this.templates[this.templateId] || {}; return params.STYLES || ''; }, ApplyTemplate: function(templateId) { if (this.templateId !== templateId) { if (this.templates[templateId]) { this.templateId = templateId; var templ = this.templates[templateId], i, doc = this.sandbox.GetDocument(), head = doc.head || doc.getElementsByTagName('HEAD')[0], styles = head.getElementsByTagName('STYLE'), links = head.getElementsByTagName('LINK'); // Clean old template styles for (i = 0; i < styles.length; i++) { if (styles[i].getAttribute('data-bx-template-style') == 'Y') BX.cleanNode(styles[i], true); } // Clean links with template styles i = 0; while (i < links.length) { if (links[i].getAttribute('data-bx-template-style') == 'Y') { BX.remove(links[i], true); } else { i++; } } // Add new node in the iframe head if (templ['STYLES']) { head.appendChild(BX.create('STYLE', {props: {type: 'text/css'}, text: templ['STYLES']}, doc)).setAttribute('data-bx-template-style', 'Y'); } if (templ && templ['EDITOR_STYLES']) { for (i = 0; i < templ['EDITOR_STYLES'].length; i++) { head.appendChild(BX.create('link', {props: {rel: 'stylesheet', href: templ['EDITOR_STYLES'][i] + '_' + this.cssCounter++}}, doc)).setAttribute('data-bx-template-style', 'Y'); } } this.On("OnApplySiteTemplate", [templateId]); } else { var _this = this; this.Request({ getData: this.GetReqData('load_site_template', { site_template: templateId } ), handler: function(res) { _this.templates[templateId] = res; _this.ApplyTemplate(templateId); } }); } } }, FormatHtml: function(html, bForceFormating) { if (html.length < this.MAX_HANDLED_FORMAT_LENGTH || bForceFormating === true) { if (!this.formatter) this.formatter = new window.BXHtmlEditor.BXCodeFormatter(this); var time1 = new Date().getTime(); html = this.formatter.Format(html); var time2 = new Date().getTime(); if (time2 - time1 > this.MAX_HANDLED_FORMAT_TIME) this.MAX_HANDLED_FORMAT_LENGTH -= 5000; } return html; }, GetFontFamilyList: function() { if (!this.fontFamilyList) { this.fontFamilyList = [ {value: ['Times New Roman', 'Times'], name: 'Times New Roman'}, {value: ['Courier New'], name: 'Courier New'}, {value: ['Arial', 'Helvetica'], name: 'Arial / Helvetica'}, {value: ['Arial Black', 'Gadget'], name: 'Arial Black'}, {value: ['Tahoma','Geneva'], name: 'Tahoma / Geneva'}, {value: 'Verdana', name: 'Verdana'}, {value: ['Georgia', 'serif'], name: 'Georgia'}, {value: 'monospace', name: 'monospace'} ]; this.On("GetFontFamilyList", [this.fontFamilyList]); } return this.fontFamilyList; }, CheckCurrentStatus: function(status) { var arAction, action, actionState, value, actionList = this.GetActiveActions(); if (status === false) { for (action in actionList) { if (actionList.hasOwnProperty(action) && this.action.IsSupported(action)) { arAction = actionList[action]; arAction.control.SetValue(false, null, action); } } } if (!this.iframeView.IsFocused()) return this.On("OnIframeBlur"); var range = this.selection.GetRange(); if (!range || !range.isValid()) return this.On("OnIframeBlur"); for (action in actionList) { if (actionList.hasOwnProperty(action) && this.action.IsSupported(action)) { arAction = actionList[action]; actionState = this.action.CheckState(action, arAction.value); value = arAction.control.GetValue(); if (actionState) { arAction.control.SetValue(true, actionState, action); } else { arAction.control.SetValue(false, null, action); } } } }, RegisterCheckableAction: function(action, params) { if (!this.checkedActionList) this.checkedActionList = {}; this.checkedActionList[action] = params; }, GetActiveActions: function() { return this.checkedActionList; }, SaveOption: function(name, value) { BX.userOptions.save('html_editor', this.config.settingsKey, name, value); }, GetCurrentCssClasses: function(filterTag) { return this.styles.GetCSS(this.templateId, this.templates[this.templateId].STYLES, this.templates[this.templateId].PATH || '', filterTag || false); }, GetStylesDescription: function(templateId) { if (!templateId) templateId = this.templateId; var res = {}; if (templateId && this.templates[templateId]) { res = this.templates[templateId].STYLES_TITLE || {}; } return res; }, IsInited: function() { return !!this.inited; }, IsContentChanged: function() { var cont1 = this.config.content.replace(/[\s\n\r\t]+/ig, ''), cont2 = this.GetContent().replace(/[\s\n\r\t]+/ig, ''); return cont1 != cont2; }, IsSubmited: function() { return this.isSubmited; }, OnSubmit: function() { if (!this.isSubmited && this.dom.cont.style.display !== 'none') { this.RemoveCursorNode(); this.isSubmited = true; if (this.iframeView.IsFocused()) this.On("OnIframeBlur"); this.On('OnSubmit'); this.SaveContent(); } }, AllowBeforeUnloadHandler: function() { this.beforeUnloadHandlerAllowed = true; }, DenyBeforeUnloadHandler: function() { this.beforeUnloadHandlerAllowed = false; }, Destroy: function() { if (this.sandbox) this.sandbox.Destroy(); if (this.Check()) BX.remove(this.dom.cont); }, Check: function() { return this.dom.cont && BX.isNodeInDom(this.dom.cont); }, IsVisible: function() { return this.Check() && this.dom.cont.offsetWidth > 0; }, GetLastSpecialchars: function() { var def = ['¢', '§', '€', '£','¥','©', '®', '«', '»', '°', '±', '¶', '…','′','″', '™', '≈', '≠', '<', '>']; if (this.config.lastSpecialchars && typeof this.config.lastSpecialchars == 'object' && this.config.lastSpecialchars.length > 1) { return this.config.lastSpecialchars; } else { return def; } }, GetIframeElement: function(id) { var doc = this.GetIframeDoc(); return doc ? doc.getElementById(id) : null; }, RegisterDialog: function(id, dialog) { window.BXHtmlEditor.dialogs[id] = dialog; }, SetConfigHeight: function(height) { this.config.height = height; if (this.IsExpanded()) { this.savedSize.configHeight = height; this.savedSize.height = height; } }, CheckBrowserCompatibility: function() { return !(BX.browser.IsOpera() || BX.browser.IsIE8() || BX.browser.IsIE7() || BX.browser.IsIE6() || !document.querySelectorAll); }, GetCursorHtml: function() { return '<span id="bx-cursor-node"> </span>'; }, SetCursorNode: function(range) { if (!range) range = this.selection.GetRange(); this.RemoveCursorNode(); this.selection.InsertHTML(this.GetCursorHtml(), range); }, RestoreCursor: function() { var cursor = this.GetIframeElement('bx-cursor-node'); if (cursor) { this.selection.SetAfter(cursor); BX.remove(cursor); } }, RemoveCursorNode: function() { if (this.synchro.IsFocusedOnTextarea()) { } else { var cursor = this.GetIframeElement('bx-cursor-node'); if (cursor) { this.selection.SetAfter(cursor); BX.remove(cursor); } } }, AddButton: function(params) { if (params.compact == undefined) params.compact = false; if (params.toolbarSort == undefined) params.toolbarSort = 301; if (params.hidden == undefined) params.hidden = false; // 1. Create Button var but = function(editor, wrap) { // Call parrent constructor but.superclass.constructor.apply(this, arguments); this.id = params.id; this.title = params.name; if (params.iconClassName) this.className += ' ' + params.iconClassName; if (params.action) this.action = params.action; if (params.disabledForTextarea !== undefined) this.disabledForTextarea = params.disabledForTextarea; this.Create(); if (params.src) this.pCont.firstChild.style.background = 'url("' + params.src + '") no-repeat scroll 0 0'; if (wrap) wrap.appendChild(this.GetCont()); }; BX.extend(but, window.BXHtmlEditor.Button); if (params.handler) but.prototype.OnClick = params.handler; window.BXHtmlEditor.Controls[params.id] = but; // 2. Add button to controls map BX.addCustomEvent(this, "GetControlsMap", function(controlsMap) { controlsMap.push({ id: params.id, compact: params.compact, hidden: params.hidden, sort: params.toolbarSort, checkWidth: params.checkWidth == undefined ? true : !!params.checkWidth, offsetWidth: (params.offsetWidth || 32) }); }); }, AddCustomParser: function(parser) { if (this.phpParser && this.phpParser.AddCustomParser) { this.phpParser.AddCustomParser(parser); } else { var _this = this; BX.addCustomEvent("OnEditorInitedAfter", function(){_this.phpParser.AddCustomParser(parser);}); } }, AddParser: function(parser) { if (parser && parser.name && typeof parser.obj == 'object') { this.parser.specialParsers[parser.name] = parser.obj; } }, InsertHtml: function(html, range) { if (!this.synchro.IsFocusedOnTextarea()) { this.Focus(); if (this.selection.lastCheckedRange && this.selection.lastCheckedRange.range && !range) { try { this.selection.SetSelection(this.selection.lastCheckedRange.range); } catch(e){} } if (!range) { range = this.selection.GetRange(); } if (!range && this.selection.lastRange) range = this.selection.lastRange; if (range) { if (!range.collapsed && range.startContainer == range.endContainer && range.startContainer.nodeName !== 'BODY') { var surNode = this.util.CheckSurrogateNode(range.startContainer); if (surNode) { this.selection.SetAfter(surNode); } } this.selection.InsertHTML(html, range); this.selection.ScrollIntoView(); } this.iframeView.copilot?.hideInvitationLine(); this.iframeView.copilot?.update(); } }, ParseContentFromBbCode: function(content) { if (this.bbCode) { content = this.bbParser.Parse(content); content = this.Parse(content, true, true); } return content; }, LoadFileDialogs: function(callback) { var _this = this; this.Request({ getData: this.GetReqData('load_file_dialogs', { editor_id: this.id } ), handler: function(res, html) { html = BX.util.trim(html); _this.dom.fileDialogsWrap.innerHTML = html; _this.fileDialogsLoaded = true; setTimeout(callback, 100); } }); }, InitAutosaveHandlers: function() { var editor = this, form = this.dom.form; try{ // BX.addCustomEvent(this, 'OnSubmit', function(){form.BXAUTOSAVE.Init();}); // to prevent save ticker after form submit, OnContentChanged is enough BX.addCustomEvent(this, 'OnContentChanged', function(){form.BXAUTOSAVE.Init();}); BX.addCustomEvent(form, 'onAutoSave', function (ob, data) { if (editor.IsShown() && !editor.IsSubmited()) { // Get it from textarea and put to form_data to saving data[editor.config.inputName] = editor.GetContent(); } }); BX.addCustomEvent(form, 'onAutoSaveRestore', function (ob, data) { if (editor.IsShown()) { editor.SetContent(data[editor.config.inputName], true); } }); }catch(e){} }, InitImageUploader: function() { if (this.config.uploadImagesFromClipboard !== false) { var uploadUrl = this.config.actionUrl; uploadUrl += (uploadUrl.indexOf('?') !== -1 ? "&" : "?") + 'action=uploadfile'; this.imageUploader = BX.Uploader.getInstance({ id: this.CID, streams: 1, allowUpload: "A", uploadFileUrl: uploadUrl, uploadMethod: "immediate", showImage: false, sortItems: false, input: null, placeHolder: null, uploadFormData: "N" }); var _this = this; BX.addCustomEvent(this, "OnImageDataUriHandle", function (editor, imageBase64) { var blob = BX.UploaderUtils.dataURLToBlob(imageBase64.src); if (blob && blob.size > 0 && blob.type.indexOf("image/") == 0) { blob.name = (blob.name || imageBase64.title || (this.GetDefaultImageName() + "." + blob.type.substr(6))); blob.uniqId = imageBase64.uniqId; blob.editorBase64Src = imageBase64.src; _this.imageUploader.onChange([blob]); } }); BX.addCustomEvent(this.imageUploader, "onFileIsCreated", function (id, item) { BX.addCustomEvent(item, "onUploadDone", function (item, result) { _this.HandleImageDataUriCaughtUploadedCallback({ src: item.file.editorBase64Src, uniqId: item.file.uniqId }, { src: result.file.uploadedPath }); }); //BX.addCustomEvent(item, "onUploadError", function(item, result){}); }); } }, GetDefaultImageName: function() { var imageName = {value: false}; this.On("OnGetDefaultUploadImageName", [imageName]); if (!imageName.value) { imageName.value = 'content-img'; } return imageName.value; }, InitClipboardHandler: function() { var _this = this; this.base64Images = []; function checkImages(images) { _this.pasteCheckItteration++; var i; for (i = 0; i < images.length; i++) { const isInlineVideo = BX.hasClass(images[i], 'bxhtmled-player-surrogate'); if (isInlineVideo) { continue; } if (!images[i].getAttribute('data-bx-paste-check')) { if (images[i].complete) _this.CheckImage(images[i], false); else BX.bind(images[i], 'load', BX.proxy(_this.CheckImage, _this)); images[i].setAttribute('data-bx-paste-check', 'Y'); } } if (_this.pasteCheckItteration === 1) setTimeout(function(){checkImages(images);}, 500); else if (_this.pasteCheckItteration < 15) setTimeout(function(){checkImages(images);}, 1000); } BX.bind(this.iframeView.element, 'paste', function (e) { var chromeVerion = 0, imageHandled = false, clipboard = e.clipboardData; if (BX.browser.IsChrome() || BX.browser.IsSafari()) { var ua = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); chromeVerion = ua ? parseInt(ua[2], 10) : Infinity; } // For firefox works wrong (see mantis:88928, mantis:122421) if (clipboard && clipboard.items && !BX.browser.IsFirefox() && (!chromeVerion || chromeVerion < 83) ) { var item = clipboard.items[0]; if (item && item.type.indexOf('image/') > -1) { var blob = item.getAsFile(); if (blob) { var reader = new FileReader(); reader.readAsDataURL(blob); reader.onload = function (event) { imageHandled = true; var img = new Image(); img.src = event.target.result; setTimeout(function() { _this.selection.InsertNode(img); _this.HandleImageDataUri(img); }, 100); } } } } if (!imageHandled) { _this.pasteCheckItteration = 0; checkImages(_this.GetIframeDoc().body.getElementsByTagName('IMG')); } }); BX.removeCustomEvent(this, 'OnImageDataUriCaughtUploaded', BX.proxy(this.HandleImageDataUriCaughtUploadedCallback, this)); BX.addCustomEvent(this, 'OnImageDataUriCaughtUploaded', BX.proxy(this.HandleImageDataUriCaughtUploadedCallback, this)); }, GetBase64Image: function(base64source) { var i, result = false; for (i = 0; i < this.base64Images.length; i++) { if (this.base64Images[i].source == base64source) { result = this.base64Images[i]; } } return result; }, RegisterBase64Image: function(base64source, status) { this.base64Images.push({ source: base64source, status: status, index: this.base64Images.length }); }, CheckImage: function(image, unbind) { if (image && image.complete && image.naturalHeight !== 0) { if (image.src.indexOf('blob:http') !== -1) { image.src = this.GetImageBase64(image); image.title = ''; } if (image.src.indexOf('data:image/') !== -1) { this.HandleImageDataUri(image); } if (unbind !== false) { BX.unbind(image, 'load', BX.proxy(this.CheckImage, this)); } } }, GetImageBase64: function(image) { const canvas = document.createElement("canvas"); let width = image.width; let height = image.height; if (width > 1440) { height = height*1440/width; width = 1440; } canvas.width = width; canvas.height = height; canvas.getContext("2d").drawImage(image, 0, 0, width, height); return canvas.toDataURL("image/jpeg", 0.7); //compress image to 70% quality }, HandleImageDataUri: function(image) { if (!image.getAttribute('data-bx-unique-id')) { this.skipPasteControl = true; if (this.pasteControl.isOpened) { this.pasteControl.Hide(); } var base64Image = this.GetBase64Image(image.src); if (base64Image === false) { this.RegisterBase64Image(image.src, 'requested'); var uniqId = 'bx_base64_id_' + Math.round(Math.random() * 1000000000); image.setAttribute('data-bx-unique-id', uniqId); image.removeAttribute('data-bx-orig-src'); this.On('OnImageDataUriHandle', [this, { src: image.src, title: image.title || '', uniqId: uniqId }]); } else { if (base64Image.status == 'uploaded') { this.HandleImageDataUriCaughtUploadedCallback( { src: image.src, title: image.title || '', uniqId: base64Image.uniqId }, { src: base64Image.fileSrc }, base64Image.htmlForInsert || null ); } } } }, HandleImageDataUriCaughtUploadedCallback: function(imageReferer, file, htmlForInsert) { if (imageReferer && imageReferer.uniqId && file && file.src) { var base64Image = this.GetBase64Image(imageReferer.src); if (base64Image && !base64Image.fileSrc) { base64Image.status = 'uploaded'; base64Image.uniqId = imageReferer.uniqId; base64Image.fileSrc = file.src; base64Image.htmlForInsert = htmlForInsert; } var i,image, images = this.GetIframeDoc().body.getElementsByTagName('IMG'); for (i = 0; i < images.length; i++) { image = images[i]; if (image.getAttribute('data-bx-unique-id') == imageReferer.uniqId || image.getAttribute('src') == imageReferer.src ) { if (htmlForInsert && htmlForInsert.replacement) { this.selection.SetAfter(image); this.selection.InsertHTML(htmlForInsert.replacement); BX.remove(image); if (htmlForInsert.callback) { setTimeout(htmlForInsert.callback, 300); } } else { image.src = file.src; image.setAttribute('src', file.src); image.setAttribute('data-bx-orig-src', file.src); image.removeAttribute('data-bx-paste-check'); image.removeAttribute('data-bx-unique-id'); } } } } this.skipPasteControl = false; } }; window.BXEditor = BXEditor; function Sandbox(callBack, config) { this.callback = callBack || BX.DoNothing; this.config = config || {}; this.editor = this.config.editor; this.iframe = this.CreateIframe(); this.bSandbox = false; // Properties to unset/protect on the window object this.windowProperties = ["parent", "top", "opener", "frameElement", "frames", "localStorage", "globalStorage", "sessionStorage", "indexedDB"]; //Properties on the window object which are set to an empty function this.windowProperties2 = ["open", "close", "openDialog", "showModalDialog", "alert", "confirm", "prompt", "openDatabase", "postMessage", "XMLHttpRequest", "XDomainRequest"]; //Properties to unset/protect on the document object this.documentProperties = ["referrer", "write", "open", "close"]; } Sandbox.prototype = { GetIframe: function() { return this.iframe; }, GetWindow: function() { this._readyError(); }, GetDocument: function() { this._readyError(); }, Destroy: function() { var iframe = this.GetIframe(); iframe.parentNode.removeChild(iframe); }, _readyError: function() { throw new Error("Sandbox: Sandbox iframe isn't loaded yet"); }, CreateIframe: function() { var _this = this, iframe = BX.create("IFRAME", { props: { className: "bx-editor-iframe", frameborder: 0, allowtransparency: "true", width: 0, height: 0, marginwidth: 0, marginheight: 0 //scrolling: 'no' } }); iframe.onload = function() { iframe.onreadystatechange = iframe.onload = null; _this.OnLoadIframe(iframe); }; iframe.onreadystatechange = function() { if (/loaded|complete/.test(iframe.readyState)) { iframe.onreadystatechange = iframe.onload = null; _this.OnLoadIframe(iframe); } }; // Append iframe to ext container this.config.cont.appendChild(iframe); return iframe; }, OnLoadIframe: function(iframe) { if (BX.isNodeInDom(iframe)) { this.InitIframe(iframe, () => { var iframeWindow = iframe.contentWindow; var iframeDocument = iframeWindow.document; iframeWindow.onerror = function(errorMessage, fileName, lineNumber) { throw new Error("Sandbox: " + errorMessage, fileName, lineNumber); }; if (this.bSandbox) { var i, length; for (i = 0, length = this.windowProperties.length; i < length; i++) { this._unset(iframeWindow, this.windowProperties[i]); } for (i=0, length = this.windowProperties2.length; i < length; i++) { this._unset(iframeWindow, this.windowProperties2[i], BX.DoNothing()); } for (i = 0, length = this.documentProperties.length; i < length; i++) { this._unset(iframeDocument, this.documentProperties[i]); } this._unset(iframeDocument, "cookie", "", true); } this.loaded = true; this.callback(this); this.editor.On('OnAfterIframeInit', [this.editor]); }); } }, InitIframe: function(iframe, callback) { iframe = this.iframe || iframe; var iframeDocument = iframe.contentWindow.document, iframeHtml = this.GetHtml(this.config.stylesheets, this.editor.GetTemplateStyles()); // Create the basic dom tree including proper DOCTYPE and charset iframeDocument.open("text/html", "replace"); iframeDocument.write(iframeHtml); iframeDocument.close(); iframeDocument.documentElement.innerHTML = iframeHtml; this.GetWindow = function() { return iframe.contentWindow; }; this.GetDocument = function() { return iframe.contentWindow.document; }; const triggerCallback = () => { if (iframeDocument.body) { if (this.editor.config.autoResizeMaxHeight === 'Infinity') { const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = this.editor.config.cssIframePath + '_' + this.editor.cssCounter++; link.onload = () => this.editor.ResizeSceleton(); iframeDocument.head.append(link); } this.editor.On('OnIframeInit'); if (typeof callback === 'function') { callback(); } } else { setTimeout(triggerCallback, 0); } }; setTimeout(triggerCallback, 0); }, GetHtml: function(css, cssText) { let bodyParams = ''; if (this.editor.config.bodyClass || this.editor.IsExpanded()) { let bodyClass = this.editor.config.bodyClass || ''; if (this.editor.IsExpanded()) bodyClass += ' fullscreen'; bodyParams += ' class="' + BX.util.trim(bodyClass) + '"'; } if (this.editor.config.bodyId) { bodyParams += ' id="' + this.editor.config.bodyId + '"'; } let headHtml = ''; if (BX.Type.isStringFilled(this.editor.config.designTokens)) { headHtml += this.editor.config.designTokens; } if (BX.Type.isStringFilled(this.editor.config.headHtml)) { headHtml += this.editor.config.headHtml; } const templ = this.editor.GetTemplateParams(); if (templ && templ['EDITOR_STYLES']) { for (let i = 0; i < templ['EDITOR_STYLES'].length; i++) { headHtml += '<link data-bx-template-style="Y" rel="stylesheet" href="' + templ['EDITOR_STYLES'][i] + '_' + this.editor.cssCounter++ + '">'; } } css = typeof css === "string" ? [css] : css; if (css) { for (let i = 0; i < css.length; i++) { headHtml += '<link rel="stylesheet" href="' + css[i] + '">'; } } headHtml += '<link rel="stylesheet" href="' + this.editor.config.cssIframePath + '_' + this.editor.cssCounter++ + '">'; if (typeof cssText === "string") { headHtml += '<style type="text/css" data-bx-template-style="Y">' + cssText + '</style>'; } if (BX.Type.isStringFilled(this.editor.config.fontSize)) { headHtml += `<style>body { font-size: ${this.editor.config.fontSize}; }</style>`; } if (this.editor.iframeCssText && this.editor.iframeCssText.length > 0) { headHtml += '<style type="text/css">' + this.editor.iframeCssText + '</style>'; } return '<!DOCTYPE html><html><head>' + headHtml + '</head><body' + bodyParams + '></body></html>'; }, /** * Method to unset/override existing variables * @example * // Make cookie unreadable and unwritable * this._unset(document, "cookie", "", true); */ _unset: function(object, property, value, setter) { try { object[property] = value; } catch(e) {} try { object.__defineGetter__(property, function() { return value; }); } catch(e) {} if (setter) { try { object.__defineSetter__(property, function() {}); } catch(e) {} } if (!crashesWhenDefineProperty(property)) { try { var config = { get: function() { return value; } }; if (setter) { config.set = function() {}; } Object.defineProperty(object, property, config); } catch(e) {} } } }; function BXEditorSelection(editor) { this.editor = editor; this.document = editor.sandbox.GetDocument(); BX.addCustomEvent(this.editor, 'OnIframeReInit', BX.proxy(function(){this.document = this.editor.sandbox.GetDocument();}, this)); // Make sure that our external range library is initialized window.rangy.init(); } BXEditorSelection.prototype = { // Get the current selection as a bookmark to be able to later restore it GetBookmark: function() { if (!this.editor.synchro.IsFocusedOnTextarea()) { var range = this.GetRange(); return range && range.cloneRange(); } return false; }, // Restore a selection SetBookmark: function(bookmark) { if (bookmark && this.editor.currentViewName !== 'code') { this.SetSelection(bookmark); } }, // Save current selection SaveBookmark: function() { this.lastRange = this.GetBookmark(); return this.lastRange; }, GetLastRange: function() { if (this.lastRange) return this.lastRange; }, // Restore selection RestoreBookmark: function() { if (this.lastRange) { this.SetBookmark(this.lastRange); this.lastRange = false; } }, /** * Set the caret in front of the given node * @param {Object} node The element or text node where to position the caret in front of */ SetBefore: function(node) { var range = rangy.createRange(this.document); range.setStartBefore(node); range.setEndBefore(node); return this.SetSelection(range); }, /** * Set the caret after the given node * * @param {Object} node The element or text node where to position the caret in front of */ SetAfter: function(node) { var range = rangy.createRange(this.document); range.setStartAfter(node); range.setEndAfter(node); return this.SetSelection(range); }, /** * Ability to select/mark nodes * * @param {Element} node The node/element to select */ SelectNode: function(node) { if (!node) return; var range = rangy.createRange(this.document), isElement = node.nodeType === 1, canHaveHTML = "canHaveHTML" in node ? node.canHaveHTML : (node.nodeName !== "IMG"), content = isElement ? node.innerHTML : node.data, isEmpty = (content === "" || content === this.editor.INVISIBLE_SPACE), styleDisplay = BX.style(node, 'display'), bBlock = (styleDisplay === "block" || styleDisplay === "list-item"); if ((BX.browser.IsIE() || BX.browser.IsIE10() || BX.browser.IsIE11()) && node && BX.util.in_array(node.nodeName.toUpperCase(), this.editor.TABLE_TAGS)) { //"TD", "TR", "TH", "TABLE", "TBODY", "CAPTION", "COL", "COLGROUP", "TFOOT", "THEAD"]; if (node.tagName == 'TABLE' || node.tagName == 'TBODY') { var firstRow = node.rows[0], lastRow = node.rows[node.rows.length - 1]; range.setStartBefore(firstRow.cells[0]); range.setEndAfter(lastRow.cells[lastRow.cells.length - 1]); } else if (node.tagName == 'TR' || node.tagName == 'TH') { range.setStartBefore(node.cells[0]); range.setEndAfter(node.cells[node.cells.length - 1]); } else { range.setStartBefore(node); range.setEndAfter(node); } this.SetSelection(range); return range; } if (isEmpty && isElement && canHaveHTML) { // Make sure that caret is visible in node by inserting a zero width no breaking space try { node.innerHTML = this.editor.INVISIBLE_SPACE; } catch(e) {} } if (canHaveHTML) range.selectNodeContents(node); else range.selectNode(node); if (canHaveHTML && isEmpty && isElement) { range.collapse(bBlock); } else if (canHaveHTML && isEmpty) { range.setStartAfter(node); range.setEndAfter(node); } try { this.SetSelection(range); } catch(e){} return range; }, /** * Get the node which contains the selection * * @param {Boolean} [controlRange] (only IE) Whether it should return the selected ControlRange element when the selection type is a "ControlRange" * @return {Object} The node that contains the caret */ GetSelectedNode: function(controlRange) { var res, selection, range; if (controlRange && this.document.selection && this.document.selection.type === "Control") { range = this.document.selection.createRange(); if (range && range.length) { res = range.item(0); } } if (!res) { selection = this.GetSelection(); if (selection.focusNode === selection.anchorNode) { res = selection.focusNode; } } if (!res) { range = this.GetRange(); res = range ? range.commonparentContainer : this.document.body } if (res && res.ownerDocument != this.editor.GetIframeDoc()) { res = this.document.body; } return res; }, ExecuteAndRestore: function(method, restoreScrollPosition) { var body = this.document.body, oldScrollTop = restoreScrollPosition && body.scrollTop, oldScrollLeft = restoreScrollPosition && body.scrollLeft, className = "_bx-editor-temp-placeholder", placeholderHTML = '<span class="' + className + '">' + this.editor.INVISIBLE_SPACE + '</span>', range = this.GetRange(), newRange; // Nothing selected, execute and say goodbye if (!range) { method(body, body); return; } var node = range.createContextualFragment(placeholderHTML); range.insertNode(node); // Make sure that a potential error doesn't cause our placeholder element to be left as a placeholder try { method(range.startContainer, range.endContainer); } catch(e3) { setTimeout(function() { throw e3; }, 0); } if (document.querySelector) { var caretPlaceholder = this.document.querySelector("." + className); if (caretPlaceholder) { newRange = rangy.createRange(this.document); newRange.selectNode(caretPlaceholder); newRange.deleteContents(); this.SetSelection(newRange); } else { // fallback for when all hell breaks loose body.focus(); } } if (restoreScrollPosition) { body.scrollTop = oldScrollTop; body.scrollLeft = oldScrollLeft; } // Remove it again, just to make sure that the placeholder is definitely out of the dom tree try { if (caretPlaceholder && caretPlaceholder.parentNode) caretPlaceholder.parentNode.removeChild(caretPlaceholder); } catch(e4) {} }, /** * Different approach of preserving the selection (doesn't modify the dom) * Takes all text nodes in the selection and saves the selection position in the first and last one */ ExecuteAndRestoreSimple: function(method) { var range = this.GetRange(), body = this.document.body, newRange, firstNode, lastNode, textNodes, rangeBackup; // Nothing selected, execute and say goodbye if (!range) { method(body, body); return; } textNodes = range.getNodes([3]); firstNode = textNodes[0] || range.startContainer; lastNode = textNodes[textNodes.length - 1] || range.endContainer; rangeBackup = { collapsed: range.collapsed, startContainer: firstNode, startOffset: firstNode === range.startContainer ? range.startOffset : 0, endContainer: lastNode, endOffset: lastNode === range.endContainer ? range.endOffset : lastNode.length }; try { method(range.startContainer, range.endContainer); } catch(e) { setTimeout(function() { throw e; }, 0); } newRange = rangy.createRange(this.document); try { newRange.setStart(rangeBackup.startContainer, rangeBackup.startOffset); } catch(e1) {} try { newRange.setEnd(rangeBackup.endContainer, rangeBackup.endOffset); } catch(e2) {} try { this.SetSelection(newRange); } catch(e3) {} }, /** * Insert html at the caret position and move the cursor after the inserted html * * @param {String} html HTML string to insert */ InsertHTML: function(html, range) { var rng = rangy.createRangyRange(this.document), node = rng.createContextualFragment(html), lastChild = node.lastChild; this.InsertNode(node, range); if (lastChild) { this.SetAfter(lastChild); } this.editor.On('OnInsertHtml'); }, /** * Insert a node at the caret position and move the cursor behind it * * @param {Object} node HTML string to insert */ InsertNode: function(node, range) { if (!range || !range.isValid || !range.isValid()) range = this.GetRange(); if (range) { range.insertNode(node); } this.editor.On('OnInsertHtml'); }, RemoveNode: function(node) { this.editor.On('OnHtmlContentChangedByControl'); var parent = node.parentNode, cursorNode = node.nextSibling; BX.remove(node); this.editor.util.Refresh(parent); if (cursorNode) { this.editor.selection.SetBefore(cursorNode); this.editor.Focus(); } this.editor.synchro.StartSync(100); }, /** * Wraps current selection with the given node * * @param {Object} node The node to surround the selected elements with * @param {Object} range Current range */ Surround: function(node, range) { range = range || this.GetRange(); if (range) { try { // This only works when the range boundaries are not overlapping other elements range.surroundContents(node); this.SelectNode(node); } catch(e) { node.appendChild(range.extractContents()); range.insertNode(node); } } }, ScrollIntoView: function() { var node, _this = this, doc = this.document, win = this.editor.sandbox.GetWindow(), bScrollBars = doc.documentElement.scrollHeight > doc.documentElement.offsetHeight; if (bScrollBars && win) { var tempNode = doc.__scrollIntoViewElement = doc.__scrollIntoViewElement || (function() { return BX.create("SPAN", {html: _this.editor.INVISIBLE_SPACE}, doc); })(), top = 0; this.InsertNode(tempNode); if (tempNode.parentNode) { node = tempNode; do { top += node.offsetTop || 0; node = node.offsetParent; } while (node); tempNode.parentNode.removeChild(tempNode); } var scrollPos = BX.GetWindowScrollPos(doc), innerSize = BX.GetWindowInnerSize(doc); if (top > scrollPos.scrollTop + innerSize.innerHeight - 40) { win.scrollTo(scrollPos.scrollLeft, top); } } }, /** * Select line where the caret is in */ SelectLine: function() { // See https://developer.mozilla.org/en/DOM/Selection/modify var bSelectionModify = "getSelection" in window && "modify" in window.getSelection(); if (bSelectionModify) { var win = this.document.defaultView, selection = win.getSelection(); selection.modify("move", "left", "lineboundary"); selection.modify("extend", "right", "lineboundary"); } else if (this.document.selection) // IE { var range = this.document.selection.createRange(), rangeTop = range.boundingTop, rangeHeight = range.boundingHeight, scrollWidth = this.document.body.scrollWidth, rangeBottom, rangeEnd, measureNode, i, j; if (!range.moveToPoint) return; if (rangeTop === 0) { // Don't know why, but when the selection ends at the end of a line // range.boundingTop is 0 measureNode = this.document.createElement("span"); this.insertNode(measureNode); rangeTop = measureNode.offsetTop; measureNode.parentNode.removeChild(measureNode); } rangeTop += 1; for (i =- 10; i < scrollWidth; i += 2) { try { range.moveToPoint(i, rangeTop); break; } catch(e1) {} } // Investigate the following in order to handle multi line selections // rangeBottom = rangeTop + (rangeHeight ? (rangeHeight - 1) : 0); rangeBottom = rangeTop; rangeEnd = this.document.selection.createRange(); for (j = scrollWidth; j >= 0; j--) { try { rangeEnd.moveToPoint(j, rangeBottom); break; } catch(e2) {} } range.setEndPoint("EndToEnd", rangeEnd); range.select(); } }, GetText: function() { var selection = this.GetSelection(); return selection ? selection.toString() : ""; }, GetNodes: function(nodeType, filter) { var range = this.GetRange(); if (range) return range.getNodes([nodeType], filter); else return []; }, GetRange: function(selection, bSetFocus) { if (!selection) { if (!this.editor.iframeView.IsFocused() && bSetFocus !== false) { var doc = this.editor.GetIframeDoc(), originalScrollTop = doc.documentElement.scrollTop || doc.body.scrollTop, originalScrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft; this.editor.iframeView.Focus(); var newScrollTop = doc.documentElement.scrollTop || doc.body.scrollTop, newScrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft; if (newScrollTop !== originalScrollTop || newScrollLeft !== originalScrollLeft) { var win = this.editor.sandbox.GetWindow(); if (win) win.scrollTo(originalScrollLeft, originalScrollTop); } } selection = this.GetSelection(); } var range = selection && selection.rangeCount && selection.getRangeAt(0); if (range.commonAncestorContainer && range.commonAncestorContainer.nodeName == '#document') { range.selectNodeContents(this.editor.GetIframeDoc().body); } return range; }, GetSelection: function(doc) { return rangy.getSelection(doc || this.document.defaultView || this.document.parentWindow); }, SetSelection: function(range) { var win = this.document.defaultView || this.document.parentWindow, selection = rangy.getSelection(win); return selection.setSingleRange(range); }, TrimRange: function() { const text = this.editor.selection.GetText(); if (BX.util.trim(text).length !== text.length) { const trimStart = text.length - text.trimStart().length; const trimEnd = text.length - text.trimEnd().length; this.MoveRange(this.GetRange().startOffset + trimStart, this.GetRange().endOffset - trimEnd); } return this.GetRange(); }, MoveRange: function(start, end) { if (end < 0) { end = 0; } const startNode = this.GetRange().startContainer; const endNode = this.GetRange().endContainer; const range = this.GetRange(); range.setStart(startNode, start); range.setEnd(endNode, end); this.GetSelection().removeAllRanges(); this.SetSelection(range); return this.GetRange(); }, GetStructuralTags: function() { if (!this.structuralTags) { var tblRe = /^TABLE/i; this.structuralTags = { 'LI': /^UL|OL|MENU/i, 'DT': /^DL/i, 'DD': /^DL/i, // table 'TD': tblRe, 'TR': tblRe, 'TH': tblRe, 'TBODY': tblRe, 'TFOOT': tblRe, 'THEAD': tblRe, 'CAPTION': tblRe, 'COL': tblRe, 'COLGROUP': tblRe }; this.structuralTagsMatchRe = /^LI|DT|DD|TD|TR|TH|TBODY|CAPTION|COL|COLGROUP|TFOOT|THEAD/i; } return this.structuralTags; }, SetCursorBeforeNode: function(e) { }, _GetNonTextLastChild: function(n) { var res = n.lastChild; while (res.nodeType != 1 && res.previousSibling) res = res.previousSibling; return res.nodeType == 1 ? res : false; }, _GetNonTextFirstChild: function(n) { var res = n.firstChild; while (res.nodeType != 1 && res.nextSibling) res = res.nextSibling; return res.nodeType == 1 ? res : false; }, _MoveCursorBeforeNode: function(node) { var _this = this, possibleParentRe, parNode, isFirstNode; this.GetStructuralTags(); // Check if it's child node which have special parent // We can't add text node and put carret <td> and <tr> or between <li>... // 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) if (node.nodeType == 1 && node.nodeName.match(this.structuralTagsMatchRe)) { isFirstNode = this._GetNonTextFirstChild(node.parentNode) === node; if (!isFirstNode) { return; } possibleParentRe = this.structuralTags[node.nodeName]; if (possibleParentRe) { parNode = BX.findParent(node, function(n) { if (n.nodeName.match(possibleParentRe)) { return true; } isFirstNode = isFirstNode && _this._GetNonTextFirstChild(n.parentNode) === n; return false; }, node.ownerDocument.BODY); if (parNode && isFirstNode) { node = parNode; // Put carret before parrent tag } else { return; } } } this.SetInvisibleTextBeforeNode(node); }, _MoveCursorAfterNode: function(node) { var _this = this, possibleParentRe, parNode, isLastNode; this.GetStructuralTags(); // Check if it's child node which have special parent // We can't add text node and put carret <td> and <tr> or between <li>... // 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) if (node.nodeType == 1 && node.nodeName.match(this.structuralTagsMatchRe)) { isLastNode = this._GetNonTextLastChild(node.parentNode) === node; if (!isLastNode) { return; } possibleParentRe = this.structuralTags[node.nodeName]; if (possibleParentRe) { parNode = BX.findParent(node, function(n) { if (n.nodeName.match(possibleParentRe)) { return true; } isLastNode = isLastNode && _this._GetNonTextLastChild(n.parentNode) === n; return false; }, node.ownerDocument.BODY); if (parNode && isLastNode) { node = parNode; // Put carret after parrent tag } else { return; } } } this.SetInvisibleTextAfterNode(node); }, SaveRange: function(bSetFocus) { var range = this.GetRange(false, bSetFocus); if (range) { this.lastCheckedRange = {endOffset: range.endOffset, endContainer: range.endContainer, range: range}; } else { // Mantis: #66025 setTimeout(BX.proxy(this.SaveRange, this), 0); } }, CheckLastRange: function(range) { return this.lastCheckedRange && this.lastCheckedRange.endOffset == range.endOffset && this.lastCheckedRange.endContainer == range.endContainer; }, SetInvisibleTextAfterNode: function(node, setCursorBefore) { var invis_text = this.editor.util.GetInvisibleTextNode(); if (node.nextSibling && node.nextSibling.nodeType == 3 && this.editor.util.IsEmptyNode(node.nextSibling)) { this.editor.util.ReplaceNode(node.nextSibling, invis_text); } else { this.editor.util.InsertAfter(invis_text, node); } if (setCursorBefore) { this.SetBefore(invis_text); } else { this.SetAfter(invis_text); } this.editor.Focus(); }, SetInvisibleTextBeforeNode: function(node) { var invis_text = this.editor.util.GetInvisibleTextNode(); if (node.previousSibling && node.previousSibling.nodeType == 3 && this.editor.util.IsEmptyNode(node.previousSibling)) { this.editor.util.ReplaceNode(node.previousSibling, invis_text); } else { node.parentNode.insertBefore(invis_text, node); } this.SetBefore(invis_text); this.editor.Focus(); }, GetCommonAncestorForRange: function(range) { return range.collapsed ? range.startContainer : rangy.dom.getCommonAncestor(range.startContainer, range.endContainer); } }; function NodeMerge(firstNode) { this.isElementMerge = (firstNode.nodeType == 1); this.firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode; this.firstNode = firstNode; this.textNodes = [this.firstTextNode]; } NodeMerge.prototype = { DoMerge: function() { var i, len = this.textNodes.length, textBits = [], textNode, parent, text; for (i = 0; i < len; ++i) { textNode = this.textNodes[i]; if (this.textNodes[i].nodeType !== 3) { return false; } parent = textNode.parentNode; textBits[i] = textNode.data; if (i) { parent.removeChild(textNode); if (!parent.hasChildNodes()) parent.parentNode.removeChild(parent); } } this.firstTextNode.data = text = textBits.join(""); return text; }, GetLength: function() { var i = this.textNodes.length, len = 0; while (i--) len += this.textNodes[i].length; return len; } // ToString: function() // { // var textBits = []; // // for (var i = 0, len = this.textNodes.length; i < len; ++i) // textBits[i] = "'" + this.textNodes[i].data + "'"; // // return "[Merge(" + textBits.join(",") + ")]"; // } }; function HTMLStyler(editor, tagNames, arStyle, cssClass, normalize) { this.editor = editor; this.document = editor.iframeView.document; this.tagNames = tagNames || [defaultTagName]; this.arStyle = arStyle || {}; this.cssClass = cssClass || ""; this.similarClassRegExp = null; this.normalize = normalize; this.applyToAnyTagName = false; } HTMLStyler.prototype = { GetStyledParent: function(node, bMatchCss) { bMatchCss = bMatchCss !== false; var cssClassMatch, cssStyleMatch; while (node) { if (node.nodeType == 1) { cssStyleMatch = this.CheckCssStyle(node, bMatchCss); cssClassMatch = this.CheckCssClass(node); if (BX.util.in_array(node.tagName.toLowerCase(), this.tagNames) && cssClassMatch && cssStyleMatch) return node; } node = node.parentNode; } return false; }, CheckCssStyle: function(node, bMatch) { return this.editor.util.CheckCss(node, this.arStyle, bMatch); }, SimplifyNodesWithCss: function(node) { var i, parent = node.parentNode; if (parent.childNodes.length == 1) // container over our node { if (node.nodeName == parent.nodeName) { for (i in this.arStyle) { if (this.arStyle.hasOwnProperty(i) && node.style[i]) { parent.style[i] = node.style[i]; } } this.editor.util.ReplaceWithOwnChildren(node); } else { for (i in this.arStyle) { if (this.arStyle.hasOwnProperty(i) && parent.style[i] && node.style[i]) { parent.style[i] = ''; } } } } }, CheckCssClass: function(node) { return !this.cssClass || (this.cssClass && BX.hasClass(node, this.cssClass)); }, // Normalizes nodes after applying a CSS class to a Range. PostApply: function(textNodes, range) { var i, firstNode = textNodes[0], lastNode = textNodes[textNodes.length - 1], merges = [], currentMerge, rangeStartNode = firstNode, rangeEndNode = lastNode, rangeStartOffset = 0, rangeEndOffset = lastNode.length, textNode, precedingTextNode; for (i = 0; i < textNodes.length; ++i) { textNode = textNodes[i]; precedingTextNode = this.GetAdjacentMergeableTextNode(textNode.parentNode, false); if (precedingTextNode) { if (!currentMerge) { currentMerge = new NodeMerge(precedingTextNode); merges.push(currentMerge); } currentMerge.textNodes.push(textNode); if (textNode === firstNode) { rangeStartNode = currentMerge.firstTextNode; rangeStartOffset = rangeStartNode.length; } if (textNode === lastNode) { rangeEndNode = currentMerge.firstTextNode; rangeEndOffset = currentMerge.GetLength(); } } else { currentMerge = null; } } // Test whether the first node after the range needs merging var nextTextNode = this.GetAdjacentMergeableTextNode(lastNode.parentNode, true); if (nextTextNode) { if (!currentMerge) { currentMerge = new NodeMerge(lastNode); merges.push(currentMerge); } currentMerge.textNodes.push(nextTextNode); } // Do the merges if (merges.length) { for (i = 0; i < merges.length; ++i) merges[i].DoMerge(); // Set the range boundaries range.setStart(rangeStartNode, rangeStartOffset); range.setEnd(rangeEndNode, rangeEndOffset); } // Simplify elements textNodes = range.getNodes([3]); for (i = 0; i < textNodes.length; ++i) { textNode = textNodes[i]; this.SimplifyNodesWithCss(textNode.parentNode); } }, NormalizeNewNode: function(node, range) { var parent = node.parentNode; if (parent && parent.nodeName !== 'BODY') { var childs = this.GetNonEmptyChilds(parent), cssStyleMatch = this.CheckCssStyle(parent, false), cssClassMatch = this.CheckCssClass(parent); if (childs.length == 1 && parent.nodeName == node.nodeName && cssStyleMatch && cssClassMatch) { parent.parentNode.insertBefore(node, parent); BX.remove(parent); } } return range; }, GetNonEmptyChilds: function(node) { var i, childs = node.childNodes, res = []; for (i = 0; i < childs.length; i++) { if (childs[i].nodeType == 1 || (childs[i].nodeType == 3 && childs[i].nodeValue != "" && childs[i].nodeValue != this.editor.INVISIBLE_SPACE && !childs[i].nodeValue.match(/^[\s\n\r\t]+$/ig))) { res.push(childs[i]); } } return res; }, GetAdjacentMergeableTextNode: function(node, forward) { var isTextNode = (node.nodeType == 3), el = isTextNode ? node.parentNode : node, adjacentNode, propName = forward ? "nextSibling" : "previousSibling"; if (isTextNode) { // Can merge if the node's previous/next sibling is a text node adjacentNode = node[propName]; if (adjacentNode && adjacentNode.nodeType == 3) { return adjacentNode; } } else { // TODO: fix it. Now code fails on this example, try to make "22" italic // <i>11 </i>22<i><br> // 33 </i><br> /* // Compare element with its sibling adjacentNode = el[propName]; if (adjacentNode && this.AreElementsMergeable(node, adjacentNode)) { return adjacentNode[forward ? "firstChild" : "lastChild"]; } */ } return null; }, AreElementsMergeable: function(el1, el2) { return rangy.dom.arrayContains(this.tagNames, (el1.tagName || "").toLowerCase()) && rangy.dom.arrayContains(this.tagNames, (el2.tagName || "").toLowerCase()) && el1.className.replace(/\s+/g, " ") == el2.className.replace(/\s+/g, " ") && this.CompareNodeAttributes(el1, el2); }, CompareNodeAttributes: function(el1, el2) { if (el1.attributes.length != el2.attributes.length) return false; var i, len = el1.attributes.length, attr1, attr2, name; for (i = 0; i < len; i++) { attr1 = el1.attributes[i]; name = attr1.name; if (name != "class") { attr2 = el2.attributes.getNamedItem(name); if (attr1.specified != attr2.specified) return false; if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) { return false; } } } return true; }, CreateContainer: function() { var el = this.document.createElement(this.tagNames[0]); if (this.cssClass) { el.className = this.cssClass; } if (this.arStyle) { for (var i in this.arStyle) { if (this.arStyle.hasOwnProperty(i)) { el.style[i] = this.arStyle[i]; } } } return el; }, ApplyToTextNode: function(textNode) { var parent = textNode.parentNode, i; if (parent.childNodes.length == 1 && BX.util.in_array(parent.tagName.toLowerCase(), this.tagNames)) { if (this.cssClass) { BX.addClass(parent, this.cssClass); } if (this.arStyle) { for (i in this.arStyle) { if (this.arStyle.hasOwnProperty(i)) { parent.style[i] = this.arStyle[i]; } } } } else { if (parent.childNodes.length == 1) // container over the text node { if (this.cssClass && BX.hasClass(parent, this.cssClass)) { BX.removeClass(parent, this.cssClass); } if (this.arStyle) { for (i in this.arStyle) { if (this.arStyle.hasOwnProperty(i) && parent.style[i]) { parent.style[i] = ''; } } } } var el = this.CreateContainer(); textNode.parentNode.insertBefore(el, textNode); el.appendChild(textNode); } }, IsRemovable: function(el) { return rangy.dom.arrayContains(this.tagNames, el.tagName.toLowerCase()) && BX.util.trim(el.className) == this.cssClass; }, IsAvailableTextNodeParent: function(node) { return node && node.nodeName && node.nodeName !== 'OL' && node.nodeName !== 'UL' && node.nodeName !== 'MENU' && // LLIST node.nodeName !== 'TBODY' && node.nodeName !== 'TFOOT' && node.nodeName !== 'THEAD' && node.nodeName !== 'TABLE' && // TABLE node.nodeName !== 'DL'; }, UndoToTextNode: function(textNode, range, styledParent) { if (!range.containsNode(styledParent)) { // Split out the portion of the parent from which we can remove the CSS class var parentRange = range.cloneRange(); parentRange.selectNode(styledParent); BX.isParentForNode(styledParent, range.endContainer) if ( range.endContainer.nodeName !== 'BODY' && parentRange.isPointInRange(range.endContainer, range.endOffset) && this.editor.util.IsSplitPoint(range.endContainer, range.endOffset) && BX.isParentForNode(styledParent, range.endContainer) ) { this.editor.util.SplitNodeAt(styledParent, range.endContainer, range.endOffset); range.setEndAfter(styledParent); } if ( range.startContainer.nodeName !== 'BODY' && parentRange.isPointInRange(range.startContainer, range.startOffset) && this.editor.util.IsSplitPoint(range.startContainer, range.startOffset) && BX.isParentForNode(styledParent, range.startContainer) ) { styledParent = this.editor.util.SplitNodeAt(styledParent, range.startContainer, range.startOffset); } } if (styledParent && styledParent.nodeName != 'BODY' && this.IsRemovable(styledParent)) { if (this.arStyle) { for (var i in this.arStyle) { if (this.arStyle.hasOwnProperty(i) && styledParent.style[i]) { styledParent.style[i] = ''; } } } if (!styledParent.style.cssText || BX.util.trim(styledParent.style.cssText) === '') { this.editor.util.ReplaceWithOwnChildren(styledParent); } else if (this.tagNames.length > 1 || this.tagNames[0] !== 'span') { this.editor.util.RenameNode(styledParent, "span"); } } }, ApplyToRange: function(range) { var textNodes = range.getNodes([3]); if (!textNodes.length) { try { var node = this.CreateContainer(); range.surroundContents(node); range = this.NormalizeNewNode(node, range); this.SelectNode(range, node); return range; } catch(e) {} } range.splitBoundaries(); textNodes = range.getNodes([3]); if (!textNodes.length && range.collapsed && range.startContainer == range.endContainer) { var inv = this.editor.util.GetInvisibleTextNode(); this.editor.selection.InsertNode(inv); textNodes = [inv]; } if (textNodes.length) { var textNode; for (var i = 0, len = textNodes.length; i < len; ++i) { textNode = textNodes[i]; if (!this.GetStyledParent(textNode) && this.IsAvailableTextNodeParent(textNode.parentNode)) { this.ApplyToTextNode(textNode); } } range.setStart(textNodes[0], 0); textNode = textNodes[textNodes.length - 1]; range.setEnd(textNode, textNode.length); if (this.normalize) { this.PostApply(textNodes, range); } } return range; }, UndoToRange: function(range, bMatchCss) { var textNodes = range.getNodes([3]), textNode, styledParent; bMatchCss = bMatchCss !== false; if (textNodes.length) { range.splitBoundaries(); textNodes = range.getNodes([3]); } else { var node = this.editor.util.GetInvisibleTextNode(); range.insertNode(node); range.selectNode(node); textNodes = [node]; } var i, len, sorted = []; for (i = 0, len = textNodes.length; i < len; i++) { sorted.push({node: textNodes[i], nesting: this.GetNodeNesting(textNodes[i])}); } sorted = sorted.sort(function(a, b){return b.nesting - a.nesting}); for (i = 0, len = sorted.length; i < len; i++) { textNode = sorted[i].node; styledParent = this.GetStyledParent(textNode, bMatchCss); if (styledParent) { this.UndoToTextNode(textNode, range, styledParent); range = this.editor.selection.GetRange(); } } if (len == 1) { this.SelectNode(range, textNodes[0]); } else { range.setStart(textNodes[0], 0); range.setEnd(textNodes[textNodes.length - 1], textNodes[textNodes.length - 1].length); this.editor.selection.SetSelection(range); if (this.normalize) { this.PostApply(textNodes, range); } } return range; }, // Node dom offset GetNodeNesting: function(node) { return this.editor.util.GetNodeDomOffset(node); }, SelectNode: function(range, node) { var isElement = node.nodeType === 1, canHaveHTML = "canHaveHTML" in node ? node.canHaveHTML : true, content = isElement ? node.innerHTML : node.data, isEmpty = (content === "" || content === this.editor.INVISIBLE_SPACE); if (isEmpty && isElement && canHaveHTML) { // Make sure that caret is visible in node by inserting a zero width no breaking space try {node.innerHTML = this.editor.INVISIBLE_SPACE;} catch(e) {} } range.selectNodeContents(node); if (isEmpty && isElement) { range.collapse(false); } else if (isEmpty) { range.setStartAfter(node); range.setEndAfter(node); } }, GetTextSelectedByRange: function(textNode, range) { var textRange = range.cloneRange(); textRange.selectNodeContents(textNode); var intersectionRange = textRange.intersection(range); var text = intersectionRange ? intersectionRange.toString() : ""; textRange.detach(); return text; }, IsAppliedToRange: function(range, bMatchCss) { var parents = [], parent, textNodes = range.getNodes([3]); bMatchCss = bMatchCss !== false; if (!textNodes.length) { parent = this.GetStyledParent(range.startContainer, bMatchCss); return parent ? [parent] : false; } var i, selectedText; for (i = 0; i < textNodes.length; ++i) { selectedText = this.GetTextSelectedByRange(textNodes[i], range); parent = this.GetStyledParent(textNodes[i], bMatchCss); if (selectedText != "" && !parent) { return false; } else { parents.push(parent); } } return parents; }, ToggleRange: function(range) { return this.IsAppliedToRange(range) ? this.UndoToRange(range) : this.ApplyToRange(range); } }; function BXEditorUndoManager(editor) { this.editor = editor; this.history = [this.editor.iframeView.GetValue()]; this.position = 1; this.document = editor.sandbox.GetDocument(); this.historyLength = 30; BX.addCustomEvent(this.editor, 'OnIframeReInit', BX.proxy(function(){this.document = this.editor.sandbox.GetDocument();}, this)); this.Init(); } BXEditorUndoManager.prototype = { Init: function() { var _this = this; BX.addCustomEvent(this.editor, "OnHtmlContentChangedByControl", BX.delegate(this.Transact, this)); BX.addCustomEvent(this.editor, "OnIframeNewWord", BX.delegate(this.Transact, this)); BX.addCustomEvent(this.editor, "OnIframeKeyup", BX.delegate(this.Transact, this)); BX.addCustomEvent(this.editor, "OnBeforeCommandExec", function(isContentAction) { if (isContentAction) { _this.Transact(); } }); // CTRL+Z and CTRL+Y and handle DEL & BACKSPACE BX.addCustomEvent(this.editor, "OnIframeKeydown", BX.proxy(this.Keydown, this)); }, Keydown: function(e, keyCode, command, selectedNode) { if ((e.ctrlKey || e.metaKey) && !e.altKey) { var isUndo = keyCode === this.editor.KEY_CODES['z'] && !e.shiftKey, isRedo = (keyCode === this.editor.KEY_CODES['z'] && e.shiftKey) || (keyCode === this.editor.KEY_CODES['y']); if (isUndo) { this.Undo(); return BX.PreventDefault(e); } else if (isRedo) { this.Redo(); return BX.PreventDefault(e); } } if (keyCode !== this.lastKey) { if (keyCode === this.editor.KEY_CODES['backspace'] || keyCode === this.editor.KEY_CODES['delete']) { this.Transact(); } this.lastKey = keyCode; } }, Transact: function() { var previousHtml = this.history[this.position - 1], currentHtml = this.editor.iframeView.GetValue().replaceAll(decodeURIComponent('%EF%BB%BF'), ''); if (currentHtml.match(/<div class="bxhtmled-copilot"(.*?)>(.*?)<\/div>/g) !== null) { return; } if (currentHtml !== previousHtml) { var length = this.history.length = this.position; if (length > this.historyLength) { this.history.shift(); this.position--; } this.position++; this.history.push(currentHtml); this.CheckControls(); } }, Undo: function() { if (this.position > 1) { this.Transact(); this.position--; this.Set(this.history[this.position - 1]); this.editor.On("OnUndo"); this.CheckControls(); } }, Redo: function() { if (this.position < this.history.length) { this.position++; this.Set(this.history[this.position - 1]); this.editor.On("OnRedo"); this.CheckControls(); } }, Set: function(html) { this.editor.iframeView.SetValue(html); this.editor.Focus(true); }, CheckControls: function() { this.editor.On("OnEnableUndo", [this.position > 1]); this.editor.On("OnEnableRedo", [this.position < this.history.length]); } }; function BXStyles(editor) { this.editor = editor; this.arStyles = {}; this.sStyles = ''; } BXStyles.prototype = { GetIframe: function(styles) { if (!this.cssIframe) { this.cssIframe = this.CreateIframe(styles); } return this.cssIframe; }, CreateIframe: function(styles) { this.cssIframe = document.body.appendChild(BX.create("IFRAME", {props: {className: "bx-editor-css-iframe"}})); this.iframeDocument = this.cssIframe.contentDocument || this.cssIframe.contentWindow.document; this.iframeDocument.open("text/html", "replace"); this.iframeDocument.write('<!DOCTYPE html><html><head><style type="text/css" data-bx-template-style="Y">' + styles + '</style></head><body></body></html>'); this.iframeDocument.close(); return this.cssIframe; }, GetCSS: function(templateId, styles, templatePath, filter) { if (!this.arStyles[templateId]) { if (!this.cssIframe) { this.cssIframe = this.CreateIframe(styles); } else { var i, doc = this.iframeDocument, head = doc.head || doc.getElementsByTagName('HEAD')[0], styleNodes = head.getElementsByTagName('STYLE'); // Clean old template styles for (i = 0; i < styleNodes.length; i++) { if (styleNodes[i].getAttribute('data-bx-template-style') == 'Y') BX.cleanNode(styleNodes[i], true); } // Add new node in the iframe head if (styles) { head.appendChild(BX.create('STYLE', {props: {type: 'text/css'}, text: styles}, doc)).setAttribute('data-bx-template-style', 'Y'); } } this.arStyles[templateId] = this.ParseCss(); } var res = this.arStyles[templateId]; if (filter) { var filteredRes = [], tag; if (typeof filter != 'object' ) { filter = [filter]; } filter.push('DEFAULT'); for (i = 0; i < filter.length; i++) { tag = filter[i]; if (res[tag] && typeof res[tag] == 'object') { filteredRes = filteredRes.concat(res[tag]); } } res = filteredRes; } return res; }, ParseCss: function() { var doc = this.iframeDocument, arAllSt = [], result = {}, rules, cssTag, arTags, i, j, k, t1, t2, l1, l2, l3; if(!doc.styleSheets) { return result; } var x1 = doc.styleSheets, stylesDescription = this.editor.GetStylesDescription(); for(i = 0, l1 = x1.length; i < l1; i++) { rules = (x1[i].rules ? x1[i].rules : x1[i].cssRules); for(j = 0, l2 = rules.length; j < l2; j++) { if (rules[j].type != rules[j].STYLE_RULE) { continue; } cssTag = rules[j].selectorText; arTags = cssTag.split(","); for(k = 0, l3 = arTags.length; k < l3; k++) { t1 = arTags[k].split(" "); t1 = t1[t1.length - 1].trim(); if(t1.substr(0, 1) == '.') { t1 = t1.substr(1); t2 = 'DEFAULT'; } else { t2 = t1.split("."); t1 = (t2.length > 1) ? t2[1] : ''; t2 = t2[0].toUpperCase(); } if(!arAllSt[t1]) { arAllSt[t1] = true; if(!result[t2]) { result[t2] = []; } result[t2].push({ className: t1, classTitle: stylesDescription[t1] || null, original: arTags[k], cssText: rules[j].style.cssText }); } } } } return result; } }; // Parse rules /** * Full HTML5 compatibility rule set * These rules define which tags and css classes are supported and which tags should be specially treated. * * Examples based on this rule set: * * <a href="http://foobar.com">foo</a> * ... becomes ... * <a href="http://foobar.com" target="_blank" rel="nofollow">foo</a> * * <img align="left" src="http://foobar.com/image.png"> * ... becomes ... * <img class="wysiwyg-float-left" src="http://foobar.com/image.png" alt=""> * * <div>foo<script>alert(document.cookie)</script></div> * ... becomes ... * <div>foo</div> * * <marquee>foo</marquee> * ... becomes ... * <span>foo</marquee> * * foo <br clear="both"> bar * ... becomes ... * foo <br class="wysiwyg-clear-both"> bar * * <div>hello <iframe src="http://google.com"></iframe></div> * ... becomes ... * <div>hello </div> * * <center>hello</center> * ... becomes ... * <div class="wysiwyg-text-align-center">hello</div> */ __BXHtmlEditorParserRules = { /** * CSS Class white-list * Following css classes won't be removed when parsed by the parser */ classes: {}, "tags": { "b": {clean_empty: true}, "strong": {clean_empty: true}, "i": {clean_empty: true}, "em": {clean_empty: true}, "u": {clean_empty: true}, "del": {clean_empty: true}, "s": {rename_tag: "del"}, "strike": {rename_tag: "del"}, // headers "h1": {}, "h2": {}, "h3": {}, "h4": {}, "h5": {}, "h6": {}, // popular tags "span": {clean_empty: true}, "p": {}, "br": {}, "div": {}, "hr": {}, "nobr": {}, "code": {}, "section": {}, "figure": {}, "figcaption": {}, "fieldset": {}, "legend": {}, // Lists "menu": {rename_tag: "ul"}, // ?? "ol": {}, "ul": {}, "li": {}, "pre": {}, // Table "table": {}, "tr": {}, "td": { "check_attributes": { "rowspan": "numbers", "colspan": "numbers" } }, "tbody": {}, "tfoot": {}, "thead": {}, "th": { "check_attributes": { "rowspan": "numbers", "colspan": "numbers" } }, "caption": {}, "col": {}, "colgroup": {}, // Definitions // <dl>, <dt>, <dd> "dl": {rename_tag: ""}, "dd": {rename_tag: ""}, "dt": {rename_tag: ""}, "iframe": {}, "noindex": {}, "font": { rename_tag: "span", convert_attributes: { face: 'fontFamily', size: 'fontSize', color: 'color' }, replace_with_children: 1 }, "embed": {}, "noembed": {}, "object": {}, "param": {}, "sup": {}, "sub": {}, "address": {}, "nav": {}, "aside": {}, "article": {}, "main": {}, "acronym": {}, "abbr": {}, "label": {}, "time": {}, "small": {}, "big": {}, "details": {}, "summary": {}, "footer": {}, "video": {}, "source": {}, "audio": {}, "nofollow": {}, // tags to remove "title": {remove: 1}, "area": {remove: 1}, "command": {remove: 1}, "noframes": {remove: 1}, "bgsound": {remove: 1}, "basefont": {remove: 1}, "head": {remove: 1}, "track": {remove: 1}, "wbr": {remove: 1}, "noscript": {remove: 1}, "svg": {remove: 1}, "keygen": {remove: 1}, "meta": {remove: 1}, "isindex": {remove: 1}, "base": {remove: 1}, "canvas": {remove: 1}, "applet": {remove: 1}, "spacer": {remove: 1}, "frame": {remove: 1}, "style": {remove: 1}, "device": {remove: 1}, "xml": {remove: 1}, "nextid": {remove: 1}, "link": {remove: 1}, "script": {remove: 1}, "comment": {remove: 1}, "frameset": {remove: 1}, // Tags to rename // to DIV "multicol": {rename_tag: "div"}, "map": {rename_tag: "div"}, "body": {rename_tag: "div"}, "html": {rename_tag: "div"}, "hgroup": {rename_tag: "div"}, "listing": {rename_tag: "div"}, "header": {rename_tag: "div"}, // to SPAN "rt": {rename_tag: "span"}, "xmp": {rename_tag: "span"}, "bdi": {rename_tag: "span"}, "progress": {rename_tag: "span"}, "dfn": {rename_tag: "span"}, "rb": {rename_tag: "span"}, "mark": {rename_tag: "span"}, "output": {rename_tag: "span"}, "marquee": {rename_tag: "span"}, "rp": {rename_tag: "span"}, "var": {rename_tag: "span"}, "tt": {rename_tag: "span"}, "blink": {rename_tag: "span"}, "plaintext": {rename_tag: "span"}, "kbd": {rename_tag: "span"}, "meter": {rename_tag: "span"}, "datalist": {rename_tag: "span"}, "samp": {rename_tag: "span"}, "bdo": {rename_tag: "span"}, "ruby": {rename_tag: "span"}, "ins": {rename_tag: "span"}, "optgroup": {rename_tag: "span"}, // Form elements "form": {}, "option": {}, "select": {}, "textarea": {}, "button": {}, "input": {}, "dir": {rename_tag: "ul"}, "a": {}, "img": { "check_attributes": { "width": "numbers", "alt": "alt", "src": "url", "height": "numbers" }, "add_class": { "align": "align_img" } }, "q": { "check_attributes": { "cite": "url" } }, "blockquote": { "check_attributes": { "cite": "url" } }, "center": { rename_tag: "div", add_css: { textAlign : 'center' } }, "cite": {} } }; })(window);