@atlaskit/editor-plugin-synced-block 5.3.32 → 5.3.34

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.
@@ -5,7 +5,7 @@ import { DOMSerializer, Fragment } from '@atlaskit/editor-prosemirror/model';
5
5
  import { TextSelection } from '@atlaskit/editor-prosemirror/state';
6
6
  import { findSelectedNodeOfType, removeParentNodeOfType, removeSelectedNode, safeInsert } from '@atlaskit/editor-prosemirror/utils';
7
7
  import { syncedBlockPluginKey } from '../pm-plugins/main';
8
- import { canBeConvertedToSyncBlock, findSyncBlock, findSyncBlockOrBodiedSyncBlock, isBodiedSyncBlockNode } from '../pm-plugins/utils/utils';
8
+ import { canBeConvertedToSyncBlock, deferDispatch, findSyncBlock, findSyncBlockOrBodiedSyncBlock, isBodiedSyncBlockNode } from '../pm-plugins/utils/utils';
9
9
  import { FLAG_ID } from '../types';
10
10
  import { pasteSyncBlockHTMLContent } from './utils';
11
11
  export const createSyncedBlock = ({
@@ -88,9 +88,7 @@ export const copySyncedBlockReferenceToClipboardEditorCommand = (syncBlockStore,
88
88
  }
89
89
  return null;
90
90
  };
91
- export const copySyncedBlockReferenceToClipboard = (syncBlockStore, inputMethod, api) => (state, _dispatch, _view) => {
92
- return copySyncedBlockReferenceToClipboardInternal(state.tr.doc.type.schema, state.tr.selection, syncBlockStore, inputMethod, api);
93
- };
91
+ export const copySyncedBlockReferenceToClipboard = (syncBlockStore, inputMethod, api) => (state, _dispatch, _view) => copySyncedBlockReferenceToClipboardInternal(state.tr.doc.type.schema, state.tr.selection, syncBlockStore, inputMethod, api);
94
92
  const copySyncedBlockReferenceToClipboardInternal = (schema, selection, syncBlockStore, inputMethod, api) => {
95
93
  const syncBlockFindResult = findSyncBlockOrBodiedSyncBlock(schema, selection);
96
94
  if (!syncBlockFindResult) {
@@ -154,9 +152,7 @@ const copySyncedBlockReferenceToClipboardInternal = (schema, selection, syncBloc
154
152
  }
155
153
  const domNode = toDOM(referenceSyncBlockNode, schema);
156
154
  copyDomNode(domNode, referenceSyncBlockNode.type, selection);
157
-
158
- // Use setTimeout to dispatch transaction in next tick and avoid re-entrant dispatch
159
- setTimeout(() => {
155
+ deferDispatch(() => {
160
156
  api === null || api === void 0 ? void 0 : api.core.actions.execute(({
161
157
  tr
162
158
  }) => {
@@ -177,7 +173,7 @@ const copySyncedBlockReferenceToClipboardInternal = (schema, selection, syncBloc
177
173
  }
178
174
  });
179
175
  });
180
- }, 0);
176
+ });
181
177
  return true;
182
178
  };
