@atlaskit/editor-plugin-annotation 29.0.6 → 29.1.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @atlaskit/editor-plugin-annotation
2
2
 
3
+ ## 29.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`afd887e901de1`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/afd887e901de1) -
8
+ Discover annotations in legacy content macros and refresh their state when nested annotations are
9
+ added while platform_editor_fix_lcm_editor_annotations is enabled. Editor-scoped renderers can
10
+ load nested LCM annotations independently of the renderer experiment.
11
+
12
+ Allow the ProseMirror test editor helper to be checked with unused declaration checks enabled.
13
+
3
14
  ## 29.0.6
4
15
 
5
16
  ### Patch Changes
@@ -14,6 +14,7 @@ var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
14
14
  var _annotation2 = require("@atlaskit/editor-common/utils/annotation");
15
15
  var _view = require("@atlaskit/editor-prosemirror/view");
16
16
  var _fg = require("@atlaskit/platform-feature-flags/fg");
17
+ var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
17
18
  var _editorCommands = require("../editor-commands");
18
19
  var _utils = require("../editor-commands/utils");
19
20
  var _nodeviews = require("../nodeviews");
@@ -409,6 +410,12 @@ var inlineCommentPlugin = exports.inlineCommentPlugin = function inlineCommentPl
409
410
  if (!(event.target instanceof HTMLElement)) {
410
411
  return false;
411
412
  }
413
+ if ((0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_fix_lcm_editor_annotations')) {
414
+ // The LCM renderer bridge owns selection so its draft-close guard runs first.
415
+ if (event.target.closest('[data-lcm-annotation-renderer]')) {
416
+ return false;
417
+ }
418
+ }
412
419
 
413
420
  // Find the nearest ancestor (or self) with the data-id attribute
414
421
  var annotationId = (_event$target$closest = event.target.closest('[data-id]')) === null || _event$target$closest === void 0 ? void 0 : _event$target$closest.getAttribute('data-id');
@@ -5,8 +5,10 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.shouldClearBookMarkCheck = exports.createPluginState = exports.createCommand = void 0;
8
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
8
9
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
10
  var _pluginStateFactory = require("@atlaskit/editor-common/utils/plugin-state-factory");
11
+ var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
10
12
  var _state = require("@atlaskit/editor-prosemirror/state");
11
13
  var _view = require("@atlaskit/editor-prosemirror/view");
12
14
  var _reducer = _interopRequireDefault(require("./reducer"));
@@ -14,12 +16,11 @@ var _utils = require("./utils");
14
16
  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; }
15
17
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
16
18
  /**
17
- * Creates a handleDocChanged function with its own deleted annotations cache.
18
- * This ensures each editor instance has its own cache, avoiding cross-contamination.
19
+ * Creates a document-sync handler with a deleted annotations cache in its closure.
19
20
  */
20
21
  var createHandleDocChanged = function createHandleDocChanged() {
21
22
  // Cache for preserving deleted annotation resolved states through delete/undo cycles
22
- // This lives in closure scope per editor instance to avoid serialization and reduce state size
23
+ // This stays outside plugin state so the undo cache is not serialized.
23
24
  var deletedAnnotationsCache = {};
24
25
  return function (tr, prevPluginState) {
25
26
  if (tr.getMeta('replaceDocument')) {
@@ -28,16 +29,24 @@ var createHandleDocChanged = function createHandleDocChanged() {
28
29
  });
29
30
  }
30
31
  var updatedState = getSelectionChangedHandler(false)(tr, prevPluginState);
32
+ var legacyContentExperimentEnabled = (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_fix_lcm_editor_annotations');
33
+ var currentDiscovery = legacyContentExperimentEnabled ? (0, _utils.getAnnotationDiscovery)(tr.doc) : undefined;
34
+ var previousAnnotationIds = currentDiscovery ? new Set((0, _utils.getAnnotationDiscovery)(tr.before).annotationIds) : undefined;
35
+ var newNestedAnnotationIds = currentDiscovery ? currentDiscovery.nestedAnnotationIds.filter(function (id) {
36
+ return !(previousAnnotationIds !== null && previousAnnotationIds !== void 0 && previousAnnotationIds.has(id)) && prevPluginState.annotations[id] === undefined && deletedAnnotationsCache[id] === undefined;
37
+ }) : [];
31
38
 
32
39
  // Collect annotation IDs currently in the document
33
- var annotationIdsInDocument = new Set();
34
- tr.doc.descendants(function (node) {
35
- node.marks.forEach(function (mark) {
36
- if (mark.type.name === 'annotation') {
37
- annotationIdsInDocument.add(mark.attrs.id);
38
- }
40
+ var annotationIdsInDocument = currentDiscovery ? new Set([].concat((0, _toConsumableArray2.default)(currentDiscovery.rootAnnotationIds), (0, _toConsumableArray2.default)(currentDiscovery.nestedAnnotationIds))) : new Set();
41
+ if (!currentDiscovery) {
42
+ tr.doc.descendants(function (node) {
43
+ node.marks.forEach(function (mark) {
44
+ if (mark.type.name === 'annotation') {
45
+ annotationIdsInDocument.add(mark.attrs.id);
46
+ }
47
+ });
39
48
  });
40
- });
49
+ }
41
50
  var annotationIdsInState = Object.keys(prevPluginState.annotations);
42
51
 
43
52
  // Early return if annotations haven't changed
@@ -45,7 +54,9 @@ var createHandleDocChanged = function createHandleDocChanged() {
45
54
  return !annotationIdsInDocument.has(id);
46
55
  });
47
56
  if (!annotationsHaveChanged) {
48
- return updatedState;
57
+ return newNestedAnnotationIds.length > 0 ? _objectSpread(_objectSpread({}, updatedState), {}, {
58
+ dirtyAnnotations: true
59
+ }) : updatedState;
49
60
  }
50
61
 
51
62
  // Cache deleted annotations to be able to restore their resolved states on undo
@@ -68,6 +79,8 @@ var createHandleDocChanged = function createHandleDocChanged() {
68
79
  });
69
80
  return _objectSpread(_objectSpread({}, updatedState), {}, {
70
81
  annotations: updatedAnnotations
82
+ }, newNestedAnnotationIds.length > 0 && {
83
+ dirtyAnnotations: true
71
84
  });
72
85
  };
73
86
  };
@@ -195,47 +208,52 @@ var getSelectionChangedHandler = function getSelectionChangedHandler(reopenComme
195
208
  getSelectionChangeHandlerOld(reopenCommentView)(tr, pluginState);
196
209
  };
197
210
  };
