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-views.js |
/** * Bitrix HTML Editor 3.0 * Date: 24.04.13 * Time: 4:23 * * Views class */ (function() { function BXEditorView(editor, element, container) { this.editor = editor; this.element = element; this.container = container; this.config = editor.config || {}; this.isShown = null; this.bbCode = editor.bbCode; BX.addCustomEvent(this.editor, "OnClickBefore", BX.proxy(this.OnClick, this)); } BXEditorView.prototype = { Focus: function() { if (!document.querySelector || this.element.ownerDocument.querySelector(":focus") === this.element) return; try{this.element.focus();}catch(e){} }, Hide: function() { this.isShown = false; this.container.style.display = "none"; }, Show: function() { this.isShown = true; this.container.style.display = ""; }, Disable: function() { this.element.setAttribute("disabled", "disabled"); }, Enable: function() { this.element.removeAttribute("disabled"); }, OnClick: function(params) { }, IsShown: function() { return !!this.isShown; } }; function BXEditorTextareaView(parent, textareaElement, container) { // Call parrent constructor BXEditorIframeView.superclass.constructor.apply(this, arguments); this.name = "textarea"; this.InitEventHandlers(); if (!this.element.value && this.editor.config.content) this.SetValue(this.editor.config.content, false); } BX.extend(BXEditorTextareaView, BXEditorView); BXEditorTextareaView.prototype.Clear = function() { this.element.value = ""; }; BXEditorTextareaView.prototype.GetValue = function(bParse) { var value = this.IsEmpty() ? "" : this.element.value; if (bParse) { value = this.parent.parse(value); } return value; }; BXEditorTextareaView.prototype.SetValue = function(html, bParse, bFormat) { if (bParse) { html = this.editor.Parse(html, true, bFormat); } this.editor.dom.pValueInput.value = this.element.value = html; }; BXEditorTextareaView.prototype.SaveValue = function() { if (this.editor.inited) { this.editor.dom.pValueInput.value = this.element.value; } }; BXEditorTextareaView.prototype.HasPlaceholderSet = function() { var placeholderText = this.element.getAttribute("placeholder") || null, value = this.element.value; return !value || (value === placeholderText); }; BXEditorTextareaView.prototype.IsEmpty = function() { var value = BX.util.trim(this.element.value); return value === '' || this.HasPlaceholderSet(); }; BXEditorTextareaView.prototype.InitEventHandlers = function() { var _this = this; BX.bind(this.element, "focus", function() { _this.editor.On("OnTextareaFocus"); _this.isFocused = true; }); BX.bind(this.element, "blur", function() { _this.editor.On("OnTextareaBlur"); _this.isFocused = false; }); BX.bind(this.element, "keydown", function(e) { _this.editor.textareaKeyDownPreventDefault = false; // Handle Ctrl+Enter if ((e.ctrlKey || e.metaKey) && !e.altKey && e.keyCode === _this.editor.KEY_CODES["enter"]) { _this.editor.On('OnCtrlEnter', [e, _this.editor.GetViewMode()]); return BX.PreventDefault(e); } _this.editor.On('OnTextareaKeydown', [e]); if (_this.editor.textareaKeyDownPreventDefault) return BX.PreventDefault(e); }); BX.bind(this.element, "keyup", function(e) { _this.editor.On('OnTextareaKeyup', [e]); }); }; BXEditorTextareaView.prototype.IsFocused = function() { return this.isFocused; }; BXEditorTextareaView.prototype.ScrollToSelectedText = function(searchText) { // http://blog.blupixelit.eu/scroll-textarea-to-selected-word-using-javascript-jquery/ // var parola_cercata = "parola"; // the searched word // var posi = jQuery('#my_textarea').val().indexOf(parola_cercata); // take the position of the word in the text // if (posi != -1) { // var target = document.getElementById("my_textarea"); // // select the textarea and the word // target.focus(); // if (target.setSelectionRange) // target.setSelectionRange(posi, posi+parola_cercata.length); // else { // var r = target.createTextRange(); // r.collapse(true); // r.moveEnd('character', posi+parola_cercata); // r.moveStart('character', posi); // r.select(); // } // var objDiv = document.getElementById("my_textarea"); // var sh = objDiv.scrollHeight; //height in pixel of the textarea (n_rows*line_height) // var line_ht = jQuery('#my_textarea').css('line-height').replace('px',''); //height in pixel of each row // var n_lines = sh/line_ht; // the total amount of lines // var char_in_line = jQuery('#insert_textarea').val().length / n_lines; // amount of chars for each line // var height = Math.floor(posi/char_in_line); // amount of lines in the textarea // jQuery('#my_textarea').scrollTop(height*line_ht); // scroll to the selected line // } else { // alert('parola '+parola_cercata+' non trovata'); // alert word not found // } }; BXEditorTextareaView.prototype.SelectText = function(searchText) { var value = this.element.value, ind = value.indexOf(searchText); if(ind != -1) { this.element.focus(); this.element.setSelectionRange(ind, ind + searchText.length); } }; BXEditorTextareaView.prototype.GetTextSelection = function() { var res = false; if (this.element.selectionStart != undefined) { res = this.element.value.substr(this.element.selectionStart, this.element.selectionEnd - this.element.selectionStart); } else if (document.selection && document.selection.createRange) { res = document.selection.createRange().text; } else if (window.getSelection) { res = window.getSelection(); res = res.toString(); } return res; }; BXEditorTextareaView.prototype.WrapWith = function (tagBegin, tagEnd, postText) { if (!tagBegin) tagBegin = ""; if (!tagEnd) tagEnd = ""; if (!postText) postText = ""; if (tagBegin.length <= 0 && tagEnd.length <= 0 && postText.length <= 0) return true; var bReplaceText = !!postText, selectedText = this.GetTextSelection(), mode = (selectedText ? 'select' : (bReplaceText ? 'after' : 'in')); //if (!this.bTextareaFocus) // this.pTextarea.focus(); // BUG IN IE if (bReplaceText) { postText = tagBegin + postText + tagEnd; } else if (selectedText) { postText = tagBegin + selectedText + tagEnd; } else { postText = tagBegin + tagEnd; } if (this.element.selectionStart != undefined) { var currentScroll = this.element.scrollTop, start = this.element.selectionStart, end = this.element.selectionEnd; this.element.value = this.element.value.substr(0, start) + postText + this.element.value.substr(end); if (mode == 'select') { this.element.selectionStart = start; this.element.selectionEnd = start + postText.length; } else if (mode == 'in') { this.element.selectionStart = this.element.selectionEnd = start + tagBegin.length; } else { this.element.selectionStart = this.element.selectionEnd = start + postText.length; } this.element.scrollTop = currentScroll; } else if (document.selection && document.selection.createRange) { var sel = document.selection.createRange(); var selection_copy = sel.duplicate(); postText = postText.replace(/\r?\n/g, '\n'); sel.text = postText; sel.setEndPoint('StartToStart', selection_copy); sel.setEndPoint('EndToEnd', selection_copy); if (mode == 'select') { sel.collapse(true); postText = postText.replace(/\r\n/g, '1'); sel.moveEnd('character', postText.length); } else if (mode == 'in') { sel.collapse(false); sel.moveEnd('character', tagBegin.length); sel.collapse(false); } else { sel.collapse(false); sel.moveEnd('character', postText.length); sel.collapse(false); } sel.select(); } else { // failed - just stuff it at the end of the message this.element.value += postText; } return true; }; BXEditorTextareaView.prototype.GetCursorPosition = function() { return this.element.selectionStart; }; class BXEditorIframeCopilot { copilotParams = {}; copilotLoaded = false; selectionTypeCaret = 'Caret'; resultNodeAttr = 'bxhtmled-copilot-result-node'; resultColor = '#8d52ec'; invitationLineModes = { NONE: 'none', LAST_LINE: 'lastLine', EACH_LINE: 'eachLine', }; invitationLineMode = this.invitationLineModes.LAST_LINE; /** * @param iframeView BXEditorIframeView * @param copilotParams {{moduleId, contextId, category, contextParameters, invitationLineMode, isMentionUnavailable}} */ constructor(iframeView, copilotParams = {}) { this.iframeView = iframeView; this.iframeContainer = iframeView.container; this.contentEditable = iframeView.element; this.copilotParams = copilotParams; this.invitationLineMode = copilotParams.invitationLineMode ?? this.invitationLineModes.LAST_LINE; this.invitationLine = this.renderInvitationLine(); this.invitationLineAbsolute = this.renderInvitationLine(); this.insertResultNode = this.renderInsertResultNode(); this.copilot = new BX.AI.Copilot({ moduleId: copilotParams.moduleId, contextId: copilotParams.contextId, category: copilotParams.category, contextParameters: copilotParams.contextParameters, autoHide: true, preventAutoHide: (event) => this.iframeContainer.contains(event.target), }); this.bindHandlers(); this.copilot.init(); } renderInvitationLine() { let placeHolder = BX.message('BXEdCopilotPlaceholder_MSGVER_2'); if (this.copilotParams.isMentionUnavailable) { placeHolder = BX.message('BXEdCopilotPlaceholderWithoutMention_MSGVER_1'); } return BX.Tag.render` <div class="bxhtmled-copilot" placeholder="${placeHolder}"></div> `; } renderInsertResultNode() { return BX.Tag.render` <span class="bxhtmled-insert-result"></span> `; } bindHandlers() { this.copilot.subscribe('finish-init', this.finishInitHandler.bind(this)); this.copilot.subscribe('aiResult', this.aiResultHandler.bind(this)); this.copilot.subscribe('save', this.saveHandler.bind(this)); this.copilot.subscribe('add_below', this.addBelowHandler.bind(this)); this.copilot.subscribe('cancel', this.cancelHandler.bind(this)); this.copilot.subscribe('hide', this.saveResultNodes.bind(this)); document.addEventListener('click', this.documentClickHandler.bind(this)); document.addEventListener('keydown', this.onWindowKeyDownHandler.bind(this)); this.contentEditable.addEventListener('input', this.onContentEditableKeyDown.bind(this)); window.addEventListener('scroll', this.onScrollHandler.bind(this), true); new ResizeObserver(this.onScrollHandler.bind(this)).observe(this.contentEditable); window.addEventListener('resize', this.handleResizeWindow.bind(this)); BX.addCustomEvent(window, "onPullEvent-unicomments", this.startAdjustAnimation.bind(this)); this.hideObserver = new MutationObserver(() => { if (this.iframeContainer.offsetHeight <= 0) { this.hideInvitationLine(); this.hideObserver.disconnect(); } }); this.hideObserver.observe(document.body, {childList: true}); } documentClickHandler(event) { if (!this.iframeContainer.contains(event.target)) { this.hideInvitationLine(); } } startAdjustAnimation() { this.animation?.stop(); this.animation = new BX.easing({ duration: 1000, start: {}, finish: {}, transition : BX.easing.makeEaseOut(BX.easing.transitions.linear), step: () => { if (this.copilot.isShown()) { this.copilot.adjust(this.getAdjustOptionsForRect(this.adjustmentRect)); } if (this.copilotBtnPopup?.isShown()) { this.adjustCopilotButton(this.getAdjustOptionsForRectSpaced(this.adjustmentRect, true)); } }, complete: () => this.animation = null, }); this.animation.animate(); } finishInitHandler() { this.copilotLoaded = true; this.updateInvitationLine(); } aiResultHandler(event) { const resultNodes = this.getResultNodes(); let lastResultSpan; if (resultNodes.length !== 0) { lastResultSpan = [...resultNodes].pop(); lastResultSpan.innerText = event.data.result; } else { lastResultSpan = this.getSpanWithText(event.data.result); BX.Dom.style(lastResultSpan, 'color', this.resultColor); BX.Dom.attr(lastResultSpan, this.resultNodeAttr, 'true'); if (this.invitationLineMode === this.invitationLineModes.LAST_LINE) { this.insertResultNode.after(BX.Tag.render`<br>`); } this.insertResultNode.after(lastResultSpan); this.insertResultNode.remove(); } this.iframeView.ScrollToInsertedText(); this.getSelection().removeAllRanges(); this.copilot.adjust(this.getAdjustOptions(lastResultSpan)); } saveHandler(event) { if (this.getResultNodes().length === 0) { const selection = this.getSelection().getRangeAt(0); const span = this.getSpanWithText(event.data.result); selection.deleteContents(); selection.insertNode(span); this.iframeView.UpdateHeight(); } this.saveResultNodes(); this.iframeView.editor.synchro.FromIframeToTextarea(true, true); this.iframeView.editor.synchro.FromTextareaToIframe(true); this.copilot.hide(); } addBelowHandler(event) { const span = this.getSpanWithText(event.data.result); let lastSelectedElement = null; if (this.getSelectionText() !== '') { lastSelectedElement = this.getSelection().getRangeAt(0).endContainer; } const isSelectionEndOutOfBounds = lastSelectedElement === this.iframeView.element; if (lastSelectedElement && !isSelectionEndOutOfBounds) { lastSelectedElement.after(span); lastSelectedElement.after(BX.Tag.render`<br>`); } else { BX.Dom.append(BX.Tag.render`<br>`, this.contentEditable); BX.Dom.append(span, this.contentEditable); BX.Dom.append(BX.Tag.render`<br>`, this.contentEditable); } this.iframeView.ScrollToInsertedText(); this.getSelection().removeAllRanges(); this.iframeView.editor.synchro.FromIframeToTextarea(true, true); this.iframeView.editor.synchro.FromTextareaToIframe(true); this.copilot.hide(); } cancelHandler() { [...this.getResultNodes()][0]?.before(this.insertResultNode); this.removeResultNodes(); if (this.showRect) { this.copilot.adjust(this.getAdjustOptionsForRect(this.showRect)); } } getSpanWithText(text) { const span = BX.Dom.create('span'); span.innerText = text; return span; } onWindowKeyDownHandler(event) { if (event.key === "Escape") { this.copilot.hide(); } } handleResizeWindow() { this.copilot.adjustWidth(this.getCopilotWidth()); } removeResultNodes() { this.getResultNodes().forEach(resultNode => resultNode.remove()); } saveResultNodes() { for (const resultNode of this.getResultNodes()) { for (const childNode of resultNode.childNodes) { resultNode.before(childNode.cloneNode(true)); } resultNode.remove(); } } onContentEditableMouseDown() { this.insertResultNode.remove(); this.hideInvitationLine(); this.hideCopilotButton(); } onIframeWindowClick() { this.update(); } onContentEditableKeyDown() { this.insertResultNode.remove(); this.updatePopup(); this.hideInvitationLine(); this.hideCopilotButton(); } onContentEditableKeyUp(e) { if (e.key === ' ') { return; } this.update(); } onScrollHandler() { if (this.contentEditable.offsetHeight <= 0) { return; } if (this.invitationLineAbsolute.parentNode) { this.showInvitationLine(); } if (this.copilotBtnPopup?.isShown() && this.getSelectionText() !== '') { const range = this.getSelection().getRangeAt(0); this.adjustCopilotButton(this.getAdjustOptions(range, true)); } if (this.copilot.isShown() && this.getSelectionText() !== '') { const range = this.getSelection().getRangeAt(0); this.copilot.adjust(this.getAdjustOptions(range)); return; } if (this.getResultNodes().pop()) { this.updatePopup(); } else if (this.adjustmentRect) { this.copilot.adjust(this.getAdjustOptionsForRect(this.adjustmentRect)); } } onContentEditableBlur() { setTimeout(() => { this.hideInvitationLine(); }, 0); } onIframeFocus() { setTimeout(() => { this.updateInvitationLine(); }, 0); } shouldBeShown() { return this.invitationLineAbsolute.offsetWidth !== 0 && !this.copilot.isShown(); } show(showFromSpace = false) { this.copilot.setContext(this.contentEditable.innerText); this.insertResultNode.remove(); this.getSelection().getRangeAt(0).insertNode(this.insertResultNode); const bindElement = { top: parseInt(this.invitationLineAbsolute.style.top), left: parseInt(this.invitationLineAbsolute.style.left), }; if (showFromSpace) { bindElement.top += 28; } const containerRect = this.iframeContainer.getBoundingClientRect(); this.adjustmentRect = { bottom: bindElement.top - containerRect.y - window.scrollY, x: bindElement.left - containerRect.x - window.scrollX, width: 0, }; this.showRect = this.adjustmentRect; this.copilot.show({ bindElement, showFromSpace, width: this.getCopilotWidth(), }); this.hideInvitationLine(); } showAtTheBottom() { this.copilot.setContext(this.contentEditable.innerText); this.insertResultNode.remove(); let emptyBr; let emptyLine; if (this.needToAppendEmptyElement()) { emptyBr = BX.Tag.render`<br>`; emptyLine = BX.Tag.render`<div></div>`; const lastNode = this.getFilteredChildNodes(this.contentEditable).pop(); if (this.contentEditable.innerText !== '' && lastNode?.tagName !== 'BR') { this.contentEditable.append(BX.Tag.render`<br>`); } this.contentEditable.append(emptyBr); this.contentEditable.append(emptyLine); } const lastNode = this.getFilteredChildNodes(this.contentEditable).pop(); const bindElement = this.getAdjustOptions(lastNode).position; emptyLine?.remove(); this.showRect = this.adjustmentRect; if (emptyBr) { emptyBr.before(this.insertResultNode); } else { this.contentEditable.append(this.insertResultNode); } this.copilot.show({ bindElement, width: this.getCopilotWidth(), }); this.hideInvitationLine(); } needToAppendEmptyElement() { const innerText = this.contentEditable.innerText; return !innerText || (innerText.at(-1) !== '\n') || (innerText.at(-2) && innerText.at(-2) !== '\n'); } update() { this.updateCopilotButton(); this.updatePopup(); this.updateInvitationLine(); } updateCopilotButton() { const shouldShowCopilotButton = !this.copilot.isShown() && this.getSelectionText() !== ''; if (shouldShowCopilotButton && !this.copilotBtnPopup?.isShown() && this.copilotLoaded) { this.showCopilotButton(); } if (!shouldShowCopilotButton) { this.hideCopilotButton(); } } showCopilotButton() { if (!this.copilotBtnPopup) { this.copilotBtnPopup = new BX.Main.Popup({ bindElement: this.contentEditable, padding: 6, borderRadius: '6px', content: this.renderCopilotButton(), autoHide: true, }); } this.copilotBtnPopup.setMaxWidth(null); this.copilotBtnPopup.setMinWidth(null); this.copilotBtnPopup.setPadding(6); this.adjustCopilotButton(this.getAdjustOptions(this.getSelection().getRangeAt(0), true)); this.copilotBtnPopup.adjustPosition(); this.copilotBtnPopup.show(); setTimeout(() => this.updateCopilotButton(), 0); } renderCopilotButton() { BX.Runtime.loadExtension('ui.icon-set.main'); this.copilotButton = BX.Tag.render` <button class="bxhtmled-copilot-btn" onclick="${this.copilotButtonClickHandler.bind(this)}"> <div class="bxhtmled-copilot-btn-icon ui-icon-set --copilot-ai"></div> ${ BX.message('BXEdCopilotButtonText') } </button> `; return this.copilotButton; } copilotButtonClickHandler() { const adjustOptions = this.getAdjustOptions(this.getSelection().getRangeAt(0)); this.copilot.setSelectedText(this.getSelection().toString()); this.copilot.show({ bindElement: adjustOptions.position, showFromPopup: true, width: this.getCopilotWidth(), }); this.hideCopilotButton(); } adjustCopilotButton(options) { if (options.hide) { this.copilotBtnPopup.setMaxWidth(0); this.copilotBtnPopup.setMinWidth(0); this.copilotBtnPopup.setPadding(0); } else { this.copilotBtnPopup.setMaxWidth(null); this.copilotBtnPopup.setMinWidth(null); this.copilotBtnPopup.setPadding(6); this.copilotBtnPopup.setBindElement(options.position); this.copilotBtnPopup.adjustPosition(); } } getCopilotWidth() { return this.contentEditable.offsetWidth - 30; } hideCopilotButton() { this.copilotBtnPopup?.close(); } updatePopup() { if (!this.copilot.isShown()) { return; } const lastResultSpan = this.getResultNodes().pop(); if (!this.isNotFocusedOrCursorAtResultNode(this.getSelection()) || !lastResultSpan) { this.copilot.hide(); return; } this.copilot.adjust(this.getAdjustOptions(lastResultSpan)); } getAdjustOptions(pivot, isCentered = false) { const pivotRect = pivot.getBoundingClientRect(); return this.getAdjustOptionsForRectSpaced(pivotRect, isCentered); } getAdjustOptionsForRectSpaced(pivotRect, isCentered = false) { const adjustment = this.getAdjustOptionsForRect(pivotRect, isCentered); adjustment.position.top += 10; return adjustment; } getAdjustOptionsForRect(pivotRect, isCentered = false) { this.adjustmentRect = pivotRect; const containerRect = this.iframeContainer.getBoundingClientRect(); return { hide: pivotRect.bottom > this.iframeView.document.documentElement.offsetHeight + 13 || pivotRect.bottom + 13 < 0, position: { top: pivotRect.bottom + containerRect.y + window.scrollY, left: pivotRect.x + containerRect.x + isCentered * (pivotRect.width / 2 - 55) + window.scrollX, }, }; } isNotFocusedOrCursorAtResultNode(selection) { if (!selection.focusNode) { return true; } return this.getResultNodes().filter(node => selection.focusNode === node || selection.focusNode.parentElement === node).length; } getResultNodes() { return [...this.contentEditable.querySelectorAll(`[${this.resultNodeAttr}=true]`)]; } updateInvitationLine() { this.insertResultNode.remove(); if (this.shouldShowInvitationLine() && this.copilotLoaded) { this.showInvitationLine(); } else { this.hideInvitationLine(); } } showInvitationLine() { const selection = this.getSelection(); if (selection.rangeCount === 0) { return false; } const lastNode = this.getFilteredChildNodes(this.contentEditable).pop(); if (this.isZwnbspNode(lastNode)) { lastNode.replaceWith(BX.Tag.render`<br>`); } const range = selection.getRangeAt(0); range.insertNode(this.invitationLine); const container = this.iframeContainer.getBoundingClientRect(); const invitationLineRect = { top: this.invitationLine.offsetTop + container.y - this.contentEditable.parentElement.scrollTop + window.scrollY, left: this.invitationLine.offsetLeft + container.x + window.scrollX, width: this.invitationLine.offsetWidth, }; this.invitationLineAbsolute.style.top = `${invitationLineRect.top}px`; this.invitationLineAbsolute.style.left = `${invitationLineRect.left}px`; this.invitationLineAbsolute.style.width = `${invitationLineRect.width}px`; this.invitationLineAbsolute.style.display = ''; this.hideInvitationLine(); document.body.append(this.invitationLineAbsolute); if (!this.invitationLineAbsolute.registered) { BX.ZIndexManager.register(this.invitationLineAbsolute); BX.ZIndexManager.bringToFront(this.invitationLineAbsolute); this.invitationLineAbsolute.registered = true; } if (!this.shouldDisplayInvitationLine()) { this.invitationLineAbsolute.style.display = 'none'; } } shouldDisplayInvitationLine() { const contentEditableRect = this.iframeContainer.getBoundingClientRect(); const invitationLineRect = this.invitationLineAbsolute.getBoundingClientRect(); return invitationLineRect.bottom + 20 < contentEditableRect.bottom && invitationLineRect.top > contentEditableRect.top; } hideInvitationLine() { this.invitationLine.remove(); this.invitationLineAbsolute.remove(); } getSelectionText() { return this.getSelection().toString().replaceAll('\n', '').trim(); } getSelection() { return this.iframeView.GetSelection(); } shouldShowInvitationLine() { if (!this.iframeContainer.contains(document.activeElement)) { return false; } if (this.invitationLineMode === this.invitationLineModes.NONE) { return false; } if (this.invitationLineMode === this.invitationLineModes.EACH_LINE) { return this.isCursorAtStartOfLine(this.getSelection()); } return this.isCursorAtNewLine(this.getSelection()); } isCursorAtStartOfLine(selection) { if (selection.type !== this.selectionTypeCaret) { return false; } this.removeZwnbspSequence(); const range = selection.getRangeAt(0); const contentEditableOffset = parseInt(getComputedStyle(this.contentEditable).paddingLeft); if (range.getBoundingClientRect().x > contentEditableOffset) { return false; } const tmpNode = BX.Tag.render`<span style="width: 10px; height: 10px;"></span>`; range.insertNode(tmpNode); const previousNode = tmpNode.previousSibling; const nextNode = tmpNode.nextSibling; const offset = tmpNode.getBoundingClientRect().x - contentEditableOffset; if ( selection.focusNode === this.contentEditable && nextNode === null && ( previousNode?.tagName === 'DIV' || this.isZwnbspNode(previousNode) ) ) { if (this.isZwnbspNode(previousNode)) { if (!previousNode.nextSibling) { previousNode.replaceWith(BX.Tag.render`<br>`); } else { previousNode.remove(); } } tmpNode.remove(); return false; } const afterBr = !previousNode || this.doesNodeMakeLine(previousNode) || tmpNode.nodeName !== '#text' && tmpNode.textContent === ''; const beforeBr = !nextNode || this.doesNodeMakeLine(nextNode) && nextNode.tagName !== 'TABLE'; const atStart = offset <= 0; tmpNode.remove(); return ((afterBr && beforeBr) || (this.isZwnbspNode(previousNode) && nextNode?.nodeName === '#text')) && atStart; } isCursorAtNewLine(selection) { if (selection.toString() !== '') { return false; } this.removeZwnbspSequence(); if (selection.focusNode.outerHTML === '<span><br></span>' || selection.focusNode.outerHTML === '<div><br></div>') { selection.focusNode.replaceWith(BX.Tag.render`<br>`); } const nodes = this.getFilteredChildNodes(this.contentEditable); const isEmptyOrOnlyLine = (nodes.length === 0) || (nodes.length === 1 && nodes[0].tagName === 'BR'); const lastNode = nodes.pop(); let doesLastNodeMakeLine = false; if (lastNode?.tagName === 'SPAN') { const spanNodes = this.getFilteredChildNodes(lastNode); const lastSpanNode = spanNodes.pop(); const oneSpanNodeBeforeLast = spanNodes.pop(); doesLastNodeMakeLine = this.doesNodeMakeLine(oneSpanNodeBeforeLast) && this.doesNodeMakeLine(lastSpanNode); } if (!this.isZwnbspNode(selection.focusNode) && selection.focusNode !== this.contentEditable && lastNode?.tagName !== 'SPAN') { return false; } const oneNodeBeforeLast = nodes.pop(); const doLastTwoNodesMakeLine = this.doesNodeMakeLine(oneNodeBeforeLast) && this.doesNodeMakeLine(lastNode) && lastNode?.tagName !== 'BLOCKQUOTE'; return (isEmptyOrOnlyLine || doLastTwoNodesMakeLine || doesLastNodeMakeLine) && this.isCursorAtTheEndOfDocument(selection); } removeZwnbspSequence() { const nodesToRemove = []; this.contentEditable.childNodes.forEach((node) => { if (this.isZwnbspNode(node) && this.isZwnbspNode(node.nextSibling)) { nodesToRemove.push(node); } }); nodesToRemove.forEach((node) => node.remove()); if (this.contentEditable.innerHTML === this.createZwnbspNode().innerHTML) { this.contentEditable.innerHTML = ''; } } getFilteredChildNodes(element) { return [...element.childNodes].filter((node) => { return node.nodeName !== '#text' || this.isZwnbspNode(node) || node.textContent !== '' }); } doesNodeMakeLine(node) { if (!node) { return false; } if (node.outerHTML === '<span><br></span>') { node.replaceWith(BX.Tag.render`<br>`); return true; } if (this.isZwnbspNode(node)) { if (!node.nextSibling) { node.replaceWith(BX.Tag.render`<br>`); } else { node.remove(); } return true; } const makingLineNodes = ['BR', 'UL', 'OL', 'PRE', 'TABLE', 'DIV', 'P', 'BLOCKQUOTE']; if (node.tagName === 'SPAN') { return this.doesNodeMakeLine(this.getFilteredChildNodes(node).pop()); } return makingLineNodes.includes(node.tagName) || makingLineNodes.includes([...node.childNodes].pop()?.tagName); } isZwnbspNode(node) { if (!node) { return false; } return BX.Tag.render`<div>${node.cloneNode()}</div>`.innerHTML === this.createZwnbspNode().innerHTML; } createZwnbspNode() { return BX.Tag.render`<div></div>`; } isCursorAtTheEndOfDocument(selection) { const offset = selection.focusOffset; const node = selection.focusNode; selection.modify("move", "forward", "character"); if (offset === selection.focusOffset && node === selection.focusNode) { return true; } else { selection.modify("move", "backward", "character"); return false; } } } function BXEditorIframeView(editor, textarea, container) { // Call parrent constructor BXEditorIframeView.superclass.constructor.apply(this, arguments); this.name = "wysiwyg"; this.caretNode = "<br>"; } BX.extend(BXEditorIframeView, BXEditorView); BXEditorIframeView.prototype.OnCreateIframe = function() { this.document = this.editor.sandbox.GetDocument(); this.element = this.document.body; this.editor.document = this.document; this.textarea = this.editor.dom.textarea; this.isFocused = false; this.InitEventHandlers(); // Check and init external range library window.rangy.init(); if (this.config.isCopilotEnabled && BX.AI?.Copilot) { this.copilot = new BXEditorIframeCopilot(this, { ...this.config.copilotParams, isMentionUnavailable: this.config.isMentionUnavailable, }); } this.Enable(); }; BXEditorIframeView.prototype.setCopilotContextParameters = function(formData) { this.config.copilotParams.contextParameters = { xmlId: formData[0], entityId: formData[1], }; }; BXEditorIframeView.prototype.ScrollToInsertedText = function() { this.document.documentElement.scrollTop = this.document.documentElement.scrollHeight; this.UpdateHeight(); }; BXEditorIframeView.prototype.UpdateHeight = function() { this.editor.UpdateHeight(); }; BXEditorIframeView.prototype.GetSelection = function() { return this.document.getSelection(); }; BXEditorIframeView.prototype.isCopilotInitialized = function() { return this.copilot && this.copilot.copilotLoaded; }; BXEditorIframeView.prototype.Clear = function() { //this.element.innerHTML = BX.browser.IsFirefox() ? this.caretNode : ""; this.element.innerHTML = this.caretNode; }; BXEditorIframeView.prototype.GetValue = function(bParse, bFormat) { this.iframeValue = this.IsEmpty() ? "" : this.editor.GetInnerHtml(this.element); this.iframeValue = this.iframeValue.replaceAll(/<span style="font-family: var\(--ui-font-family-primary, var\(--ui-font-family-helvetica\)\);">(.*?)<\/span>/g, '$1'); this.iframeValue = this.iframeValue.replace(/<span class="bxhtmled-insert-result"><\/span>/g, ''); this.editor.On('OnIframeBeforeGetValue', [this.iframeValue]); if (bParse) { this.iframeValue = this.editor.Parse(this.iframeValue, false, bFormat); } return this.iframeValue; }; BXEditorIframeView.prototype.SetValue = function(html, bParse) { if (bParse) { html = this.editor.Parse(html); } this.element.innerHTML = html; // Check last child - if it's block node in the end - add <br> tag there this.CheckContentLastChild(this.element); this.editor.On('OnIframeSetValue', [html]); }; BXEditorIframeView.prototype.Show = function() { this.isShown = true; this.container.style.display = ""; this.ReInit(); }; BXEditorIframeView.prototype.ReInit = function() { // Firefox needs this, otherwise contentEditable becomes uneditable this.Disable(); this.Enable(); this.editor.On('OnIframeReInit'); }; BXEditorIframeView.prototype.Hide = function() { this.isShown = false; this.container.style.display = "none"; }; BXEditorIframeView.prototype.Disable = function() { this.element.removeAttribute("contentEditable"); }; BXEditorIframeView.prototype.Enable = function() { this.element.setAttribute("contentEditable", "true"); }; BXEditorIframeView.prototype.Focus = function(setToEnd) { if (BX.browser.IsIE() && this.HasPlaceholderSet()) { this.Clear(); } if (!document.querySelector || this.element.ownerDocument.querySelector(":focus") !== this.element || !this.IsFocused()) { if (BX.browser.IsIOS()) { var _this = this; if (this.focusTimeout) clearTimeout(this.focusTimeout); this.focusTimeout = setTimeout(function() { var orScrollTop = document.documentElement.scrollTop || document.body.scrollTop, orScrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft; BX.focus(_this.element); window.scrollTo(orScrollLeft, orScrollTop); }, 200); } else { BX.focus(this.element); } } if (setToEnd && this.element.lastChild) { if (this.element.lastChild.nodeName === "BR") { this.editor.selection.SetBefore(this.element.lastChild); } else { this.editor.selection.SetAfter(this.element.lastChild); } } }; BXEditorIframeView.prototype.SetFocusedFlag = function(isFocused) { this.isFocused = isFocused; }; BXEditorIframeView.prototype.IsFocused = function() { return this.isFocused; }; BXEditorIframeView.prototype.GetTextContent = function(clearInvisibleSpace) { var txt = this.editor.util.GetTextContent(this.element); return clearInvisibleSpace === true ? txt.replace(/\uFEFF/ig, '') : txt; }; BXEditorIframeView.prototype.HasPlaceholderSet = function() { return this.textarea && this.GetTextContent() == this.textarea.getAttribute("placeholder"); }; BXEditorIframeView.prototype.IsEmpty = function(clearInvisibleSpace) { if (!document.querySelector) return false; var innerHTML = this.element.innerHTML, elementsWithVisualValue = "blockquote, ul, ol, img, embed, object, table, iframe, svg, video, audio, button, input, select, textarea"; return innerHTML === "" || innerHTML === this.caretNode || this.HasPlaceholderSet() || (this.GetTextContent(clearInvisibleSpace) === "" && !this.element.querySelector(elementsWithVisualValue)); }; BXEditorIframeView.prototype._initObjectResizing = function() { var properties = ["width", "height"], propertiesLength = properties.length, element = this.element; this.commands.exec("enableObjectResizing", this.config.allowObjectResizing); if (this.config.allowObjectResizing) { // IE sets inline styles after resizing objects // The following lines make sure _this the width/height css properties // are copied over to the width/height attributes if (browser.supportsEvent("resizeend")) { dom.observe(element, "resizeend", function(event) { var target = event.target || event.srcElement, style = target.style, i = 0, property; for(; i<propertiesLength; i++) { property = properties[i]; if (style[property]) { target.setAttribute(property, parseInt(style[property], 10)); style[property] = ""; } } // After resizing IE sometimes forgets to remove the old resize handles redraw(element); }); } } else { if (browser.supportsEvent("resizestart")) { dom.observe(element, "resizestart", function(event) { event.preventDefault(); }); } } }; /** * With "setActive" IE offers a smart way of focusing elements without scrolling them into view: * http://msdn.microsoft.com/en-us/library/ms536738(v=vs.85).aspx * * Other browsers need a more hacky way: (pssst don't tell my mama) * In order to prevent the element being scrolled into view when focusing it, we simply * move it out of the scrollable area, focus it, and reset it's position */ var focusWithoutScrolling = function(element) { if (element.setActive) { // Following line could cause a js error when the textarea is invisible // See https://github.com/xing/wysihtml5/issues/9 try { element.setActive(); } catch(e) {} } else { var elementStyle = element.style, originalScrollTop = doc.documentElement.scrollTop || doc.body.scrollTop, originalScrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft, originalStyles = { position: elementStyle.position, top: elementStyle.top, left: elementStyle.left, WebkitUserSelect: elementStyle.WebkitUserSelect }; dom.setStyles({ position: "absolute", top: "-99999px", left: "-99999px", // Don't ask why but temporarily setting -webkit-user-select to none makes the whole thing performing smoother WebkitUserSelect: "none" }).on(element); element.focus(); dom.setStyles(originalStyles).on(element); if (win.scrollTo) { // Some browser extensions unset this method to prevent annoyances // "Better PopUp Blocker" for Chrome http://code.google.com/p/betterpopupblocker/source/browse/trunk/blockStart.js#100 // Issue: http://code.google.com/p/betterpopupblocker/issues/detail?id=1 win.scrollTo(originalScrollLeft, originalScrollTop); } } }; /** * Taking care of events * - Simulating 'change' event on contentEditable element * - Handling drag & drop logic * - Catch paste events * - Dispatch proprietary newword:composer event * - Keyboard shortcuts */ BXEditorIframeView.prototype.InitEventHandlers = function() { var _this = this, editor = this.editor, value = this.GetValue(), element = this.element, iframeWindow = this.editor.sandbox.GetWindow(), _element = !BX.browser.IsOpera() ? element : this.editor.sandbox.GetWindow(); if (this._eventsInitedObject && this._eventsInitedObject === _element) return; this._eventsInitedObject = _element; BX.bind(_element, "focus", function() { editor.On("OnIframeFocus"); _this.isFocused = true; if (value !== _this.GetValue()) { BX.onCustomEvent(editor, "OnIframeChange"); } _this.copilot?.onIframeFocus(); }); BX.bind(_element, "blur", function() { editor.On("OnIframeBlur"); _this.isFocused = false; setTimeout(function(){value = _this.GetValue();}, 0); _this.copilot?.onContentEditableBlur(); }); BX.bind(_element, "contextmenu", function(e) { if(e && !e.ctrlKey && !e.shiftKey && (BX.getEventButton(e) & BX.MSRIGHT)) { editor.On("OnIframeContextMenu", [e, e.target || e.srcElement, _this.contMenuRangeCollapsed]); } }); BX.bind(_element, "mousedown", function(e) { var range = editor.selection.GetRange(), target = e.target || e.srcElement, bxTag = editor.GetBxTag(target); //mantis: 71174 _this.contMenuRangeCollapsed = range && range.collapsed; if (editor.synchro.IsSyncOn()) { editor.synchro.StopSync(); } if (BX.browser.IsIE10() || BX.browser.IsIE11()) { editor.phpParser.RedrawSurrogates(); } if (target.nodeName == 'BODY' || !editor.phpParser.CheckParentSurrogate(target)) { setTimeout(function() { range = editor.selection.GetRange(); if (range && range.collapsed && range.startContainer && range.startContainer == range.endContainer) { var surr = editor.phpParser.CheckParentSurrogate(range.startContainer); if (surr) { editor.selection.SetInvisibleTextAfterNode(surr); editor.selection.SetInvisibleTextBeforeNode(surr); } } }, 10); } editor.action.actions.quote.checkSpaceAfterQuotes(target); editor.selection.SaveRange(false); setTimeout(function(){editor.selection.SaveRange(false);}, 10); editor.On("OnIframeMouseDown", [e, target, bxTag]); }); BX.bind(_element, "touchend", function(){_this.Focus();}); BX.bind(_element, "touchstart", function(){_this.Focus();}); BX.bind(_element, "click", function(e) { var target = e.target || e.srcElement; editor.On("OnIframeClick", [e, target]); }); BX.bind(_element, "dblclick", function(e) { var target = e.target || e.srcElement; editor.On("OnIframeDblClick", [e, target]); }); BX.bind(_element, "mouseup", function(e) { var target = e.target || e.srcElement; if (!editor.synchro.IsSyncOn()) { editor.synchro.StartSync(); } editor.On("OnIframeMouseUp", [e, target]); }); BX.bind(iframeWindow, 'click', () => this.copilot?.onIframeWindowClick()); // Mantis: 90137 //if (BX.browser.IsIOS()) //{ // // When on iPad/iPhone/IPod after clicking outside of editor, the editor loses focus // // but the UI still acts as if the editor has focus (blinking caret and onscreen keyboard visible) // // We prevent _this by focusing a temporary input element which immediately loses focus // BX.bind(iframeWindow, "blur", function() // { // var // orScrollTop = document.documentElement.scrollTop || document.body.scrollTop, // orScrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft, // input = BX.create('INPUT', { // props:{type: 'text', value: ''} // }, iframeWindow.ownerDocument); // // try // { // editor.selection.InsertNode(input); // } // catch(e) // { // iframeWindow.appendChild(input); // } // // BX.focus(input); // BX.remove(input); // window.scrollTo(orScrollLeft, orScrollTop); // }); //} // --------- Drag & Drop events --------- BX.bind(element, "dragover", function(){editor.On("OnIframeDragOver", arguments);}); BX.bind(element, "dragenter", function(){editor.On("OnIframeDragEnter", arguments);}); BX.bind(element, "dragleave", function(){editor.On("OnIframeDragLeave", arguments);}); BX.bind(element, "dragexit", function(){editor.On("OnIframeDragExit", arguments);}); BX.bind(element, "drop", function(){editor.On("OnIframeDrop", arguments);}); // Chrome & Safari & Firefox only fire the ondrop/ondragend/... events when the ondragover event is cancelled //if (BX.browser.IsChrome() || BX.browser.IsFirefox()) // TODO: Truobles with firefox during selections http://jabber.bx/view.php?id=49370 if (BX.browser.IsFirefox()) { BX.bind(element, "dragover", function(e) { e.preventDefault(); }); BX.bind(element, "dragenter", function(e) { e.preventDefault(); }); } BX.bind(element, 'drop', BX.delegate(this.OnPasteHandler, this)); BX.bind(element, 'paste', (clipboardEvent) => { const event = new BX.Event.BaseEvent({ data: { clipboardEvent: clipboardEvent } }); BX.Event.EventEmitter.emitAsync(this.editor, 'BXEditor:onBeforePasteAsync', event).then(() => { if (!event.isDefaultPrevented()) { this.OnPasteHandler(clipboardEvent); } }); }); BX.bind(element, "keyup", function(e) { var keyCode = e.keyCode, target = editor.selection.GetSelectedNode(true); _this.SetFocusedFlag(true); if (keyCode === editor.KEY_CODES['space'] || keyCode === editor.KEY_CODES['enter']) { if (keyCode === editor.KEY_CODES['enter']) { _this.OnEnterHandlerKeyUp(e, keyCode, target); } editor.On("OnIframeNewWord"); } else { _this.OnKeyUpArrowsHandler(e, keyCode); } editor.selection.SaveRange(); editor.On('OnIframeKeyup', [e, keyCode, target]); // Mantis:#67998 if (keyCode === editor.KEY_CODES['backspace'] && BX.browser.IsChrome() && target && target.nodeType == '3' && target.nextSibling && target.nextSibling.nodeType == '3') { _this.editor.selection.ExecuteAndRestoreSimple(function() { _this.editor.util.SetTextContent(target, _this.editor.util.GetTextContent(target) + _this.editor.util.GetTextContent(target.nextSibling)); target.nextSibling.parentNode.removeChild(target.nextSibling); }); } if (!editor.util.FirstLetterSupported() && _this.editor.parser.firstNodeCheck) { _this.editor.parser.FirstLetterCheckNodes('', '', true); } //mantis:91555, mantis:93629 if (BX.browser.IsChrome()) { if (_this.stopBugusScrollTimeout) clearTimeout(_this.stopBugusScrollTimeout); _this.stopBugusScrollTimeout = setTimeout(function(){_this.stopBugusScroll = false;}, 200); } _this.copilot?.onContentEditableKeyUp(e); }); BX.bind(this.document, 'scroll', () => this.copilot?.onScrollHandler()); BX.bind(element, "mousedown", function(e) { var target = e.target || e.srcElement; if (!editor.util.CheckImageSelectSupport() && target.nodeName === 'IMG') { editor.selection.SelectNode(target); } // Handle mousedown for "code" element in IE if (!editor.util.CheckPreCursorSupport() && target.nodeName === 'PRE') { var selectedNode = editor.selection.GetSelectedNode(true); if (selectedNode && selectedNode != target) { _this.FocusPreElement(target, true); } } _this.copilot?.onContentEditableMouseDown(); }); BX.bind(element, "keydown", BX.proxy(this.KeyDown, this)); // Workaround for chrome bug with bugus scrolling to the top of the page (mantis:91555, mantis:93629) if (BX.browser.IsChrome()) { BX.bind(window, "scroll", BX.proxy(function(e) { if (_this.stopBugusScroll) { if ((!_this.savedScroll || !_this.savedScroll.scrollTop) && _this.lastSavedScroll) _this.savedScroll = _this.lastSavedScroll; if (_this.savedScroll && !_this.lastSavedScroll) _this.lastSavedScroll = _this.savedScroll; _this._RestoreScrollTop(); } }, this)); } // Show urls and srcs in tooltip when hovering links or images var nodeTitles = { IMG: BX.message.SrcTitle + ": ", A: BX.message.UrlTitle + ": " }; BX.bind(element, "mouseover", function(e) { var target = e.target || e.srcElement, value = (target.getAttribute("href") || target.getAttribute("src")), nodeName = target.nodeName; if (nodeTitles[nodeName] && !target.hasAttribute("title") && value && value.indexOf('data:image/') === -1 ) { target.setAttribute("title", nodeTitles[nodeName] + value); target.setAttribute("data-bx-clean-attribute", "title"); } }); this.editor.InitClipboardHandler(); }; BXEditorIframeView.prototype.KeyDown = function(e) { var _this = this, keyCode = e.keyCode, KEY_CODES = this.editor.KEY_CODES; this.SetFocusedFlag(true); this.editor.iframeKeyDownPreventDefault = false; // Workaround for chrome bug with bugus scrollint to the top of the page (mantis:91555, mantis:93629) if (BX.browser.IsChrome()) { this.stopBugusScroll = true; this.savedScroll = BX.GetWindowScrollPos(document); } var command = this.editor.SHORTCUTS[keyCode], selectedNode = this.editor.selection.GetSelectedNode(true), range = this.editor.selection.GetRange(), body = this.document.body, parent; if (e.key === ' ' && this.copilot?.shouldBeShown()) { this.copilot.show(true); e.preventDefault(); return; } this.copilot?.onContentEditableKeyDown(); if ((BX.browser.IsIE() || BX.browser.IsIE10() || BX.browser.IsIE11()) && !BX.util.in_array(keyCode, [16, 17, 18, 20, 65, 144, 37, 38, 39, 40])) { if (selectedNode && selectedNode.nodeName == "BODY" || range.startContainer && range.startContainer.nodeName == "BODY" || (range.startContainer == body.firstChild && range.endContainer == body.lastChild && range.startOffset == 0 && range.endOffset == body.lastChild.length)) { BX.addCustomEvent(this.editor, "OnIframeKeyup", BX.proxy(this._IEBodyClearHandler, this)); } } // Last symbol in iframe and new paragraph in IE if ((BX.browser.IsIE() || BX.browser.IsIE10() || BX.browser.IsIE11()) && keyCode == KEY_CODES['backspace']) { BX.addCustomEvent(this.editor, "OnIframeKeyup", BX.proxy(this._IEBodyClearHandlerEx, this)); } this.isUserTyping = true; if (this.typingTimeout) { this.typingTimeout = clearTimeout(this.typingTimeout); } this.typingTimeout = setTimeout(function() { _this.isUserTyping = false; }, 1000); this.editor.synchro.StartSync(200); this.editor.On('OnIframeKeydown', [e, keyCode, command, selectedNode]); if (this.editor.iframeKeyDownPreventDefault) return BX.PreventDefault(e); // Handle Shortcuts if ((e.ctrlKey || e.metaKey) && !e.altKey && command) { this.editor.action.Exec(command); return BX.PreventDefault(e); } // Bug mantis: #59759 workaround *Chrome only // Begin if ( keyCode === KEY_CODES['backspace'] && range.startOffset == 0 && range.startContainer.nodeType == 3 && range.startContainer.parentNode.firstChild == range.startContainer && range.startContainer.parentNode && range.startContainer.parentNode.nodeName == 'BLOCKQUOTE' && range.startContainer.parentNode.className ) { range.startContainer.parentNode.className = ''; } if ( !BX.browser.IsSafari() && (keyCode === KEY_CODES['backspace'] || keyCode === KEY_CODES['delete']) && range.startContainer.nodeName === '#text' && range.startContainer.previousSibling?.nodeName === 'BLOCKQUOTE' && range.startContainer.nextSibling?.nodeName !== 'BR' && range.startContainer.textContent.length === 1 ) { range.startContainer.before(BX.Tag.render`<span></span>`.innerHTML); } if ( keyCode === KEY_CODES['delete'] && range.collapsed && range.endContainer.nodeType == 3 && range.endOffset == range.endContainer.length ) { var next = this.editor.util.GetNextNotEmptySibling(range.endContainer); if (next) { if(next.nodeName == 'BR') { next = this.editor.util.GetNextNotEmptySibling(next); } if (next && next.nodeName == 'BLOCKQUOTE' && next.className) { next.className = ''; } } } // END: Bug mantis: #59759 // Clear link with image if (selectedNode && selectedNode.nodeName === "IMG" && (keyCode === KEY_CODES['backspace'] || keyCode === KEY_CODES['delete'])) { parent = selectedNode.parentNode; parent.removeChild(selectedNode); // delete image // Parent - is LINK, and it's hasn't got any other childs if (parent.nodeName === "A" && !parent.firstChild) { parent.parentNode.removeChild(parent); } setTimeout(function(){_this.editor.util.Refresh(_this.element);}, 0); BX.PreventDefault(e); } if (range.collapsed && this.OnKeyDownArrowsHandler(e, keyCode, range) === false) { return false; } // Handle Ctrl+Enter if ((e.ctrlKey || e.metaKey) && !e.altKey && keyCode === KEY_CODES["enter"]) { if (this.IsFocused()) this.editor.On("OnIframeBlur"); this.editor.On('OnCtrlEnter', [e, this.editor.GetViewMode()]); return BX.PreventDefault(e); } // Firefox's bug it remove first node for customized lists if (BX.browser.IsFirefox() && selectedNode && (keyCode === KEY_CODES["delete"] || keyCode === KEY_CODES["backspace"])) { var li = selectedNode.nodeName == 'LI' ? selectedNode : BX.findParent(selectedNode, {tag: 'LI'}, body); if (li && li.firstChild && li.firstChild.nodeName == 'I') { var ul = BX.findParent(li, {tag: 'UL'}, body); if (ul) { var customBullitClass = this.editor.action.actions.insertUnorderedList.getCustomBullitClass(ul); if (customBullitClass) { setTimeout(function() { if (ul && li && li.innerHTML !== '') { _this.editor.action.actions.insertUnorderedList.checkCustomBullitList(ul, customBullitClass); } }, 0); } } } } if (!this.editor.util.FirstLetterSupported() && _this.editor.parser.firstNodeCheck && keyCode === this.editor.KEY_CODES['backspace']) { _this.editor.parser.FirstLetterBackspaceHandler(range); } // Handle "Enter" if (!e.shiftKey && (keyCode === KEY_CODES["enter"] || keyCode === KEY_CODES["backspace"])) { return this.OnEnterHandler(e, keyCode, selectedNode, range); } if (keyCode === KEY_CODES["pageUp"] || keyCode === KEY_CODES["pageDown"]) { this.savedScroll = BX.GetWindowScrollPos(document); BX.addCustomEvent(this.editor, "OnIframeKeyup", BX.proxy(this._RestoreScrollTop, this)); setTimeout(BX.proxy(this._RestoreScrollTop, this), 0); } }; BXEditorIframeView.prototype._RestoreScrollTop = function(e) { if (this.savedScroll) { window.scrollTo(this.savedScroll.scrollLeft, this.savedScroll.scrollTop); this.savedScroll = null; } BX.removeCustomEvent(this.editor, "OnIframeKeyup", BX.proxy(this._RestoreScrollTop, this)); }; BXEditorIframeView.prototype._IEBodyClearHandler = function(e) { var p = this.document.body.firstChild; if (e.keyCode == this.editor.KEY_CODES['enter'] && p.nodeName == "P" && p != this.document.body.lastChild) { if (p.innerHTML && p.innerHTML.toLowerCase() == '<br>') { var newPar = p.nextSibling; this.editor.util.ReplaceWithOwnChildren(p); p = newPar; } } if (p && p.nodeName == "P" && p == this.document.body.lastChild) { this.editor.util.ReplaceWithOwnChildren(p); } BX.removeCustomEvent(this.editor, "OnIframeKeyup", BX.proxy(this._IEBodyClearHandler, this)); }; BXEditorIframeView.prototype._IEBodyClearHandlerEx = function(e) { var p = this.document.body.firstChild; if (e.keyCode == this.editor.KEY_CODES['backspace'] && p && p.nodeName == "P" && p == this.document.body.lastChild && (this.editor.util.IsEmptyNode(p, true, true) || p.innerHTML && p.innerHTML.toLowerCase() == '<br>')) { this.editor.util.ReplaceWithOwnChildren(p); } BX.removeCustomEvent(this.editor, "OnIframeKeyup", BX.proxy(this._IEBodyClearHandlerEx, this)); }; BXEditorIframeView.prototype.OnEnterHandler = function(e, keyCode, selectedNode, range) { // TODO: check it again later maybe chrome will fix it // mantis: 55872. Chrome 38 rendering bug workaround if (BX.browser.IsChrome()) { this.document.body.style.minHeight = (parseInt(this.document.body.style.minHeight) + 1) + 'px'; // mantis: 60033 this.document.body.style.minHeight = (parseInt(this.document.body.style.minHeight) - 1) + 'px'; } // Check selectedNode if (!selectedNode) { return; } var _this = this; function unwrap(node) { if (node) { if (node.nodeName !== "P" && node.nodeName !== "DIV") { node = BX.findParent(node, function(n) { return n.nodeName === "P" || n.nodeName === "DIV"; }, _this.document.body); } var emptyNode = _this.editor.util.GetInvisibleTextNode(); if (node) { node.parentNode.insertBefore(emptyNode, node); _this.editor.util.ReplaceWithOwnChildren(node); _this.editor.selection.SelectNode(emptyNode); } } } var list, br, blockElement, blockTags = ["LI", "P", "H1", "H2", "H3", "H4", "H5", "H6"], listTags = ["UL", "OL", "MENU"]; if (BX.util.in_array(selectedNode.nodeName, blockTags)) { blockElement = selectedNode; } else { blockElement = BX.findParent(selectedNode, function(n) { return BX.util.in_array(n.nodeName, blockTags); }, this.document.body); } if (blockElement) { if (blockElement.nodeName === "LI") { if (keyCode === _this.editor.KEY_CODES["enter"] && blockElement && blockElement.parentNode) { var bullitClass = _this.editor.action.actions.insertUnorderedList.getCustomBullitClass(blockElement.parentNode); } // Some browsers create <p> elements after leaving a list // check after keydown of backspace and return whether a <p> got inserted and unwrap it setTimeout(function() { var node = _this.editor.selection.GetSelectedNode(true); if (node) { list = BX.findParent(node, function(n) { return BX.util.in_array(n.nodeName, listTags); }, _this.document.body); // Check if it's list with custom styled bullits - we have to check it all items have same style if (keyCode === _this.editor.KEY_CODES["enter"] && blockElement && blockElement.parentNode) { _this.editor.action.actions.insertUnorderedList.checkCustomBullitList(blockElement.parentNode, bullitClass, true); } // mantis: 82028, 83178 if (list && BX.browser.IsChrome() && keyCode === _this.editor.KEY_CODES["enter"]) { var i, li = list.getElementsByTagName('LI'); for (i = 0; i < li.length; i++) { //&65279; === \uFEFF - invisible space li[i].innerHTML = li[i].innerHTML.replace(/\uFEFF/ig, ''); } } if (!list) { unwrap(node); } } }, 0); } else if (blockElement.nodeName.match(/H[1-6]/) && keyCode === this.editor.KEY_CODES["enter"]) { setTimeout(function() { unwrap(_this.editor.selection.GetSelectedNode()); }, 0); } return true; } if (keyCode === this.editor.KEY_CODES["enter"] && !BX.browser.IsFirefox() && this.editor.action.IsSupported('insertLineBreak')) { if (BX.browser.IsIE10() || BX.browser.IsIE11()) { this.editor.action.Exec('insertHTML', '<br>' + this.editor.INVISIBLE_SPACE); } else if(BX.browser.IsChrome()) { this.editor.action.Exec('insertLineBreak'); // Bug in Chrome - when you press enter but it put carret on the prev string // Chrome 43.0.2357 in Mac puts visible space instead of invisible if (BX.browser.IsMac()) { var tmpId = "bx-editor-temp-" + Math.round(Math.random() * 1000000); this.editor.action.Exec('insertHTML', '<span id="' + tmpId + '">' + this.editor.INVISIBLE_SPACE + '</span>'); var tmpElement = this.editor.GetIframeElement(tmpId); if (tmpElement) BX.remove(tmpElement); } else { this.editor.action.Exec('insertHTML', this.editor.INVISIBLE_SPACE); } } else { this.editor.action.Exec('insertLineBreak'); } return BX.PreventDefault(e); } if ((BX.browser.IsChrome() || BX.browser.IsIE10() || BX.browser.IsIE11()) && keyCode == this.editor.KEY_CODES['backspace'] && range.collapsed) { var checkNode = BX.create('SPAN', false, this.document); this.editor.selection.InsertNode(checkNode); var prev = checkNode.previousSibling; if (prev && prev.nodeType == 3 && this.editor.util.IsEmptyNode(prev, false, false)) { BX.remove(prev); } this.editor.selection.SetBefore(checkNode); BX.remove(checkNode); } }; BXEditorIframeView.prototype.OnEnterHandlerKeyUp = function(e, keyCode, node) { // Clean class of all block nodes when they created after Enter pressing // All new Ps and DIVs should be without classNames if (node) { var _this = this; if (!BX.util.in_array(node.nodeName, this.editor.GetBlockTags())) { node = BX.findParent(node, function(n) { return BX.util.in_array(n.nodeName, _this.editor.GetBlockTags()); }, this.document.body); } if (node && BX.util.in_array(node.nodeName, this.editor.GetBlockTags())) { var html = BX.util.trim(node.innerHTML).toLowerCase(); if (this.editor.util.IsEmptyNode(node, true, true) || html == '' || html == '<br>') { node.removeAttribute("class"); } } } }; BXEditorIframeView.prototype.OnKeyDownArrowsHandler = function(e, keyCode, range) { var node, parentNode, nextNode, prevNode, KC = this.editor.KEY_CODES; this.keyDownRange = range; if (keyCode === KC['right'] || keyCode === KC['down']) { node = range.endContainer; nextNode = node ? node.nextSibling : false; parentNode = node ? node.parentNode : false; if ( node.nodeType == 3 && node.length == range.endOffset && parentNode && parentNode.nodeName !== 'BODY' && (!nextNode || (nextNode && nextNode.nodeName == 'BR' && !nextNode.nextSibling)) && (this.editor.util.IsBlockElement(parentNode) || this.editor.util.IsBlockNode(parentNode)) ) { this.editor.selection.SetInvisibleTextAfterNode(parentNode, true); return BX.PreventDefault(e); } else if( node.nodeType == 3 && this.editor.util.IsEmptyNode(node) && nextNode && (this.editor.util.IsBlockElement(nextNode) || this.editor.util.IsBlockNode(nextNode)) ) { BX.remove(node); if (nextNode.firstChild) { this.editor.selection.SetBefore(nextNode.firstChild); } else { this.editor.selection.SetAfter(nextNode); } return BX.PreventDefault(e); } else if ( node.nodeType == 3 && node.length == range.endOffset && parentNode && parentNode.nodeName !== 'BODY' && nextNode && nextNode.nodeName == 'BR' && !nextNode.nextSibling && (this.editor.util.IsBlockElement(parentNode) || this.editor.util.IsBlockNode(parentNode)) ) { this.editor.selection.SetInvisibleTextAfterNode(parentNode, true); return BX.PreventDefault(e); } } else if (keyCode === KC['left'] || keyCode === KC['up']) { node = range.startContainer; parentNode = node ? node.parentNode : false; prevNode = node ? node.previousSibling : false; if ( node.nodeType == 3 && range.endOffset === 0 && parentNode && parentNode.nodeName !== 'BODY' && !prevNode && (this.editor.util.IsBlockElement(parentNode) || this.editor.util.IsBlockNode(parentNode)) ) { this.editor.selection.SetInvisibleTextBeforeNode(parentNode); return BX.PreventDefault(e); } else if( node.nodeType == 3 && this.editor.util.IsEmptyNode(node) && prevNode && (this.editor.util.IsBlockElement(prevNode) || this.editor.util.IsBlockNode(prevNode)) ) { BX.remove(node); if (prevNode.lastChild) { this.editor.selection.SetAfter(prevNode.lastChild); } else { this.editor.selection.SetBefore(prevNode); } return BX.PreventDefault(e); } } return true; }; BXEditorIframeView.prototype.OnKeyUpArrowsHandler = function(e, keyCode) { var _this = this, pre, prevToSur, nextToSur, keyDownNode, keyDownPre, range = this.editor.selection.GetRange(), node, parentNode, nextNode, prevNode, isEmpty, isSur, sameLastRange, startCont, endCont, startIsSur, endIsSur, KC = this.editor.KEY_CODES; // Arrows right or down if (keyCode === KC['right'] || keyCode === KC['down']) { this.editor.selection.GetStructuralTags(); // Moving cursor by arrows (right & down) if (range.collapsed) { node = range.endContainer; isEmpty = this.editor.util.IsEmptyNode(node); // We check if last range was the same - it means that cursor doesn't // moved when user tried to move it sameLastRange = this.editor.selection.CheckLastRange(range); nextNode = node.nextSibling; if (!this.editor.util.CheckPreCursorSupport()) { if (node.nodeName === 'PRE') { pre = node; } else if (node.nodeType == 3) { pre = BX.findParent(node, {tag: 'PRE'}, this.element); } if(pre) { if (this.keyDownRange) { keyDownNode = this.keyDownRange.endContainer; keyDownPre = keyDownNode == pre ? pre : BX.findParent(keyDownNode, function(n){return n == pre;}, this.element); } _this.FocusPreElement(pre, false, keyDownPre ? null : 'start'); } } // If cursor in the invisible node - we take next node if (node.nodeType == 3 && isEmpty && nextNode) { node = nextNode; isEmpty = this.editor.util.IsEmptyNode(node); } isSur = this.editor.util.CheckSurrogateNode(node); // It's surrogate if (isSur) { nextToSur = node.nextSibling; if (nextToSur && nextToSur.nodeType == 3 && this.editor.util.IsEmptyNode(nextToSur)) this.editor.selection._MoveCursorAfterNode(nextToSur); else this.editor.selection._MoveCursorAfterNode(node); BX.PreventDefault(e); } // If it's element else if (node.nodeType == 1 && node.nodeName != "BODY" && !isEmpty) { if (sameLastRange) { this.editor.selection._MoveCursorAfterNode(node); BX.PreventDefault(e); } } else if (sameLastRange && node.nodeType == 3 && /*node.length == range.endOffset &&*/ !isEmpty) { parentNode = node.parentNode; if (parentNode && node === parentNode.lastChild && parentNode.nodeName != "BODY") { this.editor.selection._MoveCursorAfterNode(parentNode); } } else if (node.nodeType == 3 && node.parentNode) { parentNode = node.parentNode; prevNode = parentNode.previousSibling; // It's empty invisible node before block element which was put there by us. // So we should remove it. if ( (this.editor.util.IsBlockElement(parentNode) || this.editor.util.IsBlockNode(parentNode)) && prevNode && prevNode.nodeType == 3 && this.editor.util.IsEmptyNode(prevNode) ) { BX.remove(prevNode); } } } else // Selection Shift + Right & Shift + down { startCont = range.startContainer; endCont = range.endContainer; startIsSur = this.editor.util.CheckSurrogateNode(startCont); endIsSur = this.editor.util.CheckSurrogateNode(endCont); if (startIsSur) { prevToSur = startCont.previousSibling; if (prevToSur && prevToSur.nodeType == 3 && this.editor.util.IsEmptyNode(prevToSur)) range.setStartBefore(prevToSur); else range.setStartBefore(startCont); this.editor.selection.SetSelection(range); } if (endIsSur) { nextToSur = endCont.nextSibling; if (nextToSur && nextToSur.nodeType == 3 && this.editor.util.IsEmptyNode(nextToSur)) range.setEndAfter(nextToSur); else range.setEndAfter(endCont); this.editor.selection.SetSelection(range); } } } // Arrows left or up else if (keyCode === KC['left'] || keyCode === KC['up']) { this.editor.selection.GetStructuralTags(); // Moving cursor by arrows (left & up) if (range.collapsed) { node = range.startContainer; isEmpty = this.editor.util.IsEmptyNode(node); // We check if last range was the same - it means that cursor doesn't // moved when user tried to move it sameLastRange = this.editor.selection.CheckLastRange(range); // If cursor in the invisible node - we take next node if (node.nodeType == 3 && isEmpty && node.previousSibling) { node = node.previousSibling; isEmpty = this.editor.util.IsEmptyNode(node); } if (!this.editor.util.CheckPreCursorSupport()) { if (node.nodeName === 'PRE') { pre = node; } else if (node.nodeType == 3) { pre = BX.findParent(node, {tag: 'PRE'}, this.element); } if(pre) { if (this.keyDownRange) { keyDownNode = this.keyDownRange.startContainer; keyDownPre = keyDownNode == pre ? pre : BX.findParent(keyDownNode, function(n){return n == pre;}, this.element); } _this.FocusPreElement(pre, false, keyDownPre ? null : 'end'); } } isSur = this.editor.util.CheckSurrogateNode(node); // It's surrogate if (isSur) { prevToSur = node.previousSibling; if (prevToSur && prevToSur.nodeType == 3 && this.editor.util.IsEmptyNode(prevToSur)) this.editor.selection._MoveCursorBeforeNode(prevToSur); else this.editor.selection._MoveCursorBeforeNode(node); BX.PreventDefault(e); } // If it's element else if (node.nodeType == 1 && node.nodeName != "BODY" && !isEmpty) { if (sameLastRange) { this.editor.selection._MoveCursorBeforeNode(node); BX.PreventDefault(e); } } //else if (sameLastRange && node.nodeType == 3 && range.startOffset == 0 && !isEmpty) else if (sameLastRange && node.nodeType == 3 && !isEmpty) { parentNode = node.parentNode; if (parentNode && node === parentNode.firstChild && parentNode.nodeName != "BODY") { this.editor.selection._MoveCursorBeforeNode(parentNode); } } else if (node.nodeType == 3 && node.parentNode) { parentNode = node.parentNode; prevNode = parentNode.nextSibling; // It's empty invisible node after block element which was put there by us. // So we should remove it. if ( (this.editor.util.IsBlockElement(parentNode) || this.editor.util.IsBlockNode(parentNode)) && prevNode && prevNode.nodeType == 3 && this.editor.util.IsEmptyNode(prevNode) ) { BX.remove(prevNode); } } } else // Selection Shift + left & Shift + up { startCont = range.startContainer; endCont = range.endContainer; startIsSur = this.editor.util.CheckSurrogateNode(startCont); endIsSur = this.editor.util.CheckSurrogateNode(endCont); if (startIsSur) { prevToSur = startCont.previousSibling; if (prevToSur && prevToSur.nodeType == 3 && this.editor.util.IsEmptyNode(prevToSur)) range.setStartBefore(prevToSur); else range.setStartBefore(startCont); this.editor.selection.SetSelection(range); } if (endIsSur) { nextToSur = endCont.nextSibling; if (nextToSur && nextToSur.nodeType == 3 && this.editor.util.IsEmptyNode(nextToSur)) range.setEndAfter(nextToSur); else range.setEndAfter(endCont); this.SetSelection(range); } } } this.keyDownRange = null; }; BXEditorIframeView.prototype.FocusPreElement = function(preNode, timeout, mode) { var _this = this; if (this._focusPreElementTimeout) this._focusPreElementTimeout = clearTimeout(this._focusPreElementTimeout); if (timeout) { this._focusPreElementTimeout = setTimeout(function(){ _this.FocusPreElement(preNode, false, mode); }, 100); return; } BX.focus(preNode); if (mode == 'end' && preNode.lastChild) { this.editor.selection.SetAfter(preNode.lastChild); } else if (mode == 'start' && preNode.firstChild) { this.editor.selection.SetBefore(preNode.firstChild); } }; BXEditorIframeView.prototype.OnPasteHandler = function(e) { if (!this.editor.skipPasteHandler) { this.editor.skipPasteHandler = true; this.editor.pasteNodeIndex = {}; var originalScrollTop = document.documentElement.scrollTop || document.body.scrollTop, originalScrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft, _this = this, arNodes = [], curNode, i, node, qnodes; function markGoodNode(n) { if (n && n.setAttribute) { var randValue = Math.round(Math.random() * 1000000); _this.editor.pasteNodeIndex[randValue] = true; n.setAttribute('data-bx-paste-flag', randValue); } } curNode = this.document.body; if (curNode) { qnodes = curNode.querySelectorAll("*"); for (i = 0; i < qnodes.length; i++) { if (qnodes[i].nodeType == 1 && qnodes[i].nodeName != 'BODY' && qnodes[i].nodeName != 'HEAD') { arNodes.push(qnodes[i]); } } for (i = 0; i < curNode.parentNode.childNodes.length; i++) { node = curNode.parentNode.childNodes[i]; if (node.nodeType == 1 && node.nodeName != 'BODY' && node.nodeName != 'HEAD') { arNodes.push(node); } } } for (i = 0; i < arNodes.length; i++) { markGoodNode(arNodes[i]); } var sync = this.editor.synchro.IsSyncOn(); if (sync) { this.editor.synchro.StopSync(); } if (this.editor.iframeView.pasteHandlerTimeout) { clearTimeout(this.editor.iframeView.pasteHandlerTimeout); } this.pasteHandlerTimeout = setTimeout(function() { _this.editor.SetCursorNode(); _this.editor.pasteHandleMode = true; _this.editor.bbParseContentMode = true; _this.editor.synchro.lastIframeValue = false; // Paste control: show menu after pasting content // to let user select weather insert rich content or plain text if (!_this.editor.skipPasteControl) { _this.editor.pasteControl.SaveIframeContent(_this.GetValue()); _this.editor.pasteControl.CheckAndShow(); } _this.editor.synchro.FromIframeToTextarea(true, true); _this.editor.pasteHandleMode = false; _this.editor.bbParseContentMode = false; _this.editor.synchro.lastTextareaValue = false; _this.editor.synchro.FromTextareaToIframe(true); _this.editor.RestoreCursor(); _this.editor.On("OnIframePaste"); _this.editor.On("OnIframeNewWord"); _this.editor.skipPasteHandler = false; if (sync) { _this.editor.synchro.StartSync(); } if (window.scrollTo) { window.scrollTo(originalScrollLeft, originalScrollTop); } }, 0); } }; BXEditorIframeView.prototype.InitAutoLinking = function() { var _this = this, editor = this.editor, nativeAutolinkCanBeDisabled = editor.action.IsSupportedByBrowser("autoUrlDetect"), nativeAutoLink = BX.browser.IsIE() || BX.browser.IsIE9() || BX.browser.IsIE10(); if (nativeAutolinkCanBeDisabled) editor.action.Exec("autoUrlDetect", false); if (editor.config.autoLink === false) return; // Init Autolink system var ignorableParents = {"CODE" : 1, "PRE" : 1, "A" : 1, "SCRIPT" : 1, "HEAD" : 1, "TITLE" : 1, "STYLE" : 1}, urlRegExp = /(((?:https?|ftp):\/\/|www\.)[^\s'"<]{3,500})/gi, emailRegExp = /[\.a-z0-9_\-]+@[\.a-z0-9_\-]+\.[\.a-z0-9_\-]+/gi, MAX_LENGTH = 100, BRACKETS = { ")": "(", "]": "[", "}": "{" }; this.editor.autolinkUrlRegExp = urlRegExp; this.editor.autolinkEmailRegExp = emailRegExp; function autoLinkHandler() { try { if (checkAutoLink()) { editor.selection.ExecuteAndRestore(function(startContainer, endContainer) { if (endContainer && endContainer.parentNode) autoLink(endContainer.parentNode); }); } } catch(e){} } function checkAutoLink() { var node, nodeValue, result = false, doc = editor.GetIframeDoc(), walker = doc.createTreeWalker( doc.body, NodeFilter.SHOW_TEXT, null, false ); while(node = walker.nextNode()) { nodeValue = node.nodeValue || ''; if ((nodeValue.match(emailRegExp) || nodeValue.match(urlRegExp)) && node.parentNode && node.parentNode.nodeName != 'A') { result = true; break; } } return result; } function autoLink(element) { if (element && !ignorableParents[element.nodeName]) { var ignorableParent = BX.findParent(element, function(node) { return !!ignorableParents[node.nodeName]; }, element.ownerDocument.body); if (ignorableParent) return element; if (element === element.ownerDocument.documentElement) element = element.ownerDocument.body; return parseNode(element); } } function convertUrlToLink(str) { str = BX.util.htmlspecialchars(str); return str.replace(urlRegExp, function(match, url) { var punctuation = (url.match(/([^\w\u0430-\u0456\u0451\/\-#](,?))$/i) || [])[1] || "", opening = BRACKETS[punctuation]; url = url.replace(/([^\w\u0430-\u0456\u0451\/\-#](,?))$/i, ""); if (url.split(opening).length > url.split(punctuation).length) { url = url + punctuation; punctuation = ""; } var realUrl = url, displayUrl = BX.util.htmlspecialchars(url); if (url.length > MAX_LENGTH) displayUrl = displayUrl.substr(0, MAX_LENGTH) + "..."; if (realUrl.substr(0, 4) === "www.") realUrl = "http://" + realUrl; BX.onCustomEvent(_this.editor, 'OnAfterUrlConvert', [realUrl]); return '<a href="' + realUrl + '">' + displayUrl + '</a>' + punctuation; }); } function convertEmailToLink(str) { str = BX.util.htmlspecialchars(str); return str.replace(emailRegExp, function(email) { var punctuation = (email.match(/([^\w\/\-](,?))$/i) || [])[1] || "", opening = BRACKETS[punctuation]; email = email.replace(/([^\w\/\-](,?))$/i, ""); if (email.split(opening).length > email.split(punctuation).length) { email = email + punctuation; punctuation = ""; } var realUrl = "mailto:" + email; return '<a href="' + realUrl + '">' + email + '</a>' + punctuation; }); } function getTmpDiv(doc) { var tmp = doc._bx_autolink_temp_div; if (!tmp) tmp = doc._bx_autolink_temp_div = doc.createElement("div"); return tmp; } function parseNode(element) { var res, parentNode, tmpDiv; if (element && !ignorableParents[element.nodeName]) { // Replaces the content of the text node by link if (element.nodeType === 3 && element.data.match(urlRegExp) && element.parentNode) { parentNode = element.parentNode; tmpDiv = getTmpDiv(parentNode.ownerDocument); tmpDiv.innerHTML = "<span></span>" + convertUrlToLink(element.data); tmpDiv.removeChild(tmpDiv.firstChild); while (tmpDiv.firstChild) parentNode.insertBefore(tmpDiv.firstChild, element); parentNode.removeChild(element); } else if (element.nodeType === 3 && element.data.match(emailRegExp) && element.parentNode) { parentNode = element.parentNode; tmpDiv = getTmpDiv(parentNode.ownerDocument); tmpDiv.innerHTML = "<span></span>" + convertEmailToLink(element.data); tmpDiv.removeChild(tmpDiv.firstChild); while (tmpDiv.firstChild) parentNode.insertBefore(tmpDiv.firstChild, element); parentNode.removeChild(element); } else if (element.nodeType === 1) { var childNodes = element.childNodes, i; for (i = 0; i < childNodes.length; i++) parseNode(childNodes[i]); res = element; } } return res; } if (!nativeAutoLink || (nativeAutoLink && nativeAutolinkCanBeDisabled)) { BX.addCustomEvent(editor, "OnIframeNewWord", function() { if (editor.autolinkTimeout) editor.autolinkTimeout = clearTimeout(editor.autolinkTimeout); editor.autolinkTimeout = setTimeout(autoLinkHandler, 500); }); BX.addCustomEvent(editor, "OnSubmit", function() { try { autoLink(editor.GetIframeDoc().body); } catch(e){} }); } var links = editor.sandbox.GetDocument().getElementsByTagName("a"), getTextContent = function(element) { var textContent = BX.util.trim(editor.util.GetTextContent(element)); if (textContent.substr(0, 4) === "www.") textContent = "http://" + textContent; return textContent; }; BX.addCustomEvent(editor, "OnIframeKeydown", function(e, keyCode, command, selectedNode) { if (links.length > 0 && selectedNode) { var link = BX.findParent(selectedNode, {tag: 'A'}, selectedNode.ownerDocument.body); if (link) { var textContent = getTextContent(link); setTimeout(function() { var newTextContent = getTextContent(link); if (newTextContent === textContent) return; // Only set href when new href looks like a valid url if (newTextContent.match(urlRegExp)) link.setAttribute("href", newTextContent); }, 0); } } }); }; BXEditorIframeView.prototype.IsUserTypingNow = function(e) { return this.isFocused && this.isShown && this.isUserTyping; }; BXEditorIframeView.prototype.CheckContentLastChild = function(element) { if (!element) { element = this.element; } var lastChild = element.lastChild; if (lastChild && (this.editor.util.IsEmptyNode(lastChild, true) && this.editor.util.IsBlockNode(lastChild.previousSibling) || this.editor.phpParser.IsSurrogate(lastChild))) { element.appendChild(BX.create('BR', {}, element.ownerDocument)); element.appendChild(this.editor.util.GetInvisibleTextNode()); } }; /** * Class _this takes care that the value of the composer and the textarea is always in sync */ function BXEditorViewsSynchro(editor, textareaView, iframeView) { this.INTERVAL = 500; this.editor = editor; this.textareaView = textareaView; this.iframeView = iframeView; this.lastFocused = 'wysiwyg'; this.InitEventHandlers(); } /** * Sync html from composer to textarea * Takes care of placeholders * @param {Boolean} bParseHtml Whether the html should be sanitized before inserting it into the textarea */ BXEditorViewsSynchro.prototype = { FromIframeToTextarea: function(bParseHtml, bFormat) { var value; if (this.editor.bbCode) { value = this.iframeView.GetValue(this.editor.bbParseContentMode, false); value = BX.util.trim(value); if (value !== this.lastIframeValue) { var bbCodes = this.editor.bbParser.Unparse(value); this.textareaView.SetValue(bbCodes, false, bFormat || this.editor.bbParseContentMode); if (typeof this.lastSavedIframeValue !== 'undefined' && this.lastSavedIframeValue != value) { this.editor.On("OnContentChanged", [bbCodes, value]); } this.lastSavedIframeValue = value; this.lastIframeValue = value; } } else { value = this.iframeView.GetValue(); value = BX.util.trim(value); if (value !== this.lastIframeValue) { this.textareaView.SetValue(value, true, bFormat); if (typeof this.lastSavedIframeValue !== 'undefined' && this.lastSavedIframeValue != value) { this.editor.On("OnContentChanged", [this.textareaView.GetValue() || '', value || '']); } this.lastSavedIframeValue = value; this.lastIframeValue = value; } } }, /** * Sync value of textarea to composer * Takes care of placeholders * @param {Boolean} bParseHtml Whether the html should be sanitized before inserting it into the composer */ FromTextareaToIframe: function(bParseHtml) { var value = this.textareaView.GetValue(); if (value !== this.lastTextareaValue) { if (value) { if (this.editor.bbCode) { var htmlFromBbCode = this.editor.bbParser.Parse(value); // INVISIBLE_CURSOR htmlFromBbCode = htmlFromBbCode.replace(/\u2060/ig, '<span id="bx-cursor-node"> </span>'); this.iframeView.SetValue(htmlFromBbCode, bParseHtml); } else { // INVISIBLE_CURSOR value = value.replace(/\u2060/ig, '<span id="bx-cursor-node"> </span>'); this.iframeView.SetValue(value, bParseHtml); } } else { this.iframeView.Clear(); } this.lastTextareaValue = value; this.editor.On("OnContentChanged", [value || '', this.iframeView.GetValue() || '']); } }, FullSyncFromIframe: function() { this.lastIframeValue = false; this.FromIframeToTextarea(true, true); this.lastTextareaValue = false; this.FromTextareaToIframe(true); }, Sync: function() { var bParseHtml = true; var view = this.editor.currentViewName; if (view === "split") { if (this.GetSplitMode() === "code") { this.FromTextareaToIframe(bParseHtml); } else // wysiwyg { this.FromIframeToTextarea(bParseHtml); } } else if (view === "code") { this.FromTextareaToIframe(bParseHtml); } else // wysiwyg { this.FromIframeToTextarea(bParseHtml); } }, GetSplitMode: function() { var mode = false; if (this.editor.currentViewName == "split") { if (this.editor.iframeView.IsFocused()) { mode = "wysiwyg"; } else if(this.editor.textareaView.IsFocused()) { mode = "code"; } else { mode = this.lastFocused; } } return mode; }, InitEventHandlers: function() { var _this = this; BX.addCustomEvent(this.editor, "OnTextareaFocus", function() { _this.lastFocused = 'code'; _this.StartSync(); }); BX.addCustomEvent(this.editor, "OnIframeFocus", function() { _this.lastFocused = 'wysiwyg'; _this.StartSync(); }); BX.addCustomEvent(this.editor, "OnTextareaBlur", BX.delegate(this.StopSync, this)); BX.addCustomEvent(this.editor, "OnIframeBlur", BX.delegate(this.StopSync, this)); }, StartSync: function(delay) { var _this = this; if (this.interval) { this.interval = clearTimeout(this.interval); } this.delay = delay || this.INTERVAL; // it can reduce or increase initial timeout function sync() { // set delay to normal value _this.delay = _this.INTERVAL; _this.Sync(); _this.interval = setTimeout(sync, _this.delay); } this.interval = setTimeout(sync, _this.delay); }, StopSync: function() { if (this.interval) { this.interval = clearTimeout(this.interval); } }, IsSyncOn: function() { return !!this.interval; }, OnIframeMousedown: function(e, target, bxTag) { }, IsFocusedOnTextarea: function() { return this.editor.currentViewName === "code" || this.editor.currentViewName === "split" && this.GetSplitMode() === "code"; } } // global interface window.BXEditorTextareaView = BXEditorTextareaView; window.BXEditorIframeView = BXEditorIframeView; window.BXEditorViewsSynchro = BXEditorViewsSynchro; })();