@syncfusion/ej2-richtexteditor
Version:
Essential JS 2 RichTextEditor component
879 lines • 57.4 kB
JavaScript
import * as EVENTS from '../../common/constant';
import { createElement, isNullOrUndefined as isNOU } from '@syncfusion/ej2-base';
import * as CONSTANT from './../base/constant';
/**
* ClipBoardCleanup internal component
*
* @hidden
*/
var ClipBoardCleanupAction = /** @class */ (function () {
/**
* Constructor for creating the component
*
* @param {IEditorModel} parent - specifies the parent element
* @hidden
* @private
*/
function ClipBoardCleanupAction(parent) {
this.parent = parent;
this.addEventListener();
}
/* Attaches event listeners for ClopBoard Cleanup operations */
ClipBoardCleanupAction.prototype.addEventListener = function () {
this.parent.observer.on(EVENTS.INTERNAL_DESTROY, this.destroy, this);
};
/* Attaches event listeners for ClopBoard Cleanup operations */
ClipBoardCleanupAction.prototype.removeEventListener = function () {
this.parent.observer.off(EVENTS.INTERNAL_DESTROY, this.destroy);
};
/**
* Handles the clipboard data processing for cut/copy operations
*
* @param {Range} range - The current selection range in the document
* @param {Element} editableElement - The editable container element
* @param {string} operation - The clipboard operation type ('cut', 'copy')
* @returns {ClipboardWriteEventArgs} - An object containing HTML and plain text content for clipboard operations
*/
ClipBoardCleanupAction.prototype.handleClipboardProcessing = function (range, editableElement, operation) {
this.editableElement = editableElement;
// Extract HTML and plain text from the selected range
var _a = this.extractClipboardContentFromSelection(range), htmlContent = _a.htmlContent, plainTextContent = _a.plainTextContent;
var isFullBlockSelection = this.isSelectionCoveringSingleBlock(range, htmlContent);
var isFullLISelected = this.isFullLISelected(range, htmlContent);
// Wrap list items if selection is within a list
if (this.parent.domNode.isList(range.commonAncestorContainer)) {
var formattedClipboardData = this.wrapListStructureForClipboard(range, htmlContent, plainTextContent);
htmlContent = formattedClipboardData.htmlContent;
plainTextContent = formattedClipboardData.plainTextContent;
}
else if (isFullLISelected) {
// when whole li is selected
var closestList = range.commonAncestorContainer;
// If it's a text node, move to its parent
if (closestList.nodeName === '#text') {
closestList = closestList.parentElement;
}
// If it's an LI, use it; otherwise, find the closest LI
var liElement = closestList.nodeName === 'LI' ? closestList : closestList.closest('li');
// Get the parent list (UL or OL)
var closestListParent = liElement.parentElement;
var listContainer = createElement(closestListParent.tagName);
listContainer.appendChild(createElement('li'));
listContainer.firstElementChild.innerHTML = htmlContent;
htmlContent = listContainer.outerHTML;
var temporaryWrapper = createElement('div');
temporaryWrapper.innerHTML = htmlContent;
plainTextContent = this.extractTextFromHtmlNode(temporaryWrapper);
}
// Handle cut operation
if (operation === 'cut') {
//check if table is in selection
var tables = this.collectTablesFromSelection(range);
if (tables.length > 1 || (tables.length === 1 && !this.isSelectionInsideNestedTable(range))) {
//handle table cut behavior
this.isTableSelection = true;
this.clearSelectedTableContent(range);
}
else {
//check if a whole block level element is selected
this.isTableSelection = false;
var isSelectionSameBlock = (this.parent.domNode.getImmediateBlockNode(range.startContainer) ===
this.parent.domNode.getImmediateBlockNode(range.endContainer));
//check if media element alone is selected
var containsMediaContent = this.containsMediaElement(range);
// Normalize inline elements at the boundaries
this.cleanEmptyInlineBoundaries(range);
//to get the nested list range end container
var nestedListEndContainer = void 0;
var rootParentLIElement = void 0;
if (range.commonAncestorContainer.nodeName === 'LI') {
rootParentLIElement = range.commonAncestorContainer;
nestedListEndContainer = this.parent.domNode.getImmediateBlockNode(range.endContainer);
}
range.deleteContents();
var sharedContainer = range.commonAncestorContainer;
if (containsMediaContent) {
this.adjustCursorAfterMediaCut(range);
}
else if (isFullBlockSelection) {
this.fullBlockElementCursorPosition(sharedContainer);
}
else if (range.collapsed && sharedContainer.nodeName !== '#text' && !isSelectionSameBlock &&
!isNOU(range.startContainer.childNodes[range.startOffset])) {
this.restructureContentPostCut(range, rootParentLIElement, nestedListEndContainer);
}
}
}
return { htmlContent: htmlContent, plainTextContent: plainTextContent, operation: operation };
};
/* Sets the cursor position after a block-level element is cut*/
ClipBoardCleanupAction.prototype.fullBlockElementCursorPosition = function (blockContainer) {
var resolvedBlockElement = this.parent.domNode.getImmediateBlockNode(blockContainer);
var brElement = document.createElement('br');
if (resolvedBlockElement.childElementCount === 2) {
var focusElement = this.getFirstBlockElement(resolvedBlockElement);
focusElement.appendChild(brElement);
resolvedBlockElement.innerHTML = resolvedBlockElement.firstElementChild.outerHTML;
this.parent.nodeSelection.setCursorPoint(this.parent.currentDocument, resolvedBlockElement.firstElementChild, 0);
}
else {
resolvedBlockElement.innerHTML = '<br>';
this.parent.nodeSelection.setCursorPoint(this.parent.currentDocument, resolvedBlockElement, 0);
}
};
/*Adjusts the cursor position after a media element is cut from the document*/
ClipBoardCleanupAction.prototype.adjustCursorAfterMediaCut = function (selectionRange) {
var startNode = selectionRange.startContainer;
var isVideoWrapperSpan = startNode.nodeName === 'SPAN' && startNode.className === 'e-video-wrap';
if (isVideoWrapperSpan) {
var mediaContainer = startNode;
var contentContainer = this.parent.domNode.getImmediateBlockNode(mediaContainer);
var mediaIndex = void 0;
for (var index = 0; index < contentContainer.childNodes.length; index++) {
if (contentContainer.childNodes[index] === mediaContainer) {
mediaIndex = index;
break;
}
}
if (mediaIndex === 0) {
this.setCursorPosition(contentContainer);
}
else {
this.setCursorPosition(contentContainer.childNodes[mediaIndex - 1]);
}
contentContainer.removeChild(mediaContainer);
}
else {
var brElement = document.createElement('br');
if (!isNOU(startNode.childNodes[selectionRange.startOffset]) &&
startNode.childNodes[selectionRange.startOffset].textContent.trim() === '' &&
this.isContentContainerEmpty(startNode)) {
startNode.replaceChild(brElement, startNode.childNodes[selectionRange.startOffset]);
this.parent.nodeSelection.setCursorPoint(this.parent.currentDocument, startNode, startNode.childElementCount);
}
else if (this.isContentContainerEmpty(startNode)) {
startNode.appendChild(brElement);
this.parent.nodeSelection.setCursorPoint(this.parent.currentDocument, startNode, startNode.childElementCount);
}
else {
this.parent.nodeSelection.setCursorPoint(this.parent.currentDocument, startNode, selectionRange.startOffset);
}
}
};
/* Determines whether the li element is fully selected. */
ClipBoardCleanupAction.prototype.isFullLISelected = function (range, htmlContent) {
var selectedElement;
if (range.commonAncestorContainer.nodeName === '#text') {
selectedElement = range.commonAncestorContainer.parentElement;
}
else {
selectedElement = range.commonAncestorContainer;
}
if ((selectedElement.nodeName === 'LI' || selectedElement.closest('li'))
&& this.isSelectionCoveringSingleBlock(range, htmlContent)) {
return true;
}
return false;
};
/* Wraps list elements (UL/OL) with their parent container if the selection is inside <li> */
ClipBoardCleanupAction.prototype.wrapListStructureForClipboard = function (selectionRange, htmlContent, plainTextContent) {
var listContainerTag = selectionRange.commonAncestorContainer.nodeName.toLowerCase();
var listContainer = createElement(listContainerTag);
listContainer.innerHTML = htmlContent;
var listItems = Array.from(listContainer.querySelectorAll('li'));
if (listItems.length > 1) {
htmlContent = listContainer.outerHTML;
}
var temporaryWrapper = createElement('div');
temporaryWrapper.innerHTML = htmlContent;
plainTextContent = this.extractTextFromHtmlNode(temporaryWrapper);
return { htmlContent: htmlContent, plainTextContent: plainTextContent };
};
/* Extracts both HTML and plain text content from the current selection range. */
ClipBoardCleanupAction.prototype.extractClipboardContentFromSelection = function (range) {
var htmlContent = '';
var plainTextContent = '';
htmlContent = this.getHTMLFromSelectionRange(range);
var temporaryContainer = createElement('div');
temporaryContainer.innerHTML = htmlContent;
htmlContent = this.normalizeInlineElementWrapping(range, htmlContent);
// If fails this operation will be handled in wrapListStructureForClipboard method
if (!(this.parent.domNode.isList(range.commonAncestorContainer) ||
this.isFullLISelected(range, htmlContent))) {
plainTextContent = this.extractTextFromHtmlNode(temporaryContainer);
}
return { htmlContent: htmlContent, plainTextContent: plainTextContent };
};
/* Extracts HTML content and ensures inline elements are properly wrapped if present in the selection.*/
ClipBoardCleanupAction.prototype.normalizeInlineElementWrapping = function (selectionRange, htmlContent) {
var startNode = selectionRange.startContainer;
// Check if the selection starts with non-empty text
if (startNode.textContent.trim() !== '') {
var ancestorNode = selectionRange.commonAncestorContainer;
var isTextNode = ancestorNode.nodeName === '#text';
var isNonBlockNode = !this.parent.domNode.isBlockNode((isTextNode ? ancestorNode.parentNode : ancestorNode));
if (isNonBlockNode) {
htmlContent = this.getWrappedAroundInlineElement((isTextNode ? ancestorNode.parentNode : ancestorNode), htmlContent);
}
}
// Special handling for video wrapper elements
var isVideoWrapper = startNode.nodeName === 'SPAN' &&
startNode.classList.length > 0 && startNode.classList.contains('e-video-wrap');
if (isVideoWrapper) {
htmlContent = startNode.outerHTML;
}
return htmlContent;
};
/* Extracts HTML content from the current selection range by wrapping it in a temporary container */
ClipBoardCleanupAction.prototype.getHTMLFromSelectionRange = function (selectionRange) {
var clonedSelection = selectionRange.cloneContents();
var temporaryContainer = createElement('div');
temporaryContainer.appendChild(clonedSelection);
return temporaryContainer.innerHTML;
};
/* Returns the outer HTML of the nearest inline-level ancestor after replacing its inner content with the provided HTML content. */
ClipBoardCleanupAction.prototype.getWrappedAroundInlineElement = function (inlineAncestor, contentToWrap) {
var contentContainer = createElement('div');
contentContainer.innerHTML = contentToWrap;
//to retrieve only the wrapper inline element without any inner html in it
do {
var clonedInlineAncestor = inlineAncestor.cloneNode(true);
//swapping the existing html and cloned inline wrapper
clonedInlineAncestor.innerHTML = contentContainer.innerHTML;
contentContainer.innerHTML = clonedInlineAncestor.outerHTML;
inlineAncestor = inlineAncestor.parentElement;
} while (!isNOU(inlineAncestor) && !this.parent.domNode.isBlockNode(inlineAncestor));
return contentContainer.innerHTML;
};
/* Checks if the current selection range contains a media element (IMG, VIDEO, AUDIO). */
ClipBoardCleanupAction.prototype.containsMediaElement = function (range) {
var potentialMediaNode = !isNOU(range.commonAncestorContainer.childNodes[range.startOffset]) ?
(range.commonAncestorContainer.childNodes[range.startOffset]) : null;
return (!isNOU(potentialMediaNode) &&
((potentialMediaNode.nodeName === 'IMG') ||
(potentialMediaNode.nodeName === 'VIDEO') || (potentialMediaNode.nodeName === 'AUDIO')));
};
/* Retrieves all unique table elements within the current selection range. */
ClipBoardCleanupAction.prototype.collectTablesFromSelection = function (selectionRange) {
var identifiedTables = [];
// Get the parent table for startContainer
var startTable = this.locateOutermostTableAncestor(selectionRange.startContainer, selectionRange);
if (!isNOU(startTable) && identifiedTables.indexOf(startTable) < 0) {
identifiedTables.push(startTable);
}
// Get the parent table for endContainer
var endTable = this.locateOutermostTableAncestor(selectionRange.endContainer, selectionRange);
if (!isNOU(endTable) && identifiedTables.indexOf(endTable) < 0) {
identifiedTables.push(endTable);
}
return identifiedTables;
};
/* Locates the outermost parent table element that contains the given node. */
ClipBoardCleanupAction.prototype.locateOutermostTableAncestor = function (currentNode, selectionRange) {
var traversalNode = currentNode;
var detectedTable = null;
if (traversalNode === this.editableElement) {
var offsetIndex = selectionRange.startContainer === traversalNode ?
selectionRange.startOffset : selectionRange.endOffset;
var candidateElement = selectionRange.commonAncestorContainer.childNodes[offsetIndex];
return !isNOU(candidateElement) ? candidateElement.nodeName === 'TABLE' ? candidateElement : null : null;
}
else {
traversalNode = this.parent.domNode.getImmediateBlockNode(traversalNode);
while (!isNOU(traversalNode.closest('table')) &&
this.editableElement.contains(traversalNode.closest('table'))) {
traversalNode = detectedTable = traversalNode.closest('table');
// to get the root parent element of table
traversalNode = traversalNode.parentElement;
}
return detectedTable;
}
};
/* Determines whether the current selection is within a nested table structure. */
ClipBoardCleanupAction.prototype.isSelectionInsideNestedTable = function (selectionRange) {
if (selectionRange.startContainer !== this.editableElement && selectionRange.endContainer !== this.editableElement) {
var startContainer = selectionRange.startContainer.nodeName === '#text' ?
selectionRange.startContainer.parentElement : selectionRange.startContainer;
var endContainer = selectionRange.endContainer.nodeName === '#text' ?
selectionRange.endContainer.parentElement : selectionRange.endContainer;
return startContainer.closest('table') === endContainer.closest('table');
}
return false;
};
/* Clears selected content within tables and manages cursor positioning after cut operation. */
ClipBoardCleanupAction.prototype.clearSelectedTableContent = function (selectionRange) {
var _this = this;
var activeSelection = window.getSelection();
var tablesToClean = [];
var selectedNodes = Array.from(selectionRange.commonAncestorContainer.childNodes).filter(function (childNode) {
if (childNode.nodeName === 'TABLE') {
tablesToClean.push(childNode);
}
return selectionRange.intersectsNode(childNode);
});
// Remove intermediate nodes
selectedNodes.forEach(function (node) {
_this.processSelectedContentWithinRange(node, selectionRange, tablesToClean);
});
// Clean up empty tables
this.removeEmptyTables(tablesToClean);
// Position cursor
selectionRange.collapse(true);
activeSelection.removeAllRanges();
activeSelection.addRange(selectionRange);
this.adjustCursorPostTableCut(selectionRange);
};
/* Removes intermediateNodes that are fully selected within the given range. */
ClipBoardCleanupAction.prototype.removeSelectedNodes = function (inspectedElement, selectionRange) {
if (!isNOU(inspectedElement) && selectionRange.intersectsNode(inspectedElement)) {
var isFullySelected = this.isFullHTMLElementSelected(inspectedElement, selectionRange);
if (isFullySelected) {
// Entire element is selected
inspectedElement.remove();
return true;
}
}
return false;
};
/* Recursively filters and processes selected content within a range, including table cells and block-level elements. */
ClipBoardCleanupAction.prototype.processSelectedContentWithinRange = function (contentFragment, selectionRange, tablesToClean) {
var _this = this;
if (!isNOU(contentFragment) && selectionRange.intersectsNode(contentFragment)) {
var isElementRemoved = false;
if (this.parent.domNode.isBlockNode(contentFragment) &&
['TD', 'TH'].indexOf(contentFragment.nodeName) < 0) {
if (contentFragment.nodeName === 'TR' && isNOU(contentFragment.querySelector('th'))) {
isElementRemoved = this.removeSelectedNodes(contentFragment, selectionRange);
}
else if (contentFragment.nodeName !== 'TR') {
isElementRemoved = this.removeSelectedNodes(contentFragment, selectionRange);
}
}
if (!isElementRemoved) {
if (contentFragment.nodeName === 'TABLE') {
tablesToClean.push(contentFragment);
var parentTable_1 = contentFragment;
var selectedRows = Array.from(parentTable_1.querySelectorAll('tr')).filter(function (rowCandidate) {
return rowCandidate.nodeName === 'TR' && selectionRange.intersectsNode(rowCandidate) &&
rowCandidate.closest('table') === parentTable_1;
});
selectedRows.forEach(function (tableRow) {
_this.processSelectedContentWithinRange(tableRow, selectionRange, tablesToClean);
});
}
else if (contentFragment.nodeName === 'TR') {
var associatedTable_1 = contentFragment.closest('table');
var selectedCells = Array.from(contentFragment.childNodes).filter(function (cellCandidate) {
return cellCandidate.nodeName !== '#text' && ['TD', 'TH'].indexOf(cellCandidate.nodeName) >= 0 &&
selectionRange.intersectsNode(cellCandidate) &&
cellCandidate.closest('table') === associatedTable_1;
});
selectedCells.forEach(function (tableCell) {
_this.processSelectedContentWithinRange(tableCell, selectionRange, tablesToClean);
});
}
else if (this.parent.domNode.isBlockNode(contentFragment) &&
['TD', 'TH'].indexOf(contentFragment.nodeName) >= 0 &&
this.isFullHTMLElementSelected(contentFragment, selectionRange)) {
contentFragment.innerHTML = '';
}
else if ((this.parent.domNode.isBlockNode(contentFragment) &&
contentFragment.nodeName !== 'TABLE' && this.hasContainsAnyBlockNode(contentFragment)) ||
(['TD', 'TH'].indexOf(contentFragment.nodeName) >= 0)) {
var selectedChildNodes = Array.from(contentFragment.childNodes);
selectedChildNodes.forEach(function (nestedNode) {
if (nestedNode.nodeName === 'TABLE') {
tablesToClean.push(nestedNode);
var parentTable = nestedNode;
var selectedRows = Array.from(parentTable.querySelectorAll('tr')).filter(function (rowCandidate) {
return rowCandidate.nodeName === 'TR' && selectionRange.intersectsNode(rowCandidate) &&
rowCandidate.closest('table') === nestedNode;
});
selectedRows.forEach(function (tableRow) {
_this.processSelectedContentWithinRange(tableRow, selectionRange, tablesToClean);
});
}
else {
_this.processSelectedContentWithinRange(nestedNode, selectionRange, tablesToClean);
}
});
}
else if (contentFragment.nodeType === Node.ELEMENT_NODE &&
isNOU(contentFragment.closest('table')) &&
contentFragment !== this.parent.domNode.getImmediateBlockNode(selectionRange.startContainer) &&
contentFragment !== this.parent.domNode.getImmediateBlockNode(selectionRange.endContainer)) {
var partialRange = selectionRange.cloneRange();
partialRange.setEnd(contentFragment, contentFragment.childNodes.length);
partialRange.deleteContents();
}
else if (contentFragment.nodeType === Node.ELEMENT_NODE) {
this.traverseElementTextNodes(contentFragment, selectionRange);
}
else {
this.clearIntersectingContent(contentFragment, selectionRange);
}
}
}
};
/* To retrive the text nodes in element and remove using the help of clearIntersectingContent method */
ClipBoardCleanupAction.prototype.traverseElementTextNodes = function (elementNode, selectionRange) {
var _this = this;
// Traverse inline text nodes within the fragment
var intersectingTextNodes = [];
var textNodeIterator = document.createNodeIterator(elementNode, NodeFilter.SHOW_TEXT, {
acceptNode: function (textCandidate) {
// Filter out empty or whitespace-only text nodes
return (selectionRange.intersectsNode(textCandidate) && textCandidate.textContent.trim()) ?
NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
}
});
var currentTextNode = textNodeIterator.nextNode();
while (currentTextNode) {
intersectingTextNodes.push(currentTextNode);
currentTextNode = textNodeIterator.nextNode();
}
intersectingTextNodes.forEach(function (textNode) {
_this.clearIntersectingContent(textNode, selectionRange);
});
};
/* To check and remove the element if full element is selected */
ClipBoardCleanupAction.prototype.isFullHTMLElementSelected = function (blockElement, selectionRange) {
var elementRange = document.createRange();
elementRange.selectNode(blockElement);
return selectionRange.compareBoundaryPoints(Range.START_TO_START, elementRange) <= 0 &&
selectionRange.compareBoundaryPoints(Range.END_TO_END, elementRange) >= 0;
};
/* To check if any block level elements constains inside */
ClipBoardCleanupAction.prototype.hasContainsAnyBlockNode = function (blockElement) {
return !isNOU(blockElement) && this.parent.domNode.isBlockNode(blockElement) &&
CONSTANT.BLOCK_TAGS.some(function (tag) { return blockElement.querySelector(tag) !== null; });
};
/* Iterates through table list and removes any table that is empty. */
ClipBoardCleanupAction.prototype.removeEmptyTables = function (tableElements) {
for (var tableIndex = tableElements.length - 1; tableIndex >= 0; tableIndex--) {
this.removeEmptyTableElement(tableElements[tableIndex]);
}
};
/* Sets the cursor position appropriately after a table cut operation within an editable container. */
ClipBoardCleanupAction.prototype.adjustCursorPostTableCut = function (selectionRange) {
var cursorElement = selectionRange.startContainer;
if (this.isContentContainerEmpty(this.editableElement) &&
this.editableElement.childElementCount === 0) {
if (this.editableElement.innerHTML === '') {
this.editableElement.innerHTML = '<p><br></p>';
}
this.parent.nodeSelection.setCursorPoint(this.parent.currentDocument, this.editableElement.firstChild, 0);
}
else if (cursorElement.nodeName === '#text' &&
cursorElement.textContent === '' && this.isContentContainerEmpty(cursorElement.parentElement)) {
cursorElement = this.parent.domNode.getImmediateBlockNode(cursorElement);
cursorElement.appendChild(document.createElement('br'));
this.parent.nodeSelection.setCursorPoint(this.parent.currentDocument, cursorElement, cursorElement.childElementCount);
}
else if (cursorElement.nodeName === '#text' &&
cursorElement.textContent.length > 0) {
this.setCursorPosition(cursorElement);
}
else if (this.parent.domNode.isBlockNode(cursorElement)) {
if (cursorElement.nodeName === 'TABLE') {
this.parent.nodeSelection.setCursorPoint(this.parent.currentDocument, cursorElement, 0);
}
else {
this.setCursorPosition(cursorElement);
}
}
else {
cursorElement = selectionRange.commonAncestorContainer.childNodes[selectionRange.startOffset];
if (!isNOU(cursorElement)) {
if (this.isContentContainerEmpty(cursorElement)) {
cursorElement.appendChild(document.createElement('br'));
this.parent.nodeSelection.setCursorPoint(this.parent.currentDocument, cursorElement, cursorElement.childElementCount);
}
else {
this.setCursorPosition(cursorElement);
}
}
}
};
/* Removes a table element if it lacks meaningful content. */
ClipBoardCleanupAction.prototype.removeEmptyTableElement = function (tableElement) {
if (!isNOU(tableElement.parentElement)) {
var isContentInsignificant = this.isContentContainerEmpty(tableElement);
var isElementAttached = tableElement.parentElement.contains(tableElement);
if (isContentInsignificant && isElementAttached) {
tableElement.parentElement.removeChild(tableElement);
return true;
}
}
return false;
};
/* Removes content from a given DOM fragment that intersects with the selection range. */
ClipBoardCleanupAction.prototype.clearIntersectingContent = function (contentFragment, selectionRange) {
if (!isNOU(contentFragment) && selectionRange.intersectsNode(contentFragment)) {
var updatedTextContent = void 0;
// Handle partial text node selection
if (contentFragment.nodeName === '#text') {
updatedTextContent = this.extractUnselectedTextContent(contentFragment, selectionRange);
contentFragment.textContent = updatedTextContent;
}
var EndRangeBlockAncestor = this.parent.domNode.getImmediateBlockNode(selectionRange.endContainer);
var inlineWrapperParent = this.parent.domNode.getImmediateBlockNode(contentFragment);
// Cleanup if the updated text content is empty
if (updatedTextContent === '' &&
!this.parent.domNode.isBlockNode(contentFragment.parentElement)) {
var inlineWrapper = contentFragment.parentElement;
while (!this.parent.domNode.isBlockNode(inlineWrapper.parentElement)) {
if (inlineWrapper.parentElement.childNodes.length === 1) {
inlineWrapper = inlineWrapper.parentElement;
}
else {
break;
}
}
this.removeEmptyContentContainer(inlineWrapper);
}
if (this.parent.domNode.isBlockNode(inlineWrapperParent) && this.isContentContainerEmpty(inlineWrapperParent) &&
inlineWrapperParent === EndRangeBlockAncestor &&
this.isTableSelection && ['TD', 'TH'].indexOf(inlineWrapperParent.nodeName) < 0) {
if ((!this.editableElement.contains(inlineWrapperParent.closest('table'))) ||
(this.editableElement.contains(inlineWrapperParent.closest('table')) &&
inlineWrapperParent.childElementCount === 0)) {
// remove element if it is not inside table selection
this.removeDomElement(inlineWrapperParent, inlineWrapperParent.parentElement);
}
}
}
};
/* Removes a parent container from the DOM if it is empty and still attached. */
ClipBoardCleanupAction.prototype.removeEmptyContentContainer = function (containerCandidate) {
if (!isNOU(containerCandidate) && (['TD', 'TH'].indexOf(containerCandidate.nodeName) < 0) &&
containerCandidate !== this.editableElement) {
var isContainerEmpty = this.isContentContainerEmpty(containerCandidate);
var isContainerAttached = containerCandidate.parentElement.contains(containerCandidate);
if (isContainerEmpty && isContainerAttached) {
containerCandidate.parentElement.removeChild(containerCandidate);
}
}
};
/* Preserves text outside the selection range in a partially selected text node. */
ClipBoardCleanupAction.prototype.extractUnselectedTextContent = function (textFragment, range) {
var fullText = textFragment.textContent;
var startIndex = 0;
var endIndex = fullText.length;
if (range.startContainer === textFragment) {
startIndex = range.startOffset;
}
if (range.endContainer === textFragment) {
endIndex = range.endOffset;
}
var leadingText = fullText.substring(0, startIndex);
var trailingText = fullText.substring(endIndex);
return leadingText + trailingText;
};
/* Checks whether a given container is visually and semantically empty. */
ClipBoardCleanupAction.prototype.isContentContainerEmpty = function (containerElement) {
return !isNOU(containerElement) && containerElement.nodeName !== '#text' &&
isNOU(containerElement.querySelector('video')) &&
isNOU(containerElement.querySelector('audio')) &&
isNOU(containerElement.querySelector('img')) &&
containerElement.textContent.trim().length === 0;
};
/* Check if the element has more than one BR element */
ClipBoardCleanupAction.prototype.hasMoreThanOneBRElement = function (currentEle) {
var lastChild = !isNOU(currentEle) ? currentEle.lastChild : null;
var previousSibling = !isNOU(lastChild) ? lastChild.previousSibling : null;
return lastChild && lastChild.nodeName === 'BR' &&
(!isNOU(previousSibling) && previousSibling.nodeName === 'BR');
};
/* Removes empty inline text nodes at the start and end of a selection range. */
ClipBoardCleanupAction.prototype.cleanEmptyInlineBoundaries = function (selectionRange) {
var isStartTextNode = selectionRange.startContainer.nodeName === '#text';
var isEndTextNode = selectionRange.endContainer.nodeName === '#text';
var startWrapperEle = selectionRange.startContainer.parentElement;
var endWrapperEle = selectionRange.endContainer.parentElement;
if (isStartTextNode && !this.parent.domNode.isBlockNode(startWrapperEle) &&
this.extractUnselectedTextContent(selectionRange.startContainer, selectionRange) === '') {
this.removeInlineWrapper(startWrapperEle, selectionRange);
}
if (isEndTextNode && !this.parent.domNode.isBlockNode(endWrapperEle) &&
this.extractUnselectedTextContent(selectionRange.endContainer, selectionRange) === '') {
this.removeInlineWrapper(endWrapperEle, selectionRange);
}
};
/* To remove the inline element wrapper around it */
ClipBoardCleanupAction.prototype.removeInlineWrapper = function (wrapperEle, selectionRange) {
var partialRange = selectionRange.cloneRange();
partialRange.setEnd(wrapperEle, wrapperEle.childNodes.length);
partialRange.deleteContents();
if (wrapperEle.textContent.trim() === '' && wrapperEle.childElementCount === 0 &&
!isNOU(wrapperEle.parentElement)) {
while (!this.parent.domNode.isBlockNode(wrapperEle.parentElement) &&
this.isContentContainerEmpty(wrapperEle.parentElement) &&
wrapperEle.parentElement.childElementCount === 1 &&
wrapperEle.parentElement.firstElementChild === wrapperEle) {
wrapperEle = wrapperEle.parentElement;
}
if (!isNOU(wrapperEle.parentElement) && wrapperEle.parentElement.contains(wrapperEle)) {
wrapperEle.parentElement.removeChild(wrapperEle);
}
}
};
/* Updates the DOM structure and cursor position after cutting selected content. */
ClipBoardCleanupAction.prototype.restructureContentPostCut = function (selectionRange, rootParentLIElement, nestedListEndContainer) {
//range End container Element after cut
var rangeEndContainer = selectionRange.startContainer.childNodes[selectionRange.startOffset];
//range Start container Element after cut
var rangeStartContainer = this.isParentLIEmpty(rootParentLIElement) ?
rootParentLIElement : rangeEndContainer.previousSibling;
// check if the start range element is empty
var emptyElementAtStart = this.getStartRangeEmptyElement(this.parent.domNode.getImmediateBlockNode(rangeStartContainer), selectionRange);
// check if the end range element is empty
var emptyElementAtEnd = this.getEndRangeEmptyElement(rangeEndContainer, nestedListEndContainer);
var brElement = document.createElement('br');
if (!isNOU(emptyElementAtStart) && !isNOU(emptyElementAtEnd)) {
// When both start and end range element empty, cursor placement and empty element removal
if (this.isParentLIEmpty(rootParentLIElement)) {
// Place cursor at parent LI element
rootParentLIElement.insertBefore(brElement, rootParentLIElement.firstElementChild);
this.parent.nodeSelection.setCursorPoint(this.parent.currentDocument, rootParentLIElement, 0);
}
else {
emptyElementAtStart.appendChild(brElement);
// to set the cursor position at the last br element if element has n number of br element inside it
this.parent.nodeSelection.setCursorPoint(this.parent.currentDocument, emptyElementAtStart, emptyElementAtStart.childElementCount);
}
this.removeDomElement(emptyElementAtEnd, emptyElementAtEnd.parentElement);
}
else if (!isNOU(emptyElementAtEnd) && !isNOU(rangeStartContainer)) {
// When only end range element empty, cursor placement and empty element removal
if (selectionRange.commonAncestorContainer.nodeName === 'LI') {
// Place cursor at parent LI element
this.setParentLICursorPosition(rangeStartContainer);
}
else {
this.setCursorPosition(this.getDeeptestBlockElement(rangeStartContainer));
}
this.removeDomElement(emptyElementAtEnd, emptyElementAtEnd.parentElement);
}
else if (selectionRange.commonAncestorContainer.nodeName === 'LI') {
// when nested cut selection is done
this.mergeContentWithinSameContainer(rangeStartContainer, rangeEndContainer, nestedListEndContainer);
}
else {
// Across elements cut selection is done
this.mergeContentAcrossBlocks(rangeStartContainer, rangeEndContainer);
}
};
/* To set cursor position at root parent list in nested selection use case */
ClipBoardCleanupAction.prototype.setParentLICursorPosition = function (rangeStartContainer) {
var rootParentLIElement = this.parent.domNode.getImmediateBlockNode(rangeStartContainer);
var brElement = document.createElement('br');
var nearestBlockElement = rootParentLIElement.querySelector('ul') ||
rootParentLIElement.querySelector('ol');
if (rangeStartContainer.nodeName === 'BR' || (rangeStartContainer.nodeName === '#text' &&
rangeStartContainer.textContent.trim() === '' && !isNOU(rangeStartContainer.previousElementSibling) &&
rangeStartContainer.previousElementSibling.nodeName === 'BR')) {
// if parent li has br element as last child
if (rangeStartContainer.nodeName === 'BR') {
rootParentLIElement.insertBefore(brElement, nearestBlockElement);
}
else {
rootParentLIElement.replaceChild(brElement, rangeStartContainer);
}
var cursorOffset = void 0;
var cursorBRElement = nearestBlockElement.previousElementSibling;
for (var index = 0; index < rootParentLIElement.childNodes.length; index++) {
if (rootParentLIElement.childNodes[index] === cursorBRElement) {
cursorOffset = index;
break;
}
}
this.parent.nodeSelection.setCursorPoint(this.parent.currentDocument, rootParentLIElement, cursorOffset);
}
else if (rangeStartContainer === rootParentLIElement) {
rangeStartContainer.insertBefore(brElement, rangeStartContainer.firstElementChild);
this.parent.nodeSelection.setCursorPoint(this.parent.currentDocument, rangeStartContainer, 0);
}
else {
this.setCursorPosition(rangeStartContainer);
}
};
/* To get the range Start container empty element */
ClipBoardCleanupAction.prototype.getStartRangeEmptyElement = function (emptyElement, selectionRange) {
if (!isNOU(emptyElement)) {
var elementToRemove = void 0;
if (selectionRange.commonAncestorContainer.nodeName === 'LI') {
if (this.isParentLIEmpty(selectionRange.commonAncestorContainer)) {
elementToRemove = selectionRange.commonAncestorContainer;
}
else {
return null;
}
}
else {
elementToRemove = this.getDeeptestBlockElement(emptyElement);
elementToRemove = this.isContentContainerEmpty(elementToRemove) ? elementToRemove : null;
}
return elementToRemove;
}
else {
return null;
}
};
/* To get the range end container empty element */
ClipBoardCleanupAction.prototype.getEndRangeEmptyElement = function (emptyElement, nestedListEndContainer) {
if (!isNOU(emptyElement)) {
var elementToRemove = void 0;
if (isNOU(nestedListEndContainer)) {
elementToRemove = this.getFirstBlockElement(emptyElement);
}
else {
elementToRemove = nestedListEndContainer;
}
elementToRemove = this.isContentContainerEmpty(elementToRemove) ? elementToRemove : null;
return elementToRemove;
}
else {
return null;
}
};
/* To get the LI element which has UL as its first child */
ClipBoardCleanupAction.prototype.isParentLIEmpty = function (liElement) {
if (!isNOU(liElement) && liElement.nodeName === 'LI' && !isNOU(liElement.firstElementChild)) {
if (this.parent.domNode.isList(liElement.firstElementChild)) {
var nearestBlockElement = liElement.firstElementChild;
if (isNOU(nearestBlockElement.previousSibling) || (nearestBlockElement.previousSibling.textContent.trim() === '' &&
nearestBlockElement.previousSibling.nodeName === '#text' &&
isNOU(nearestBlockElement.previousSibling.previousElementSibling))) {
return true;
}
}
return false;
}
return false;
};
/* Checks if the current selection range fully covers a single block-level element. */
ClipBoardCleanupAction.prototype.isSelectionCoveringSingleBlock = function (selectionRange, htmlContent) {
var blockContainer;
if (!this.parent.domNode.isBlockNode(selectionRange.commonAncestorContainer)) {
blockContainer = this.parent.domNode.getImmediateBlockNode(selectionRange.commonAncestorContainer);
}
else {
blockContainer = selectionRange.commonAncestorContainer;
}
return blockContainer.innerHTML.trim() === htmlContent.trim();
};
/* Removes a specified DOM element from its container with special handling for <code> elements. */
ClipBoardCleanupAction.prototype.removeDomElement = function (removableElement, containerElement) {
if (!isNOU(removableElement) && !isNOU(containerElement) && removableElement !== this.editableElement) {
if (this.hasMoreThanOneBRElement(removableElement)) {
removableElement.removeChild(removableElement.lastChild);
}
else {
//To get any parent element of container if they are empty
while (this.isContentContainerEmpty(removableElement.parentElement) &&
removableElement.parentElement.childElementCount === 1 &&
removableElement.parentElement !== this.editableElement) {
removableElement = removableElement.parentElement;
containerElement = removableElement.parentElement;
}
if (!isNOU(removableElement.nodeName) && removableElement.nodeName === 'CODE' &&
removableElement.parentElement.nodeName === 'PRE') {
removableElement = containerElement;
containerElement = containerElement.parentElement;
}
if (containerElement.contains(removableElement)) {
containerElement.removeChild(removableElement);
}
}
}
};
/* Extracts and removes inline elements from a block container while preserving their content. */
ClipBoardCleanupAction.prototype.extractInlineContentFromBlock = function (blockContainer) {
var inlineContentWrapper = createElement('div');
var childNodesList = Array.from(blockContainer.childNodes);
for (var index = 0; index < childNodesList.length; index++) {
if (!this.parent.domNode.isBlockNode(childNodesList[index])) {
if (childNodesList[index].nodeName === 'BR') {
// to remove first occuring br tag
this.removeDomElement(childNodesList[index], blockContainer);
break;
}
if (childNodesList[index].nodeName === 'CODE' &&
!isNOU(childNodesList[index].parentElement) &&
childNodesList[index].parentElement.nodeName === 'PRE') {
inlineContentWrapper.innerHTML += childNodesList[index].innerHTML;
}
else {
inlineContentWrapper.innerHTML += childNodesList[index].nodeName === '#text' ?
childNodesList[index].textContent :
childNodesList[index].outerHTML;
}
this.removeDomElement(childNodesList[index], blockContainer);
}
}
if (this.isContentContainerEmpty(blockContainer)) {
this.removeDomElement(blockContainer, blockContainer.parentElement);
}
blockContainer = inlineContentWrapper;
return blockContainer;
};
/* Returns the inner most block element */
ClipBoardCleanupAction.prototype.getDeeptestBlockElement = function (blockContainer) {
if (!isNOU(blockContainer) && this.parent.domNode.isBlockNode(blockContainer)) {
while (!isNOU(blockContainer.lastElementChild) &&
this.parent.domNode.isBlockNode(blockContainer.lastElementChild)) {
blockContainer = blockContainer.lastElementChild;
}
return blockContainer;
}
return null;
};
/* Returns the first occuring block element */
ClipBoardCleanupAction.prototype.getFirstBlockElement = function (blockContainer) {
if (!isNOU(blockContainer) && this.parent.domNode.isBlockNode(blockContainer)) {
while (!isNOU(blockContainer.firstChild) && !isNOU(blockContainer.firstElementChild)) {
if ((blockContainer.firstChild.nodeType === Node.TEXT_NODE &&
blockContainer.firstChild.textContent.trim().length > 0) ||
(blockContainer.firstChild.nodeType !== Node.TEXT_NODE &&
!this.parent.domNode.isBlockNode(blockContainer.firstChild))) {
break;
}
blockContainer = blockContainer.firstElementChild;
}
return blockContainer;
}
return null;
};
/* Merges content between two elements located in different block containers and handles cleanup. */
ClipBoardCleanupAction.prototype.mergeContentAcrossBlocks = function (startBlockElement, endBlockElement) {
// Traverse to the deepest inline element within the start block
if (this.parent.domNode.isBlockNode(startBlockElement)) {
startBlockElement = this.getDeeptestBlockElement(startBlockElement);
}
// Traverse to the shallowest inline element within the end block
if (this.parent.domNode.isBlockNode(endBlockElement)) {
endBlockElement = this.getFirstBlockElement(endBlockElement);
}
// Extract inline content from the end block if needed
if (this.parent.domNode.isBlockNode(endBlockElement)) {
endBlockElement = this.extractInlineContentFromBlock(endBlockElement);
}
if (!isNOU(startBlockElement)) {
if (!isNOU(startBlockElement.lastChild)) {
// Set cursor to the last child of the start block
this.setCursorPosition(startBlockElement.lastChild);
}
else {
this.setCursorPosition(startBlockElement);
}
// Merge content
startBlockElement.insertAdjacentHTML('beforeend', endBlockElement.innerHTML);
}
};
/* Merges content between two elements that share the same parent container and performs cleanup. */
ClipBoardCleanupAction.prototype.mergeContentWithinSameContainer = function (startLiElement, endLiElement, nestedListEndContainer) {
var canMergeListElement = startLiElement.nodeName !== 'LI' && (startLiElement.textContent.trim() !== '' ||
(startLiElement.nodeName === '#text' && !isNOU(startLiElement.previousElementSibling) &&
startLiElement.previousElementSibling.nodeName !== 'BR'));
if (!isNOU(nestedListEndContainer) && this.isParentLI