UNPKG

@atlaskit/renderer

Version:
255 lines (249 loc) • 8.86 kB
/* eslint-disable jsdoc/require-jsdoc -- internal step helpers */ import { fg } from '@atlaskit/platform-feature-flags'; import { AddMarkStep } from '@atlaskit/editor-prosemirror/transform'; export function getStartPos(element) { return parseInt(element.dataset.rendererStartPos || '-1', 10); } var getNodeType = function getNodeType(element) { return element.dataset.nodeType; }; function isPositionPointer(element) { return getStartPos(element) > -1; } export function findParent(element) { var parentElement = element.parentElement; if (!parentElement || isRendererRoot(parentElement)) { return null; } if (isPositionPointer(parentElement)) { return parentElement; } return findParent(parentElement); } function findMediaParent(element) { var parentElement = element.parentElement; if (!parentElement || isRendererRoot(parentElement)) { return null; } if (parentElement.dataset.nodeType === 'mediaSingle') { return parentElement; } return findMediaParent(parentElement); } function findParentBeforePointer(element) { var parentElement = element.parentElement; if (isRendererRoot(parentElement) || !parentElement) { return null; } if (isPositionPointer(parentElement)) { return element; } return findParentBeforePointer(parentElement); } function isElementNode(node) { return node.nodeType === Node.ELEMENT_NODE; } function isTextNode(node) { return node.nodeType === Node.TEXT_NODE; } function isHighlightTextNode(node) { var _dataset; // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting return !!(node !== null && node !== void 0 && (_dataset = node.dataset) !== null && _dataset !== void 0 && _dataset.highlighted); } function isNodeInlineMark(node) { return isElementNode(node) && Boolean(node.dataset.rendererMark); } function isElementInlineMark(element) { return !!element && Boolean(element.dataset.rendererMark); } function isNodeInlineTextMark(node) { if (node === null) { return false; } var isInlineMark = isElementNode(node) && Boolean(node.dataset.rendererMark); if (!isInlineMark) { return false; } /** * This checks if the element has any descendents with the data-inline-card * attribute set to 'true'. If it does, we should not consider it as an * inline text mark. **/ if (hasInlineNodeDescendant(node)) { return false; } return true; } /** * This checks all the descendents of a node and returns true if it reaches * a descendant with the data-inline-card attribute set to 'true'. */ function hasInlineNodeDescendant(node) { if (isElementNode(node)) { if (fg('editor_inline_comments_on_inline_nodes')) { if (node.dataset.annotationInlineNode === 'true') { return true; } } else { if (node.dataset.inlineCard === 'true') { return true; } } return Array.from(node.childNodes).some(hasInlineNodeDescendant); } return false; } function resolveNodePos(node) { var resolvedPos = 0; var prev = node.previousSibling; while (prev) { if (prev.nodeType === Node.COMMENT_NODE) { // Comment nodes should not result in the position being // incremented, so we skip them. prev = prev.previousSibling; continue; } if (prev && (isTextNode(prev) || isHighlightTextNode(prev))) { resolvedPos += (prev.textContent || '').length; } else if (prev) { if (fg('editor_inline_comments_on_inline_nodes')) { if (isNodeInlineTextMark(prev) && prev.textContent) { resolvedPos += prev.textContent.length; } else { resolvedPos += 1; } } else { if (isNodeInlineMark(prev) && prev.textContent) { resolvedPos += prev.textContent.length; } else { resolvedPos += 1; } } } prev = prev.previousSibling; } return resolvedPos; } export function isRendererRoot(element) { return !!element && element.classList.contains('ak-renderer-document'); } export function resolvePos(node, offset) { var findEnd = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; // If the passed node doesn't exist, we should abort if (!node) { return false; } if (fg('editor_inline_comments_on_inline_nodes')) { var _node$parentElement; var startPosAncestor = (_node$parentElement = node.parentElement) === null || _node$parentElement === void 0 ? void 0 : _node$parentElement.closest('[data-renderer-start-pos'); var potentialParent = fg('editor_inline_comments_on_inline_nodes') ? 'data-annotation-mark' : 'data-inline-card'; if (startPosAncestor !== null && startPosAncestor !== void 0 && startPosAncestor.hasAttribute(potentialParent)) { if (findEnd) { return parseInt((startPosAncestor === null || startPosAncestor === void 0 ? void 0 : startPosAncestor.getAttribute('data-renderer-start-pos')) || '-1', 10) + 1; } else { return parseInt((startPosAncestor === null || startPosAncestor === void 0 ? void 0 : startPosAncestor.getAttribute('data-renderer-start-pos')) || '-1', 10); } } } /** * If anchor node is an element (e.g. after a triple click) the anchorOffset * is the number of child nodes preceding the anchor. * https://developer.mozilla.org/en-US/docs/Web/API/Selection/anchorOffset * * For end pos we calculate the number of characters by stepping through * each of the node's children. */ if (node instanceof HTMLElement && isPositionPointer(node)) { if (findEnd) { var count = getStartPos(node); var children = node.childNodes; for (var i = 0; i < offset; i++) { var _children$i; count += ((_children$i = children[i]) === null || _children$i === void 0 || (_children$i = _children$i.textContent) === null || _children$i === void 0 ? void 0 : _children$i.length) || 1; } return count; } return getStartPos(node) + offset; } var parent = findParent(node); // Similar to above, if we cant find a parent position pointer // we should not proceed. if (!parent) { return false; } var resolvedPos = getStartPos(parent); var current = node; if (current.parentElement && current.parentElement !== parent) { // Find the parent element that is a direct child of the position pointer // the outer most element from our text position. var preParentPointer = findParentBeforePointer(current.parentElement); // If our range is inside an inline node // We need to move our pointers to parent element // since we don't want to count text inside inline nodes at all if (fg('editor_inline_comments_on_inline_nodes')) { if (!(isNodeInlineTextMark(preParentPointer) || isHighlightTextNode(preParentPointer))) { current = current.parentElement; offset = 0; } } else { if (!(isElementInlineMark(preParentPointer) || isHighlightTextNode(preParentPointer))) { current = current.parentElement; offset = 0; } } resolvedPos += resolveNodePos(current); while (current && current.parentElement !== parent) { current = current.parentNode; if (current) { var nodePos = resolveNodePos(current); resolvedPos += nodePos; } } } else { resolvedPos += resolveNodePos(current); } return resolvedPos + offset; } export function getPosFromRange(range) { var startContainer = range.startContainer, startOffset = range.startOffset, endContainer = range.endContainer, endOffset = range.endOffset; var possibleMediaOrMediaSingleElement = findParent(startContainer); // Video hover targets return media single, not media, thus, the extra check in condition. var isMediaOrMediaSingle = possibleMediaOrMediaSingleElement && // Ignored via go/ees005 // eslint-disable-next-line require-unicode-regexp /media|mediaSingle/.test(getNodeType(possibleMediaOrMediaSingleElement) || ''); if (isMediaOrMediaSingle) { var pos; var mediaSingleElement = getNodeType(possibleMediaOrMediaSingleElement) === 'mediaSingle' ? possibleMediaOrMediaSingleElement : findMediaParent(possibleMediaOrMediaSingleElement); if (mediaSingleElement) { pos = getStartPos(mediaSingleElement); } if (pos !== undefined) { return { from: pos, to: pos }; } } var from = resolvePos(startContainer, startOffset); var findEnd = true; var to = resolvePos(endContainer, endOffset, findEnd); if (from === false || to === false) { return false; } return { from: from, to: to }; } export function createAnnotationStep(from, to, opts) { return new AddMarkStep(Math.min(from, to), Math.max(from, to), opts.schema.marks.annotation.create({ id: opts.annotationId, annotationType: opts.annotationType })); }