@atlaskit/editor-plugin-annotation 2.8.3 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/editor-commands/index.js +50 -1
  3. package/dist/cjs/pm-plugins/annotation-manager-hooks.js +166 -4
  4. package/dist/cjs/pm-plugins/inline-comment.js +169 -30
  5. package/dist/cjs/pm-plugins/plugin-factory.js +51 -1
  6. package/dist/cjs/pm-plugins/reducer.js +18 -1
  7. package/dist/cjs/pm-plugins/toolbar.js +10 -7
  8. package/dist/cjs/pm-plugins/types.js +2 -0
  9. package/dist/es2019/editor-commands/index.js +42 -0
  10. package/dist/es2019/pm-plugins/annotation-manager-hooks.js +160 -5
  11. package/dist/es2019/pm-plugins/inline-comment.js +146 -9
  12. package/dist/es2019/pm-plugins/plugin-factory.js +51 -1
  13. package/dist/es2019/pm-plugins/reducer.js +22 -1
  14. package/dist/es2019/pm-plugins/toolbar.js +10 -8
  15. package/dist/es2019/pm-plugins/types.js +2 -0
  16. package/dist/esm/editor-commands/index.js +49 -0
  17. package/dist/esm/pm-plugins/annotation-manager-hooks.js +167 -5
  18. package/dist/esm/pm-plugins/inline-comment.js +159 -20
  19. package/dist/esm/pm-plugins/plugin-factory.js +51 -1
  20. package/dist/esm/pm-plugins/reducer.js +18 -1
  21. package/dist/esm/pm-plugins/toolbar.js +10 -7
  22. package/dist/esm/pm-plugins/types.js +2 -0
  23. package/dist/types/editor-commands/index.d.ts +5 -0
  24. package/dist/types/pm-plugins/annotation-manager-hooks.d.ts +4 -1
  25. package/dist/types/pm-plugins/plugin-factory.d.ts +4 -0
  26. package/dist/types/pm-plugins/types.d.ts +26 -1
  27. package/dist/types-ts4.5/editor-commands/index.d.ts +5 -0
  28. package/dist/types-ts4.5/pm-plugins/annotation-manager-hooks.d.ts +4 -1
  29. package/dist/types-ts4.5/pm-plugins/plugin-factory.d.ts +4 -0
  30. package/dist/types-ts4.5/pm-plugins/types.d.ts +26 -1
  31. package/package.json +6 -3
@@ -9,6 +9,7 @@ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/de
9
9
  var _utils = require("@atlaskit/editor-common/utils");
10
10
  var _state = require("@atlaskit/editor-prosemirror/state");
11
11
  var _view = require("@atlaskit/editor-prosemirror/view");
12
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
12
13
  var _reducer = _interopRequireDefault(require("./reducer"));
13
14
  var _utils2 = require("./utils");
