UNPKG

@atlaskit/editor-plugin-annotation

Version:

Annotation plugin for @atlaskit/editor-core

250 lines (241 loc) • 12.5 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; import React from 'react'; import { AnnotationTypes } from '@atlaskit/adf-schema/annotation'; import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, CONTENT_COMPONENT, EVENT_TYPE } from '@atlaskit/editor-common/analytics/types/enums'; import { RESOLVE_METHOD } from '@atlaskit/editor-common/analytics/types/inline-comment-events'; import { isOfflineMode } from '@atlaskit/editor-common/connectivity/isOfflineMode'; import { useSharedPluginStateSelector } from '@atlaskit/editor-common/useSharedPluginStateSelector'; import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/useSharedPluginStateWithSelector'; import { getAnnotationInlineNodeTypes, getRangeInlineNodeNames } from '@atlaskit/editor-common/utils/annotation'; import { NodeSelection } from '@atlaskit/editor-prosemirror/state'; import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils'; import { fg } from '@atlaskit/platform-feature-flags/fg'; import { closeComponent, createAnnotation, removeInlineCommentNearSelection, setInlineCommentDraftState, updateInlineCommentResolvedState } from '../editor-commands'; import { getAllAnnotations, getAnnotationViewKey, getSelectionPositions } from '../pm-plugins/utils'; import { AnnotationTestIds } from '../types'; import { AnnotationViewWrapper } from './AnnotationViewWrapper'; var findPosForDOM = function findPosForDOM(sel) { var $from = sel.$from, from = sel.from; // Retrieve current TextNode var index = $from.index(); var node = index < $from.parent.childCount && $from.parent.child(index); // Right edge of a mark. if (!node && $from.nodeBefore && $from.nodeBefore.isText && $from.nodeBefore.marks.find(function (mark) { return mark.type.name === 'annotation'; })) { return from - 1; } return from; }; var selector = function selector(states) { var _states$annotationSta, _states$annotationSta2, _states$annotationSta3, _states$annotationSta4, _states$annotationSta5, _states$annotationSta6, _states$annotationSta7; return { annotations: (_states$annotationSta = states.annotationState) === null || _states$annotationSta === void 0 ? void 0 : _states$annotationSta.annotations, bookmark: (_states$annotationSta2 = states.annotationState) === null || _states$annotationSta2 === void 0 ? void 0 : _states$annotationSta2.bookmark, isInlineCommentViewClosed: (_states$annotationSta3 = states.annotationState) === null || _states$annotationSta3 === void 0 ? void 0 : _states$annotationSta3.isInlineCommentViewClosed, isOpeningMediaCommentFromToolbar: (_states$annotationSta4 = states.annotationState) === null || _states$annotationSta4 === void 0 ? void 0 : _states$annotationSta4.isOpeningMediaCommentFromToolbar, selectAnnotationMethod: (_states$annotationSta5 = states.annotationState) === null || _states$annotationSta5 === void 0 ? void 0 : _states$annotationSta5.selectAnnotationMethod, selectedAnnotations: (_states$annotationSta6 = states.annotationState) === null || _states$annotationSta6 === void 0 ? void 0 : _states$annotationSta6.selectedAnnotations, isAnnotationManagerEnabled: (_states$annotationSta7 = states.annotationState) === null || _states$annotationSta7 === void 0 ? void 0 : _states$annotationSta7.isAnnotationManagerEnabled }; }; export function InlineCommentView(_ref) { var providers = _ref.providers, editorView = _ref.editorView, editorAnalyticsAPI = _ref.editorAnalyticsAPI, editorAPI = _ref.editorAPI, dispatchAnalyticsEvent = _ref.dispatchAnalyticsEvent; // As inlineComment is the only annotation present, this function is not generic var inlineCommentProvider = providers.inlineComment; var state = editorView.state, dispatch = editorView.dispatch; var CreateComponent = inlineCommentProvider.createComponent, ViewComponent = inlineCommentProvider.viewComponent; var _useSharedPluginState = useSharedPluginStateWithSelector(editorAPI, ['annotation'], selector), annotations = _useSharedPluginState.annotations, bookmark = _useSharedPluginState.bookmark, isInlineCommentViewClosed = _useSharedPluginState.isInlineCommentViewClosed, isOpeningMediaCommentFromToolbar = _useSharedPluginState.isOpeningMediaCommentFromToolbar, selectAnnotationMethod = _useSharedPluginState.selectAnnotationMethod, selectedAnnotations = _useSharedPluginState.selectedAnnotations, isAnnotationManagerEnabled = _useSharedPluginState.isAnnotationManagerEnabled; var annotationsList = getAllAnnotations(editorView.state.doc); var selection = getSelectionPositions(state, bookmark); var position = findPosForDOM(selection); var isExtensionSelection = selection instanceof NodeSelection && selection.node.type.name === 'extension'; var dom; var domLookupError; try { var domRef = findDomRefAtPos(position, editorView.domAtPos.bind(editorView)); if (fg('cc_maui_annotations_on_extensions') && isExtensionSelection) { if (domRef instanceof HTMLElement) { dom = domRef; } } else { // Preserve the existing text/media and gate-off anchor lookup. // eslint-disable-next-line @atlaskit/editor/no-as-casting dom = domRef; } } catch (error) { domLookupError = error; } // Native embed extension nodes are rendered by a node view. Depending on // the wrapper, domAtPos can return no element (or throw) even though // EditorView.nodeDOM can. Use that direct lookup as a block-comment anchor. if (fg('cc_maui_annotations_on_extensions') && !dom && isExtensionSelection) { var nodeDom = editorView.nodeDOM(selection.from); if (nodeDom instanceof HTMLElement) { dom = nodeDom; } } if (!dom && domLookupError) { // eslint-disable-next-line no-console console.warn(domLookupError); if (dispatchAnalyticsEvent) { var payload = { action: ACTION.ERRORED, actionSubject: ACTION_SUBJECT.CONTENT_COMPONENT, eventType: EVENT_TYPE.OPERATIONAL, attributes: { component: CONTENT_COMPONENT.INLINE_COMMENT, selection: selection.toJSON(), position: position, docSize: editorView.state.doc.nodeSize, error: domLookupError.toString() } }; dispatchAnalyticsEvent(payload); } } // Network Status var networkStatusSelector = useSharedPluginStateSelector(editorAPI, 'connectivity.mode'); if (!dom) { return null; } // Create Component if (bookmark) { if (!CreateComponent) { return null; } var inlineNodeTypes = getRangeInlineNodeNames({ doc: state.doc, pos: selection }); //getting all text between bookmarked positions var textSelection = state.doc.textBetween(selection.from, selection.to); return /*#__PURE__*/React.createElement("div", { "data-testid": AnnotationTestIds.floatingComponent, "data-editor-popup": "true" }, /*#__PURE__*/React.createElement(CreateComponent, { dom: dom, textSelection: textSelection // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onCreate: function onCreate(id) { if (!isAnnotationManagerEnabled) { var createAnnotationResult = createAnnotation(editorAnalyticsAPI, editorAPI)(id, AnnotationTypes.INLINE_COMMENT, inlineCommentProvider.supportedBlockNodes, inlineCommentProvider.isBlockNodeSupported)(editorView.state, editorView.dispatch); !editorView.hasFocus() && editorView.focus(); if (!createAnnotationResult) { throw new Error('Failed to create annotation'); } } } // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onCreateError: function onCreateError(id) { return ( // This is called when optimistic create inline comment encounters an error, // in which case the optimistically added annotation must be removed. removeInlineCommentNearSelection(id, inlineCommentProvider.supportedBlockNodes, inlineCommentProvider.isBlockNodeSupported)(editorView.state, editorView.dispatch) ); } // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onClose: function onClose() { if (!isAnnotationManagerEnabled) { setInlineCommentDraftState(editorAnalyticsAPI, undefined, editorAPI)(false)(editorView.state, editorView.dispatch); !editorView.hasFocus() && editorView.focus(); } }, inlineNodeTypes: inlineNodeTypes, isOpeningMediaCommentFromToolbar: isOpeningMediaCommentFromToolbar, isOffline: isOfflineMode(networkStatusSelector) })); } // View Component var activeAnnotations = // eslint-disable-next-line @atlassian/perf-linting/no-expensive-computations-in-render -- Ignored via go/ees017 (to be fixed) (selectedAnnotations === null || selectedAnnotations === void 0 ? void 0 : selectedAnnotations.filter(function (mark) { return annotations && annotations[mark.id] === false; })) || []; if (!ViewComponent || activeAnnotations.length === 0) { return null; } var onAnnotationViewed = function onAnnotationViewed() { if (!dispatchAnalyticsEvent) { return; } // fire analytics var targetNode = editorView.state.doc.nodeAt(position); var targetNodeType = targetNode === null || targetNode === void 0 ? void 0 : targetNode.type.name; var commentTargetType = targetNode ? 'other' : 'unknown'; if (targetNodeType === 'extension') { commentTargetType = (targetNode === null || targetNode === void 0 ? void 0 : targetNode.attrs.extensionKey) === 'native-embed:maui' ? 'remixEmbed' : 'other'; } else if (['media', 'mediaSingle', 'mediaInline'].includes(targetNodeType !== null && targetNodeType !== void 0 ? targetNodeType : '')) { commentTargetType = 'media'; } else if (targetNode !== null && targetNode !== void 0 && targetNode.isText) { commentTargetType = 'text'; } var payload = { action: ACTION.VIEWED, actionSubject: ACTION_SUBJECT.ANNOTATION, actionSubjectId: ACTION_SUBJECT_ID.INLINE_COMMENT, eventType: EVENT_TYPE.TRACK, attributes: { overlap: activeAnnotations.length ? activeAnnotations.length - 1 : 0, targetNodeType: targetNodeType, commentTargetType: commentTargetType, method: selectAnnotationMethod } }; dispatchAnalyticsEvent(payload); }; if (isInlineCommentViewClosed || !selectedAnnotations) { return null; } // For view mode, the finding of inline node types is a bit more complex, // that's why we will not provide it as a `inlineNodeTypes` props to the view component, // to speed up the rendering process. var getInlineNodeTypes = function getInlineNodeTypes(annotationId) { return getAnnotationInlineNodeTypes(editorView.state, annotationId); }; return /*#__PURE__*/React.createElement(AnnotationViewWrapper, { "data-editor-popup": "true", "data-testid": AnnotationTestIds.floatingComponent, key: getAnnotationViewKey(activeAnnotations), onViewed: onAnnotationViewed }, /*#__PURE__*/React.createElement(ViewComponent, { annotationsList: annotationsList, annotations: activeAnnotations, getInlineNodeTypes: getInlineNodeTypes, dom: dom // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onDelete: function onDelete(id) { return removeInlineCommentNearSelection(id, inlineCommentProvider.supportedBlockNodes, inlineCommentProvider.isBlockNodeSupported)(editorView.state, dispatch); } // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onResolve: function onResolve(id) { return updateInlineCommentResolvedState(editorAnalyticsAPI)(_defineProperty({}, id, true), RESOLVE_METHOD.COMPONENT)(editorView.state, editorView.dispatch); } // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onClose: function onClose() { closeComponent()(editorView.state, editorView.dispatch); }, isOpeningMediaCommentFromToolbar: isOpeningMediaCommentFromToolbar, isOffline: isOfflineMode(networkStatusSelector) })); }