@atlaskit/renderer
Version:
Renderer component
399 lines (391 loc) • 14 kB
JavaScript
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
import _createClass from "@babel/runtime/helpers/createClass";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import { AnnotationTypes } from '@atlaskit/adf-schema';
import { canApplyAnnotationOnRange, getAnnotationIdsFromRange, getAnnotationInlineNodeTypes, isEmptyTextSelectionRenderer } from '@atlaskit/editor-common/utils';
import { JSONTransformer } from '@atlaskit/editor-json-transformer';
import { TextSelection } from '@atlaskit/editor-prosemirror/state';
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
import { AddNodeMarkStep, RemoveMarkStep, RemoveNodeMarkStep } from '@atlaskit/editor-prosemirror/transform';
import { fg } from '@atlaskit/platform-feature-flags';
import { createAnnotationStep, getPosFromRange } from '../steps';
import { getRendererRangeInlineNodeNames, getRendererRangeAncestorNodeNames } from './get-renderer-range-inline-node-names';
import { getIndexMatch } from './matches-utils';
import { getSelectionContext as _getSelectionContext } from './selection';
var RendererActions = /*#__PURE__*/function () {
// Any kind of refence is allowed
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function RendererActions() {
var initFromContext = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
_classCallCheck(this, RendererActions);
// This is our psuedo feature flag for now
// This module can only be used when wrapped with
// the <RendererContext> component for now.
_defineProperty(this, "initFromContext", false);
this.initFromContext = initFromContext;
this.transformer = new JSONTransformer();
}
//#region private
return _createClass(RendererActions, [{
key: "_privateRegisterRenderer",
value: function _privateRegisterRenderer(ref, doc, schema, onAnalyticsEvent) {
if (!this.initFromContext) {
return;
} else if (!this.ref) {
this.ref = ref;
} else if (this.ref !== ref) {
throw new Error("Renderer has already been registered! It's not allowed to re-register with another new Renderer instance.");
}
this.doc = doc;
this.schema = schema;
this.onAnalyticsEvent = onAnalyticsEvent;
}
}, {
key: "_privateUnregisterRenderer",
value: function _privateUnregisterRenderer() {
this.doc = undefined;
this.ref = undefined;
this.schema = undefined;
}
/**
* Validate whether we can create an annotation between two positions
*/
}, {
key: "_privateValidatePositionsForAnnotation",
value: function _privateValidatePositionsForAnnotation(from, to) {
if (!this.doc || !this.schema) {
return false;
}
var currentSelection = TextSelection.create(this.doc, from, to);
if (isEmptyTextSelectionRenderer(currentSelection, this.schema)) {
return false;
}
var result = canApplyAnnotationOnRange({
from: from,
to: to
}, this.doc, this.schema);
return result;
}
//#endregion
}, {
key: "deleteAnnotation",
value: function deleteAnnotation(annotationId, annotationType) {
if (!this.doc || !this.schema || !this.schema.marks.annotation) {
return false;
}
var mark = this.schema.marks.annotation.create({
id: annotationId,
annotationType: annotationType
});
var from;
var to;
var nodePos;
var step;
this.doc.descendants(function (node, pos) {
var found = mark.isInSet(node.marks);
if (found && node.type.name === 'media') {
nodePos = pos;
}
if (found && !from) {
// Set both here incase it only spans one node.
from = pos;
to = pos + node.nodeSize;
} else if (found && from) {
// If the mark spans multiple nodes,
// we'll keep setting the end until no longer found.
to = pos + node.nodeSize;
}
return true;
});
if (nodePos !== undefined) {
step = new RemoveNodeMarkStep(nodePos, mark);
} else {
if (from === undefined || to === undefined) {
return false;
}
step = new RemoveMarkStep(from, to, mark);
}
var _step$apply = step.apply(this.doc),
doc = _step$apply.doc,
failed = _step$apply.failed;
if (this.onAnalyticsEvent) {
var payload = {
action: ACTION.DELETED,
actionSubject: ACTION_SUBJECT.ANNOTATION,
actionSubjectId: ACTION_SUBJECT_ID.INLINE_COMMENT,
eventType: EVENT_TYPE.TRACK,
attributes: {
inlineNodeNames: step instanceof RemoveMarkStep ? getRendererRangeInlineNodeNames({
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
pos: {
from: from,
to: to
},
actions: this
}) : undefined
}
};
this.onAnalyticsEvent(payload);
}
if (!failed && doc) {
return {
step: step,
doc: this.transformer.encode(doc)
};
}
return false;
}
}, {
key: "annotate",
value: function annotate(range, annotationId, _annotationType) {
if (!this.doc || !this.schema || !this.schema.marks.annotation) {
return false;
}
var pos = getPosFromRange(range);
if (!pos) {
return false;
}
var from = pos.from,
to = pos.to;
var validPositions = this._privateValidatePositionsForAnnotation(from, to);
if (!validPositions) {
return false;
}
return this.applyAnnotation(pos, {
annotationId: annotationId,
annotationType: AnnotationTypes.INLINE_COMMENT
});
}
}, {
key: "isValidAnnotationRange",
value: function isValidAnnotationRange(range) {
if (!range) {
return false;
}
if (fg('editor_inline_comments_on_inline_nodes')) {
if (this.isRendererWithinRange(range)) {
return false;
}
}
var pos = getPosFromRange(range);
if (!pos || !this.doc) {
return false;
}
return this._privateValidatePositionsForAnnotation(pos.from, pos.to);
}
}, {
key: "isRangeAnnotatable",
value: function isRangeAnnotatable(range) {
try {
var _startContainer$paren, _endContainer$parentE;
if (!range) {
return false;
}
var startContainer = range.startContainer,
endContainer = range.endContainer;
if ((_startContainer$paren = startContainer.parentElement) !== null && _startContainer$paren !== void 0 && _startContainer$paren.closest('.ak-renderer-extension') || (_endContainer$parentE = endContainer.parentElement) !== null && _endContainer$parentE !== void 0 && _endContainer$parentE.closest('.ak-renderer-extension')) {
return false;
}
return this.isValidAnnotationRange(range);
} catch (_unused) {
// isValidAnnotationRange can fail when called inside nested renderers.
// while isRendererWithinRange guards against this to some degree -- the classnames
// are controlled by product -- and we don't have platform guarantees on them.
//
// Currently there is a mix of logic across the platform and confluence on determining
// positions that are annotatable. This is a defensive check to ensure we don't throw an error
// in cases where the range is not valid.
return false;
}
}
// eslint-disable-next-line @repo/internal/deprecations/deprecation-ticket-required -- Ignored via go/ED-25883
/**
* This is replaced by `isRangeAnnotatable`.
*
* @deprecated
**/
}, {
key: "isRendererWithinRange",
value: function isRendererWithinRange(range) {
var startContainer = range.startContainer,
endContainer = range.endContainer;
if (startContainer.parentElement && startContainer.parentElement.closest('.ak-renderer-extension') || endContainer.parentElement && endContainer.parentElement.closest('.ak-renderer-extension')) {
return true;
}
return false;
}
}, {
key: "isValidAnnotationPosition",
value: function isValidAnnotationPosition(pos) {
if (!pos || !this.doc) {
return false;
}
return this._privateValidatePositionsForAnnotation(pos.from, pos.to);
}
/**
* Note: False indicates that the selection not able to be calculated.
*/
}, {
key: "getPositionFromRange",
value: function getPositionFromRange(range) {
if (!this.doc || !this.schema || !range) {
return false;
}
return getPosFromRange(range);
}
}, {
key: "getSelectionContext",
value: function getSelectionContext() {
return _getSelectionContext({
doc: this.doc,
schema: this.schema
});
}
}, {
key: "getAnnotationMarks",
value: function getAnnotationMarks() {
var schema = this.schema,
doc = this.doc;
if (!schema || !doc) {
return [];
}
var annotationMarkType = schema.marks.annotation;
if (!annotationMarkType) {
return [];
}
var marks = [];
doc.descendants(function (node) {
var annotationsMark = node.marks.filter(function (m) {
return m.type === annotationMarkType;
});
if (!annotationsMark || !annotationsMark.length) {
return true;
}
marks.push.apply(marks, _toConsumableArray(annotationsMark));
return false;
});
var uniqueMarks = new Map();
marks.forEach(function (m) {
uniqueMarks.set(m.attrs.id, m);
});
return Array.from(uniqueMarks.values());
}
}, {
key: "getAnnotationsByPosition",
value: function getAnnotationsByPosition(range) {
if (!this.doc || !this.schema) {
return [];
}
var pos = getPosFromRange(range);
if (!pos || !this.doc) {
return [];
}
return getAnnotationIdsFromRange(pos, this.doc, this.schema);
}
}, {
key: "applyAnnotation",
value: function applyAnnotation(pos, annotation) {
if (!this.doc || !pos || !this.schema) {
return false;
}
var from = pos.from,
to = pos.to;
var annotationId = annotation.annotationId,
annotationType = annotation.annotationType;
var step;
var targetNodeType;
// As part of fix for RAP, `from` points to the position right before media node
// hence, -1 is not needed
var beforeNodePos = from;
var possibleNode = this.doc.nodeAt(beforeNodePos);
if ((possibleNode === null || possibleNode === void 0 ? void 0 : possibleNode.type.name) === 'media') {
targetNodeType = 'media';
step = new AddNodeMarkStep(beforeNodePos, this.schema.marks.annotation.create({
id: annotationId,
type: annotationType
}));
} else {
var resolvedNode = this.doc.resolve(from).node();
// annotation is technically on text, but the context is caption
targetNodeType = resolvedNode.type.name === 'caption' ? 'caption' : 'text';
step = createAnnotationStep(from, to, {
annotationId: annotationId,
annotationType: annotationType,
schema: this.schema
});
}
var _step$apply2 = step.apply(this.doc),
doc = _step$apply2.doc,
failed = _step$apply2.failed;
if (failed || !doc) {
return false;
}
var originalSelection = doc.textBetween(from, to);
var _getIndexMatch = getIndexMatch(this.doc, this.schema, originalSelection, from),
numMatches = _getIndexMatch.numMatches,
matchIndex = _getIndexMatch.matchIndex,
blockNodePos = _getIndexMatch.blockNodePos;
return {
step: step,
doc: this.transformer.encode(doc),
inlineNodeTypes: getRendererRangeInlineNodeNames({
actions: this,
pos: {
from: from,
to: to
}
}),
ancestorNodeTypes: getRendererRangeAncestorNodeNames({
actions: this,
pos: {
from: from,
to: to
}
}),
originalSelection: originalSelection,
numMatches: numMatches,
matchIndex: matchIndex,
pos: blockNodePos,
targetNodeType: targetNodeType
};
}
}, {
key: "generateAnnotationIndexMatch",
value: function generateAnnotationIndexMatch(pos) {
if (!this.doc || !pos || !this.schema) {
return false;
}
var from = pos.from,
to = pos.to;
var originalSelection = this.doc.textBetween(from, to);
var _getIndexMatch2 = getIndexMatch(this.doc, this.schema, originalSelection, from),
numMatches = _getIndexMatch2.numMatches,
matchIndex = _getIndexMatch2.matchIndex,
blockNodePos = _getIndexMatch2.blockNodePos;
return {
originalSelection: originalSelection,
numMatches: numMatches,
matchIndex: matchIndex,
pos: blockNodePos
};
}
// Ignored via go/ees007
// eslint-disable-next-line @atlaskit/editor/enforce-todo-comment-format
// TODO: Do not forget to remove `undefined` when the `editor_inline_comments_on_inline_nodes` is removed.
}, {
key: "getInlineNodeTypes",
value: function getInlineNodeTypes(annotationId) {
if (!this.doc || !this.schema) {
return [];
}
return getAnnotationInlineNodeTypes({
doc: this.doc,
schema: this.schema
}, annotationId);
}
}]);
}();
export { RendererActions as default };