14
15
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
@@ -26,6 +27,10 @@ var handleDocChanged = function handleDocChanged(tr, prevPluginState) {
26
27
  * We clear bookmark on the following conditions:
27
28
  * 1. if current selection is an empty selection, or
28
29
  * 2. if the current selection and bookmark selection are different
30
+ * @param tr
31
+ * @param editorState
32
+ * @param bookmark
33
+ * @example
29
34
  */
30
35
  var shouldClearBookMarkCheck = exports.shouldClearBookMarkCheck = function shouldClearBookMarkCheck(tr, editorState, bookmark) {
31
36
  if (editorState.selection.empty || !bookmark) {
@@ -53,7 +58,7 @@ var shouldClearBookMarkCheck = exports.shouldClearBookMarkCheck = function shoul
53
58
  // by default we discard bookmark
54
59
  return true;
55
60
  };
56
- var getSelectionChangedHandler = function getSelectionChangedHandler(reopenCommentView) {
61
+ var getSelectionChangeHandlerOld = function getSelectionChangeHandlerOld(reopenCommentView) {
57
62
  return function (tr, pluginState) {
58
63
  if (pluginState.skipSelectionHandling) {
59
64
  return _objectSpread(_objectSpread({}, pluginState), {}, {
@@ -93,6 +98,51 @@ var getSelectionChangedHandler = function getSelectionChangedHandler(reopenComme
93
98
  });
94
99
  };
95
100
  };
101
+ var getSelectionChangeHandlerNew = function getSelectionChangeHandlerNew(reopenCommentView) {
102
+ return function (tr, pluginState) {
103
+ if (pluginState.skipSelectionHandling) {
104
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
105
+ skipSelectionHandling: false
106
+ }, reopenCommentView && {
107
+ isInlineCommentViewClosed: false
108
+ });
109
+ }
110
+ var selectedAnnotations = (0, _utils2.findAnnotationsInSelection)(tr.selection, tr.doc);
111
+
112
+ // NOTE: I've left this commented code here as a reference that the previous old code would reset the selected annotations
113
+ // if the selection is empty. If this is no longer needed, we can remove this code.
114
+ // clean up with platform_editor_comments_api_manager_select
115
+ // if (selectedAnnotations.length === 0) {
116
+ // return {
117
+ // ...pluginState,
118
+ // pendingSelectedAnnotations: selectedAnnotations,
119
+ // pendingSelectedAnnotationsUpdateCount:
120
+ // pluginState.pendingSelectedAnnotationsUpdateCount + 1,
121
+ // isInlineCommentViewClosed: true,
122
+ // selectAnnotationMethod: undefined,
123
+ // };
124
+ // }
125
+
126
+ if ((0, _utils2.isSelectedAnnotationsChanged)(selectedAnnotations, pluginState.pendingSelectedAnnotations)) {
127
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
128
+ pendingSelectedAnnotations: selectedAnnotations,
129
+ pendingSelectedAnnotationsUpdateCount: pluginState.pendingSelectedAnnotationsUpdateCount + 1
130
+ }, reopenCommentView && {
131
+ isInlineCommentViewClosed: false
132
+ });
133
+ }
134
+ return _objectSpread(_objectSpread(_objectSpread({}, pluginState), reopenCommentView && {
135
+ isInlineCommentViewClosed: false
136
+ }), {}, {
137
+ selectAnnotationMethod: undefined
138
+ });
139
+ };
140
+ };
141
+ var getSelectionChangedHandler = function getSelectionChangedHandler(reopenCommentView) {
142
+ return function (tr, pluginState) {
143
+ return (0, _platformFeatureFlags.fg)('platform_editor_comments_api_manager_select') ? getSelectionChangeHandlerNew(reopenCommentView)(tr, pluginState) : getSelectionChangeHandlerOld(reopenCommentView)(tr, pluginState);
144
+ };
145
+ };
96
146
  var _pluginFactory = (0, _utils.pluginFactory)(_utils2.inlineCommentPluginKey, _reducer.default, {
97
147
  onSelectionChanged: getSelectionChangedHandler(true),
98
148
  onDocChanged: handleDocChanged,
@@ -32,12 +32,14 @@ var _default = exports.default = function _default(pluginState, action) {
32
32
  annotations: {}
33
33
  });
34
34
  case _types.ACTIONS.CLOSE_COMPONENT:
35
- return _objectSpread(_objectSpread({}, pluginState), {}, {
35
+ return _objectSpread(_objectSpread(_objectSpread({}, pluginState), {}, {
36
36
  isInlineCommentViewClosed: true,
37
37
  isDrafting: false,
38
38
  isOpeningMediaCommentFromToolbar: false
39
39
  }, (0, _platformFeatureFlags.fg)('platform_editor_annotation_selected_annotation') && {
40
40
  selectedAnnotations: []
41
+ }), (0, _platformFeatureFlags.fg)('platform_editor_comments_api_manager_select') && {
42
+ selectedAnnotations: []
41
43
  });
42
44
  case _types.ACTIONS.ADD_INLINE_COMMENT:
43
45
  var updatedPluginState = getNewDraftState(pluginState, action.data.drafting, action.data.editorState);
@@ -46,6 +48,8 @@ var _default = exports.default = function _default(pluginState, action) {
46
48
  annotations: _objectSpread(_objectSpread({}, pluginState.annotations), action.data.inlineComments),
47
49
  isInlineCommentViewClosed: false,
48
50
  selectAnnotationMethod: undefined
51
+ }, (0, _platformFeatureFlags.fg)('platform_editor_comments_api_manager_select') && {
52
+ skipSelectionHandling: true
49
53
  });
50
54
  case _types.ACTIONS.INLINE_COMMENT_SET_VISIBLE:
51
55
  var isVisible = action.data.isVisible;
@@ -69,6 +73,19 @@ var _default = exports.default = function _default(pluginState, action) {
69
73
  skipSelectionHandling: true,
70
74
  isInlineCommentViewClosed: false
71
75
  });
76
+ case _types.ACTIONS.FLUSH_PENDING_SELECTIONS:
77
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
78
+ selectedAnnotations: action.data.canSetAsSelectedAnnotations ? (0, _toConsumableArray2.default)(pluginState.pendingSelectedAnnotations) : pluginState.selectedAnnotations,
79
+ pendingSelectedAnnotations: [],
80
+ isInlineCommentViewClosed: false
81
+ });
82
+ case _types.ACTIONS.SET_PENDING_SELECTIONS:
83
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
84
+ pendingSelectedAnnotations: (0, _toConsumableArray2.default)(action.data.selectedAnnotations),
85
+ pendingSelectedAnnotationsUpdateCount: pluginState.pendingSelectedAnnotationsUpdateCount + 1,
86
+ skipSelectionHandling: true,
87
+ isInlineCommentViewClosed: false
88
+ });
72
89
  default:
73
90
  return pluginState;
74
91
  }
@@ -137,7 +137,7 @@ var buildToolbar = exports.buildToolbar = function buildToolbar(editorAnalyticsA
137
137
  }
138
138
  if ((0, _platformFeatureFlags.fg)('platform_editor_comments_api_manager')) {
139
139
  if (!annotationManager) {
140
- // TODO: EDITOR-188 - If we've reached here and the manager is not initialized, we should
140
+ // TODO: EDITOR-595 - If we've reached here and the manager is not initialized, we should
141
141
  // dispatch an analytics event to indicate that the user has clicked the button but
142
142
  // the action was not completed.
143
143
  return false;
@@ -151,16 +151,19 @@ var buildToolbar = exports.buildToolbar = function buildToolbar(editorAnalyticsA
151
151
  }
152
152
  });
153
153
  createCommentExperience === null || createCommentExperience === void 0 || createCommentExperience.initExperience.start();
