@atlaskit/editor-plugin-annotation
Version:
Annotation plugin for @atlaskit/editor-core
210 lines (202 loc) • 9.47 kB
JavaScript
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)
}));
}