UNPKG

@atlaskit/renderer

Version:
399 lines (391 loc) • 14 kB
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 };