UNPKG

@atlaskit/editor-plugin-annotation

Version:

Annotation plugin for @atlaskit/editor-core

210 lines (202 loc) 9.47 kB
import React from 'react'; import { AnnotationTypes } from '@atlaskit/adf-schema'; import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, CONTENT_COMPONENT, EVENT_TYPE, RESOLVE_METHOD } from '@atlaskit/editor-common/analytics'; import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks'; import { useSharedPluginStateSelector } from '@atlaskit/editor-common/use-shared-plugin-state-selector'; import { getAnnotationInlineNodeTypes, getRangeInlineNodeNames } from '@atlaskit/editor-common/utils'; import { isOfflineMode } from '@atlaskit/editor-plugin-connectivity'; import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments'; 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'; const findPosForDOM = sel => { const { $from, from } = sel; // Retrieve current TextNode const index = $from.index(); const 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(mark => mark.type.name === 'annotation')) { return from - 1; } return from; }; const 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({ providers, editorView, editorAnalyticsAPI, editorAPI, dispatchAnalyticsEvent }) { // As inlineComment is the only annotation present, this function is not generic const { inlineComment: inlineCommentProvider } = providers; const { state, dispatch } = editorView; const { createComponent: CreateComponent, viewComponent: ViewComponent } = inlineCommentProvider; const { annotations, bookmark, isInlineCommentViewClosed, isOpeningMediaCommentFromToolbar, selectAnnotationMethod, selectedAnnotations, isAnnotationManagerEnabled } = useSharedPluginStateWithSelector(editorAPI, ['annotation'], selector); const annotationsList = getAllAnnotations(editorView.state.doc); const selection = getSelectionPositions(state, bookmark); const position = findPosForDOM(selection); let dom; try { // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting dom = findDomRefAtPos(position, editorView.domAtPos.bind(editorView)); } catch (error) { // eslint-disable-next-line no-console console.warn(error); if (dispatchAnalyticsEvent) { const payload = { action: ACTION.ERRORED, actionSubject: ACTION_SUBJECT.CONTENT_COMPONENT, eventType: EVENT_TYPE.OPERATIONAL, attributes: { component: CONTENT_COMPONENT.INLINE_COMMENT, selection: selection.toJSON(), position, docSize: editorView.state.doc.nodeSize, error: error.toString() } }; dispatchAnalyticsEvent(payload); } } // Network Status const networkStatusSelector = useSharedPluginStateSelector(editorAPI, 'connectivity.mode', { disabled: editorExperiment('platform_editor_offline_editing_web', false) }); if (!dom) { return null; } // Create Component if (bookmark) { if (!CreateComponent) { return null; } const inlineNodeTypes = getRangeInlineNodeNames({ doc: state.doc, pos: selection }); //getting all text between bookmarked positions const 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: id => { if (!isAnnotationManagerEnabled) { const createAnnotationResult = createAnnotation(editorAnalyticsAPI, editorAPI)(id, AnnotationTypes.INLINE_COMMENT, inlineCommentProvider.supportedBlockNodes)(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) , 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 const 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(mark => annotations && annotations[mark.id] === false)) || []; if (!ViewComponent || activeAnnotations.length === 0) { return null; } const onAnnotationViewed = () => { var _editorView$state$doc; if (!dispatchAnalyticsEvent) { return; } // fire analytics const 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: (_editorView$state$doc = editorView.state.doc.nodeAt(position)) === null || _editorView$state$doc === void 0 ? void 0 : _editorView$state$doc.type.name, 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. const getInlineNodeTypes = annotationId => 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: id => removeInlineCommentNearSelection(id, inlineCommentProvider.supportedBlockNodes)(editorView.state, dispatch) // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onResolve: id => updateInlineCommentResolvedState(editorAnalyticsAPI)({ [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: () => { closeComponent()(editorView.state, editorView.dispatch); }, isOpeningMediaCommentFromToolbar: isOpeningMediaCommentFromToolbar, isOffline: isOfflineMode(networkStatusSelector) })); }