@syncfusion/ej2-richtexteditor
Version:
Essential JS 2 RichTextEditor component
900 lines (898 loc) • 81.6 kB
JavaScript
import { addClass, attributes, Browser, closest, detach, isNullOrUndefined as isNOU, isNullOrUndefined, removeClass } from '@syncfusion/ej2-base';
import { hasAnyFormatting, isIDevice, removeClassWithAttr, scrollToCursor } from '../../common/util';
import { InsertHtml } from '../../editor-manager/plugin/inserthtml';
import { NodeSelection } from '../../selection/selection';
import * as classes from '../base/classes';
import { CLS_TABLE_MULTI_CELL, CLS_TABLE_SEL, CLS_TABLE_SEL_END } from '../../common/constant';
import * as CONSTANT from '../base/constant';
import * as events from '../base/constant';
import { RenderType } from '../base/enum';
import { getDefaultValue, getTextNodesUnder, sanitizeHelper } from '../base/util';
import { HTMLFormatter } from '../formatter/html-formatter';
import { ContentRender } from '../renderer/content-renderer';
import { IframeContentRender } from '../renderer/iframe-content-renderer';
import { ON_BEGIN } from './../../common/constant';
import { HtmlToolbarStatus } from './html-toolbar-status';
import { XhtmlValidation } from './xhtml-validation';
/**
* `HtmlEditor` module is used to HTML editor
*/
var HtmlEditor = /** @class */ (function () {
function HtmlEditor(parent, serviceLocator) {
this.rangeCollection = [];
this.isImageDelete = false;
this.isMention = false;
this.parent = parent;
this.locator = serviceLocator;
this.renderFactory = this.locator.getService('rendererFactory');
this.xhtmlValidation = new XhtmlValidation(parent);
this.addEventListener();
this.isDestroyed = false;
this.isCopyAll = false;
this.isSlashMenuOpen = false;
this.isPreviousNodeBrAfterBackSpace = false;
}
/**
* Destroys the Markdown.
*
* @function destroy
* @returns {void}
* @hidden
*/
HtmlEditor.prototype.destroy = function () {
if (this.isDestroyed) {
return;
}
if (this.clickTimeout) {
clearTimeout(this.clickTimeout);
this.clickTimeout = null;
}
this.removeEventListener();
this.locator = null;
this.contentRenderer = null;
this.renderFactory = null;
this.toolbarUpdate = null;
this.nodeSelectionObj = null;
this.isCopyAll = null;
this.isSlashMenuOpen = null;
if (this.rangeCollection.length > 0) {
this.rangeCollection = [];
}
if (this.rangeElement) {
this.rangeElement = null;
}
if (this.oldRangeElement) {
this.oldRangeElement = null;
}
if (this.deleteRangeElement) {
this.deleteRangeElement = null;
}
if (this.deleteOldRangeElement) {
this.deleteOldRangeElement = null;
}
if (this.saveSelection) {
this.saveSelection = null;
}
if (this.xhtmlValidation) {
this.xhtmlValidation = null;
}
this.isDestroyed = true;
};
/**
* @param {string} value - specifies the string value
* @returns {void}
* @hidden
*/
HtmlEditor.prototype.sanitizeHelper = function (value) {
value = sanitizeHelper(value, this.parent);
return value;
};
HtmlEditor.prototype.addEventListener = function () {
if (this.parent.isDestroyed) {
return;
}
this.nodeSelectionObj = new NodeSelection(this.parent.inputElement);
this.parent.on(events.initialLoad, this.instantiateRenderer, this);
this.parent.on(events.htmlToolbarClick, this.onToolbarClick, this);
this.parent.on(events.slashMenuOpening, this.onSlashMenuOpen, this);
this.parent.on(events.keyDown, this.onKeyDown, this);
this.parent.on(events.keyUp, this.onKeyUp, this);
this.parent.on(events.initialEnd, this.render, this);
this.parent.on(events.modelChanged, this.onPropertyChanged, this);
this.parent.on(events.destroy, this.destroy, this);
this.parent.on(events.selectAll, this.selectAll, this);
this.parent.on(events.selectRange, this.selectRange, this);
this.parent.on(events.getSelectedHtml, this.getSelectedHtml, this);
this.parent.on(events.selectionSave, this.onSelectionSave, this);
this.parent.on(events.selectionRestore, this.onSelectionRestore, this);
this.parent.on(events.readOnlyMode, this.updateReadOnly, this);
this.parent.on(events.paste, this.onPaste, this);
this.parent.on(events.tableclass, this.isTableClassAdded, this);
this.parent.on(events.onHandleFontsizeChange, this.onHandleFontsizeChange, this);
};
HtmlEditor.prototype.onSlashMenuOpen = function () {
this.isSlashMenuOpen = true;
};
HtmlEditor.prototype.updateReadOnly = function () {
if (this.parent.readonly) {
attributes(this.parent.contentModule.getEditPanel(), { contenteditable: 'false' });
addClass([this.parent.element], classes.CLS_RTE_READONLY);
}
else {
attributes(this.parent.contentModule.getEditPanel(), { contenteditable: 'true' });
removeClass([this.parent.element], classes.CLS_RTE_READONLY);
}
};
HtmlEditor.prototype.onSelectionSave = function () {
var currentDocument = this.contentRenderer.getDocument();
var range = this.nodeSelectionObj.getRange(currentDocument);
this.saveSelection = this.nodeSelectionObj.save(range, currentDocument);
};
HtmlEditor.prototype.onSelectionRestore = function (e) {
this.parent.isBlur = false;
this.contentRenderer.getEditPanel().focus({ preventScroll: true });
if ((isNullOrUndefined(e.items) || e.items) && (!isNullOrUndefined(this.saveSelection))) {
this.saveSelection.restore();
}
};
HtmlEditor.prototype.isTableClassAdded = function () {
var tableElement = this.parent.inputElement.querySelectorAll('table');
for (var i = 0; i < tableElement.length; i++) {
// e-rte-table class is added to the table element for styling.
// e-rte-paste-table class is added for pasted table element from MS Word and other sources such as Web will not have any styles.
// e-rte-custom-table class is added for custom table element will not have any styles.
if (!tableElement[i].classList.contains('e-rte-table') && !tableElement[i].classList.contains('e-rte-paste-table')
&& !tableElement[i].classList.contains('e-rte-custom-table')) {
tableElement[i].classList.add('e-rte-table');
}
}
};
HtmlEditor.prototype.onHandleFontsizeChange = function (e) {
var keyboardArgs = e.args;
var args = { name: 'dropDownSelect' };
args.item = {
command: 'Font',
subCommand: 'FontSize'
};
var items = this.parent.fontSize.items;
var activeElem;
if (this.parent.toolbarModule && this.parent.toolbarModule.dropDownModule &&
this.parent.toolbarModule.dropDownModule.fontSizeDropDown && !isNOU(this.parent.toolbarModule.dropDownModule.fontSizeDropDown.activeElem[0].textContent) && this.parent.toolbarModule.dropDownModule.fontSizeDropDown.activeElem[0].textContent !== '') {
activeElem = this.parent.toolbarModule.dropDownModule.fontSizeDropDown.activeElem[0].textContent;
}
else {
var fontSizeValue = void 0;
var selection = this.parent.contentModule.getDocument().getSelection();
if (selection && selection.focusNode && selection.focusNode.parentElement) {
fontSizeValue = document.defaultView.getComputedStyle(selection.focusNode.parentElement, null).getPropertyValue('font-size');
}
else {
fontSizeValue = this.parent.fontSize.width;
}
fontSizeValue = isNOU(fontSizeValue) ? this.parent.fontSize.width : fontSizeValue;
var actualTxtFontValues = fontSizeValue.match(/^([\d.]+)(\D+)$/);
var size_1 = parseInt(actualTxtFontValues[1], 10);
var unit = actualTxtFontValues[2];
var defaultFontValues = items[1].value.match(/^([\d.]+)(\D+)$/);
if (defaultFontValues[2] === unit) {
var index = items.findIndex(function (_a) {
var value = _a.value;
return parseInt(value, 10) >= size_1;
});
activeElem = items[index].text;
}
else {
var convertedSize_1 = this.convertFontSize(size_1, unit, defaultFontValues[2]);
var index = items.findIndex(function (_a) {
var value = _a.value;
return parseInt(value, 10) >= convertedSize_1;
});
activeElem = items[index].text;
}
}
var fontIndex = items.findIndex(function (size) { return size.text === (activeElem === 'Font Size' ? 'Default' : activeElem); });
if (keyboardArgs.action === 'increase-fontsize' && fontIndex !== -1) {
if (fontIndex >= items.length - 1) {
var fontValues = items[fontIndex].value.match(/^([\d.]+)(\D+)$/);
if (fontValues) {
var size = parseInt(fontValues[1], 10);
var unit = fontValues[2];
var roundedSize = size % 10 === 0 ? Math.ceil((size + 1) / 10) * 10 : Math.ceil(size / 10) * 10;
args.item.value = roundedSize.toLocaleString() + unit;
args.item.text = roundedSize.toLocaleString() + ' ' + unit;
}
this.parent.fontSize.items.push(args.item);
}
else {
args.item.value = items[fontIndex + 1].value;
args.item.text = items[fontIndex + 1].text;
}
}
else if (keyboardArgs.action === 'decrease-fontsize' && fontIndex !== -1 && fontIndex > 0) {
args.item.value = items[fontIndex - 1].value;
args.item.text = items[fontIndex - 1].text;
}
else {
if (fontIndex >= 0 && fontIndex < items.length && items[fontIndex]) {
args.item.value = items[fontIndex].value;
args.item.text = items[fontIndex].text;
}
}
this.parent.formatter.process(this.parent, args, keyboardArgs);
};
HtmlEditor.prototype.convertFontSize = function (value, originalUnit, targetUnit) {
if (CONSTANT.supportedUnits.indexOf(originalUnit) !== -1 || CONSTANT.supportedUnits.indexOf(targetUnit) !== -1) {
originalUnit = 'px';
}
var convertedValue = value * CONSTANT.conversionFactors[originalUnit][targetUnit];
return convertedValue;
};
HtmlEditor.prototype.onKeyUp = function (e) {
var args = e.args;
var restrictKeys = [8, 9, 13, 17, 18, 20, 27, 36, 37, 38, 39, 40, 44, 45, 46, 91,
112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123];
var range = this.parent.getRange();
var regEx = new RegExp('\u200B', 'g');
var isEmptyNode = range.startContainer === range.endContainer && range.startOffset === range.endOffset &&
range.startOffset === 1 && range.startContainer.textContent.length === 1 &&
range.startContainer.textContent.charCodeAt(0) === 8203 &&
range.startContainer.textContent.replace(regEx, '').length === 0;
var isMention = false;
if (range.startContainer === range.endContainer &&
range.startOffset === range.endOffset && (range.startContainer !== this.parent.inputElement && range.startOffset !== 0)) {
var mentionStartNode = range.startContainer.nodeType === 3 ?
range.startContainer : range.startContainer.childNodes[range.startOffset - 1];
isMention = args.keyCode === 16 &&
mentionStartNode.textContent.charCodeAt(0) === 8203 &&
!isNOU(mentionStartNode.previousSibling) && mentionStartNode.previousSibling.contentEditable === 'false';
}
if (this.isCopyAll) {
return;
}
var pointer;
var isRootParent = false;
if (!this.isSlashMenuOpen &&
restrictKeys.indexOf(args.keyCode) < 0 && !args.shiftKey && !args.ctrlKey && !args.altKey &&
!isEmptyNode && !isMention) {
pointer = range.startOffset;
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
range.startContainer.nodeName === '#text' ? range.startContainer.parentElement !== this.parent.inputElement ? range.startContainer.parentElement.classList.add('currentStartMark')
: isRootParent = true : range.startContainer.classList.add('currentStartMark');
if (range.startContainer.textContent.charCodeAt(0) === 8203) {
var previousLength_1 = range.startContainer.textContent.length;
var previousRange = range.startOffset;
this.removeZeroWidthSpaces(range.startContainer, regEx);
pointer = previousRange === 0 ? previousRange : previousRange - (previousLength_1 - range.startContainer.textContent.length);
this.parent.formatter.editorManager.nodeSelection.setCursorPoint(this.parent.contentModule.getDocument(), range.startContainer, pointer);
}
var previousLength = this.parent.inputElement.innerHTML.length;
var currentLength = this.parent.inputElement.innerHTML.replace(regEx, '').length;
var focusNode = range.startContainer;
if (previousLength > currentLength && !isRootParent) {
if (focusNode.textContent.trim().length !== 0 && focusNode.previousSibling) {
var tempSpan = document.createElement('span');
tempSpan.className = 'tempSpan';
range.insertNode(tempSpan);
}
var currentChild = this.parent.inputElement.firstChild;
while (!isNOU(currentChild)) {
if (currentChild.nodeName === '#text') {
currentChild = currentChild.nextElementSibling;
continue;
}
if (currentChild.textContent.replace(regEx, '').trim().length > 0 && currentChild.textContent.includes('\u200B')) {
this.removeZeroWidthSpaces(currentChild, regEx);
}
currentChild = currentChild.nextElementSibling;
}
var tempSpanToRemove = this.parent.inputElement.querySelector('.tempSpan');
if (tempSpanToRemove && tempSpanToRemove.previousSibling && focusNode.textContent.trim().length !== 0) {
focusNode = tempSpanToRemove.previousSibling;
pointer = tempSpanToRemove.previousSibling.textContent.length;
var parentElement = tempSpanToRemove.parentNode;
parentElement.removeChild(tempSpanToRemove);
tempSpanToRemove = null;
}
var currentElement = this.parent.inputElement.querySelector('.currentStartMark');
var currentChildNode = currentElement ? currentElement.childNodes : [];
if (currentChildNode.length > 1) {
for (var i = 0; i < currentChildNode.length; i++) {
if (currentChildNode[i].nodeName === '#text' && currentChildNode[i].textContent.length === 0) {
detach(currentChildNode[i]);
i--;
}
if (!isNOU(currentChildNode[i]) && focusNode.textContent.replace(regEx, '') === currentChildNode[i].textContent) {
var iscursorLeft = pointer <= focusNode.textContent.indexOf('\u200B');
pointer = focusNode.textContent.length > 1 ?
((focusNode.textContent === currentChildNode[i].textContent || iscursorLeft) ? pointer :
pointer - (focusNode.textContent.length - focusNode.textContent.replace(regEx, '').length)) :
focusNode.textContent.length;
focusNode = currentChildNode[i];
}
}
}
else if (currentChildNode.length === 1) {
if (focusNode.textContent.replace(regEx, '') === currentChildNode[0].textContent) {
focusNode = currentChildNode[0];
}
}
this.parent.formatter.editorManager.nodeSelection.setCursorPoint(this.parent.contentModule.getDocument(), focusNode, pointer);
}
var currentElem = this.parent.inputElement.querySelector('.currentStartMark');
if (!isNOU(currentElem)) {
currentElem.classList.remove('currentStartMark');
if (currentElem.getAttribute('class').trim() === '') {
currentElem.removeAttribute('class');
}
}
if (!isNOU(range.startContainer.previousSibling) && !isNOU(range.startContainer.previousSibling.parentElement) &&
range.startContainer.parentElement === range.startContainer.previousSibling.parentElement &&
range.startContainer.previousSibling.textContent.charCodeAt(0) === 8203 &&
range.startContainer.previousSibling.textContent.length <= 1) {
range.startContainer.previousSibling.textContent = range.startContainer.previousSibling.textContent.replace(regEx, '');
}
if (range.endContainer.textContent.charCodeAt(range.endOffset) === 8203) {
pointer = range.startOffset;
range.endContainer.textContent = range.endContainer.textContent.replace(regEx, '');
this.parent.formatter.editorManager.nodeSelection.setCursorPoint(this.parent.contentModule.getDocument(), range.startContainer, pointer);
}
}
this.isSlashMenuOpen = false;
};
HtmlEditor.prototype.removeZeroWidthSpaces = function (node, regex) {
var _this = this;
if (node.nodeType === Node.TEXT_NODE) {
if (node.textContent !== null) {
node.textContent = node.textContent.replace(regex, '');
}
return;
}
node.childNodes.forEach(function (child) {
_this.removeZeroWidthSpaces(child, regex);
});
};
HtmlEditor.prototype.onKeyDown = function (e) {
var _this = this;
if (e.args.ctrlKey && e.args.keyCode === 65) {
this.isCopyAll = true;
}
else {
this.isCopyAll = false;
}
var currentRange;
var args = e.args;
if (this.parent.inputElement.querySelectorAll('.e-cell-select:not(table)').length > 1 &&
(args.keyCode === 8 || args.keyCode === 32 || args.keyCode === 13 || args.keyCode === 46)) {
this.tableSelectionKeyAction(e);
this.parent.autoResize();
return;
}
if (Browser.info.name === 'chrome') {
currentRange = this.parent.getRange();
this.backSpaceCleanup(e, currentRange);
this.deleteCleanup(e, currentRange);
}
var isCodeBlock = false;
if (!isNOU(this.parent.codeBlockModule)) {
currentRange = this.parent.getRange();
isCodeBlock = this.parent.formatter.editorManager.codeBlockObj.
isSelectionWithinCodeBlock(currentRange, currentRange.startContainer, currentRange.endContainer);
}
if (args.keyCode === 9 && this.parent.enableTabKey && !isCodeBlock) {
this.parent.formatter.saveData(e);
if (!this.indentTab()) {
return;
}
if (!isNOU(args.target) && isNullOrUndefined(closest(args.target, '.e-rte-toolbar'))) {
var range = this.nodeSelectionObj.getRange(this.contentRenderer.getDocument());
var parentNode = this.nodeSelectionObj.getParentNodeCollection(range);
if (!((parentNode[0].nodeName === 'LI' || closest(parentNode[0], 'li') ||
closest(parentNode[0], 'table')))) {
args.preventDefault();
var selection = this.contentRenderer.getDocument().getSelection().getRangeAt(0);
var alignmentNodes = this.parent.formatter.editorManager.domNode.blockNodes();
if (this.parent.enterKey === 'BR') {
if (selection.startOffset !== selection.endOffset && selection.startOffset === 0) {
var save = this.nodeSelectionObj.save(range, this.contentRenderer.getDocument());
this.parent.formatter.editorManager.domNode.setMarker(save);
alignmentNodes = this.parent.formatter.editorManager.domNode.blockNodes();
this.parent.formatter.editorManager.domNode.convertToBlockNodes(alignmentNodes, false);
this.marginTabAdd(args.shiftKey, alignmentNodes);
save = this.parent.formatter.editorManager.domNode.saveMarker(save);
save.restore();
}
else {
InsertHtml.Insert(this.contentRenderer.getDocument(), ' ', this.parent.element);
this.rangeCollection.push(this.nodeSelectionObj.getRange(this.contentRenderer.getDocument()));
}
}
else {
if (selection.startOffset !== selection.endOffset && selection.startOffset === 0) {
this.marginTabAdd(args.shiftKey, alignmentNodes);
}
else {
InsertHtml.Insert(this.contentRenderer.getDocument(), ' ', this.parent.element);
this.rangeCollection.push(this.nodeSelectionObj.getRange(this.contentRenderer.getDocument()));
}
}
}
}
this.parent.formatter.saveData(e);
}
// Prevents the link from being added when a space, enter, or parenthesis key is pressed.
// This ensures that parentheses are not mistakenly included as part of the URL.
var regex = /[^\w\s\\/\\.\\:@-]/g;
if (e.args.action === 'space' || e.args.action === 'enter' || e.args.keyCode === 13 || regex.test(e.args.key)) {
this.spaceLink(e.args);
if (this.parent.editorMode === 'HTML' && !this.parent.readonly) {
var currentLength = this.parent.getText().trim().replace(/(\r\n|\n|\r|\t)/gm, '').replace(/\u200B/g, '').length;
var selectionLength = this.parent.getSelection().length;
var totalLength = (currentLength - selectionLength) + 1;
if (!(this.parent.maxLength === -1 || totalLength <= this.parent.maxLength) &&
e.args.keyCode === 13) {
e.args.preventDefault();
return;
}
else {
this.parent.notify(events.enterHandler, { args: e.args });
scrollToCursor(this.parent.contentModule.getDocument(), this.parent.inputElement);
}
}
}
if (e.args.action === 'space') {
var currentRange_1 = this.parent.getRange();
var editorValue = currentRange_1.startContainer.textContent.slice(0, currentRange_1.startOffset);
var orderedList_1 = this.isOrderedList(editorValue);
var unOrderedList = this.isUnOrderedList(editorValue);
var checkList = this.isCheckList(editorValue);
var hasSplitedText = false;
if (orderedList_1 || unOrderedList || checkList) {
hasSplitedText = this.hasMultipleTextNode(currentRange_1);
if (hasSplitedText && !this.isMention) {
var element = currentRange_1.startContainer;
element = this.parent.formatter.editorManager.domNode.getImmediateBlockNode(element);
if (element.childNodes.length > 0 && !element.innerHTML.includes('<br>')) {
hasSplitedText = false;
}
}
}
if (!hasSplitedText && ((orderedList_1 && !unOrderedList && !checkList)
|| (unOrderedList && !orderedList_1 && !checkList) || (checkList && !orderedList_1 && !unOrderedList))) {
var eventArgs_1 = {
callBack: null,
event: e.args,
name: 'keydown-handler',
enterKey: this.parent.enterKey,
shiftEnterKey: this.parent.shiftEnterKey,
maxLength: this.parent.maxLength
};
var actionBeginArgs = {
cancel: false,
item: { command: 'Lists', subCommand: orderedList_1 ? 'OL' : 'UL' },
name: 'actionBegin',
originalEvent: e.args,
requestType: orderedList_1 ? 'OL' : 'UL'
};
this.parent.trigger(events.actionBegin, actionBeginArgs, function (actionBeginArgs) {
if (!actionBeginArgs.cancel) {
_this.parent.formatter.editorManager.observer.notify(ON_BEGIN, eventArgs_1);
_this.parent.trigger(events.actionComplete, {
editorMode: _this.parent.editorMode,
elements: _this.parent.formatter.editorManager.domNode.blockNodes(),
event: e.args,
name: events.actionComplete,
range: _this.parent.getRange(),
requestType: orderedList_1 ? 'OL' : 'UL'
});
}
});
}
}
if (Browser.info.name === 'chrome' && (!isNullOrUndefined(this.rangeElement) && !isNullOrUndefined(this.oldRangeElement) ||
!isNullOrUndefined(this.deleteRangeElement) && !isNullOrUndefined(this.deleteOldRangeElement)) &&
currentRange.startContainer.parentElement.tagName !== 'TD' && currentRange.startContainer.parentElement.tagName !== 'TH') {
this.rangeElement = null;
this.oldRangeElement = null;
this.deleteRangeElement = null;
this.deleteOldRangeElement = null;
if (!this.isImageDelete) {
args.preventDefault();
}
args.preventDefault();
}
this.parent.autoResize();
};
/**
* Checks if inserting a tab would exceed the maxLength constraint.
*
* @returns {boolean} True if allowed, false if it would exceed maxLength.
*/
HtmlEditor.prototype.indentTab = function () {
var tabSpaceLength = 4;
var maxLength = this.parent.maxLength;
var currentLength = this.parent.getText().replace(/(\r\n|\n|\r|\t)/gm, '').replace(/\u200B/g, '').length;
var selectionLength = this.parent.getSelection().length;
return maxLength === -1 || (currentLength - selectionLength + tabSpaceLength) <= maxLength;
};
HtmlEditor.prototype.isOrderedList = function (editorValue) {
editorValue = editorValue.replace(/\u200B/g, '');
var olListStartRegex = [/^[1]+[.]+$/, /^[i]+[.]+$/, /^[a]+[.]+$/];
if (!isNullOrUndefined(editorValue)) {
for (var i = 0; i < olListStartRegex.length; i++) {
if (olListStartRegex[i].test(editorValue)) {
return true;
}
}
}
return false;
};
HtmlEditor.prototype.isUnOrderedList = function (editorValue) {
editorValue = editorValue.replace(/\u200B/g, '');
var ulListStartRegex = [/^[*]$/, /^[-]$/];
if (!isNullOrUndefined(editorValue)) {
for (var i = 0; i < ulListStartRegex.length; i++) {
if (ulListStartRegex[i].test(editorValue)) {
return true;
}
}
}
return false;
};
HtmlEditor.prototype.isCheckList = function (editorValue) {
editorValue = editorValue.replace(/\u200B/g, '');
// Updated regex to match checkbox patterns with at most one space: [], [x], [ ], [x ], [ x], [ x ]
var ulListStartRegex = [/^\[\s?\]$/, /^\[\s?x\s?\]$/i];
if (!isNullOrUndefined(editorValue)) {
for (var i = 0; i < ulListStartRegex.length; i++) {
if (ulListStartRegex[i].test(editorValue)) {
return true;
}
}
}
return false;
};
HtmlEditor.prototype.hasMultipleTextNode = function (range) {
this.isMention = false;
if (range && range.startContainer && range.startContainer.parentNode) {
var parentNode = range.startContainer.parentNode;
if (range.startContainer.previousElementSibling &&
range.startContainer.previousElementSibling.classList.contains('e-mention-chip')
&& !range.startContainer.previousElementSibling.isContentEditable) {
this.isMention = true;
return true;
}
if (this.parent.enterKey === 'BR' || closest(parentNode, 'table')) {
return false;
}
var childNodes = parentNode.childNodes;
var textNodes = [];
for (var i = 0; i < childNodes.length; i++) {
var node = childNodes[i];
if (node && node.nodeType === Node.TEXT_NODE) {
textNodes.push(node);
if (textNodes.length > 1) {
return true;
}
}
}
}
return false;
};
//Determines if the cursor is truly at the start of a block element
HtmlEditor.prototype.isCursorAtBlockStart = function (range) {
if (range.startOffset !== 0 || range.endOffset !== 0) {
return false;
}
// Get the node where cursor is positioned
var cursorNode = range.startContainer;
// If cursor is in a text node, check its parent
var elementAtCursor = cursorNode.nodeType === Node.TEXT_NODE ?
cursorNode.parentElement : cursorNode;
// First, check if we're in a table cell - we don't want to handle these
if (elementAtCursor.tagName === 'TD' || elementAtCursor.tagName === 'TH') {
return false;
}
// Find the block-level ancestor
var blockNode = this.parent.formatter.editorManager.domNode.getImmediateBlockNode(elementAtCursor);
// If cursor is directly in a block element at position 0, it's at the start
if (cursorNode === blockNode && range.startOffset === 0) {
return true;
}
// Otherwise, we need to check if the cursor is positioned at the absolute beginning of content
var currentNode = elementAtCursor;
var previousContentFound = false;
// Walk up the DOM tree until we reach the block parent
while (currentNode && currentNode !== blockNode) {
// Check if there's any previous sibling with content
var sibling = currentNode.previousSibling;
while (sibling) {
// Skip empty text nodes
if (sibling.nodeType === Node.TEXT_NODE && (!sibling.textContent || !sibling.textContent.trim())) {
sibling = sibling.previousSibling;
continue;
}
// If we found any non-empty previous sibling, cursor is not at block start
previousContentFound = true;
break;
}
if (previousContentFound) {
break;
}
// Move up to parent and check again
currentNode = currentNode.parentNode;
}
// If we reached the block parent without finding previous content, cursor is at start
return !previousContentFound;
};
HtmlEditor.prototype.backSpaceCleanup = function (e, currentRange) {
var isLiElement = false;
var isPreviousNotContentEditable = true;
if (!isNOU(currentRange.startContainer.previousSibling) &&
currentRange.startContainer.previousSibling.nodeName === 'SPAN') {
isPreviousNotContentEditable = currentRange.startContainer.previousSibling.contentEditable === 'false' ? false : true;
}
var checkNode = currentRange.startContainer.nodeName === '#text' ? currentRange.startContainer.parentElement : currentRange.startContainer;
var isSelectedPositionNotStart = closest(currentRange.startContainer.nodeName === '#text' ? currentRange.startContainer.parentElement : currentRange.startContainer, 'li') ?
checkNode.nodeName !== 'li' && isNOU(checkNode.previousSibling) : true;
// Method to determine if cursor is truly at start
var isCursorAtStart = this.isCursorAtBlockStart(currentRange);
if (e.args.code === 'Backspace' &&
e.args.keyCode === 8 &&
isCursorAtStart && currentRange.startContainer.textContent !== ' ' && currentRange.startContainer.nodeValue !== '\u00A0' &&
this.parent.getSelection().length === 0 && currentRange.startContainer.textContent.length > 0 &&
isPreviousNotContentEditable && isSelectedPositionNotStart) {
if ((!this.parent.formatter.editorManager.domNode.isBlockNode(checkNode) &&
!isNOU(checkNode.previousSibling) && checkNode.previousSibling.nodeName === 'BR') ||
(!isNOU(currentRange.startContainer.previousSibling) && currentRange.startContainer.previousSibling.nodeName === 'BR')) {
return;
}
var isRangeCollapsed = currentRange.startOffset === currentRange.endOffset &&
currentRange.startContainer === currentRange.endContainer;
if (isRangeCollapsed && this.shouldPreventListRemoval()) {
return;
}
var immediateBlockNode = this.parent.formatter.editorManager.domNode.
getImmediateBlockNode(currentRange.startContainer);
if (!isNOU(this.parent.codeBlockModule)) {
var blockNode = immediateBlockNode !== this.parent.inputElement ? immediateBlockNode : currentRange.startContainer;
var firstPosition = this.parent.formatter.editorManager.nodeSelection.findFirstContentNode(blockNode);
var cursorAtFirstPosition = firstPosition && firstPosition.node === (currentRange.startContainer.nodeName === 'CODE' ? currentRange.startContainer.firstChild : currentRange.startContainer) &&
currentRange.startOffset === 0;
var isBlockPreviousElement = this.parent.formatter.editorManager.codeBlockObj.findParentOrPreviousSiblingCodeBlock(currentRange);
var isCodeBlockElement = this.parent.formatter.editorManager.codeBlockObj.isValidCodeBlockStructure(blockNode);
if ((!isNOU(isBlockPreviousElement) && cursorAtFirstPosition) || (!isNOU(isCodeBlockElement) && cursorAtFirstPosition)) {
return;
}
}
this.rangeElement = this.getRootBlockNode(currentRange.startContainer);
if (this.rangeElement.tagName === 'OL' || this.rangeElement.tagName === 'UL') {
var liElement = this.getRangeLiNode(currentRange.startContainer);
if (liElement.previousElementSibling && this.parent.inputElement.contains(liElement.previousElementSibling)
&& liElement.previousElementSibling.childElementCount > 0) {
this.oldRangeElement = liElement.previousElementSibling.lastElementChild.nodeName === 'BR' ?
liElement.previousElementSibling : liElement.previousElementSibling.lastChild;
if (!isNOU(liElement.lastElementChild) && liElement.lastElementChild.nodeName !== 'BR' &&
isNOU(liElement.lastElementChild.previousSibling) && liElement.lastChild.nodeName !== '#text') {
this.rangeElement = liElement.lastElementChild;
isLiElement = true;
}
else {
this.rangeElement = liElement;
}
}
}
else if (this.rangeElement === this.parent.inputElement || immediateBlockNode === this.parent.inputElement || this.rangeElement.tagName === 'TABLE' ||
(!isNOU(this.rangeElement.previousElementSibling) && this.rangeElement.previousElementSibling.tagName === 'TABLE')) {
return;
}
else {
this.oldRangeElement = this.rangeElement.previousElementSibling;
}
var findBlockElement = this.parent.formatter.editorManager.domNode.blockNodes();
if (!isNOU(findBlockElement[0]) && currentRange.collapsed && currentRange.startOffset === 0 && currentRange.endOffset === 0 && findBlockElement[0].style.marginLeft !== '') {
findBlockElement[0].style.marginLeft = (parseInt(findBlockElement[0].style.marginLeft, 10) <= 20) ? '' : (parseInt(findBlockElement[0].style.marginLeft, 10) - 20 + 'px');
}
var findBlockElementSibiling = findBlockElement[0].previousSibling ?
findBlockElement[0].previousSibling : this.findPreviousElementSibling(findBlockElement[0]);
if (isNOU(this.oldRangeElement) && isNOU(findBlockElement[0].previousSibling)) {
return;
}
else if (findBlockElementSibiling) {
var prevSibling = findBlockElementSibiling;
var currentElement = findBlockElement[0];
if (prevSibling.textContent.trim()) {
this.removeLastBr(prevSibling);
var lastPosition = this.parent.formatter.editorManager.nodeSelection.findLastTextPosition(prevSibling);
var cursorpointer = lastPosition.offset;
var lastChild = lastPosition.node;
var childNodes = Array.from(currentElement.childNodes);
var save = this.nodeSelectionObj.save(currentRange, this.contentRenderer.getDocument());
if (this.isPreviousNodeBrAfterBackSpace) {
this.parent.formatter.editorManager.domNode.setMarker(save);
}
var previousBlockElements = this.parent.formatter.editorManager.domNode.getImmediateBlockNode(lastChild);
for (var i = 0; i < childNodes.length; i++) {
previousBlockElements.appendChild(childNodes[i].cloneNode(true));
}
if (this.isPreviousNodeBrAfterBackSpace) {
save = this.parent.formatter.editorManager.domNode.saveMarker(save);
save.restore();
}
else {
this.parent.formatter.editorManager.nodeSelection.setCursorPoint(this.parent.contentModule.getDocument(), lastChild, cursorpointer);
}
var currentParent = currentElement.parentNode;
currentParent.removeChild(currentElement);
while (!currentParent.hasChildNodes() && currentParent.textContent === '') {
var tempParent = currentParent.parentNode;
currentParent.parentNode.removeChild(currentParent);
currentParent = tempParent;
}
e.args.preventDefault();
}
else {
prevSibling.parentNode.removeChild(prevSibling);
}
}
else {
if (this.oldRangeElement.tagName === 'OL' || this.oldRangeElement.tagName === 'UL') {
this.oldRangeElement = this.oldRangeElement.lastElementChild.lastElementChild
? this.oldRangeElement.lastElementChild.lastElementChild :
this.oldRangeElement.lastElementChild;
}
var lastNode = this.oldRangeElement.lastChild ? this.oldRangeElement.lastChild : this.oldRangeElement;
while (lastNode.nodeType !== 3 && lastNode.nodeName !== '#text' &&
lastNode.nodeName !== 'BR' && !isNOU(lastNode.lastChild)) {
lastNode = lastNode.lastChild;
}
if (lastNode.nodeName === 'IMG') {
this.parent.formatter.editorManager.nodeSelection.setCursorPoint(this.parent.contentModule.getDocument(), lastNode.parentElement, lastNode.parentElement.childNodes.length);
}
else {
this.parent.formatter.editorManager.nodeSelection.setCursorPoint(this.parent.contentModule.getDocument(),
// eslint-disable-next-line
lastNode, lastNode.textContent.length);
}
var checkParent = false;
if (this.oldRangeElement && this.oldRangeElement.nodeName !== '#text' && this.oldRangeElement.querySelectorAll('BR').length === 1) {
var brElement = this.oldRangeElement.querySelector('BR');
if (brElement && isNOU(brElement.nextSibling)) {
var brParentElement = brElement.parentNode;
var currentState = this.oldRangeElement.innerHTML;
this.parent.formatter.saveData(currentState);
detach(brElement);
if (brParentElement && brParentElement.childNodes.length === 0) {
detach(brParentElement);
checkParent = true;
}
}
}
if (!isNOU(this.rangeElement) && this.oldRangeElement !== this.rangeElement && !checkParent) {
while (this.rangeElement.firstChild) {
if (this.oldRangeElement.nodeName === '#text') {
this.oldRangeElement.parentElement.appendChild(this.rangeElement.childNodes[0]);
}
else {
this.oldRangeElement.appendChild(this.rangeElement.childNodes[0]);
}
}
// eslint-disable-next-line
!isLiElement ? detach(this.rangeElement) : detach(this.rangeElement.parentElement);
this.oldRangeElement.normalize();
}
}
}
if (e.args.code === 'Backspace' && e.args.keyCode === 8 &&
currentRange.startContainer.nodeType !== Node.TEXT_NODE) {
var ChildNode = !isNOU(currentRange.startContainer.childNodes[currentRange.startOffset - 1]) &&
!isNOU(currentRange.startContainer.childNodes[currentRange.startOffset - 1].isContentEditable) &&
!currentRange.startContainer.childNodes[currentRange.startOffset - 1].isContentEditable ?
currentRange.startContainer.childNodes[currentRange.startOffset - 1] : null;
var index = currentRange.startOffset > 1 ? currentRange.startOffset - 1 : 0;
if (ChildNode) {
ChildNode.remove();
e.args.preventDefault();
}
else if ((checkNode && checkNode.textContent.trim() === '') ||
(currentRange.startContainer.childNodes[index] &&
currentRange.startContainer.childNodes[index].textContent.trim() === '')) {
var node = checkNode && checkNode.textContent.trim() === '' ? checkNode : currentRange.startContainer.childNodes[index];
if (hasAnyFormatting(node) && node.previousSibling && node.previousSibling.textContent.trim() === '' &&
node !== this.parent.inputElement) {
this.parent.formatter.editorManager.nodeSelection.setCursorPoint(this.parent.contentModule.getDocument(), node.previousSibling, 0);
detach(node);
e.args.preventDefault();
}
}
}
};
HtmlEditor.prototype.shouldPreventListRemoval = function () {
var findBlockElements = this.parent.formatter.editorManager.domNode.blockNodes();
if (findBlockElements[0] && findBlockElements[0].previousSibling) {
var prevSiblings = findBlockElements[0].previousSibling;
if (prevSiblings.nodeType === Node.ELEMENT_NODE) {
var listElement = prevSiblings.matches('ol, ul') ? prevSiblings
: prevSiblings.querySelector('ol, ul');
if (listElement) {
var liTags = listElement.querySelectorAll('li');
// The list is empty if it has no <li> tags
if (liTags.length === 0) {
return true;
}
// The list is considered removable if only the last item is empty
var lastLi = liTags[liTags.length - 1];
return lastLi && lastLi.textContent.trim() === '';
}
}
}
return false;
};
HtmlEditor.prototype.findPreviousElementSibling = function (element) {
var current = element;
while (current) {
var prevSibling = current.previousElementSibling;
if (prevSibling) {
return prevSibling;
}
current = current.parentElement;
if (current && current === this.parent.inputElement) {
return null;
}
}
return null;
};
//Finds the last significant node within the given element.
HtmlEditor.prototype.getLastNode = function (node) {
while (node && node.lastChild) {
node = node.lastChild;
}
// Skip empty text nodes by checking if the node is a text node and contains only whitespace.
while (node && node.nodeType === Node.TEXT_NODE && !node.nodeValue.trim()) {
node = node.previousSibling;
}
return node;
};
// Removes the last <br> element from the given element if it is the last meaningful node.
HtmlEditor.prototype.removeLastBr = function (element) {
// Get the last meaningful node of the given element.
var lastNode = this.getLastNode(element);
// If the last node is a <br> element, remove it from the DOM.
if (lastNode && lastNode.nodeName === 'BR' && lastNode.parentNode) {
this.isPreviousNodeBrAfterBackSpace = !isNOU(lastNode.previousSibling) &&
lastNode.previousSibling.nodeName === 'BR' ? true : false;
lastNode.parentNode.removeChild(lastNode);
}
};
HtmlEditor.prototype.deleteCleanup = function (e, currentRange) {
var isLiElement = false;
var liElement;
var rootElement;
if (!isNOU(this.parent.codeBlockModule)) {
var codePos = this.parent.formatter.editorManager.codeBlockObj.getCodeBlockPosition(currentRange);
if (e.args.code === 'Delete' && (!isNOU(this.parent.formatter.editorManager.codeBlockObj.isValidCodeBlockStructure(currentRange.startContainer)) ||
(!isNOU(codePos.nextSiblingCodeBlockElement) && codePos.cursorAtLastPosition))) {
return;
}
}
if (e.args.code === 'Delete' && e.args.keyCode === 46 &&
this.parent.contentModule.getText().trim().replace(/(\r\n|\n|\r|\t)/gm, '').replace(/\u200B/g, '').length !== 0 && this.parent.getSelection().length === 0 && currentRange.startContainer.parentElement.tagName !== 'TD' &&
currentRange.startContainer.parentElement.tagName !== 'TH') {
this.deleteRangeElement = rootElement = this.getRootBlockNode(currentRange.startContainer);
if (this.deleteRangeElement.tagName === 'OL' || this.deleteRangeElement.tagName === 'UL') {
liElement = this.getRangeLiNode(currentRange.startContainer);
if (liElement.nextElementSibling && liElement.nextElementSibling.childElementCount > 0
&& !liElement.nextElementSibling.querySelector('BR')) {
if (!isNullOrUndefined(liElement.lastElementChild)) {
this.deleteRangeElement = liElement.lastElementChild;
isLiElement = true;
}
else {
this.deleteRangeElement = liElement;
}
}
else {
this.deleteRangeElement = this.getRangeElement(liElement);
}
}
else if (this.deleteRangeElement.nodeType === 3 || (this.deleteRangeElement.tagName === 'TABLE' ||
(!isNullOrUndefined(this.deleteRangeElement.nextElementSibling) && t