154
- var _annotationManager$st = annotationManager.startDraft(),
155
- success = _annotationManager$st.success;
156
- if (!success) {
157
- // TODO: EDITOR-188 - Report start draft attempt failed.
154
+ var result = annotationManager.startDraft();
155
+ if (result.success) {
156
+ // TODO: EDITOR-595 - Ensure and anlytic is fired to indicate that the user has started a draft.
157
+ } else {
158
+ // TODO: EDITOR-595 - Fire an analytics event to indicate that the user has clicked the button
159
+ // but the action was not completed, the result should contain a reason.
158
160
  }
159
161
  } else {
160
- // TODO: EDITOR-188 - Dispatch analytics event
162
+ // TODO: EDITOR-595 - Track the toolbar comment button was clicked but the preemptive gate
163
+ // check returned false and the draft cannot be started.
161
164
  }
162
165
  }).catch(function () {
163
- // TODO: EDITOR-188 - Handle preemptive gate check error and dispatch analytics event
166
+ // TODO: EDITOR-595 - Handle preemptive gate check error. Something went very wrong in the gate.
164
167
  });
165
168
  return true;
166
169
  } else {
@@ -14,5 +14,7 @@ var ACTIONS = exports.ACTIONS = /*#__PURE__*/function (ACTIONS) {
14
14
  ACTIONS[ACTIONS["CLOSE_COMPONENT"] = 6] = "CLOSE_COMPONENT";
15
15
  ACTIONS[ACTIONS["SET_SELECTED_ANNOTATION"] = 7] = "SET_SELECTED_ANNOTATION";
16
16
  ACTIONS[ACTIONS["SET_HOVERED_ANNOTATION"] = 8] = "SET_HOVERED_ANNOTATION";
17
+ ACTIONS[ACTIONS["FLUSH_PENDING_SELECTIONS"] = 9] = "FLUSH_PENDING_SELECTIONS";
18
+ ACTIONS[ACTIONS["SET_PENDING_SELECTIONS"] = 10] = "SET_PENDING_SELECTIONS";
17
19
  return ACTIONS;
18
20
  }({});
@@ -31,6 +31,21 @@ export const closeComponent = () => createCommand({
31
31
  export const clearDirtyMark = () => createCommand({
32
32
  type: ACTIONS.INLINE_COMMENT_CLEAR_DIRTY_MARK
33
33
  });
34
+ export const flushPendingSelections = canSetAsSelectedAnnotations => createCommand({
35
+ type: ACTIONS.FLUSH_PENDING_SELECTIONS,
36
+ data: {
37
+ canSetAsSelectedAnnotations
38
+ }
39
+ });
40
+ export const setPendingSelectedAnnotation = id => createCommand({
41
+ type: ACTIONS.SET_PENDING_SELECTIONS,
42
+ data: {
43
+ selectedAnnotations: [{
44
+ id,
45
+ type: AnnotationTypes.INLINE_COMMENT
46
+ }]
47
+ }
48
+ });
34
49
  const removeInlineCommentFromNode = (id, supportedBlockNodes = [], state, dispatch) => {
35
50
  const {
36
51
  tr,
@@ -94,6 +109,31 @@ export const removeInlineCommentNearSelection = (id, supportedNodes = []) => (st
94
109
  }
95
110
  return true;
96
111
  };
112
+ export const removeInlineCommentFromDoc = (id, supportedNodes = []) => (state, dispatch) => {
113
+ const {
114
+ tr
115
+ } = state;
116
+ state.doc.descendants((node, pos) => {
117
+ // Inline comment on mediaInline is not supported as part of comments on media project
118
+ // Thus, we skip the decoration for mediaInline node
119
+ if (node.type.name === 'mediaInline') {
120
+ return false;
121
+ }
122
+ const isSupportedBlockNode = node.isBlock && (supportedNodes === null || supportedNodes === void 0 ? void 0 : supportedNodes.includes(node.type.name));
123
+ node.marks.filter(mark => mark.type === state.schema.marks.annotation && mark.attrs.id === id).forEach(mark => {
124
+ if (isSupportedBlockNode) {
125
+ tr.removeNodeMark(pos, mark);
126
+ } else {
127
+ tr.removeMark(pos, pos + node.nodeSize, mark);
128
+ }
129
+ });
130
+ });
131
+ if (dispatch) {
132
+ dispatch(tr);
133
+ return true;
134
+ }
135
+ return false;
136
+ };
97
137
  const getDraftCommandAction = (drafting, targetType, targetNodeId, supportedBlockNodes, isOpeningMediaCommentFromToolbar) => {
98
138
  return editorState => {
99
139
  // validate selection only when entering draft mode
@@ -117,6 +157,8 @@ const getDraftCommandAction = (drafting, targetType, targetNodeId, supportedBloc
117
157
  /**
118
158
  * Show active inline comments for a given block node, otherwise,
119
159
  * return false if the node has no comments or no unresolved comments.
160
+ * @param supportedBlockNodes
161
+ * @example
120
162
  */
121
163
  export const showInlineCommentForBlockNode = (supportedBlockNodes = []) => (node, viewMethod, isOpeningMediaCommentFromToolbar) => (state, dispatch) => {
122
164
  const pluginState = getPluginState(state);
@@ -1,13 +1,15 @@
1
1
  import { AnnotationTypes } from '@atlaskit/adf-schema';
2
- import { getRangeInlineNodeNames } from '@atlaskit/editor-common/utils';
2
+ import { getAnnotationInlineNodeTypes, getRangeInlineNodeNames } from '@atlaskit/editor-common/utils';
3
3
  import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils';
4
- import { setInlineCommentDraftState, createAnnotation } from '../editor-commands';
4
+ import { fg } from '@atlaskit/platform-feature-flags';
5
+ import { setInlineCommentDraftState, createAnnotation, setSelectedAnnotation, closeComponent, setHoveredAnnotation, removeInlineCommentFromDoc } from '../editor-commands';
5
6
  import { AnnotationSelectionType } from '../types';
6
7
  import { inlineCommentPluginKey, isSelectionValid } from './utils';
7
8
  const ERROR_REASON_DRAFT_NOT_STARTED = 'draft-not-started';
8
9
  const ERROR_REASON_DRAFT_IN_PROGRESS = 'draft-in-progress';
9
10
  const ERROR_REASON_RANGE_MISSING = 'range-no-longer-exists';
10
11
  const ERROR_REASON_RANGE_INVALID = 'invalid-range';
12
+ const ERROR_REASON_ID_INVALID = 'id-not-valid';
11
13
  const domRefFromPos = (view, position) => {
12
14
  let dom;
13
15
  try {
@@ -38,9 +40,10 @@ export const allowAnnotation = (editorView, options) => () => {
38
40
  return isSelectionValid(editorView.state) === AnnotationSelectionType.VALID;
39
41
  };
40
42
  export const startDraft = (editorView, options) => () => {
41
- var _getRangeInlineNodeNa, _options$annotationMa;
43
+ var _getRangeInlineNodeNa, _options$annotationMa2;
42
44
  const {
43
- isDrafting
45
+ isDrafting,
46
+ selectedAnnotations
44
47
  } = inlineCommentPluginKey.getState(editorView.state) || {};
45
48
  if (isDrafting) {
46
49
  return {
@@ -48,6 +51,24 @@ export const startDraft = (editorView, options) => () => {
48
51
  reason: ERROR_REASON_DRAFT_IN_PROGRESS
49
52
  };
50
53
  }
54
+ if (!!(selectedAnnotations !== null && selectedAnnotations !== void 0 && selectedAnnotations.length) && fg('platform_editor_comments_api_manager_select')) {
55
+ // if there are selected annotations when starting a draft, we need to clear the selected annotations
56
+ // before we start the draft.
57
+ closeComponent()(editorView.state, editorView.dispatch);
58
+
59
+ // not only that but we need to also deselect any other annotations that are currently selected.
60
+ selectedAnnotations === null || selectedAnnotations === void 0 ? void 0 : selectedAnnotations.forEach(annotation => {
61
+ var _options$annotationMa, _getAnnotationInlineN;
62
+ (_options$annotationMa = options.annotationManager) === null || _options$annotationMa === void 0 ? void 0 : _options$annotationMa.emit({
63
+ name: 'annotationSelectionChanged',
64
+ data: {
65
+ annotationId: annotation.id,
66
+ isSelected: false,
67
+ inlineNodeTypes: (_getAnnotationInlineN = getAnnotationInlineNodeTypes(editorView.state, annotation.id)) !== null && _getAnnotationInlineN !== void 0 ? _getAnnotationInlineN : []
68
+ }
69
+ });
70
+ });
71
+ }
51
72
  setInlineCommentDraftState(options.editorAnalyticsAPI)(true)(editorView.state, editorView.dispatch);
52
73
  const {
53
74
  draftDecorationSet
@@ -70,7 +91,7 @@ export const startDraft = (editorView, options) => () => {
70
91
  to: decorations[decorations.length - 1].to
71
92
  }
72
93
  })) !== null && _getRangeInlineNodeNa !== void 0 ? _getRangeInlineNodeNa : [];
73
- (_options$annotationMa = options.annotationManager) === null || _options$annotationMa === void 0 ? void 0 : _options$annotationMa.emit({
94
+ (_options$annotationMa2 = options.annotationManager) === null || _options$annotationMa2 === void 0 ? void 0 : _options$annotationMa2.emit({
74
95
  name: 'draftAnnotationStarted',
75
96
  data: {
76
97
  targetElement,
@@ -111,6 +132,7 @@ export const clearDraft = (editorView, options) => () => {
111
132
  };
112
133
  };
113
134
  export const applyDraft = (editorView, options) => id => {
135
+ var _options$annotationMa3, _getAnnotationInlineN2;
114
136
  const {
115
137
  isDrafting,
116
138
  draftDecorationSet,
@@ -136,6 +158,17 @@ export const applyDraft = (editorView, options) => id => {
136
158
  // Using the original decoration from position we should be able to locate the new target element.
137
159
  // This is because the new annotation will be created at the same position as the draft decoration.
138
160
  const targetElement = domRefFromPos(editorView, from);
161
+
162
+ // When a draft is applied it is automatically selected, so we need to set the selected annotation.
163
+ // emit the event for the selected annotation.
164
+ (_options$annotationMa3 = options.annotationManager) === null || _options$annotationMa3 === void 0 ? void 0 : _options$annotationMa3.emit({
165
+ name: 'annotationSelectionChanged',
166
+ data: {
167
+ annotationId: id,
168
+ isSelected: true,
169
+ inlineNodeTypes: (_getAnnotationInlineN2 = getAnnotationInlineNodeTypes(editorView.state, id)) !== null && _getAnnotationInlineN2 !== void 0 ? _getAnnotationInlineN2 : []
170
+ }
171
+ });
139
172
  return {
140
173
  success: true,
141
174
  // Get the dom element from the newly created annotation and return it here.
@@ -178,4 +211,126 @@ export const getDraft = (editorView, options) => () => {
178
211
  targetElement,
179
212
  actionResult: undefined
180
213
  };
214
+ };
215
+ export const setIsAnnotationSelected = (editorView, options) => (id, isSelected) => {
216
+ var _selectedAnnotations$;
217
+ const {
218
+ annotations,
219
+ isDrafting,
220
+ selectedAnnotations
221
+ } = inlineCommentPluginKey.getState(editorView.state) || {};
222
+ if (isDrafting) {
223
+ return {
224
+ success: false,
225
+ reason: ERROR_REASON_DRAFT_IN_PROGRESS
226
+ };
227
+ }
228
+
229
+ // If there is no annotation state with this id then we can assume the annotation is invalid.
230
+ if (!(annotations !== null && annotations !== void 0 && annotations.hasOwnProperty(id))) {
231
+ return {
232
+ success: false,
233
+ reason: ERROR_REASON_ID_INVALID
234
+ };
235
+ }
236
+ const isCurrentlySelectedIndex = (_selectedAnnotations$ = selectedAnnotations === null || selectedAnnotations === void 0 ? void 0 : selectedAnnotations.findIndex(annotation => annotation.id === id)) !== null && _selectedAnnotations$ !== void 0 ? _selectedAnnotations$ : -1;
237
+ const isCurrentlySelected = isCurrentlySelectedIndex !== -1;
238
+ if (isSelected !== isCurrentlySelected) {
239
+ // the annotation is selection is changing.
240
+ if (isCurrentlySelected && !isSelected) {
241
+ var _options$annotationMa4, _getAnnotationInlineN3;
242
+ // the selected annotaion is being unselected, so we need to close the view.
243
+ closeComponent()(editorView.state, editorView.dispatch);
244
+ (_options$annotationMa4 = options.annotationManager) === null || _options$annotationMa4 === void 0 ? void 0 : _options$annotationMa4.emit({
245
+ name: 'annotationSelectionChanged',
246
+ data: {
247
+ annotationId: id,
248
+ isSelected: false,
249
+ inlineNodeTypes: (_getAnnotationInlineN3 = getAnnotationInlineNodeTypes(editorView.state, id)) !== null && _getAnnotationInlineN3 !== void 0 ? _getAnnotationInlineN3 : []
250
+ }
251
+ });
252
+ } else if (!isCurrentlySelected && isSelected) {
253
+ var _options$annotationMa6, _getAnnotationInlineN5;
254
+ // the annotation is currently not selected and is being selected, so we need to open the view.
255
+ setSelectedAnnotation(id)(editorView.state, editorView.dispatch);
256
+
257
+ // the current annotations are going to be unselected. So we need to notify listeners of this change also.
258
+ selectedAnnotations === null || selectedAnnotations === void 0 ? void 0 : selectedAnnotations.forEach(annotation => {
259
+ if (annotation.id !== id) {
260
+ var _options$annotationMa5, _getAnnotationInlineN4;
261
+ (_options$annotationMa5 = options.annotationManager) === null || _options$annotationMa5 === void 0 ? void 0 : _options$annotationMa5.emit({
262
+ name: 'annotationSelectionChanged',
263
+ data: {
264
+ annotationId: annotation.id,
265
+ isSelected: false,
266
+ inlineNodeTypes: (_getAnnotationInlineN4 = getAnnotationInlineNodeTypes(editorView.state, annotation.id)) !== null && _getAnnotationInlineN4 !== void 0 ? _getAnnotationInlineN4 : []
267
+ }
268
+ });
269
+ }
270
+ });
271
+
272
+ // Lastly we need to emit the event for the selected annotation.
273
+ (_options$annotationMa6 = options.annotationManager) === null || _options$annotationMa6 === void 0 ? void 0 : _options$annotationMa6.emit({
274
+ name: 'annotationSelectionChanged',
275
+ data: {
276
+ annotationId: id,
277
+ isSelected: true,
278
+ inlineNodeTypes: (_getAnnotationInlineN5 = getAnnotationInlineNodeTypes(editorView.state, id)) !== null && _getAnnotationInlineN5 !== void 0 ? _getAnnotationInlineN5 : []
279
+ }
280
+ });
281
+ }
282
+ }
283
+ return {
284
+ success: true,
285
+ isSelected
286
+ };
287
+ };
288
+ export const setIsAnnotationHovered = (editorView, options) => (id, isHovered) => {
289
+ var _hoveredAnnotations$f;
290
+ const {
291
+ annotations,
292
+ hoveredAnnotations
293
+ } = inlineCommentPluginKey.getState(editorView.state) || {};
294
+
295
+ // If there is no annotation state with this id then we can assume the annotation is invalid.
296
+ if (!(annotations !== null && annotations !== void 0 && annotations.hasOwnProperty(id))) {
297
+ return {
298
+ success: false,
299
+ reason: ERROR_REASON_ID_INVALID
300
+ };
301
+ }
302
+ const isCurrentlyHoveredIndex = (_hoveredAnnotations$f = hoveredAnnotations === null || hoveredAnnotations === void 0 ? void 0 : hoveredAnnotations.findIndex(annotation => annotation.id === id)) !== null && _hoveredAnnotations$f !== void 0 ? _hoveredAnnotations$f : -1;
303
+ const isCurrentlyHovered = isCurrentlyHoveredIndex !== -1;
304
+ if (isHovered !== isCurrentlyHovered) {
305
+ // the annotation in hovered is changing.
306
+ if (isCurrentlyHovered && !isHovered) {
307
+ // the hovered annotaion is being unhovered, so we should remove the hover state.
308
+ setHoveredAnnotation('')(editorView.state, editorView.dispatch);
309
+ } else if (!isCurrentlyHovered && isHovered) {
310
+ // the annotation is currently not hovered and is being hovered.
311
+ setHoveredAnnotation(id)(editorView.state, editorView.dispatch);
312
+ }
313
+ }
314
+ return {
315
+ success: true,
316
+ isHovered
317
+ };
318
+ };
319
+ export const clearAnnotation = (editorView, options) => id => {
320
+ const {
321
+ annotations
322
+ } = inlineCommentPluginKey.getState(editorView.state) || {};
323
+
324
+ // If there is no annotation state with this id then we can assume the annotation is invalid.
325
+ if (!(annotations !== null && annotations !== void 0 && annotations.hasOwnProperty(id))) {
326
+ return {
327
+ success: false,
328
+ reason: ERROR_REASON_ID_INVALID
329
+ };
330
+ }
331
+ removeInlineCommentFromDoc(id, options.provider.supportedBlockNodes)(editorView.state, editorView.dispatch);
332
+ return {
333
+ success: true,
334
+ actionResult: undefined
335
+ };
181
336
  };
@@ -1,11 +1,12 @@
1
1
  import { AnnotationTypes } from '@atlaskit/adf-schema';
2
2
  import { RESOLVE_METHOD } from '@atlaskit/editor-common/analytics';
3
3
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
4
+ import { getAnnotationInlineNodeTypes } from '@atlaskit/editor-common/utils';
4
5
  import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
5
6
  import { fg } from '@atlaskit/platform-feature-flags';
6
- import { clearDirtyMark, closeComponent, setHoveredAnnotation, setInlineCommentsVisibility, setSelectedAnnotation, updateInlineCommentResolvedState, updateMouseState } from '../editor-commands';
7
+ import { clearDirtyMark, closeComponent, setHoveredAnnotation, setInlineCommentsVisibility, setSelectedAnnotation, flushPendingSelections, updateInlineCommentResolvedState, updateMouseState, setPendingSelectedAnnotation, setInlineCommentDraftState } from '../editor-commands';
7
8
  import { getAnnotationViewClassname, getBlockAnnotationViewClassname } from '../nodeviews';
8
- import { allowAnnotation, applyDraft, clearDraft, getDraft, startDraft } from './annotation-manager-hooks';
9
+ import { allowAnnotation, applyDraft, clearDraft, clearAnnotation, getDraft, setIsAnnotationHovered, setIsAnnotationSelected, startDraft } from './annotation-manager-hooks';
9
10
  import { createPluginState } from './plugin-factory';
10
11
  import { decorationKey, getAllAnnotations, getPluginState, inlineCommentPluginKey } from './utils';
11
12
  const fetchProviderStates = async (provider, annotationIds) => {
@@ -49,7 +50,9 @@ const initialState = (disallowOnWhitespace = false, featureFlagsPluginState) =>
49
50
  isVisible: true,
50
51
  skipSelectionHandling: false,
51
52
  featureFlagsPluginState,
52
- isDrafting: false
53
+ isDrafting: false,
54
+ pendingSelectedAnnotations: [],
55
+ pendingSelectedAnnotationsUpdateCount: 0
53
56
  };
54
57
  };
55
58
  const hideToolbar = (state, dispatch) => () => {
@@ -103,12 +106,33 @@ export const inlineCommentPlugin = options => {
103
106
  key: inlineCommentPluginKey,
104
107
  state: createPluginState(options.dispatch, initialState(provider.disallowOnWhitespace, featureFlagsPluginState)),
105
108
  view(editorView) {
109
+ let allowAnnotationFn;
110
+ let startDraftFn;
111
+ let clearDraftFn;
112
+ let applyDraftFn;
113
+ let getDraftFn;
114
+ let setIsAnnotationSelectedFn;
115
+ let setIsAnnotationHoveredFn;
116
+ let clearAnnotationFn;
106
117
  if (annotationManager && fg('platform_editor_comments_api_manager')) {
107
- annotationManager.hook('allowAnnotation', allowAnnotation(editorView, options));
108
- annotationManager.hook('startDraft', startDraft(editorView, options));
109
- annotationManager.hook('clearDraft', clearDraft(editorView, options));
110
- annotationManager.hook('applyDraft', applyDraft(editorView, options));
111
- annotationManager.hook('getDraft', getDraft(editorView, options));
118
+ allowAnnotationFn = allowAnnotation(editorView, options);
119
+ startDraftFn = startDraft(editorView, options);
120
+ clearDraftFn = clearDraft(editorView, options);
121
+ applyDraftFn = applyDraft(editorView, options);
122
+ getDraftFn = getDraft(editorView, options);
123
+ annotationManager.hook('allowAnnotation', allowAnnotationFn);
124
+ annotationManager.hook('startDraft', startDraftFn);
125
+ annotationManager.hook('clearDraft', clearDraftFn);
126
+ annotationManager.hook('applyDraft', applyDraftFn);
127
+ annotationManager.hook('getDraft', getDraftFn);
128
+ if (fg('platform_editor_comments_api_manager_select')) {
129
+ setIsAnnotationSelectedFn = setIsAnnotationSelected(editorView, options);
130
+ setIsAnnotationHoveredFn = setIsAnnotationHovered(editorView, options);
131
+ clearAnnotationFn = clearAnnotation(editorView, options);
132
+ annotationManager.hook('setIsAnnotationSelected', setIsAnnotationSelectedFn);
133
+ annotationManager.hook('setIsAnnotationHovered', setIsAnnotationHoveredFn);
134
+ annotationManager.hook('clearAnnotation', clearAnnotationFn);
135
+ }
112
136
  }
113
137
  // Get initial state
114
138
  // Need to pass `editorView` to mitigate editor state going stale
@@ -160,6 +184,13 @@ export const inlineCommentPlugin = options => {
160
184
 
161
185
  // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
162
186
  editorView.root.addEventListener('mouseup', mouseUp);
187
+
188
+ /**
189
+ * This flag is used to prevent the preemptive gate from being called multiple times while a check is in-flight.
190
+ * If a check is still pending then it's most likely because the product is busy and trying to block the
191
+ * selection of an annotation.
192
+ */
193
+ let isPreemptiveGateActive = false;
163
194
  return {
164
195
  update(view, _prevState) {
165
196
  var _prevSelectedAnnotati;
@@ -182,6 +213,83 @@ export const inlineCommentPlugin = options => {
182
213
  // The selectComponentExperience is using a simplified object, which is why it's type asserted.
183
214
  (_options$selectCommen = options.selectCommentExperience) === null || _options$selectCommen === void 0 ? void 0 : _options$selectCommen.selectAnnotation.complete(selectedAnnotationId);
184
215
  }
216
+ if (fg('platform_editor_comments_api_manager_select')) {
217
+ // In the Editor, Annotations can be selected in three ways:
218
+ // 1. By clicking on the annotation in the editor
219
+ // 2. By using the annotation manager to select the annotation
220
+ // 3. By moving the cursor to the annotation and using the keyboard to select it
221
+ // Item 1 & 3 need to be protected by the preemptive gate. This is because these actions could be performed by a user
222
+ // at a time when changing the selection could cause data loss.
223
+ // The following preemptive check is designed to cover these items.
224
+
225
+ const {
226
+ pendingSelectedAnnotations,
227
+ pendingSelectedAnnotationsUpdateCount
228
+ } = getPluginState(view.state) || {};
229
+ const {
230
+ pendingSelectedAnnotationsUpdateCount: prevPendingSelectedAnnotationsUpdateCount
231
+ } = getPluginState(_prevState) || {};
232
+ if (!isPreemptiveGateActive && pendingSelectedAnnotationsUpdateCount !== prevPendingSelectedAnnotationsUpdateCount && !!(pendingSelectedAnnotations !== null && pendingSelectedAnnotations !== void 0 && pendingSelectedAnnotations.length)) {
233
+ // Need to set a lock to avoid calling gate multiple times. The lock will be released
234
+ // when the preemptive gate is complete.
235
+ isPreemptiveGateActive = true;
236
+ annotationManager === null || annotationManager === void 0 ? void 0 : annotationManager.checkPreemptiveGate().then(canSelectAnnotation => {
237
+ const {
238
+ isDrafting,
239
+ pendingSelectedAnnotations: latestPendingSelectedAnnotations,
240
+ selectedAnnotations: latestSelectedAnnotations
241
+ } = getPluginState(view.state) || {};
242
+ if (canSelectAnnotation) {
243
+ if (isDrafting) {
244
+ // The user must have chosen to discard there draft. So before we flush the pending selections
245
+ // we need to clear the draft if there is one.
246
+ setInlineCommentDraftState(options.editorAnalyticsAPI)(false)(view.state, view.dispatch);
247
+ }
248
+
249
+ // Flush the pending selections into the selected annotations list.
250
+ flushPendingSelections(true)(view.state, view.dispatch);
251
+ latestSelectedAnnotations === null || latestSelectedAnnotations === void 0 ? void 0 : latestSelectedAnnotations.filter(annotation => (latestPendingSelectedAnnotations === null || latestPendingSelectedAnnotations === void 0 ? void 0 : latestPendingSelectedAnnotations.findIndex(pendingAnnotation => pendingAnnotation.id === annotation.id)) === -1).forEach(annotation => {
252
+ var _options$annotationMa, _getAnnotationInlineN;
253
+ (_options$annotationMa = options.annotationManager) === null || _options$annotationMa === void 0 ? void 0 : _options$annotationMa.emit({
254
+ name: 'annotationSelectionChanged',
255
+ data: {
256
+ annotationId: annotation.id,
257
+ isSelected: false,
258
+ inlineNodeTypes: (_getAnnotationInlineN = getAnnotationInlineNodeTypes(editorView.state, annotation.id)) !== null && _getAnnotationInlineN !== void 0 ? _getAnnotationInlineN : []
259
+ }
260
+ });
261
+ });
262
+
263
+ // Notify the annotation manager that the pending selection has changed.
264
+ latestPendingSelectedAnnotations === null || latestPendingSelectedAnnotations === void 0 ? void 0 : latestPendingSelectedAnnotations.forEach(({
265
+ id
266
+ }) => {
267
+ var _options$annotationMa2, _getAnnotationInlineN2;
268
+ (_options$annotationMa2 = options.annotationManager) === null || _options$annotationMa2 === void 0 ? void 0 : _options$annotationMa2.emit({
269
+ name: 'annotationSelectionChanged',
270
+ data: {
271
+ annotationId: id,
272
+ isSelected: true,
273
+ inlineNodeTypes: (_getAnnotationInlineN2 = getAnnotationInlineNodeTypes(view.state, id)) !== null && _getAnnotationInlineN2 !== void 0 ? _getAnnotationInlineN2 : []
274
+ }
275
+ });
276
+ });
277
+ } else {
278
+ // Clears the pending selections if the preemptive gate returns false.
279
+ // We should need to worry about dispatching change events here because the pending selections
280
+ // are being aborted and the selections will remain unchanged.
281
+ flushPendingSelections(false)(view.state, view.dispatch);
282
+ }
283
+ }).catch(error => {
284
+ // TODO: EDITOR-595 - Ensure and anlytic is fired to indicate which reports on the error.
285
+
286
+ // If an error has occured we will clear any pending selections to avoid accidentally setting the wrong thing.
287
+ flushPendingSelections(false)(view.state, view.dispatch);
288
+ }).finally(() => {
289
+ isPreemptiveGateActive = false;
290
+ });
291
+ }
292
+ }
185
293
  const {
186
294
  dirtyAnnotations
187
295
  } = getPluginState(view.state) || {};
@@ -197,6 +305,16 @@ export const inlineCommentPlugin = options => {
197
305
  if (updateSubscriber) {
198
306
  updateSubscriber.off('resolve', resolve).off('delete', resolve).off('unresolve', unResolve).off('create', unResolve).off('setvisibility', setVisibility).off('setselectedannotation', setSelectedAnnotationFn).off('sethoveredannotation', setHoveredAnnotationFn).off('removehoveredannotation', removeHoveredannotationFn).off('closeinlinecomment', closeInlineCommentFn);
199
307
  }
308
+ if (annotationManager && fg('platform_editor_comments_api_manager')) {
309
+ annotationManager.unhook('allowAnnotation', allowAnnotationFn);
310
+ annotationManager.unhook('startDraft', startDraftFn);
311
+ annotationManager.unhook('clearDraft', clearDraftFn);
312
+ annotationManager.unhook('applyDraft', applyDraftFn);
313
+ annotationManager.unhook('getDraft', getDraftFn);
314
+ annotationManager.unhook('setIsAnnotationSelected', setIsAnnotationSelectedFn);
315
+ annotationManager.unhook('setIsAnnotationHovered', setIsAnnotationHoveredFn);
316
+ annotationManager.unhook('clearAnnotation', clearAnnotationFn);
317
+ }
200
318
  }
201
319
  };
202
320
  },
@@ -242,7 +360,22 @@ export const inlineCommentPlugin = options => {
242
360
  if (!isUnresolved) {
243
361
  return false;
244
362
  }
245
- setSelectedAnnotation(annotationId)(view.state, view.dispatch);
363
+ if (fg('platform_editor_comments_api_manager_select')) {
364
+ var _pluginState$pendingS;
365
+ // The manager disable setting the selected annotation on click because in the editor this is already
366
+ // handled by the selection update handler. When the manager is enabled, and a selection changes it's pushed into
367
+ // the pendingSelectedAnnotations array. This is then used to update the selection when the preemptive gate
368
+ // is released.
369
+ const isPendingSelection = pluginState === null || pluginState === void 0 ? void 0 : (_pluginState$pendingS = pluginState.pendingSelectedAnnotations) === null || _pluginState$pendingS === void 0 ? void 0 : _pluginState$pendingS.some(selectedAnnotation => selectedAnnotation.id === annotationId);
370
+ // If the annotation is selected and the inline comment view is open, do nothing
371
+ // as the user is already in the comment view.
372
+ if (isPendingSelection) {
373
+ return false;
374
+ }
375
+ setPendingSelectedAnnotation(annotationId)(view.state, view.dispatch);
376
+ } else {
377
+ setSelectedAnnotation(annotationId)(view.state, view.dispatch);
378
+ }
246
379
  return true;
247
380
  }
248
381
  },
@@ -258,6 +391,10 @@ export const inlineCommentPlugin = options => {
258
391
  } = getPluginState(state) || {};
259
392
  let decorations = draftDecorationSet !== null && draftDecorationSet !== void 0 ? draftDecorationSet : DecorationSet.empty;
260
393
  const focusDecorations = [];
394
+
395
+ // TODO: EDITOR-760 - This needs to be optimised, it's not a good idea to scan the entire document
396
+ // everytime we need to update the decorations. This handler will be called alot. We should be caching
397
+ // the decorations in plugin state and only updating them when required.
261
398
  state.doc.descendants((node, pos) => {
262
399
  var _provider$supportedBl;
263
400
  // Inline comment on mediaInline is not supported as part of comments on media project