183
179
  export const editSyncedBlockSource = (syncBlockStore, api) => (state, dispatch, _view) => {
@@ -202,7 +198,9 @@ export const editSyncedBlockSource = (syncBlockStore, api) => (state, dispatch,
202
198
  window.open(syncBlockURL, '_blank');
203
199
  } else {
204
200
  var _api$analytics6, _api$analytics6$actio;
205
- const tr = state.tr;
201
+ const {
202
+ tr
203
+ } = state;
206
204
  api === null || api === void 0 ? void 0 : (_api$analytics6 = api.analytics) === null || _api$analytics6 === void 0 ? void 0 : (_api$analytics6$actio = _api$analytics6.actions) === null || _api$analytics6$actio === void 0 ? void 0 : _api$analytics6$actio.attachAnalyticsEvent({
207
205
  eventType: EVENT_TYPE.OPERATIONAL,
208
206
  action: ACTION.ERROR,
@@ -268,7 +266,9 @@ export const unsync = (storeManager, isBodiedSyncBlock, view) => {
268
266
  }
269
267
  if (isBodiedSyncBlock) {
270
268
  const content = syncBlock === null || syncBlock === void 0 ? void 0 : syncBlock.node.content;
271
- const tr = state.tr;
269
+ const {
270
+ tr
271
+ } = state;
272
272
  tr.replaceWith(syncBlock.pos, syncBlock.pos + syncBlock.node.nodeSize, content).setMeta('deletionReason', 'source-block-unsynced');
273
273
  view.dispatch(tr);
274
274
  return true;
@@ -1,3 +1,4 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
1
2
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
2
3
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
3
4
  import { createSelectionClickHandler } from '@atlaskit/editor-common/selection';
@@ -16,13 +17,15 @@ import { handleBodiedSyncBlockRemoval } from './utils/handle-bodied-sync-block-r
16
17
  import { shouldIgnoreDomEvent } from './utils/ignore-dom-event';
17
18
  import { calculateDecorations } from './utils/selection-decorations';
18
19
  import { hasEditInSyncBlock, trackSyncBlocks } from './utils/track-sync-blocks';
19
- import { wasInlineExtensionInsertedInBodiedSyncBlock, sliceFullyContainsNode } from './utils/utils';
20
+ import { deferDispatch, wasInlineExtensionInsertedInBodiedSyncBlock, sliceFullyContainsNode } from './utils/utils';
20
21
  export const syncedBlockPluginKey = new PluginKey('syncedBlockPlugin');
21
22
  const mapRetryCreationPosMap = (oldMap, newRetryCreationPos, mapPos) => {
22
23
  const resourceId = newRetryCreationPos === null || newRetryCreationPos === void 0 ? void 0 : newRetryCreationPos.resourceId;
23
24
  const newMap = new Map(oldMap);
24
25
  if (resourceId) {
25
- const pos = newRetryCreationPos.pos;
26
+ const {
27
+ pos
28
+ } = newRetryCreationPos;
26
29
  if (!pos) {
27
30
  newMap.delete(resourceId);
28
31
  } else {
@@ -41,18 +44,15 @@ const mapRetryCreationPosMap = (oldMap, newRetryCreationPos, mapPos) => {
41
44
  return newMap;
42
45
  };
43
46
  const showCopiedFlag = api => {
44
- // Use setTimeout to dispatch transaction in next tick and avoid re-entrant dispatch
45
- setTimeout(() => {
47
+ deferDispatch(() => {
46
48
  api === null || api === void 0 ? void 0 : api.core.actions.execute(({
47
49
  tr
48
- }) => {
49
- return tr.setMeta(syncedBlockPluginKey, {
50
- activeFlag: {
51
- id: FLAG_ID.SYNC_BLOCK_COPIED
52
- }
53
- });
54
- });
55
- }, 0);
50
+ }) => tr.setMeta(syncedBlockPluginKey, {
51
+ activeFlag: {
52
+ id: FLAG_ID.SYNC_BLOCK_COPIED
53
+ }
54
+ }));
55
+ });
56
56
  };
57
57
  const showInlineExtensionInSyncBlockWarningIfNeeded = (tr, state, api, inlineExtensionFlagShown) => {
58
58
  var _api$connectivity, _api$connectivity$sha;
@@ -66,18 +66,15 @@ const showInlineExtensionInSyncBlockWarningIfNeeded = (tr, state, api, inlineExt
66
66
  // Only show the flag on the first instance per sync block (same as UNPUBLISHED_SYNC_BLOCK_PASTED)
67
67
  if (resourceId && !inlineExtensionFlagShown.has(resourceId)) {
68
68
  inlineExtensionFlagShown.add(resourceId);
69
- // Use setTimeout to dispatch in next tick and avoid re-entrant dispatch from filterTransaction
70
- setTimeout(() => {
69
+ deferDispatch(() => {
71
70
  api === null || api === void 0 ? void 0 : api.core.actions.execute(({
72
71
  tr
73
- }) => {
74
- return tr.setMeta(syncedBlockPluginKey, {
75
- activeFlag: {
76
- id: FLAG_ID.INLINE_EXTENSION_IN_SYNC_BLOCK
77
- }
78
- });
79
- });
80
- }, 0);
72
+ }) => tr.setMeta(syncedBlockPluginKey, {
73
+ activeFlag: {
74
+ id: FLAG_ID.INLINE_EXTENSION_IN_SYNC_BLOCK
75
+ }
76
+ }));
77
+ });
81
78
  }
82
79
  };
83
80
  const getDeleteReason = tr => {
@@ -87,37 +84,166 @@ const getDeleteReason = tr => {
87
84
  }
88
85
  return reason;
89
86
  };
87
+ const filterTransactionOnline = ({
88
+ tr,
89
+ state,
90
+ syncBlockStore,
91
+ api,
92
+ confirmationTransactionRef,
93
+ bodiedSyncBlockRemoved,
94
+ bodiedSyncBlockAdded,
95
+ inlineExtensionFlagShown
96
+ }) => {
97
+ const {
98
+ removed: syncBlockRemoved,
99
+ added: syncBlockAdded
100
+ } = trackSyncBlocks(node => node.type.name === 'syncBlock', tr, state);
101
+ syncBlockRemoved.forEach(syncBlock => {
102
+ var _api$analytics, _api$analytics$action;
103
+ api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : (_api$analytics$action = _api$analytics.actions) === null || _api$analytics$action === void 0 ? void 0 : _api$analytics$action.fireAnalyticsEvent({
104
+ action: ACTION.DELETED,
105
+ actionSubject: ACTION_SUBJECT.SYNCED_BLOCK,
106
+ actionSubjectId: ACTION_SUBJECT_ID.REFERENCE_SYNCED_BLOCK_DELETE,
107
+ attributes: {
108
+ resourceId: syncBlock.attrs.resourceId,
109
+ blockInstanceId: syncBlock.attrs.localId
110
+ },
111
+ eventType: EVENT_TYPE.OPERATIONAL
112
+ });
113
+ });
114
+ syncBlockAdded.forEach(syncBlock => {
115
+ if (fg('platform_synced_block_patch_3')) {
116
+ var _api$analytics2, _api$analytics2$actio;
117
+ api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : (_api$analytics2$actio = _api$analytics2.actions) === null || _api$analytics2$actio === void 0 ? void 0 : _api$analytics2$actio.fireAnalyticsEvent({
118
+ action: ACTION.INSERTED,
119
+ actionSubject: ACTION_SUBJECT.DOCUMENT,
120
+ actionSubjectId: ACTION_SUBJECT_ID.SYNCED_BLOCK,
121
+ attributes: {
122
+ resourceId: syncBlock.attrs.resourceId,
123
+ blockInstanceId: syncBlock.attrs.localId
124
+ },
125
+ eventType: EVENT_TYPE.TRACK
126
+ });
127
+ } else {
128
+ var _api$analytics3, _api$analytics3$actio;
129
+ api === null || api === void 0 ? void 0 : (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 ? void 0 : (_api$analytics3$actio = _api$analytics3.actions) === null || _api$analytics3$actio === void 0 ? void 0 : _api$analytics3$actio.fireAnalyticsEvent({
130
+ action: ACTION.INSERTED,
131
+ actionSubject: ACTION_SUBJECT.SYNCED_BLOCK,
132
+ actionSubjectId: ACTION_SUBJECT_ID.REFERENCE_SYNCED_BLOCK_CREATE,
133
+ attributes: {
134
+ resourceId: syncBlock.attrs.resourceId,
135
+ blockInstanceId: syncBlock.attrs.localId
136
+ },
137
+ eventType: EVENT_TYPE.OPERATIONAL
138
+ });
139
+ }
140
+ });
141
+ if (bodiedSyncBlockRemoved.length > 0) {
142
+ // eslint-disable-next-line no-param-reassign
143
+ confirmationTransactionRef.current = tr;
144
+ return handleBodiedSyncBlockRemoval(bodiedSyncBlockRemoved, syncBlockStore, api, confirmationTransactionRef, getDeleteReason(tr));
145
+ }
146
+ if (bodiedSyncBlockAdded.length > 0) {
147
+ if (tr.getMeta(pmHistoryPluginKey)) {
148
+ // We don't allow bodiedSyncBlock creation via redo, however, we need to return true here to let transaction through so history can be updated properly.
149
+ // If we simply returns false, creation from redo is blocked as desired, but this results in editor showing redo as possible even though it's not.
150
+ // After true is returned here and the node is created, we delete the node in the filterTransaction immediately, which cancels out the creation
151
+ return true;
152
+ }
153
+ handleBodiedSyncBlockCreation(bodiedSyncBlockAdded, state, api);
154
+ return true;
155
+ }
156
+ showInlineExtensionInSyncBlockWarningIfNeeded(tr, state, api, inlineExtensionFlagShown);
157
+ return true;
158
+ };
159
+ const filterTransactionOffline = ({
160
+ tr,
161
+ state,
162
+ api,
163
+ isConfirmedSyncBlockDeletion,
164
+ bodiedSyncBlockRemoved,
165
+ bodiedSyncBlockAdded
166
+ }) => {
167
+ const {
168
+ removed: syncBlockRemoved,
169
+ added: syncBlockAdded
170
+ } = trackSyncBlocks(node => node.type.name === 'syncBlock', tr, state);
171
+ let errorFlag = false;
172
+ if (isConfirmedSyncBlockDeletion || bodiedSyncBlockRemoved.length > 0 || syncBlockRemoved.length > 0) {
173
+ errorFlag = FLAG_ID.CANNOT_DELETE_WHEN_OFFLINE;
174
+ } else if (bodiedSyncBlockAdded.length > 0 || syncBlockAdded.length > 0) {
175
+ errorFlag = FLAG_ID.CANNOT_CREATE_WHEN_OFFLINE;
176
+ } else if (hasEditInSyncBlock(tr, state)) {
177
+ errorFlag = FLAG_ID.CANNOT_EDIT_WHEN_OFFLINE;
178
+ }
179
+ if (errorFlag) {
180
+ deferDispatch(() => {
181
+ api === null || api === void 0 ? void 0 : api.core.actions.execute(({
182
+ tr
183
+ }) => tr.setMeta(syncedBlockPluginKey, {
184
+ activeFlag: {
185
+ id: errorFlag
186
+ }
187
+ }));
188
+ });
189
+ return false;
190
+ }
191
+ return true;
192
+ };
193
+
194
+ /**
195
+ * Encapsulates mutable state that persists across transactions in the
196
+ * synced block plugin. Replaces module-level closure variables so state
197
+ * is explicitly scoped to a single plugin instance.
198
+ */
199
+ class SyncedBlockPluginContext {
200
+ constructor() {
201
+ _defineProperty(this, "confirmationTransactionRef", {
202
+ current: undefined
203
+ });
204
+ _defineProperty(this, "_isCopyEvent", false);
205
+ _defineProperty(this, "unpublishedFlagShown", new Set());
206
+ _defineProperty(this, "inlineExtensionFlagShown", new Set());
207
+ }
208
+ get isCopyEvent() {
209
+ return this._isCopyEvent;
210
+ }
211
+ markCopyEvent() {
212
+ this._isCopyEvent = true;
213
+ }
214
+ consumeCopyEvent() {
215
+ const was = this._isCopyEvent;
216
+ this._isCopyEvent = false;
217
+ return was;
218
+ }
219
+ }
90
220
  export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api) => {
221
+ var _ctx$confirmationTran, _ctx$unpublishedFlagS, _ctx$inlineExtensionF;
91
222
  const {
92
223
  useLongPressSelection = false
93
224
  } = options || {};
94
- const confirmationTransactionRef = {
225
+ const ctx = fg('platform_synced_block_patch_5') ? new SyncedBlockPluginContext() : undefined;
226
+ const confirmationTransactionRef = (_ctx$confirmationTran = ctx === null || ctx === void 0 ? void 0 : ctx.confirmationTransactionRef) !== null && _ctx$confirmationTran !== void 0 ? _ctx$confirmationTran : {
95
227
  current: undefined
96
228
  };
97
- // Track if a copy event occurred to distinguish copy from drag and drop
98
229
  let isCopyEvent = false;
99
- // Track which sync blocks have already triggered the unpublished flag
100
- const unpublishedFlagShown = new Set();
101
- // Track which sync blocks have already triggered the inline extension in sync block flag
102
- const inlineExtensionFlagShown = new Set();
230
+ const unpublishedFlagShown = (_ctx$unpublishedFlagS = ctx === null || ctx === void 0 ? void 0 : ctx.unpublishedFlagShown) !== null && _ctx$unpublishedFlagS !== void 0 ? _ctx$unpublishedFlagS : new Set();
231
+ const inlineExtensionFlagShown = (_ctx$inlineExtensionF = ctx === null || ctx === void 0 ? void 0 : ctx.inlineExtensionFlagShown) !== null && _ctx$inlineExtensionF !== void 0 ? _ctx$inlineExtensionF : new Set();
103
232
 
104
233
  // Set up callback to detect unpublished sync blocks when they're fetched
105
234
  syncBlockStore.referenceManager.setOnUnpublishedSyncBlockDetected(resourceId => {
106
235
  // Only show the flag once per sync block
107
236
  if (!unpublishedFlagShown.has(resourceId)) {
108
237
  unpublishedFlagShown.add(resourceId);
109
- // Use setTimeout to dispatch transaction in next tick and avoid re-entrant dispatch
110
- setTimeout(() => {
238
+ deferDispatch(() => {
111
239
  api === null || api === void 0 ? void 0 : api.core.actions.execute(({
112
240
  tr
113
- }) => {
114
- return tr.setMeta(syncedBlockPluginKey, {
115
- activeFlag: {
116
- id: FLAG_ID.UNPUBLISHED_SYNC_BLOCK_PASTED
117
- }
118
- });
119
- });
120
- }, 0);
241
+ }) => tr.setMeta(syncedBlockPluginKey, {
242
+ activeFlag: {
243
+ id: FLAG_ID.UNPUBLISHED_SYNC_BLOCK_PASTED
244
+ }
245
+ }));
246
+ });
121
247
  }
122
248
  });
123
249
  return new SafePlugin({
@@ -159,22 +285,21 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
159
285
  },
160
286
  props: {
161
287
  nodeViews: {
162
- syncBlock: (node, view, getPos, _decorations) => {
163
- // To support SSR, pass `syncBlockStore` here
164
- // and do not use lazy loading.
165
- // We cannot start rendering and then load `syncBlockStore` asynchronously,
166
- // because obtaining it is asynchronous (sharedPluginState.currentState() is delayed).
167
- return new SyncBlockView({
168
- api,
169
- options,
170
- node,
171
- view,
172
- getPos,
173
- portalProviderAPI: pmPluginFactoryParams.portalProviderAPI,
174
- eventDispatcher: pmPluginFactoryParams.eventDispatcher,
175
- syncBlockStore: syncBlockStore
176
- }).init();
177
- },
288
+ syncBlock: (node, view, getPos, _decorations) =>
289
+ // To support SSR, pass `syncBlockStore` here
290
+ // and do not use lazy loading.
291
+ // We cannot start rendering and then load `syncBlockStore` asynchronously,
292
+ // because obtaining it is asynchronous (sharedPluginState.currentState() is delayed).
293
+ new SyncBlockView({
294
+ api,
295
+ options,
296
+ node,
297
+ view,
298
+ getPos,
299
+ portalProviderAPI: pmPluginFactoryParams.portalProviderAPI,
300
+ eventDispatcher: pmPluginFactoryParams.eventDispatcher,
301
+ syncBlockStore: syncBlockStore
302
+ }).init(),
178
303
  bodiedSyncBlock: lazyBodiedSyncBlockView({
179
304
  pluginOptions: options,
180
305
  pmPluginFactoryParams,
@@ -224,7 +349,11 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
224
349
  return shouldIgnoreDomEvent(view, event, api);
225
350
  },
226
351
  copy: () => {
227
- isCopyEvent = true;
352
+ if (ctx) {
353
+ ctx.markCopyEvent();
354
+ } else {
355
+ isCopyEvent = true;
356
+ }
228
357
  return false;
229
358
  }
230
359
  },
@@ -236,8 +365,10 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
236
365
  const {
237
366
  schema
238
367
  } = state;
239
- const isCopy = isCopyEvent;
240
- isCopyEvent = false;
368
+ const isCopy = ctx ? ctx.consumeCopyEvent() : isCopyEvent;
369
+ if (!ctx) {
370
+ isCopyEvent = false;
371
+ }
241
372
  if (!syncBlockStore || !isCopy) {
242
373
  return slice;
243
374
  }
@@ -280,7 +411,6 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
280
411
  const {
281
412
  added
282
413
  } = trackSyncBlocks(node => node.type.name === 'syncBlock', tr, state);
283
- // Mark newly added sync blocks so we can detect unpublished status when data is fetched
284
414
  added.forEach(nodeInfo => {
285
415
  var _nodeInfo$attrs;
286
416
  if ((_nodeInfo$attrs = nodeInfo.attrs) !== null && _nodeInfo$attrs !== void 0 && _nodeInfo$attrs.resourceId) {
@@ -288,11 +418,6 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
288
418
  }
289
419
  });
290
420
  }
291
-
292
- // Ignore transactions that don't change the document
293
- // or are from remote (collab) or already confirmed sync block deletion
294
- // We only care about local changes that change the document
295
- // and are not yet confirmed for sync block deletion
296
421
  if (!tr.docChanged || Boolean(tr.getMeta('isRemote')) || !isOffline && isConfirmedSyncBlockDeletion) {
297
422
  return true;
298
423
  }
@@ -300,14 +425,33 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
300
425
  removed: bodiedSyncBlockRemoved,
301
426
  added: bodiedSyncBlockAdded
302
427
  } = trackSyncBlocks(syncBlockStore.sourceManager.isSourceBlock, tr, state);
428
+ if (fg('platform_synced_block_patch_5')) {
429
+ return isOffline ? filterTransactionOffline({
430
+ tr,
431
+ state,
432
+ api,
433
+ isConfirmedSyncBlockDeletion,
434
+ bodiedSyncBlockRemoved,
435
+ bodiedSyncBlockAdded
436
+ }) : filterTransactionOnline({
437
+ tr,
438
+ state,
439
+ syncBlockStore,
440
+ api,
441
+ confirmationTransactionRef,
442
+ bodiedSyncBlockRemoved,
443
+ bodiedSyncBlockAdded,
444
+ inlineExtensionFlagShown
445
+ });
446
+ }
303
447
  if (!isOffline) {
304
448
  const {
305
449
  removed: syncBlockRemoved,
306
450
  added: syncBlockAdded
307
451
  } = trackSyncBlocks(node => node.type.name === 'syncBlock', tr, state);
308
452
  syncBlockRemoved.forEach(syncBlock => {
309
- var _api$analytics, _api$analytics$action;
310
- api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : (_api$analytics$action = _api$analytics.actions) === null || _api$analytics$action === void 0 ? void 0 : _api$analytics$action.fireAnalyticsEvent({
453
+ var _api$analytics4, _api$analytics4$actio;
454
+ api === null || api === void 0 ? void 0 : (_api$analytics4 = api.analytics) === null || _api$analytics4 === void 0 ? void 0 : (_api$analytics4$actio = _api$analytics4.actions) === null || _api$analytics4$actio === void 0 ? void 0 : _api$analytics4$actio.fireAnalyticsEvent({
311
455
  action: ACTION.DELETED,
312
456
  actionSubject: ACTION_SUBJECT.SYNCED_BLOCK,
313
457
  actionSubjectId: ACTION_SUBJECT_ID.REFERENCE_SYNCED_BLOCK_DELETE,
@@ -320,8 +464,8 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
320
464
  });
321
465
  syncBlockAdded.forEach(syncBlock => {
322
466
  if (fg('platform_synced_block_patch_3')) {
323
- var _api$analytics2, _api$analytics2$actio;
324
- api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : (_api$analytics2$actio = _api$analytics2.actions) === null || _api$analytics2$actio === void 0 ? void 0 : _api$analytics2$actio.fireAnalyticsEvent({
467
+ var _api$analytics5, _api$analytics5$actio;
468
+ api === null || api === void 0 ? void 0 : (_api$analytics5 = api.analytics) === null || _api$analytics5 === void 0 ? void 0 : (_api$analytics5$actio = _api$analytics5.actions) === null || _api$analytics5$actio === void 0 ? void 0 : _api$analytics5$actio.fireAnalyticsEvent({
325
469
  action: ACTION.INSERTED,
326
470
  actionSubject: ACTION_SUBJECT.DOCUMENT,
327
471
  actionSubjectId: ACTION_SUBJECT_ID.SYNCED_BLOCK,
@@ -332,8 +476,8 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
332
476
  eventType: EVENT_TYPE.TRACK
333
477
  });
334
478
  } else {
335
- var _api$analytics3, _api$analytics3$actio;
336
- api === null || api === void 0 ? void 0 : (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 ? void 0 : (_api$analytics3$actio = _api$analytics3.actions) === null || _api$analytics3$actio === void 0 ? void 0 : _api$analytics3$actio.fireAnalyticsEvent({
479
+ var _api$analytics6, _api$analytics6$actio;
480
+ api === null || api === void 0 ? void 0 : (_api$analytics6 = api.analytics) === null || _api$analytics6 === void 0 ? void 0 : (_api$analytics6$actio = _api$analytics6.actions) === null || _api$analytics6$actio === void 0 ? void 0 : _api$analytics6$actio.fireAnalyticsEvent({
337
481
  action: ACTION.INSERTED,
338
482
  actionSubject: ACTION_SUBJECT.SYNCED_BLOCK,
339
483
  actionSubjectId: ACTION_SUBJECT_ID.REFERENCE_SYNCED_BLOCK_CREATE,
@@ -350,7 +494,7 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
350
494
  return handleBodiedSyncBlockRemoval(bodiedSyncBlockRemoved, syncBlockStore, api, confirmationTransactionRef, getDeleteReason(tr));
351
495
  }
352
496
  if (bodiedSyncBlockAdded.length > 0) {
353
- if (Boolean(tr.getMeta(pmHistoryPluginKey))) {
497
+ if (tr.getMeta(pmHistoryPluginKey)) {
354
498
  // We don't allow bodiedSyncBlock creation via redo, however, we need to return true here to let transaction through so history can be updated properly.
355
499
  // If we simply returns false, creation from redo is blocked as desired, but this results in editor showing redo as possible even though it's not.
356
500
  // After true is returned here and the node is created, we delete the node in the filterTransaction immediately, which cancels out the creation
@@ -361,36 +505,32 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
361
505
  }
362
506
  showInlineExtensionInSyncBlockWarningIfNeeded(tr, state, api, inlineExtensionFlagShown);
363
507
  return true;
364
- } else {
365
- const {
366
- removed: syncBlockRemoved,
367
- added: syncBlockAdded
368
- } = trackSyncBlocks(node => node.type.name === 'syncBlock', tr, state);
369
- let errorFlag = false;
508
+ }
509
+ const {
510
+ removed: syncBlockRemoved,
511
+ added: syncBlockAdded
512
+ } = trackSyncBlocks(node => node.type.name === 'syncBlock', tr, state);
513
+ let errorFlag = false;
370
514
 
371
- // Disable (bodied)syncBlock node deletion/creation/edition in offline mode and trigger an error flag instead
372
- if (isConfirmedSyncBlockDeletion || bodiedSyncBlockRemoved.length > 0 || syncBlockRemoved.length > 0) {
373
- errorFlag = FLAG_ID.CANNOT_DELETE_WHEN_OFFLINE;
374
- } else if (bodiedSyncBlockAdded.length > 0 || syncBlockAdded.length > 0) {
375
- errorFlag = FLAG_ID.CANNOT_CREATE_WHEN_OFFLINE;
376
- } else if (hasEditInSyncBlock(tr, state)) {
377
- errorFlag = FLAG_ID.CANNOT_EDIT_WHEN_OFFLINE;
378
- }
379
- if (errorFlag) {
380
- // Use setTimeout to dispatch transaction in next tick and avoid re-entrant dispatch
381
- setTimeout(() => {
382
- api === null || api === void 0 ? void 0 : api.core.actions.execute(({
383
- tr
384
- }) => {
385
- return tr.setMeta(syncedBlockPluginKey, {
386
- activeFlag: {
387
- id: errorFlag
388
- }
389
- });
390
- });
391
- }, 0);
392
- return false;
393
- }
515
+ // Disable (bodied)syncBlock node deletion/creation/edition in offline mode and trigger an error flag instead
516
+ if (isConfirmedSyncBlockDeletion || bodiedSyncBlockRemoved.length > 0 || syncBlockRemoved.length > 0) {
517
+ errorFlag = FLAG_ID.CANNOT_DELETE_WHEN_OFFLINE;
518
+ } else if (bodiedSyncBlockAdded.length > 0 || syncBlockAdded.length > 0) {
519
+ errorFlag = FLAG_ID.CANNOT_CREATE_WHEN_OFFLINE;
520
+ } else if (hasEditInSyncBlock(tr, state)) {
521
+ errorFlag = FLAG_ID.CANNOT_EDIT_WHEN_OFFLINE;
522
+ }
523
+ if (errorFlag) {
524
+ deferDispatch(() => {
525
+ api === null || api === void 0 ? void 0 : api.core.actions.execute(({
526
+ tr
527
+ }) => tr.setMeta(syncedBlockPluginKey, {
528
+ activeFlag: {
529
+ id: errorFlag
530
+ }
531
+ }));
532
+ });
533
+ return false;
394
534
  }
395
535
  return true;
396
536
  },
@@ -401,22 +541,23 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
401
541
  }
402
542
  });
403
543
  for (const tr of trs) {
404
- if (!tr.getMeta(pmHistoryPluginKey)) {
405
- continue;
406
- }
407
- const {
408
- added
409
- } = trackSyncBlocks(syncBlockStore.sourceManager.isSourceBlock, tr, oldState);
410
- if (added.length > 0) {
411
- // Delete bodiedSyncBlock if it's originated from history, i.e. redo creation
412
- // See filterTransaction above for more details
413
- const tr = newState.tr;
414
- added.forEach(node => {
415
- if (node.from !== undefined && node.to !== undefined) {
416
- tr.delete(node.from, node.to);
417
- }
418
- });
419
- return tr;
544
+ if (tr.getMeta(pmHistoryPluginKey)) {
545
+ const {
546
+ added
547
+ } = trackSyncBlocks(syncBlockStore.sourceManager.isSourceBlock, tr, oldState);
548
+ if (added.length > 0) {
549
+ // Delete bodiedSyncBlock if it's originated from history, i.e. redo creation
550
+ // See filterTransaction above for more details
551
+ const {
552
+ tr
553
+ } = newState;
554
+ added.forEach(node => {
555
+ if (node.from !== undefined && node.to !== undefined) {
556
+ tr.delete(node.from, node.to);
557
+ }
558
+ });
559
+ return tr;
560
+ }
420
561
  }
421
562
  }
422
563
  return null;
@@ -1,6 +1,7 @@
1
1
  import { TextSelection } from '@atlaskit/editor-prosemirror/state';
2
2
  import { FLAG_ID } from '../../types';
3
3
  import { syncedBlockPluginKey } from '../main';
4
+ import { deferDispatch } from './utils';
4
5
  const onRetry = (api, resourceId) => {
5
6
  return () => {
6
7
  var _api$core, _api$core2;
@@ -77,7 +78,7 @@ export const handleBodiedSyncBlockCreation = (bodiedSyncBlockAdded, editorState,
77
78
  to: node.to
78
79
  };
79
80
  const resourceId = node.attrs.resourceId;
80
- setTimeout(() => {
81
+ deferDispatch(() => {
81
82
  var _api$core3;
82
83
  api === null || api === void 0 ? void 0 : (_api$core3 = api.core) === null || _api$core3 === void 0 ? void 0 : _api$core3.actions.execute(({
83
84
  tr
@@ -2,6 +2,19 @@ import { expandSelectionToBlockRange } from '@atlaskit/editor-common/selection';
2
2
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
3
3
  import { ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
4
4
  import { findParentNodeOfType, findParentNodeOfTypeClosestToPos, findSelectedNodeOfType } from '@atlaskit/editor-prosemirror/utils';
5
+ import { fg } from '@atlaskit/platform-feature-flags';
6
+
7
+ /**
8
+ * Defers a callback to the next microtask (when gated) or next macrotask via setTimeout(0).
9
+ * Used to avoid re-entrant ProseMirror dispatch cycles.
10
+ */
11
+ export const deferDispatch = fn => {
12
+ if (fg('platform_synced_block_patch_5')) {
13
+ queueMicrotask(fn);
14
+ } else {
15
+ setTimeout(fn, 0);
16
+ }
17
+ };
5
18
  export const findSyncBlock = (schema, selection) => {
6
19
  const {
7
20
  syncBlock
@@ -5,7 +5,7 @@ import { DOMSerializer, Fragment } from '@atlaskit/editor-prosemirror/model';
5
5
  import { TextSelection } from '@atlaskit/editor-prosemirror/state';
6
6
  import { findSelectedNodeOfType, removeParentNodeOfType, removeSelectedNode, safeInsert } from '@atlaskit/editor-prosemirror/utils';
7
7
  import { syncedBlockPluginKey } from '../pm-plugins/main';
8
- import { canBeConvertedToSyncBlock, findSyncBlock, findSyncBlockOrBodiedSyncBlock, isBodiedSyncBlockNode } from '../pm-plugins/utils/utils';
8
+ import { canBeConvertedToSyncBlock, deferDispatch, findSyncBlock, findSyncBlockOrBodiedSyncBlock, isBodiedSyncBlockNode } from '../pm-plugins/utils/utils';
9
9
  import { FLAG_ID } from '../types';
10
10
  import { pasteSyncBlockHTMLContent } from './utils';
11
11
  export var createSyncedBlock = function createSyncedBlock(_ref) {
@@ -147,9 +147,7 @@ var copySyncedBlockReferenceToClipboardInternal = function copySyncedBlockRefere
147
147
  }
148
148
  var domNode = toDOM(referenceSyncBlockNode, schema);
149
149
  copyDomNode(domNode, referenceSyncBlockNode.type, selection);
150
-
151
- // Use setTimeout to dispatch transaction in next tick and avoid re-entrant dispatch
152
- setTimeout(function () {
150
+ deferDispatch(function () {
153
151
  api === null || api === void 0 || api.core.actions.execute(function (_ref3) {
154
152
  var _api$analytics4;
155
153
  var tr = _ref3.tr;
@@ -169,7 +167,7 @@ var copySyncedBlockReferenceToClipboardInternal = function copySyncedBlockRefere
169
167
  }
170
168
  });
171
169
  });
172
- }, 0);
170
+ });
173
171
  return true;
174
172
  };
175
173
  export var editSyncedBlockSource = function editSyncedBlockSource(syncBlockStore, api) {