@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
180 lines (171 loc) • 6.16 kB
JavaScript
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
import { AnnotationTypes } from '@atlaskit/adf-schema';
import { fg } from '@atlaskit/platform-feature-flags';
export var canApplyAnnotationOnRange = function canApplyAnnotationOnRange(rangeSelection, doc, schema) {
var from = rangeSelection.from,
to = rangeSelection.to;
if (isNaN(from + to) || to - from <= 0 || to < 0 || from < 0) {
return false;
}
var foundInvalid = false;
doc.nodesBetween(rangeSelection.from, rangeSelection.to, function (node, _pos, parent) {
// Special exception for hardBreak nodes
if (schema.nodes.hardBreak === node.type) {
return false;
}
// For block elements or text nodes, we want to check
// if annotations are allowed inside this tree
// or if we're leaf and not text
if (fg('editor_inline_comments_on_inline_nodes')) {
var isAllowedInlineNode = ['emoji', 'status', 'date', 'mention', 'inlineCard'].includes(node.type.name);
if (node.isInline && !node.isText && !isAllowedInlineNode || node.isLeaf && !node.isText && !isAllowedInlineNode || node.isText && !(parent !== null && parent !== void 0 && parent.type.allowsMarkType(schema.marks.annotation))) {
foundInvalid = true;
return false;
}
} else {
if (node.isInline && !node.isText || node.isLeaf && !node.isText || node.isText && !(parent !== null && parent !== void 0 && parent.type.allowsMarkType(schema.marks.annotation))) {
foundInvalid = true;
return false;
}
}
return true;
});
return !foundInvalid;
};
export var getAnnotationIdsFromRange = function getAnnotationIdsFromRange(rangeSelection, doc, schema) {
var from = rangeSelection.from,
to = rangeSelection.to;
var annotations = new Set();
doc.nodesBetween(from, to, function (node) {
if (!node.marks) {
return true;
}
node.marks.forEach(function (mark) {
if (mark.type === schema.marks.annotation && mark.attrs) {
annotations.add(mark.attrs.id);
}
});
return true;
});
return Array.from(annotations);
};
/*
* verifies if node contains annotation mark
*/
export function hasAnnotationMark(node, state) {
var annotationMark = state.schema.marks.annotation;
return !!(annotationMark && node && node.marks.length && annotationMark.isInSet(node.marks));
}
/*
* verifies that slice contains any annotations
*/
export function containsAnyAnnotations(slice, state) {
if (!slice.content.size) {
return false;
}
var hasAnnotation = false;
slice.content.forEach(function (node) {
hasAnnotation = hasAnnotation || hasAnnotationMark(node, state);
// return early if annotation found already
if (hasAnnotation) {
return true;
}
// check annotations in descendants
node.descendants(function (node) {
if (hasAnnotationMark(node, state)) {
hasAnnotation = true;
return false;
}
return true;
});
});
return hasAnnotation;
}
/**
* This returns a list of node names that are inline nodes in the range.
*/
export function getRangeInlineNodeNames(_ref) {
var doc = _ref.doc,
pos = _ref.pos;
if (!fg('editor_inline_comments_on_inline_nodes')) {
return undefined;
}
var nodeNames = new Set();
try {
doc.nodesBetween(pos.from, pos.to, function (node) {
if (node.isInline) {
nodeNames.add(node.type.name);
}
});
// We sort the list alphabetically to make human consumption of the list easier (in tools like the analytics extension)
var sortedNames = _toConsumableArray(nodeNames).sort();
return sortedNames;
} catch (_unused) {
// Some callers have existing gaps where the positions are not valid,
// and so when called in the current document can throw an error if out of range.
//
// This is a defensive check to ensure we don't throw an error in those cases.
return undefined;
}
}
/**
* This function returns a list of node types that are wrapped by an annotation mark.
*
* The `undefined` will be returned if `editor_inline_comments_on_inline_nodes` is off.
*
* @todo: Do not forget to remove `undefined` when the
* `editor_inline_comments_on_inline_nodes` is removed.
*/
export function getAnnotationInlineNodeTypes(state, annotationId) {
if (!fg('editor_inline_comments_on_inline_nodes')) {
return undefined;
}
var mark = state.schema.marks.annotation.create({
id: annotationId,
annotationType: AnnotationTypes.INLINE_COMMENT
});
var inlineNodeNames = new Set();
state.doc.descendants(function (node, pos) {
if (mark.isInSet(node.marks)) {
inlineNodeNames.add(node.type.name);
}
return true;
});
// This sorting is done to make human consumption easier (ie. in dev tools, test snapshots, analytics events, ...)
return _toConsumableArray(inlineNodeNames).sort();
}
/*
Get the annotations marks from the given position and add them to the original marks array if they exist.
Used with the creation of the inline nodes: emoji, status, dates, mentions & inlineCards.
*/
export function getAnnotationMarksForPos(pos) {
if (!fg('editor_inline_comments_paste_insert_nodes')) {
return undefined;
}
var annotationMarks = pos.marks().filter(function (mark) {
return mark.type === pos.doc.type.schema.marks.annotation;
});
return annotationMarks;
}
/**
* Checks if selection contains only empty text
* e.g. when you select across multiple empty paragraphs
*/
export function isEmptyTextSelection(selection, schema) {
var _schema$nodes = schema.nodes,
text = _schema$nodes.text,
paragraph = _schema$nodes.paragraph;
var hasContent = false;
selection.content().content.descendants(function (node) {
// for empty paragraph - consider empty (nothing to comment on)
if (node.type === paragraph && !node.content.size) {
return false;
}
// for not a text or nonempty text - consider nonempty (can comment if the node is supported for annotations)
if (node.type !== text || !node.textContent) {
hasContent = true;
}
return !hasContent;
});
return !hasContent;
}