211
+ var createAnnotationPluginFactory = function createAnnotationPluginFactory(onDocChanged) {
212
+ return (0, _pluginStateFactory.pluginFactory)(_utils.inlineCommentPluginKey, _reducer.default, {
213
+ onSelectionChanged: getSelectionChangedHandler(true),
214
+ onDocChanged: onDocChanged,
215
+ mapping: function mapping(tr, pluginState, editorState) {
216
+ var draftDecorationSet = pluginState.draftDecorationSet,
217
+ bookmark = pluginState.bookmark;
218
+ var mappedDecorationSet = _view.DecorationSet.empty,
219
+ mappedBookmark;
220
+ var hasMappedDecorations = false;
221
+ if (draftDecorationSet) {
222
+ mappedDecorationSet = draftDecorationSet.map(tr.mapping, tr.doc);
223
+ }
224
+ hasMappedDecorations = mappedDecorationSet.find(undefined, undefined, function (spec) {
225
+ return Object.values(_utils.decorationKey).includes(spec.key);
226
+ }).length > 0;
198
227
 
199
- // Create the handler with cache once at module level
200
- var handleDocChangedWithSync = createHandleDocChanged();
201
- var dest = (0, _pluginStateFactory.pluginFactory)(_utils.inlineCommentPluginKey, _reducer.default, {
202
- onSelectionChanged: getSelectionChangedHandler(true),
203
- onDocChanged: handleDocChangedWithSync,
204
- mapping: function mapping(tr, pluginState, editorState) {
205
- var draftDecorationSet = pluginState.draftDecorationSet,
206
- bookmark = pluginState.bookmark;
207
- var mappedDecorationSet = _view.DecorationSet.empty,
208
- mappedBookmark;
209
- var hasMappedDecorations = false;
210
- if (draftDecorationSet) {
211
- mappedDecorationSet = draftDecorationSet.map(tr.mapping, tr.doc);
212
- }
213
- hasMappedDecorations = mappedDecorationSet.find(undefined, undefined, function (spec) {
214
- return Object.values(_utils.decorationKey).includes(spec.key);
215
- }).length > 0;
228
+ // When changes to decoration target make decoration invalid (e.g. delete text, add mark to node),
229
+ // we need to reset bookmark to hide create component and to avoid invalid draft being published
230
+ // We only perform this change when document selection has changed.
231
+ if (!hasMappedDecorations && shouldClearBookMarkCheck(tr, editorState, bookmark)) {
232
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
233
+ draftDecorationSet: mappedDecorationSet,
234
+ bookmark: undefined
235
+ });
236
+ }
237
+ if (bookmark) {
238
+ mappedBookmark = bookmark.map(tr.mapping);
239
+ }
216
240
 
217
- // When changes to decoration target make decoration invalid (e.g. delete text, add mark to node),
218
- // we need to reset bookmark to hide create component and to avoid invalid draft being published
219
- // We only perform this change when document selection has changed.
220
- if (!hasMappedDecorations && shouldClearBookMarkCheck(tr, editorState, bookmark)) {
241
+ // return same pluginState if mappings did not change
242
+ if (mappedBookmark === bookmark && mappedDecorationSet === draftDecorationSet) {
243
+ return pluginState;
244
+ }
221
245
  return _objectSpread(_objectSpread({}, pluginState), {}, {
222
246
  draftDecorationSet: mappedDecorationSet,
223
- bookmark: undefined
247
+ bookmark: mappedBookmark
224
248
  });
225
249
  }
226
- if (bookmark) {
227
- mappedBookmark = bookmark.map(tr.mapping);
228
- }
229
-
230
- // return same pluginState if mappings did not change
231
- if (mappedBookmark === bookmark && mappedDecorationSet === draftDecorationSet) {
232
- return pluginState;
233
- }
234
- return _objectSpread(_objectSpread({}, pluginState), {}, {
235
- draftDecorationSet: mappedDecorationSet,
236
- bookmark: mappedBookmark
237
- });
238
- }
239
- });
240
- var createPluginState = exports.createPluginState = dest.createPluginState;
241
- var createCommand = exports.createCommand = dest.createCommand;
250
+ });
251
+ };
252
+ var handleDocChangedWithSync = createHandleDocChanged();
253
+ // Keep the control cohort's existing module-level cache lifetime unchanged.
254
+ var legacyDest = createAnnotationPluginFactory(handleDocChangedWithSync);
255
+ var commandFactory = (0, _pluginStateFactory.pluginFactory)(_utils.inlineCommentPluginKey, _reducer.default);
256
+ var createPluginState = exports.createPluginState = function createPluginState(dispatch, initialState) {
257
+ return ((0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_fix_lcm_editor_annotations') ? createAnnotationPluginFactory(createHandleDocChanged()) : legacyDest).createPluginState(dispatch, initialState);
258
+ };
259
+ var createCommand = exports.createCommand = commandFactory.createCommand;
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.addDraftDecoration = void 0;
8
8
  exports.annotationExists = annotationExists;
9
- exports.getPluginState = exports.getDraftCommandAnalyticsPayload = exports.getAnnotationViewKey = exports.getAllAnnotations = exports.findAnnotationsInSelection = exports.decorationKey = void 0;
9
+ exports.getPluginState = exports.getDraftCommandAnalyticsPayload = exports.getAnnotationViewKey = exports.getAnnotationDiscovery = exports.getAllAnnotations = exports.findAnnotationsInSelection = exports.decorationKey = void 0;
10
10
  exports.getSelectionPositions = getSelectionPositions;
11
11
  exports.hasInvalidNodes = exports.hasAnyUnResolvedAnnotationInPage = void 0;
12
12
  exports.hasInvalidWhitespaceNode = hasInvalidWhitespaceNode;
@@ -16,12 +16,14 @@ exports.shouldHideRemixAnnotation = exports.resolveDraftBookmark = exports.isSup
16
16
  exports.stripNonExistingAnnotations = stripNonExistingAnnotations;
17
17
  exports.surroundingMarks = void 0;
18
18
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
19
+ var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
19
20
  var _annotation = require("@atlaskit/adf-schema/annotation");
20
21
  var _enums = require("@atlaskit/editor-common/analytics/types/enums");
21
22
  var _mediaSingle = require("@atlaskit/editor-common/media-single");
22
23
  var _annotation2 = require("@atlaskit/editor-common/styles/annotation");
23
24
  var _annotation3 = require("@atlaskit/editor-common/utils/annotation");
24
25
  var _nodes = require("@atlaskit/editor-common/utils/nodes");
26
+ var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
25
27
  var _state = require("@atlaskit/editor-prosemirror/state");
26
28
  var _view = require("@atlaskit/editor-prosemirror/view");
27
29
  var _fg = require("@atlaskit/platform-feature-flags/fg");
@@ -79,7 +81,111 @@ var reorderAnnotations = function reorderAnnotations(annotations, $from) {
79
81
  });
80
82
  });
81
83
  };
