export function isElement(o: any): boolean { return typeof HTMLElement === 'object' ? o instanceof HTMLElement : o && typeof o === 'object' && o !== null && o.nodeType === 1 && typeof o.nodeName === 'string'; } export function getMostOuterContainer(node: Node, rootElement: Node): Node { if (!(node instanceof Node) || !(rootElement instanceof Node)) { throw new Error('The given argument must be an instance of Node!'); } if (node.parentNode && node.parentNode !== rootElement) { return getMostOuterContainer(node.parentNode, rootElement); } return node; } export function unwrap(node: Node): boolean { if (!(node instanceof Node)) { throw new Error('The given argument must be an instance of Node!'); } // get the element's parent node const parent = node.parentNode; if (parent) { // move all children out of the element while (node.firstChild) { parent.insertBefore(node.firstChild, node); } // remove the empty element parent.removeChild(node); return true; } return false; } export function getFirstEndNode(node: Node): Node { if (!(node instanceof Node)) { throw new Error('The given argument must be an instance of Node!'); } return node.childNodes.length > 0 ? getFirstEndNode(node.childNodes[0]) : node; } export function getLastEndNode(node: Node): Node { if (!(node instanceof Node)) { throw new Error('The given argument must be an instance of Node!'); } const { lastChild } = node; return lastChild ? getLastEndNode(lastChild) : node; } export function getNextNode(node: Node, rootNode: Node): Node | false { if (!(node instanceof Node) || !(rootNode instanceof Node)) { throw new Error('The given argument must be an instance of Node!'); } if (!node || node === rootNode) { return false; } if (node.nextSibling) { return getFirstEndNode(node.nextSibling); } if (node.parentNode) { return getNextNode(node.parentNode, rootNode); } return false; } export const getClosestAttribute = ( node: Node & ParentNode, attributeName: string, defaultValue: any = '' ): any => (node).hasAttribute && (node).hasAttribute(attributeName) ? (node).getAttribute(attributeName) : node.parentNode ? getClosestAttribute(node.parentNode, attributeName) : defaultValue; export const placeCaretAtEnd = (el: HTMLElement) => { el.focus(); if (typeof window.getSelection !== 'undefined' && typeof document.createRange !== 'undefined') { const range = document.createRange(); range.selectNodeContents(el); range.collapse(false); const sel = window.getSelection(); if (sel) { sel.removeAllRanges(); sel.addRange(range); } } }; export const updatePastedTextNode = (rootNode: HTMLElement, disableLinebreaks: boolean) => { const divs = rootNode.childNodes; if (disableLinebreaks) { removeLinebreaks(rootNode); } else { for (const child of divs) { // get the text nodes and wrap it in Div and span in order to apply styles const text = child.textContent || ''; if (child.nodeType === Node.TEXT_NODE) { if ((text.match(/\n/g) || []).length) { // To support multi line const textElements = text.split('\n'); for (const element of textElements) { const textWrapper = document.createElement('span'); const wrapperDiv = document.createElement('div'); textWrapper.innerText = element.trim(); wrapperDiv.appendChild(textWrapper); child.parentNode && child.parentNode.insertBefore(wrapperDiv, child); placeCaretAtEnd(textWrapper); } } else { updateTextWrapper(child); } } } } }; export const removeLinebreaks = (rootNode: HTMLElement) => { const divs = rootNode.childNodes; divs.forEach((child, key) => { if (key !== 0) { if (rootNode.firstChild) { const parentText = rootNode.firstChild.textContent || ''; const childText = child.textContent || ''; rootNode.firstChild.textContent = `${parentText} ${childText}`; } rootNode.removeChild(child); } }); if (rootNode && rootNode.textContent) { rootNode.textContent = rootNode.textContent.split('\n').join(' '); } }; export const updateTextWrapper = (child: ChildNode) => { const textWrapper = document.createElement('span'); const wrapperDiv = document.createElement('div'); textWrapper.innerText = child.textContent ? child.textContent.trim() : ''; wrapperDiv.appendChild(textWrapper); child.replaceWith(wrapperDiv); placeCaretAtEnd(wrapperDiv); }; export const traverse = (node: Node, cb: (node: Node) => any): void => { cb(node); for (let i = 0; i < node.childNodes.length; i++) { traverse(node.childNodes[i], cb); } }; export function fixContentEditableDOM(rootElement: HTMLElement, node?: HTMLElement): void { const currentNode = node || rootElement; if (!(currentNode instanceof HTMLElement) || !(rootElement instanceof HTMLElement)) { throw new Error('The given argument must be an instance of HTMLElement!'); } for (const child of currentNode.children) { if (!(child instanceof HTMLElement)) { continue; } if (child.nodeName === 'SPAN') { if (child.style.fontSize === '1rem') { child.style.fontSize = ''; if (child.style.length === 0) { unwrap(child); } } } if ( rootElement.childElementCount === 1 && rootElement.firstChild && rootElement.firstChild.nodeName === 'DIV' && rootElement.firstChild.textContent === '' ) { rootElement.firstChild.remove(); } if (child.parentNode !== rootElement) { if (child.nodeName === 'DIV' && child.style.length === 0) { unwrap(child); } } fixContentEditableDOM(rootElement, child); } }