import DOMUtils from 'tinymce/core/api/dom/DOMUtils'; interface Bookmark { startContainer: Node; startOffset: number; endContainer?: Node; endOffset?: number; } /** * Returns a range bookmark. This will convert indexed bookmarks into temporary span elements with * index 0 so that they can be restored properly after the DOM has been modified. Text bookmarks will not have spans * added to them since they can be restored after a dom operation. * * So this:

||

* becomes:

||

* * @param {DOMRange} rng DOM Range to get bookmark on. * @return {Object} Bookmark object. */ const create = (dom: DOMUtils, rng: Range): Bookmark => { const bookmark: Partial = {}; const setupEndPoint = (start?: boolean) => { let container = rng[start ? 'startContainer' : 'endContainer']; let offset = rng[start ? 'startOffset' : 'endOffset']; if (container.nodeType === 1) { const offsetNode = dom.create('span', { 'data-mce-type': 'bookmark' }); if (container.hasChildNodes()) { offset = Math.min(offset, container.childNodes.length - 1); if (start) { container.insertBefore(offsetNode, container.childNodes[offset]); } else { dom.insertAfter(offsetNode, container.childNodes[offset]); } } else { container.appendChild(offsetNode); } container = offsetNode; offset = 0; } bookmark[start ? 'startContainer' : 'endContainer'] = container; bookmark[start ? 'startOffset' : 'endOffset'] = offset; }; setupEndPoint(true); if (!rng.collapsed) { setupEndPoint(); } return bookmark as Bookmark; }; /** * Moves the selection to the current bookmark and removes any selection container wrappers. * * @param {Object} bookmark Bookmark object to move selection to. */ const resolve = (dom: DOMUtils, bookmark: Bookmark): Range => { const restoreEndPoint = (start?: boolean) => { const nodeIndex = (container: Node) => { let node = container.parentNode?.firstChild; let idx = 0; while (node) { if (node === container) { return idx; } // Skip data-mce-type=bookmark nodes if (node.nodeType !== 1 || (node as Element).getAttribute('data-mce-type') !== 'bookmark') { idx++; } node = node.nextSibling; } return -1; }; let container = bookmark[start ? 'startContainer' : 'endContainer']; let offset = bookmark[start ? 'startOffset' : 'endOffset']; if (!container) { return; } if (container.nodeType === 1 && container.parentNode) { const node = container; offset = nodeIndex(container); container = container.parentNode; dom.remove(node); } bookmark[start ? 'startContainer' : 'endContainer'] = container; bookmark[start ? 'startOffset' : 'endOffset'] = offset as number; }; restoreEndPoint(true); restoreEndPoint(); const rng = dom.createRng(); rng.setStart(bookmark.startContainer, bookmark.startOffset); if (bookmark.endContainer) { rng.setEnd(bookmark.endContainer, bookmark.endOffset as number); } return rng; }; export { create, resolve };