@syncfusion/ej2-richtexteditor
Version:
Essential JS 2 RichTextEditor component
318 lines (317 loc) • 14 kB
JavaScript
import { isNullOrUndefined } from '@syncfusion/ej2-base';
/**
* DOMTreeMethods - A `TreeWalkder` API implementation to get the block and text nodes in the selection.
*/
var DOMMethods = /** @class */ (function () {
function DOMMethods(editElement) {
this.directRangeElems = ['IMG', 'TABLE', 'AUDIO', 'VIDEO', 'HR'];
this.BLOCK_TAGS = ['address', 'article', 'aside', 'audio', 'blockquote',
'canvas', 'details', 'dd', 'div', 'dl', 'dt', 'fieldset', 'figcaption', 'figure', 'footer',
'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'li', 'main', 'nav',
'noscript', 'output', 'p', 'pre', 'section', 'td', 'tfoot', 'th',
'video', 'body'];
this.editableElement = editElement;
this.currentDocument = editElement.ownerDocument;
}
/**
* Method to get the block nodes inside the given Block node `TreeWalker` API.
*
* @returns {HTMLElement[]} The block node element.
*
*
*/
DOMMethods.prototype.getBlockNode = function () {
var _this = this;
var blockCollection = [];
var selection = this.currentDocument.getSelection();
var range = selection.getRangeAt(0);
// To find the direct range.
var directRange = range.startContainer === this.editableElement && range.startContainer === range.endContainer &&
range.startContainer.nodeName !== '#text';
if (directRange) {
if (range.startOffset === range.endOffset) {
var isDirectRangeElems = this.editableElement.childNodes[range.startOffset] &&
this.directRangeElems.indexOf(this.editableElement.childNodes[range.startOffset].nodeName) > -1;
if (isDirectRangeElems) {
blockCollection.push(this.editableElement.childNodes[range.startOffset]);
}
}
else {
var isElementRange = range.endOffset === range.startOffset + 1;
if (isElementRange) {
blockCollection.push(this.editableElement.childNodes[range.startOffset]);
}
}
if (blockCollection.length > 0) {
return blockCollection;
}
}
else {
var start = range.startContainer.nodeType === Node.TEXT_NODE ?
range.startContainer.parentElement : range.startContainer;
var end = range.endContainer.nodeType === Node.TEXT_NODE ?
range.endContainer.parentElement : range.endContainer;
var endBlockNode = this.isBlockNode(end) ? end : this.getParentBlockNode(end);
var blockNodeWalker = this.currentDocument.createTreeWalker(this.editableElement, NodeFilter.SHOW_ELEMENT, {
acceptNode: function (node) {
if (!range.intersectsNode(node)) {
return NodeFilter.FILTER_REJECT;
}
return _this.isBlockNode(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
}
});
blockNodeWalker.currentNode = start;
while (blockNodeWalker.currentNode) {
if (this.isBlockNode(blockNodeWalker.currentNode)) {
this.addToBlockCollection(blockCollection, blockNodeWalker, range);
blockNodeWalker.nextNode();
}
else {
blockNodeWalker.previousNode();
}
if (blockNodeWalker.currentNode === end || blockNodeWalker.currentNode === endBlockNode) {
this.addToBlockCollection(blockCollection, blockNodeWalker, range);
break;
}
}
}
return blockCollection;
};
DOMMethods.prototype.addToBlockCollection = function (blockCollection, blockNodeWalker, range) {
var currentNode = blockNodeWalker.currentNode;
if (blockNodeWalker.currentNode && blockCollection.indexOf(blockNodeWalker.currentNode) === -1) {
if (currentNode.nodeName === 'LI') {
var isDirectChild = !currentNode.parentNode.closest('li');
if (isDirectChild) {
blockCollection.push(blockNodeWalker.currentNode);
}
else {
var commonAncestor = range.commonAncestorContainer;
var parentLI = currentNode.parentElement.closest('li');
var isNestedLI = currentNode.nodeName === 'LI' && !!parentLI;
var ancestorElement = commonAncestor.nodeType === 3 ? commonAncestor.parentElement : commonAncestor;
var isListAncestor = !!ancestorElement.closest('ul,ol');
if (isNestedLI && isListAncestor) {
blockCollection.push(blockNodeWalker.currentNode);
}
else {
return;
}
}
}
else {
blockCollection.push(blockNodeWalker.currentNode);
}
}
};
/**
* Method to get the text nodes inside the given Block node `TreeWalker` API.
*
* @param {HTMLElement} blockElem - specifies the parent block element.
* @returns {Text[]} The Text Nodes.
*
*
*/
DOMMethods.prototype.getTextNodes = function (blockElem) {
var nodeCollection = [];
var selection = this.currentDocument.getSelection();
var range = selection.getRangeAt(0);
var textNodeWalker = this.currentDocument.createTreeWalker(blockElem, NodeFilter.SHOW_TEXT, {
acceptNode: function (node) {
if (!range.intersectsNode(node)) {
return NodeFilter.FILTER_REJECT;
}
return NodeFilter.FILTER_ACCEPT;
}
});
var textNode = textNodeWalker.nextNode();
while (textNode) {
nodeCollection.push(textNode);
textNode = textNodeWalker.nextNode();
}
return nodeCollection;
};
/**
* isBlockNode method
*
* @param {Element} element - specifies the node element.
* @returns {boolean} - sepcifies the boolean value
* @hidden
*/
DOMMethods.prototype.isBlockNode = function (element) {
return (!!element && (element.nodeType === Node.ELEMENT_NODE && this.BLOCK_TAGS.indexOf(element.tagName.toLowerCase()) >= 0));
};
/**
* Retrieves the last text node within the provided node and its descendants.
*
* This method uses a TreeWalker to traverse all text nodes in the given node's subtree,
* and returns the last text node found.
*
* @param {Node} node - The root node from which to begin searching for text nodes.
* @returns {Node | null} - The last text node within the node, or null if no text nodes are found.
*/
DOMMethods.prototype.getLastTextNode = function (node) {
var treeWalker = this.currentDocument.createTreeWalker(node, NodeFilter.SHOW_TEXT, null);
var lastTextNode = null;
var currentNode = treeWalker.nextNode();
while (currentNode) {
lastTextNode = currentNode;
currentNode = treeWalker.nextNode();
}
return lastTextNode;
};
/**
* Retrieves the first text node within the provided node and its descendants.
*
* This method uses a TreeWalker to traverse all text nodes in the given node's subtree,
* and returns the first text node found.
*
* @param {Node} node - The root node from which to begin searching for text nodes.
* @returns {Node | null} - The first text node within the node, or null if no text nodes are found.
*/
DOMMethods.prototype.getFirstTextNode = function (node) {
var treeWalker = this.currentDocument.createTreeWalker(node, NodeFilter.SHOW_TEXT, null);
var firstTextNode = treeWalker.nextNode();
return firstTextNode;
};
/**
* Retrieves the parent block node of the given inline node.
*
* This method uses a TreeWalker to traverse the DOM tree and find the nearest ancestor of the given node
* that is a block element.
*
* @param {Node} node - The node for which to find the parent block node.
* @returns {Node} - The parent block node of the given node.
* @hidden
*/
DOMMethods.prototype.getParentBlockNode = function (node) {
var _this = this;
var treeWalker = this.currentDocument.createTreeWalker(this.editableElement, // root
NodeFilter.SHOW_ELEMENT, // whatToShow
{
acceptNode: function (currentNode) {
// Check if the node is a block element
return _this.isBlockNode(currentNode) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
}
});
treeWalker.currentNode = node;
var blockParent = treeWalker.parentNode();
return blockParent;
};
/**
* Retrieves the top-most node in the DOM that is not a block-level element.
* If the given text node is part of a block element, it returns the text node itself.
* Otherwise, it traverses upwards through its parent nodes until it finds a node
* that is either a block-level node or a node that contains different text content than the provided `text`.
*
* @param {Text} text - The text node from which to start the search. This can be a child of an inline element.
* @returns {HTMLElement | Text} - The top-most parent element that is not a block node, or the text node itself if it's inside a block-level element.
* @hidden
*
*/
DOMMethods.prototype.getTopMostNode = function (text) {
if (this.isBlockNode(text.parentNode)) {
return text;
}
var parent = text.parentNode;
while (parent) {
if (!this.isBlockNode(parent.parentNode) && text.textContent === parent.textContent) {
parent = parent.parentNode;
}
else {
return parent;
}
}
return parent;
};
/**
* Finds the nearest parent `<li>` element of a given node until the search reaches the editable element.
*
* @private
* @param {Node | null} node - The starting node from which to search for the parent `<li>` element.
* @returns {HTMLLIElement | null} - The nearest parent `<li>` element if found, otherwise `null`.
*/
DOMMethods.prototype.findParentLiElementUntilEditable = function (node) {
var currentNode = node;
while (currentNode && currentNode !== this.editableElement) {
if (currentNode.nodeType === Node.ELEMENT_NODE) {
var element = currentNode;
if (element.tagName === 'LI') {
return element;
}
}
currentNode = currentNode.parentNode;
}
return null;
};
/**
* Retrieves all `<li>` elements that are part of the current selection range.
* This includes `<li>` elements that are fully or partially selected.
*
* @private
* @returns {HTMLLIElement[]} - An array of selected `<li>` elements.
*/
DOMMethods.prototype.getSelectedLiElements = function () {
var selection = this.currentDocument.getSelection();
var range = selection.getRangeAt(0);
var nodeInRange = function (rng, node) {
var nodeRange = rng.cloneRange();
nodeRange.selectNodeContents(node);
return (rng.compareBoundaryPoints(Range.END_TO_START, nodeRange) < 0 &&
rng.compareBoundaryPoints(Range.START_TO_END, nodeRange) > 0);
};
var walker = this.currentDocument.createTreeWalker(range.commonAncestorContainer, NodeFilter.SHOW_ALL, {
acceptNode: function (node) {
return nodeInRange(range, node)
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_REJECT;
}
});
var liElements = new Set();
var startLi = this.findParentLiElementUntilEditable(walker.currentNode);
if (startLi) {
liElements.add(startLi);
}
while (walker.nextNode()) {
var li = this.findParentLiElementUntilEditable(walker.currentNode);
if (li) {
liElements.add(li);
}
}
return Array.from(liElements);
};
/**
* Retrieves all `<li>` elements within the current selection range,
* including nested `<li>` elements if they are fully or partially selected.
*
* @public
* @returns {HTMLElement[]} - An array of `<li>` elements found within the selection range.
*/
DOMMethods.prototype.getLiElementsInRange = function () {
var liElements = [];
var blockNodes = this.getSelectedLiElements();
var _loop_1 = function (i) {
var li = this_1.findParentLiElementUntilEditable(blockNodes[i]);
if (li && li.querySelectorAll('li').length > 0) {
var selection = this_1.currentDocument.getSelection();
var range_1 = selection.getRangeAt(0);
var liList = li.querySelectorAll('li');
liList.forEach(function (li) {
if (range_1.intersectsNode(li) && liElements.indexOf(li) === -1) {
liElements.push(li);
}
});
}
if (!isNullOrUndefined(li) && liElements.indexOf(li) === -1) {
liElements.push(li);
}
};
var this_1 = this;
for (var i = 0; i < blockNodes.length; i++) {
_loop_1(i);
}
return liElements;
};
return DOMMethods;
}());
export { DOMMethods };