@atlaskit/renderer
Version:
Renderer component
255 lines (249 loc) • 8.86 kB
JavaScript
/* 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
}));
}