84
+ var legacyContentAnnotationsByDoc = new WeakMap();
85
+ var legacyContentAnnotationIdsByExtension = new WeakMap();
86
+ var readNestedContentNodes = function readNestedContentNodes(parameters) {
87
+ if (!parameters || (0, _typeof2.default)(parameters) !== 'object') {
88
+ return undefined;
89
+ }
90
+ var nestedContent = parameters.nestedContent;
91
+ if (!nestedContent || (0, _typeof2.default)(nestedContent) !== 'object') {
92
+ return undefined;
93
+ }
94
+ var nestedDocument = nestedContent;
95
+ return nestedDocument.type === 'doc' && Array.isArray(nestedDocument.content) ? nestedDocument.content : undefined;
96
+ };
97
+ var getNestedContentAnnotationIds = function getNestedContentAnnotationIds(extension) {
98
+ var cachedIds = legacyContentAnnotationIdsByExtension.get(extension);
99
+ if (cachedIds) {
100
+ return cachedIds;
101
+ }
102
+ var nestedNodes = readNestedContentNodes(extension.attrs.parameters);
103
+ if (!nestedNodes) {
104
+ var noIds = Object.freeze([]);
105
+ legacyContentAnnotationIdsByExtension.set(extension, noIds);
106
+ return noIds;
107
+ }
108
+ var annotationIds = new Set();
109
+ var visitedAdfNodes = new WeakSet();
110
+ var pending = (0, _toConsumableArray2.default)(nestedNodes).reverse();
111
+ while (pending.length > 0) {
112
+ var _adfNode$attrs;
113
+ var value = pending.pop();
114
+ if (!value || (0, _typeof2.default)(value) !== 'object' || visitedAdfNodes.has(value)) {
115
+ continue;
116
+ }
117
+ visitedAdfNodes.add(value);
118
+ var adfNode = value;
119
+ if (Array.isArray(adfNode.marks)) {
120
+ adfNode.marks.forEach(function (rawMark) {
121
+ if (!rawMark || (0, _typeof2.default)(rawMark) !== 'object') {
122
+ return;
123
+ }
124
+ var annotationMark = rawMark;
125
+ var attrs = annotationMark.attrs;
126
+ if (annotationMark.type !== 'annotation' || !validateAnnotationAttributes(attrs)) {
127
+ return;
128
+ }
129
+ annotationIds.add(attrs.id);
130
+ });
131
+ }
132
+ if (adfNode.type === 'extension' && ((_adfNode$attrs = adfNode.attrs) === null || _adfNode$attrs === void 0 ? void 0 : _adfNode$attrs.extensionType) === 'com.atlassian.confluence.migration' && adfNode.attrs.extensionKey === 'legacy-content') {
133
+ var _nestedNodes = readNestedContentNodes(adfNode.attrs.parameters);
134
+ if (_nestedNodes) {
135
+ for (var i = _nestedNodes.length - 1; i >= 0; i--) {
136
+ pending.push(_nestedNodes[i]);
137
+ }
138
+ }
139
+ }
140
+ if (Array.isArray(adfNode.content)) {
141
+ for (var _i = adfNode.content.length - 1; _i >= 0; _i--) {
142
+ pending.push(adfNode.content[_i]);
143
+ }
144
+ }
145
+ }
146
+ var ids = Object.freeze((0, _toConsumableArray2.default)(annotationIds));
147
+ legacyContentAnnotationIdsByExtension.set(extension, ids);
148
+ return ids;
149
+ };
150
+
151
+ /** Discover annotation IDs from immutable legacy-content ADF without repeatedly scanning it. */
152
+ var getAnnotationDiscovery = exports.getAnnotationDiscovery = function getAnnotationDiscovery(doc) {
153
+ var cachedDiscovery = legacyContentAnnotationsByDoc.get(doc);
154
+ if (cachedDiscovery) {
155
+ return cachedDiscovery;
156
+ }
157
+ var allAnnotationIds = new Set();
158
+ var rootAnnotationIds = new Set();
159
+ var nestedAnnotationIds = new Set();
160
+ doc.descendants(function (node) {
161
+ node.marks.filter(function (mark) {
162
+ return mark.type.name === 'annotation';
163
+ }).forEach(function (mark) {
164
+ if (validateAnnotationMark(mark)) {
165
+ rootAnnotationIds.add(mark.attrs.id);
166
+ allAnnotationIds.add(mark.attrs.id);
167
+ }
168
+ });
169
+ if (node.type.name === 'extension' && node.attrs.extensionType === 'com.atlassian.confluence.migration' && node.attrs.extensionKey === 'legacy-content') {
170
+ getNestedContentAnnotationIds(node).forEach(function (id) {
171
+ nestedAnnotationIds.add(id);
172
+ allAnnotationIds.add(id);
173
+ });
174
+ }
175
+ return true;
176
+ });
177
+ var discovery = {
178
+ annotationIds: Object.freeze((0, _toConsumableArray2.default)(allAnnotationIds)),
179
+ rootAnnotationIds: Object.freeze((0, _toConsumableArray2.default)(rootAnnotationIds)),
180
+ nestedAnnotationIds: Object.freeze((0, _toConsumableArray2.default)(nestedAnnotationIds))
181
+ };
182
+ legacyContentAnnotationsByDoc.set(doc, discovery);
183
+ return discovery;
184
+ };
82
185
  var getAllAnnotations = exports.getAllAnnotations = function getAllAnnotations(doc) {
186
+ if ((0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_fix_lcm_editor_annotations')) {
187
+ return (0, _toConsumableArray2.default)(getAnnotationDiscovery(doc).annotationIds);
188
+ }
83
189
  var allAnnotationIds = new Set();
84
190
  doc.descendants(function (node) {
85
191
  node.marks.filter(function (mark) {
@@ -93,29 +199,30 @@ var getAllAnnotations = exports.getAllAnnotations = function getAllAnnotations(d
93
199
  });
94
200
  return Array.from(allAnnotationIds);
95
201
  };
96
-
97
- /*
98
- * verifies if annotation mark contains valid attributes
99
- */
100
- var validateAnnotationMark = function validateAnnotationMark(annotationMark) {
101
- var _ref = annotationMark.attrs,
102
- id = _ref.id,
103
- annotationType = _ref.annotationType;
104
- return validateAnnotationId(id) && validateAnnotationType(annotationType);
105
- function validateAnnotationId(id) {
106
- if (!id || typeof id !== 'string') {
107
- return false;
108
- }
109
- var invalidIds = ['null', 'undefined'];
110
- return !invalidIds.includes(id.toLowerCase());
202
+ var validateAnnotationId = function validateAnnotationId(id) {
203
+ if (!id || typeof id !== 'string') {
204
+ return false;
111
205
  }
112
- function validateAnnotationType(type) {
113
- if (!type || typeof type !== 'string') {
114
- return false;
115
- }
116
- var allowedTypes = Object.values(_annotation.AnnotationTypes);
117
- return allowedTypes.includes(type);
206
+ var invalidIds = ['null', 'undefined'];
207
+ return !invalidIds.includes(id.toLowerCase());
208
+ };
209
+ var validateAnnotationType = function validateAnnotationType(type) {
210
+ if (!type || typeof type !== 'string') {
211
+ return false;
118
212
  }
213
+ var allowedTypes = Object.values(_annotation.AnnotationTypes);
214
+ return allowedTypes.some(function (allowedType) {
215
+ return allowedType === type;
216
+ });
217
+ };
218
+ var validateAnnotationAttributes = function validateAnnotationAttributes(attrs) {
219
+ if (!attrs || (0, _typeof2.default)(attrs) !== 'object') {
220
+ return false;
221
+ }
222
+ return 'id' in attrs && 'annotationType' in attrs && validateAnnotationId(attrs.id) && validateAnnotationType(attrs.annotationType);
223
+ };
224
+ var validateAnnotationMark = function validateAnnotationMark(annotationMark) {
225
+ return validateAnnotationAttributes(annotationMark.attrs);
119
226
  };
120
227
  var decorationKey = exports.decorationKey = {
121
228
  block: 'blockCommentDecoration',
@@ -306,8 +413,8 @@ var getDraftCommandAnalyticsPayload = exports.getDraftCommandAnalyticsPayload =
306
413
  };
307
414
  }
308
415
  if ((0, _fg.fg)('editor_inline_comments_on_inline_nodes')) {
309
- var _ref2 = getPluginState(state) || {},
310
- bookmark = _ref2.bookmark;
416
+ var _ref = getPluginState(state) || {},
417
+ bookmark = _ref.bookmark;
311
418
  attributes.inlineNodeNames = (0, _annotation3.getRangeInlineNodeNames)({
312
419
  doc: state.doc,
313
420
  pos: resolveDraftBookmark(state, bookmark)
@@ -328,8 +435,8 @@ var isSelectionValid = exports.isSelectionValid = function isSelectionValid(stat
328
435
  var supportedBlockNodes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
329
436
  var isBlockNodeSupported = arguments.length > 2 ? arguments[2] : undefined;
330
437
  var selection = state.selection;
331
- var _ref3 = getPluginState(state) || {},
332
- disallowOnWhitespace = _ref3.disallowOnWhitespace;
438
+ var _ref2 = getPluginState(state) || {},
439
+ disallowOnWhitespace = _ref2.disallowOnWhitespace;
333
440
  var allowedInlineNodes = ['emoji', 'status', 'date', 'mention', 'inlineCard'];
334
441
  var isSelectionEmpty = selection.empty;
335
442
  var isTextOrAllSelection = selection instanceof _state.TextSelection || selection instanceof _state.AllSelection;
@@ -4,6 +4,7 @@ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
4
4
  import { getAnnotationInlineNodeTypes } from '@atlaskit/editor-common/utils/annotation';
5
5
  import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
6
6
  import { fg } from '@atlaskit/platform-feature-flags/fg';
7
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
7
8
  import { clearDirtyMark, closeComponent, setHoveredAnnotation, setInlineCommentsVisibility, setSelectedAnnotation, flushPendingSelections, updateInlineCommentResolvedState, updateMouseState, setPendingSelectedAnnotation, setInlineCommentDraftState, setInlineCommentsFetched } from '../editor-commands';
8
9
  import { resetUserIntent, setUserIntent } from '../editor-commands/utils';
9
10
  import { getAnnotationViewClassname, getBlockAnnotationViewClassname } from '../nodeviews';
@@ -353,6 +354,12 @@ export const inlineCommentPlugin = options => {
353
354
  if (!(event.target instanceof HTMLElement)) {
354
355
  return false;
355
356
  }
357
+ if (isExperimentEnabled('platform_editor_fix_lcm_editor_annotations')) {
358
+ // The LCM renderer bridge owns selection so its draft-close guard runs first.
359
+ if (event.target.closest('[data-lcm-annotation-renderer]')) {
360
+ return false;
361
+ }
362
+ }
356
363
 
357
364
  // Find the nearest ancestor (or self) with the data-id attribute
358
365
  const annotationId = (_event$target$closest = event.target.closest('[data-id]')) === null || _event$target$closest === void 0 ? void 0 : _event$target$closest.getAttribute('data-id');
@@ -1,16 +1,16 @@
1
1
  import { pluginFactory } from '@atlaskit/editor-common/utils/plugin-state-factory';
2
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
2
3
  import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
3
4
  import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
4
5
  import reducer from './reducer';
5
- import { decorationKey, findAnnotationsInSelection, inlineCommentPluginKey, isBlockNodeAnnotationsSelected, isSelectedAnnotationsChanged } from './utils';
6
+ import { decorationKey, getAnnotationDiscovery, findAnnotationsInSelection, inlineCommentPluginKey, isBlockNodeAnnotationsSelected, isSelectedAnnotationsChanged } from './utils';
6
7
 
7
8
  /**
8
- * Creates a handleDocChanged function with its own deleted annotations cache.
9
- * This ensures each editor instance has its own cache, avoiding cross-contamination.
9
+ * Creates a document-sync handler with a deleted annotations cache in its closure.
10
10
  */
11
11
  const createHandleDocChanged = () => {
12
12
  // Cache for preserving deleted annotation resolved states through delete/undo cycles
13
- // This lives in closure scope per editor instance to avoid serialization and reduce state size
13
+ // This stays outside plugin state so the undo cache is not serialized.
14
14
  const deletedAnnotationsCache = {};
15
15
  return (tr, prevPluginState) => {
16
16
  if (tr.getMeta('replaceDocument')) {
@@ -20,22 +20,31 @@ const createHandleDocChanged = () => {
20
20
  };
21
21
  }
22
22
  const updatedState = getSelectionChangedHandler(false)(tr, prevPluginState);
23
+ const legacyContentExperimentEnabled = isExperimentEnabled('platform_editor_fix_lcm_editor_annotations');
24
+ const currentDiscovery = legacyContentExperimentEnabled ? getAnnotationDiscovery(tr.doc) : undefined;
25
+ const previousAnnotationIds = currentDiscovery ? new Set(getAnnotationDiscovery(tr.before).annotationIds) : undefined;
26
+ const newNestedAnnotationIds = currentDiscovery ? currentDiscovery.nestedAnnotationIds.filter(id => !(previousAnnotationIds !== null && previousAnnotationIds !== void 0 && previousAnnotationIds.has(id)) && prevPluginState.annotations[id] === undefined && deletedAnnotationsCache[id] === undefined) : [];
23
27
 
24
28
  // Collect annotation IDs currently in the document
25
- const annotationIdsInDocument = new Set();
26
- tr.doc.descendants(node => {
27
- node.marks.forEach(mark => {
28
- if (mark.type.name === 'annotation') {
29
- annotationIdsInDocument.add(mark.attrs.id);
30
- }
29
+ const annotationIdsInDocument = currentDiscovery ? new Set([...currentDiscovery.rootAnnotationIds, ...currentDiscovery.nestedAnnotationIds]) : new Set();
30
+ if (!currentDiscovery) {
31
+ tr.doc.descendants(node => {
32
+ node.marks.forEach(mark => {
33
+ if (mark.type.name === 'annotation') {
34
+ annotationIdsInDocument.add(mark.attrs.id);
35
+ }
36
+ });
31
37
  });
32
- });
38
+ }
33
39
  const annotationIdsInState = Object.keys(prevPluginState.annotations);
34
40
 
35
41
  // Early return if annotations haven't changed
36
42
  const annotationsHaveChanged = annotationIdsInDocument.size !== annotationIdsInState.length || annotationIdsInState.some(id => !annotationIdsInDocument.has(id));
37
43
  if (!annotationsHaveChanged) {
38
- return updatedState;
44
+ return newNestedAnnotationIds.length > 0 ? {
45
+ ...updatedState,
46
+ dirtyAnnotations: true
47
+ } : updatedState;
39
48
  }
40
49
 
41
50
  // Cache deleted annotations to be able to restore their resolved states on undo
@@ -58,7 +67,10 @@ const createHandleDocChanged = () => {
58
67
  });
59
68
  return {
60
69
  ...updatedState,
61
- annotations: updatedAnnotations
70
+ annotations: updatedAnnotations,
71
+ ...(newNestedAnnotationIds.length > 0 && {
72
+ dirtyAnnotations: true
73
+ })
62
74
  };
63
75
  };
64
76
  };
@@ -194,12 +206,9 @@ const getSelectionChangedHandler = reopenCommentView => (tr, pluginState) => plu
194
206
  getSelectionChangeHandlerNew(reopenCommentView)(tr, pluginState) :
195
207
  // else if platform_editor_comments_api_manager == false
196
208
  getSelectionChangeHandlerOld(reopenCommentView)(tr, pluginState);
197
-
198
- // Create the handler with cache once at module level
199
- const handleDocChangedWithSync = createHandleDocChanged();
200
- const dest = pluginFactory(inlineCommentPluginKey, reducer, {
209
+ const createAnnotationPluginFactory = onDocChanged => pluginFactory(inlineCommentPluginKey, reducer, {
201
210
  onSelectionChanged: getSelectionChangedHandler(true),
202
- onDocChanged: handleDocChangedWithSync,
211
+ onDocChanged,
203
212
  mapping: (tr, pluginState, editorState) => {
204
213
  const {
205
214
  draftDecorationSet,
@@ -238,5 +247,9 @@ const dest = pluginFactory(inlineCommentPluginKey, reducer, {
238
247
  };
239
248
  }
240
249
  });
241
- export const createPluginState = dest.createPluginState;
242
- export const createCommand = dest.createCommand;
250
+ const handleDocChangedWithSync = createHandleDocChanged();
251
+ // Keep the control cohort's existing module-level cache lifetime unchanged.
252
+ const legacyDest = createAnnotationPluginFactory(handleDocChangedWithSync);
253
+ const commandFactory = pluginFactory(inlineCommentPluginKey, reducer);
254
+ export const createPluginState = (dispatch, initialState) => (isExperimentEnabled('platform_editor_fix_lcm_editor_annotations') ? createAnnotationPluginFactory(createHandleDocChanged()) : legacyDest).createPluginState(dispatch, initialState);
255
+ export const createCommand = commandFactory.createCommand;
@@ -4,6 +4,7 @@ import { currentMediaNodeWithPos } from '@atlaskit/editor-common/media-single';
4
4
  import { AnnotationSharedClassNames, BlockAnnotationSharedClassNames } from '@atlaskit/editor-common/styles/annotation';
5
5
  import { canApplyAnnotationOnRange, getAnnotationIdsFromRange, getRangeInlineNodeNames, hasAnnotationMark, isEmptyTextSelection } from '@atlaskit/editor-common/utils/annotation';
6
6
  import { isParagraph, isText } from '@atlaskit/editor-common/utils/nodes';
7
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
7
8
  import { AllSelection, NodeSelection, PluginKey, TextSelection } from '@atlaskit/editor-prosemirror/state';
8
9
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
9
10
  import { fg } from '@atlaskit/platform-feature-flags/fg';
@@ -53,7 +54,109 @@ const reorderAnnotations = (annotations, $from) => {
53
54
  const idSet = surroundingMarks($from).map(filterAnnotationIds);
54
55
  annotations.sort((a, b) => sum(idSet, ids => ids.indexOf(a.id)) - sum(idSet, ids => ids.indexOf(b.id)));
55
56
  };
57
+ const legacyContentAnnotationsByDoc = new WeakMap();
58
+ const legacyContentAnnotationIdsByExtension = new WeakMap();
59
+ const readNestedContentNodes = parameters => {
60
+ if (!parameters || typeof parameters !== 'object') {
61
+ return undefined;
62
+ }
63
+ const nestedContent = parameters.nestedContent;
64
+ if (!nestedContent || typeof nestedContent !== 'object') {
65
+ return undefined;
66
+ }
67
+ const nestedDocument = nestedContent;
68
+ return nestedDocument.type === 'doc' && Array.isArray(nestedDocument.content) ? nestedDocument.content : undefined;
69
+ };
70
+ const getNestedContentAnnotationIds = extension => {
71
+ const cachedIds = legacyContentAnnotationIdsByExtension.get(extension);
72
+ if (cachedIds) {
73
+ return cachedIds;
74
+ }
75
+ const nestedNodes = readNestedContentNodes(extension.attrs.parameters);
76
+ if (!nestedNodes) {
77
+ const noIds = Object.freeze([]);
78
+ legacyContentAnnotationIdsByExtension.set(extension, noIds);
79
+ return noIds;
80
+ }
81
+ const annotationIds = new Set();
82
+ const visitedAdfNodes = new WeakSet();
83
+ const pending = [...nestedNodes].reverse();
84
+ while (pending.length > 0) {
85
+ var _adfNode$attrs;
86
+ const value = pending.pop();
87
+ if (!value || typeof value !== 'object' || visitedAdfNodes.has(value)) {
88
+ continue;
89
+ }
90
+ visitedAdfNodes.add(value);
91
+ const adfNode = value;
92
+ if (Array.isArray(adfNode.marks)) {
93
+ adfNode.marks.forEach(rawMark => {
94
+ if (!rawMark || typeof rawMark !== 'object') {
95
+ return;
96
+ }
97
+ const annotationMark = rawMark;
98
+ const attrs = annotationMark.attrs;
99
+ if (annotationMark.type !== 'annotation' || !validateAnnotationAttributes(attrs)) {
100
+ return;
101
+ }
102
+ annotationIds.add(attrs.id);
103
+ });
104
+ }
105
+ if (adfNode.type === 'extension' && ((_adfNode$attrs = adfNode.attrs) === null || _adfNode$attrs === void 0 ? void 0 : _adfNode$attrs.extensionType) === 'com.atlassian.confluence.migration' && adfNode.attrs.extensionKey === 'legacy-content') {
106
+ const nestedNodes = readNestedContentNodes(adfNode.attrs.parameters);
107
+ if (nestedNodes) {
108
+ for (let i = nestedNodes.length - 1; i >= 0; i--) {
109
+ pending.push(nestedNodes[i]);
110
+ }
111
+ }
112
+ }
113
+ if (Array.isArray(adfNode.content)) {
114
+ for (let i = adfNode.content.length - 1; i >= 0; i--) {
115
+ pending.push(adfNode.content[i]);
116
+ }
117
+ }
118
+ }
119
+ const ids = Object.freeze([...annotationIds]);
120
+ legacyContentAnnotationIdsByExtension.set(extension, ids);
121
+ return ids;
122
+ };
123
+
124
+ /** Discover annotation IDs from immutable legacy-content ADF without repeatedly scanning it. */
125
+ export const getAnnotationDiscovery = doc => {
126
+ const cachedDiscovery = legacyContentAnnotationsByDoc.get(doc);
127
+ if (cachedDiscovery) {
128
+ return cachedDiscovery;
129
+ }
130
+ const allAnnotationIds = new Set();
131
+ const rootAnnotationIds = new Set();
132
+ const nestedAnnotationIds = new Set();
133
+ doc.descendants(node => {
134
+ node.marks.filter(mark => mark.type.name === 'annotation').forEach(mark => {
135
+ if (validateAnnotationMark(mark)) {
136
+ rootAnnotationIds.add(mark.attrs.id);
137
+ allAnnotationIds.add(mark.attrs.id);
138
+ }
139
+ });
140
+ if (node.type.name === 'extension' && node.attrs.extensionType === 'com.atlassian.confluence.migration' && node.attrs.extensionKey === 'legacy-content') {
141
+ getNestedContentAnnotationIds(node).forEach(id => {
142
+ nestedAnnotationIds.add(id);
143
+ allAnnotationIds.add(id);
144
+ });
145
+ }
146
+ return true;
147
+ });
148
+ const discovery = {
149
+ annotationIds: Object.freeze([...allAnnotationIds]),
150
+ rootAnnotationIds: Object.freeze([...rootAnnotationIds]),
151
+ nestedAnnotationIds: Object.freeze([...nestedAnnotationIds])
152
+ };
153
+ legacyContentAnnotationsByDoc.set(doc, discovery);
154
+ return discovery;
155
+ };
56
156
  export const getAllAnnotations = doc => {
157
+ if (isExperimentEnabled('platform_editor_fix_lcm_editor_annotations')) {
158
+ return [...getAnnotationDiscovery(doc).annotationIds];
159
+ }
57
160
  const allAnnotationIds = new Set();
58
161
  doc.descendants(node => {
59
162
  node.marks.filter(mark => mark.type.name === 'annotation')
@@ -63,31 +166,27 @@ export const getAllAnnotations = doc => {
63
166
  });
64
167
  return Array.from(allAnnotationIds);
65
168
  };
66
-
67
- /*
68
- * verifies if annotation mark contains valid attributes
69
- */
70
- const validateAnnotationMark = annotationMark => {
71
- const {
72
- id,
73
- annotationType
74
- } = annotationMark.attrs;
75
- return validateAnnotationId(id) && validateAnnotationType(annotationType);
76
- function validateAnnotationId(id) {
77
- if (!id || typeof id !== 'string') {
78
- return false;
79
- }
80
- const invalidIds = ['null', 'undefined'];
81
- return !invalidIds.includes(id.toLowerCase());
169
+ const validateAnnotationId = id => {
170
+ if (!id || typeof id !== 'string') {
171
+ return false;
82
172
  }
83
- function validateAnnotationType(type) {
84
- if (!type || typeof type !== 'string') {
85
- return false;
86
- }
87
- const allowedTypes = Object.values(AnnotationTypes);
88
- return allowedTypes.includes(type);
173
+ const invalidIds = ['null', 'undefined'];
174
+ return !invalidIds.includes(id.toLowerCase());
175
+ };
176
+ const validateAnnotationType = type => {
177
+ if (!type || typeof type !== 'string') {
178
+ return false;
179
+ }
180
+ const allowedTypes = Object.values(AnnotationTypes);
181
+ return allowedTypes.some(allowedType => allowedType === type);
182
+ };
183
+ const validateAnnotationAttributes = attrs => {
184
+ if (!attrs || typeof attrs !== 'object') {
185
+ return false;
89
186
  }
187
+ return 'id' in attrs && 'annotationType' in attrs && validateAnnotationId(attrs.id) && validateAnnotationType(attrs.annotationType);
90
188
  };
189
+ const validateAnnotationMark = annotationMark => validateAnnotationAttributes(annotationMark.attrs);
91
190
  export const decorationKey = {
92
191
  block: 'blockCommentDecoration',
93
192
  inline: 'inlineCommentDecoration'
@@ -7,6 +7,7 @@ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
7
7
  import { getAnnotationInlineNodeTypes } from '@atlaskit/editor-common/utils/annotation';
8
8
  import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
9
9
  import { fg } from '@atlaskit/platform-feature-flags/fg';
10
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
10
11
  import { clearDirtyMark, closeComponent, setHoveredAnnotation, setInlineCommentsVisibility, setSelectedAnnotation, flushPendingSelections, updateInlineCommentResolvedState, updateMouseState, setPendingSelectedAnnotation, setInlineCommentDraftState, setInlineCommentsFetched } from '../editor-commands';
11
12
  import { resetUserIntent, setUserIntent } from '../editor-commands/utils';
12
13
  import { getAnnotationViewClassname, getBlockAnnotationViewClassname } from '../nodeviews';
@@ -402,6 +403,12 @@ export var inlineCommentPlugin = function inlineCommentPlugin(options) {
402
403
  if (!(event.target instanceof HTMLElement)) {
403
404
  return false;
404
405
  }
406
+ if (isExperimentEnabled('platform_editor_fix_lcm_editor_annotations')) {
407
+ // The LCM renderer bridge owns selection so its draft-close guard runs first.
408
+ if (event.target.closest('[data-lcm-annotation-renderer]')) {
409
+ return false;
410
+ }
411
+ }
405
412
 
406
413
  // Find the nearest ancestor (or self) with the data-id attribute
407
414
  var annotationId = (_event$target$closest = event.target.closest('[data-id]')) === null || _event$target$closest === void 0 ? void 0 : _event$target$closest.getAttribute('data-id');
@@ -1,19 +1,20 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
1
2
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
3
  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; }
3
4
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
5
  import { pluginFactory } from '@atlaskit/editor-common/utils/plugin-state-factory';
6
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
5
7
  import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
6
8
  import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
7
9
  import reducer from './reducer';
8
- import { decorationKey, findAnnotationsInSelection, inlineCommentPluginKey, isBlockNodeAnnotationsSelected, isSelectedAnnotationsChanged } from './utils';
10
+ import { decorationKey, getAnnotationDiscovery, findAnnotationsInSelection, inlineCommentPluginKey, isBlockNodeAnnotationsSelected, isSelectedAnnotationsChanged } from './utils';
9
11
 
10
12
  /**
11
- * Creates a handleDocChanged function with its own deleted annotations cache.
12
- * This ensures each editor instance has its own cache, avoiding cross-contamination.
13
+ * Creates a document-sync handler with a deleted annotations cache in its closure.
13
14
  */
14
15
  var createHandleDocChanged = function createHandleDocChanged() {
15
16
  // Cache for preserving deleted annotation resolved states through delete/undo cycles
16
- // This lives in closure scope per editor instance to avoid serialization and reduce state size
17
+ // This stays outside plugin state so the undo cache is not serialized.
17
18
  var deletedAnnotationsCache = {};
18
19
  return function (tr, prevPluginState) {
19
20
  if (tr.getMeta('replaceDocument')) {
@@ -22,16 +23,24 @@ var createHandleDocChanged = function createHandleDocChanged() {
22
23
  });
23
24
  }
24
25
  var updatedState = getSelectionChangedHandler(false)(tr, prevPluginState);
26
+ var legacyContentExperimentEnabled = isExperimentEnabled('platform_editor_fix_lcm_editor_annotations');
27
+ var currentDiscovery = legacyContentExperimentEnabled ? getAnnotationDiscovery(tr.doc) : undefined;
28
+ var previousAnnotationIds = currentDiscovery ? new Set(getAnnotationDiscovery(tr.before).annotationIds) : undefined;
29
+ var newNestedAnnotationIds = currentDiscovery ? currentDiscovery.nestedAnnotationIds.filter(function (id) {
30
+ return !(previousAnnotationIds !== null && previousAnnotationIds !== void 0 && previousAnnotationIds.has(id)) && prevPluginState.annotations[id] === undefined && deletedAnnotationsCache[id] === undefined;
31
+ }) : [];
25
32
 
26
33
  // Collect annotation IDs currently in the document
27
- var annotationIdsInDocument = new Set();
28
- tr.doc.descendants(function (node) {
29
- node.marks.forEach(function (mark) {
30
- if (mark.type.name === 'annotation') {
31
- annotationIdsInDocument.add(mark.attrs.id);
32
- }
34
+ var annotationIdsInDocument = currentDiscovery ? new Set([].concat(_toConsumableArray(currentDiscovery.rootAnnotationIds), _toConsumableArray(currentDiscovery.nestedAnnotationIds))) : new Set();
35
+ if (!currentDiscovery) {
36
+ tr.doc.descendants(function (node) {
37
+ node.marks.forEach(function (mark) {
38
+ if (mark.type.name === 'annotation') {
39
+ annotationIdsInDocument.add(mark.attrs.id);
40
+ }
41
+ });
33
42
  });
34
- });
43
+ }
35
44
  var annotationIdsInState = Object.keys(prevPluginState.annotations);
36
45
 
37
46
  // Early return if annotations haven't changed
@@ -39,7 +48,9 @@ var createHandleDocChanged = function createHandleDocChanged() {
39
48
  return !annotationIdsInDocument.has(id);
40
49
  });
41
50
  if (!annotationsHaveChanged) {
42
- return updatedState;
51
+ return newNestedAnnotationIds.length > 0 ? _objectSpread(_objectSpread({}, updatedState), {}, {
52
+ dirtyAnnotations: true
53
+ }) : updatedState;
43
54
  }
44
55
 
45
56
  // Cache deleted annotations to be able to restore their resolved states on undo
@@ -62,6 +73,8 @@ var createHandleDocChanged = function createHandleDocChanged() {
62
73
  });
63
74
  return _objectSpread(_objectSpread({}, updatedState), {}, {
64
75
  annotations: updatedAnnotations
76
+ }, newNestedAnnotationIds.length > 0 && {
77
+ dirtyAnnotations: true
65
78
  });
66
79
  };
67
80
  };
@@ -189,47 +202,52 @@ var getSelectionChangedHandler = function getSelectionChangedHandler(reopenComme
189
202
  getSelectionChangeHandlerOld(reopenCommentView)(tr, pluginState);
190
203
  };
191
204
  };
205
+ var createAnnotationPluginFactory = function createAnnotationPluginFactory(onDocChanged) {
206
+ return pluginFactory(inlineCommentPluginKey, reducer, {
207
+ onSelectionChanged: getSelectionChangedHandler(true),
208
+ onDocChanged: onDocChanged,
209
+ mapping: function mapping(tr, pluginState, editorState) {
210
+ var draftDecorationSet = pluginState.draftDecorationSet,
211
+ bookmark = pluginState.bookmark;
212
+ var mappedDecorationSet = DecorationSet.empty,
213
+ mappedBookmark;
214
+ var hasMappedDecorations = false;
215
+ if (draftDecorationSet) {
216
+ mappedDecorationSet = draftDecorationSet.map(tr.mapping, tr.doc);
217
+ }
218
+ hasMappedDecorations = mappedDecorationSet.find(undefined, undefined, function (spec) {
219
+ return Object.values(decorationKey).includes(spec.key);
220
+ }).length > 0;
192
221
 
193
- // Create the handler with cache once at module level
194
- var handleDocChangedWithSync = createHandleDocChanged();
195
- var dest = pluginFactory(inlineCommentPluginKey, reducer, {
196
- onSelectionChanged: getSelectionChangedHandler(true),
197
- onDocChanged: handleDocChangedWithSync,
198
- mapping: function mapping(tr, pluginState, editorState) {
199
- var draftDecorationSet = pluginState.draftDecorationSet,
200
- bookmark = pluginState.bookmark;
201
- var mappedDecorationSet = DecorationSet.empty,
202
- mappedBookmark;
203
- var hasMappedDecorations = false;
204
- if (draftDecorationSet) {
205
- mappedDecorationSet = draftDecorationSet.map(tr.mapping, tr.doc);
206
- }
207
- hasMappedDecorations = mappedDecorationSet.find(undefined, undefined, function (spec) {
208
- return Object.values(decorationKey).includes(spec.key);
209
- }).length > 0;
222
+ // When changes to decoration target make decoration invalid (e.g. delete text, add mark to node),
223
+ // we need to reset bookmark to hide create component and to avoid invalid draft being published
224
+ // We only perform this change when document selection has changed.
225
+ if (!hasMappedDecorations && shouldClearBookMarkCheck(tr, editorState, bookmark)) {
226
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
227
+ draftDecorationSet: mappedDecorationSet,
228
+ bookmark: undefined
229
+ });
230
+ }
231
+ if (bookmark) {
232
+ mappedBookmark = bookmark.map(tr.mapping);
233
+ }
210
234
 
211
- // When changes to decoration target make decoration invalid (e.g. delete text, add mark to node),
212
- // we need to reset bookmark to hide create component and to avoid invalid draft being published
213
- // We only perform this change when document selection has changed.
214
- if (!hasMappedDecorations && shouldClearBookMarkCheck(tr, editorState, bookmark)) {
235
+ // return same pluginState if mappings did not change
236
+ if (mappedBookmark === bookmark && mappedDecorationSet === draftDecorationSet) {
237
+ return pluginState;
238
+ }
215
239
  return _objectSpread(_objectSpread({}, pluginState), {}, {
216
240
  draftDecorationSet: mappedDecorationSet,
217
- bookmark: undefined
241
+ bookmark: mappedBookmark
218
242
  });
219
243
  }
220
- if (bookmark) {
221
- mappedBookmark = bookmark.map(tr.mapping);
222
- }
223
-
224
- // return same pluginState if mappings did not change
225
- if (mappedBookmark === bookmark && mappedDecorationSet === draftDecorationSet) {
226
- return pluginState;
227
- }
228
- return _objectSpread(_objectSpread({}, pluginState), {}, {
229
- draftDecorationSet: mappedDecorationSet,
230
- bookmark: mappedBookmark
231
- });
232
- }
233
- });
234
- export var createPluginState = dest.createPluginState;
235
- export var createCommand = dest.createCommand;
244
+ });
245
+ };
246
+ var handleDocChangedWithSync = createHandleDocChanged();
247
+ // Keep the control cohort's existing module-level cache lifetime unchanged.
248
+ var legacyDest = createAnnotationPluginFactory(handleDocChangedWithSync);
249
+ var commandFactory = pluginFactory(inlineCommentPluginKey, reducer);
250
+ export var createPluginState = function createPluginState(dispatch, initialState) {
251
+ return (isExperimentEnabled('platform_editor_fix_lcm_editor_annotations') ? createAnnotationPluginFactory(createHandleDocChanged()) : legacyDest).createPluginState(dispatch, initialState);
252
+ };
253
+ export var createCommand = commandFactory.createCommand;
@@ -1,10 +1,12 @@
1
1
  import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
+ import _typeof from "@babel/runtime/helpers/typeof";
2
3
  import { AnnotationTypes } from '@atlaskit/adf-schema/annotation';
3
4
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics/types/enums';
4
5
  import { currentMediaNodeWithPos } from '@atlaskit/editor-common/media-single';
5
6
  import { AnnotationSharedClassNames, BlockAnnotationSharedClassNames } from '@atlaskit/editor-common/styles/annotation';
6
7
  import { canApplyAnnotationOnRange, getAnnotationIdsFromRange, getRangeInlineNodeNames, hasAnnotationMark, isEmptyTextSelection } from '@atlaskit/editor-common/utils/annotation';
7
8
  import { isParagraph, isText } from '@atlaskit/editor-common/utils/nodes';
9
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
8
10
  import { AllSelection, NodeSelection, PluginKey, TextSelection } from '@atlaskit/editor-prosemirror/state';
9
11
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
10
12
  import { fg } from '@atlaskit/platform-feature-flags/fg';
@@ -62,7 +64,111 @@ var reorderAnnotations = function reorderAnnotations(annotations, $from) {
62
64
  });
63
65
  });
64
66
  };
67
+ var legacyContentAnnotationsByDoc = new WeakMap();
68
+ var legacyContentAnnotationIdsByExtension = new WeakMap();
69
+ var readNestedContentNodes = function readNestedContentNodes(parameters) {
70
+ if (!parameters || _typeof(parameters) !== 'object') {
71
+ return undefined;
72
+ }
73
+ var nestedContent = parameters.nestedContent;
74
+ if (!nestedContent || _typeof(nestedContent) !== 'object') {
75
+ return undefined;
76
+ }
77
+ var nestedDocument = nestedContent;
78
+ return nestedDocument.type === 'doc' && Array.isArray(nestedDocument.content) ? nestedDocument.content : undefined;
79
+ };
80
+ var getNestedContentAnnotationIds = function getNestedContentAnnotationIds(extension) {
81
+ var cachedIds = legacyContentAnnotationIdsByExtension.get(extension);
82
+ if (cachedIds) {
83
+ return cachedIds;
84
+ }
85
+ var nestedNodes = readNestedContentNodes(extension.attrs.parameters);
86
+ if (!nestedNodes) {
87
+ var noIds = Object.freeze([]);
88
+ legacyContentAnnotationIdsByExtension.set(extension, noIds);
89
+ return noIds;
90
+ }
91
+ var annotationIds = new Set();
92
+ var visitedAdfNodes = new WeakSet();
93
+ var pending = _toConsumableArray(nestedNodes).reverse();
94
+ while (pending.length > 0) {
95
+ var _adfNode$attrs;
96
+ var value = pending.pop();
97
+ if (!value || _typeof(value) !== 'object' || visitedAdfNodes.has(value)) {
98
+ continue;
99
+ }
100
+ visitedAdfNodes.add(value);
101
+ var adfNode = value;
102
+ if (Array.isArray(adfNode.marks)) {
103
+ adfNode.marks.forEach(function (rawMark) {
104
+ if (!rawMark || _typeof(rawMark) !== 'object') {
105
+ return;
106
+ }
107
+ var annotationMark = rawMark;
108
+ var attrs = annotationMark.attrs;
109
+ if (annotationMark.type !== 'annotation' || !validateAnnotationAttributes(attrs)) {
110
+ return;
111
+ }
112
+ annotationIds.add(attrs.id);
113
+ });
114
+ }
115
+ if (adfNode.type === 'extension' && ((_adfNode$attrs = adfNode.attrs) === null || _adfNode$attrs === void 0 ? void 0 : _adfNode$attrs.extensionType) === 'com.atlassian.confluence.migration' && adfNode.attrs.extensionKey === 'legacy-content') {
116
+ var _nestedNodes = readNestedContentNodes(adfNode.attrs.parameters);
117
+ if (_nestedNodes) {
118
+ for (var i = _nestedNodes.length - 1; i >= 0; i--) {
119
+ pending.push(_nestedNodes[i]);
120
+ }
121
+ }
122
+ }
123
+ if (Array.isArray(adfNode.content)) {
124
+ for (var _i = adfNode.content.length - 1; _i >= 0; _i--) {
125
+ pending.push(adfNode.content[_i]);
126
+ }
127
+ }
128
+ }
129
+ var ids = Object.freeze(_toConsumableArray(annotationIds));
130
+ legacyContentAnnotationIdsByExtension.set(extension, ids);
131
+ return ids;
132
+ };
133
+
134
+ /** Discover annotation IDs from immutable legacy-content ADF without repeatedly scanning it. */
135
+ export var getAnnotationDiscovery = function getAnnotationDiscovery(doc) {
136
+ var cachedDiscovery = legacyContentAnnotationsByDoc.get(doc);
137
+ if (cachedDiscovery) {
138
+ return cachedDiscovery;
139
+ }
140
+ var allAnnotationIds = new Set();
141
+ var rootAnnotationIds = new Set();
142
+ var nestedAnnotationIds = new Set();
143
+ doc.descendants(function (node) {
144
+ node.marks.filter(function (mark) {
145
+ return mark.type.name === 'annotation';
146
+ }).forEach(function (mark) {
147
+ if (validateAnnotationMark(mark)) {
148
+ rootAnnotationIds.add(mark.attrs.id);
149
+ allAnnotationIds.add(mark.attrs.id);
150
+ }
151
+ });
152
+ if (node.type.name === 'extension' && node.attrs.extensionType === 'com.atlassian.confluence.migration' && node.attrs.extensionKey === 'legacy-content') {
153
+ getNestedContentAnnotationIds(node).forEach(function (id) {
154
+ nestedAnnotationIds.add(id);
155
+ allAnnotationIds.add(id);
156
+ });
157
+ }
158
+ return true;
159
+ });
160
+ var discovery = {
161
+ annotationIds: Object.freeze(_toConsumableArray(allAnnotationIds)),
162
+ rootAnnotationIds: Object.freeze(_toConsumableArray(rootAnnotationIds)),
163
+ nestedAnnotationIds: Object.freeze(_toConsumableArray(nestedAnnotationIds))
164
+ };
165
+ legacyContentAnnotationsByDoc.set(doc, discovery);
166
+ return discovery;
167
+ };
65
168
  export var getAllAnnotations = function getAllAnnotations(doc) {
169
+ if (isExperimentEnabled('platform_editor_fix_lcm_editor_annotations')) {
170
+ return _toConsumableArray(getAnnotationDiscovery(doc).annotationIds);
171
+ }
66
172
  var allAnnotationIds = new Set();
67
173
  doc.descendants(function (node) {
68
174
  node.marks.filter(function (mark) {
@@ -76,29 +182,30 @@ export var getAllAnnotations = function getAllAnnotations(doc) {
76
182
  });
77
183
  return Array.from(allAnnotationIds);
78
184
  };
79
-
80
- /*
81
- * verifies if annotation mark contains valid attributes
82
- */
83
- var validateAnnotationMark = function validateAnnotationMark(annotationMark) {
84
- var _ref = annotationMark.attrs,
85
- id = _ref.id,
86
- annotationType = _ref.annotationType;
87
- return validateAnnotationId(id) && validateAnnotationType(annotationType);
88
- function validateAnnotationId(id) {
89
- if (!id || typeof id !== 'string') {
90
- return false;
91
- }
92
- var invalidIds = ['null', 'undefined'];
93
- return !invalidIds.includes(id.toLowerCase());
185
+ var validateAnnotationId = function validateAnnotationId(id) {
186
+ if (!id || typeof id !== 'string') {
187
+ return false;
94
188
  }
95
- function validateAnnotationType(type) {
96
- if (!type || typeof type !== 'string') {
97
- return false;
98
- }
99
- var allowedTypes = Object.values(AnnotationTypes);
100
- return allowedTypes.includes(type);
189
+ var invalidIds = ['null', 'undefined'];
190
+ return !invalidIds.includes(id.toLowerCase());
191
+ };
192
+ var validateAnnotationType = function validateAnnotationType(type) {
193
+ if (!type || typeof type !== 'string') {
194
+ return false;
101
195
  }
196
+ var allowedTypes = Object.values(AnnotationTypes);
197
+ return allowedTypes.some(function (allowedType) {
198
+ return allowedType === type;
199
+ });
200
+ };
201
+ var validateAnnotationAttributes = function validateAnnotationAttributes(attrs) {
202
+ if (!attrs || _typeof(attrs) !== 'object') {
203
+ return false;
204
+ }
205
+ return 'id' in attrs && 'annotationType' in attrs && validateAnnotationId(attrs.id) && validateAnnotationType(attrs.annotationType);
206
+ };
207
+ var validateAnnotationMark = function validateAnnotationMark(annotationMark) {
208
+ return validateAnnotationAttributes(annotationMark.attrs);
102
209
  };
103
210
  export var decorationKey = {
104
211
  block: 'blockCommentDecoration',
@@ -289,8 +396,8 @@ export var getDraftCommandAnalyticsPayload = function getDraftCommandAnalyticsPa
289
396
  };
290
397
  }
291
398
  if (fg('editor_inline_comments_on_inline_nodes')) {
292
- var _ref2 = getPluginState(state) || {},
293
- bookmark = _ref2.bookmark;
399
+ var _ref = getPluginState(state) || {},
400
+ bookmark = _ref.bookmark;
294
401
  attributes.inlineNodeNames = getRangeInlineNodeNames({
295
402
  doc: state.doc,
296
403
  pos: resolveDraftBookmark(state, bookmark)
@@ -311,8 +418,8 @@ export var isSelectionValid = function isSelectionValid(state) {
311
418
  var supportedBlockNodes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
312
419
  var isBlockNodeSupported = arguments.length > 2 ? arguments[2] : undefined;
313
420
  var selection = state.selection;
314
- var _ref3 = getPluginState(state) || {},
315
- disallowOnWhitespace = _ref3.disallowOnWhitespace;
421
+ var _ref2 = getPluginState(state) || {},
422
+ disallowOnWhitespace = _ref2.disallowOnWhitespace;
316
423
  var allowedInlineNodes = ['emoji', 'status', 'date', 'mention', 'inlineCard'];
317
424
  var isSelectionEmpty = selection.empty;
318
425
  var isTextOrAllSelection = selection instanceof TextSelection || selection instanceof AllSelection;
@@ -11,6 +11,13 @@ import type { InlineCommentPluginState } from './types';
11
11
  * @param $pos Position to center search around
12
12
  */
13
13
  export declare const surroundingMarks: ($pos: ResolvedPos) => (readonly Mark[])[];
14
+ type AnnotationDiscovery = {
15
+ annotationIds: readonly string[];
16
+ nestedAnnotationIds: readonly string[];
17
+ rootAnnotationIds: readonly string[];
18
+ };
19
+ /** Discover annotation IDs from immutable legacy-content ADF without repeatedly scanning it. */
20
+ export declare const getAnnotationDiscovery: (doc: Node) => AnnotationDiscovery;
14
21
  export declare const getAllAnnotations: (doc: Node) => string[];
15
22
  export declare const decorationKey: {
16
23
  block: string;
@@ -52,3 +59,4 @@ export declare function isSelectedAnnotationsChanged(oldSelectedAnnotations: Ann
52
59
  */
53
60
  export declare const isBlockNodeAnnotationsSelected: (selection: Selection, selectedAnnotations?: AnnotationInfo[]) => boolean;
54
61
  export declare const hasAnyUnResolvedAnnotationInPage: (state: EditorState) => boolean;
62
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-annotation",
3
- "version": "29.0.6",
3
+ "version": "29.1.0",
4
4
  "description": "Annotation plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -30,6 +30,7 @@
30
30
  "@atlaskit/editor-toolbar": "^2.8.0",
31
31
  "@atlaskit/editor-toolbar-model": "^1.2.0",
32
32
  "@atlaskit/icon": "^38.0.0",
33
+ "@atlaskit/platform-feature-experiments": "^0.3.0",
33
34
  "@atlaskit/platform-feature-flags": "^2.2.0",
34
35
  "@atlaskit/tmp-editor-statsig": "^246.0.0",
35
36
  "@babel/runtime": "^7.0.0"