@atlaskit/editor-plugin-synced-block 8.4.2 → 8.4.4
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 +23 -0
- package/dist/cjs/pm-plugins/main.js +81 -17
- package/dist/es2019/pm-plugins/main.js +61 -1
- package/dist/esm/pm-plugins/main.js +65 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-synced-block
|
|
2
2
|
|
|
3
|
+
## 8.4.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`0805dc02ccee3`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/0805dc02ccee3) -
|
|
8
|
+
Fix partial cut handling for source synced blocks behind platform_synced_block_patch_13.
|
|
9
|
+
- Updated dependencies
|
|
10
|
+
|
|
11
|
+
## 8.4.3
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- [`085a281306c03`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/085a281306c03) -
|
|
16
|
+
Add defensive mechanisms for synced block EntityNotFound errors:
|
|
17
|
+
- Add retry with exponential backoff when fetching synced block references returns EntityNotFound
|
|
18
|
+
(up to 3 retries with 2s/4s/8s delays)
|
|
19
|
+
- Add transformPasted handler to convert any bodiedSyncBlock nodes arriving via paste into
|
|
20
|
+
syncBlock references, preventing createBlock from being called with the wrong parentId
|
|
21
|
+
|
|
22
|
+
Both changes are gated behind `platform_synced_block_patch_13`.
|
|
23
|
+
|
|
24
|
+
- Updated dependencies
|
|
25
|
+
|
|
3
26
|
## 8.4.2
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
|
@@ -18,9 +18,10 @@ var _utils = require("@atlaskit/editor-common/utils");
|
|
|
18
18
|
var _editorPluginConnectivity = require("@atlaskit/editor-plugin-connectivity");
|
|
19
19
|
var _state = require("@atlaskit/editor-prosemirror/state");
|
|
20
20
|
var _transform = require("@atlaskit/editor-prosemirror/transform");
|
|
21
|
-
var
|
|
21
|
+
var _view2 = require("@atlaskit/editor-prosemirror/view");
|
|
22
22
|
var _editorSyncedBlockProvider = require("@atlaskit/editor-synced-block-provider");
|
|
23
23
|
var _utils2 = require("@atlaskit/editor-synced-block-provider/utils");
|
|
24
|
+
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
24
25
|
var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
|
|
25
26
|
var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
|
|
26
27
|
var _bodiedSyncedBlock = require("../nodeviews/bodiedSyncedBlock");
|
|
@@ -153,6 +154,7 @@ var filterTransactionOnline = function filterTransactionOnline(_ref3) {
|
|
|
153
154
|
// events this lets us triangulate cross-product paste behaviour without standing up a
|
|
154
155
|
// dedicated `cross_product_paste` event subject.
|
|
155
156
|
var isPaste = Boolean((_tr$getMeta = tr.getMeta('paste')) !== null && _tr$getMeta !== void 0 ? _tr$getMeta : tr.getMeta('uiEvent') === 'paste');
|
|
157
|
+
var isPasteOrDrop = isPaste || tr.getMeta('uiEvent') === 'drop';
|
|
156
158
|
syncBlockAdded.forEach(function (syncBlock) {
|
|
157
159
|
var _api$analytics2;
|
|
158
160
|
api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || (_api$analytics2 = _api$analytics2.actions) === null || _api$analytics2 === void 0 || _api$analytics2.fireAnalyticsEvent({
|
|
@@ -180,6 +182,16 @@ var filterTransactionOnline = function filterTransactionOnline(_ref3) {
|
|
|
180
182
|
// After true is returned here and the node is created, we delete the node in the filterTransaction immediately, which cancels out the creation
|
|
181
183
|
return true;
|
|
182
184
|
}
|
|
185
|
+
|
|
186
|
+
// Defense-in-depth: if a bodiedSyncBlock arrives via paste or drag-and-drop,
|
|
187
|
+
// it may have bypassed transformCopied (e.g. cut, browser clipboard).
|
|
188
|
+
// Do not call createBlock — it would use this page's parentId which may
|
|
189
|
+
// be wrong. The bodiedSyncBlock content will still be inserted but will
|
|
190
|
+
// not be registered as a source block in Block Service, which is safer
|
|
191
|
+
// than creating a zombie block under the wrong ARI.
|
|
192
|
+
if (isPasteOrDrop && (0, _platformFeatureFlags.fg)('platform_synced_block_patch_13')) {
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
183
195
|
(0, _handleBodiedSyncBlockCreation.handleBodiedSyncBlockCreation)(bodiedSyncBlockAdded, state, api);
|
|
184
196
|
return true;
|
|
185
197
|
}
|
|
@@ -232,7 +244,7 @@ var buildStatusDecorations = function buildStatusDecorations(doc, syncBlockStore
|
|
|
232
244
|
// Fast path: when all status flags are off and no creations are in flight,
|
|
233
245
|
// no node can produce a decoration — skip the full doc traversal.
|
|
234
246
|
if (!isOffline && !isViewMode && !isDragging && !syncBlockStore.sourceManager.hasPendingCreations()) {
|
|
235
|
-
return
|
|
247
|
+
return _view2.DecorationSet.empty;
|
|
236
248
|
}
|
|
237
249
|
var offlineDecorations = [];
|
|
238
250
|
var viewModeDecorations = [];
|
|
@@ -240,22 +252,22 @@ var buildStatusDecorations = function buildStatusDecorations(doc, syncBlockStore
|
|
|
240
252
|
var dragDecorations = [];
|
|
241
253
|
doc.descendants(function (node, pos) {
|
|
242
254
|
if (node.type.name === 'bodiedSyncBlock' && isOffline) {
|
|
243
|
-
offlineDecorations.push(
|
|
255
|
+
offlineDecorations.push(_view2.Decoration.node(pos, pos + node.nodeSize, {
|
|
244
256
|
class: _syncBlock.SyncBlockStateCssClassName.disabledClassName
|
|
245
257
|
}));
|
|
246
258
|
}
|
|
247
259
|
if (syncBlockStore.isSyncBlock(node) && isViewMode) {
|
|
248
|
-
viewModeDecorations.push(
|
|
260
|
+
viewModeDecorations.push(_view2.Decoration.node(pos, pos + node.nodeSize, {
|
|
249
261
|
class: _syncBlock.SyncBlockStateCssClassName.viewModeClassName
|
|
250
262
|
}));
|
|
251
263
|
}
|
|
252
264
|
if (node.type.name === 'bodiedSyncBlock' && syncBlockStore.sourceManager.isPendingCreation(node.attrs.resourceId)) {
|
|
253
|
-
loadingDecorations.push(
|
|
265
|
+
loadingDecorations.push(_view2.Decoration.node(pos, pos + node.nodeSize, {
|
|
254
266
|
class: _syncBlock.SyncBlockStateCssClassName.creationLoadingClassName
|
|
255
267
|
}));
|
|
256
268
|
}
|
|
257
269
|
if (isDragging && syncBlockStore.isSyncBlock(node)) {
|
|
258
|
-
dragDecorations.push(
|
|
270
|
+
dragDecorations.push(_view2.Decoration.node(pos, pos + node.nodeSize, {
|
|
259
271
|
class: _syncBlock.SyncBlockStateCssClassName.draggingClassName
|
|
260
272
|
}));
|
|
261
273
|
}
|
|
@@ -263,7 +275,7 @@ var buildStatusDecorations = function buildStatusDecorations(doc, syncBlockStore
|
|
|
263
275
|
// only traverse the top-level node of the document, as syncBlock and bodiedSyncBlock are top-level nodes
|
|
264
276
|
return false;
|
|
265
277
|
});
|
|
266
|
-
return
|
|
278
|
+
return _view2.DecorationSet.create(doc, [].concat(offlineDecorations, viewModeDecorations, loadingDecorations, dragDecorations));
|
|
267
279
|
};
|
|
268
280
|
|
|
269
281
|
/**
|
|
@@ -278,6 +290,7 @@ var SyncedBlockPluginContext = /*#__PURE__*/function () {
|
|
|
278
290
|
current: undefined
|
|
279
291
|
});
|
|
280
292
|
(0, _defineProperty2.default)(this, "_isCopyEvent", false);
|
|
293
|
+
(0, _defineProperty2.default)(this, "_isCutEvent", false);
|
|
281
294
|
(0, _defineProperty2.default)(this, "unpublishedFlagShown", new Set());
|
|
282
295
|
(0, _defineProperty2.default)(this, "extensionFlagShown", new Set());
|
|
283
296
|
}
|
|
@@ -291,6 +304,11 @@ var SyncedBlockPluginContext = /*#__PURE__*/function () {
|
|
|
291
304
|
value: function markCopyEvent() {
|
|
292
305
|
this._isCopyEvent = true;
|
|
293
306
|
}
|
|
307
|
+
}, {
|
|
308
|
+
key: "markCutEvent",
|
|
309
|
+
value: function markCutEvent() {
|
|
310
|
+
this._isCutEvent = true;
|
|
311
|
+
}
|
|
294
312
|
}, {
|
|
295
313
|
key: "consumeCopyEvent",
|
|
296
314
|
value: function consumeCopyEvent() {
|
|
@@ -298,6 +316,13 @@ var SyncedBlockPluginContext = /*#__PURE__*/function () {
|
|
|
298
316
|
this._isCopyEvent = false;
|
|
299
317
|
return was;
|
|
300
318
|
}
|
|
319
|
+
}, {
|
|
320
|
+
key: "consumeCutEvent",
|
|
321
|
+
value: function consumeCutEvent() {
|
|
322
|
+
var was = this._isCutEvent;
|
|
323
|
+
this._isCutEvent = false;
|
|
324
|
+
return was;
|
|
325
|
+
}
|
|
301
326
|
}]);
|
|
302
327
|
}();
|
|
303
328
|
var createPlugin = exports.createPlugin = function createPlugin(options, pmPluginFactoryParams, syncBlockStore, api) {
|
|
@@ -396,7 +421,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
|
|
|
396
421
|
// When the perf gate is ON and the doc has synced blocks we do a
|
|
397
422
|
// single traversal here; afterwards `apply()` will map or rebuild
|
|
398
423
|
// only when a status signal changes.
|
|
399
|
-
var initStatusDecorationSet = docHasSyncedBlocks && isPerfExperimentOn ? buildStatusDecorations(instance.doc, syncBlockStore, initIsOffline, initIsViewMode, initIsDragging) :
|
|
424
|
+
var initStatusDecorationSet = docHasSyncedBlocks && isPerfExperimentOn ? buildStatusDecorations(instance.doc, syncBlockStore, initIsOffline, initIsViewMode, initIsDragging) : _view2.DecorationSet.empty;
|
|
400
425
|
return {
|
|
401
426
|
selectionDecorationSet: (0, _selectionDecorations.calculateDecorations)(instance.doc, instance.selection, instance.schema),
|
|
402
427
|
activeFlag: false,
|
|
@@ -482,7 +507,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
|
|
|
482
507
|
if (isPerfExperimentOn) {
|
|
483
508
|
if (!nextHasSyncedBlocks) {
|
|
484
509
|
// No synced blocks → keep empty status decorations
|
|
485
|
-
nextStatusDecorationSet =
|
|
510
|
+
nextStatusDecorationSet = _view2.DecorationSet.empty;
|
|
486
511
|
} else {
|
|
487
512
|
var _api$connectivity4, _api$editorViewMode3, _api$userIntent3;
|
|
488
513
|
// Read current shared-state signals
|
|
@@ -570,7 +595,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
|
|
|
570
595
|
decorations: function decorations(state) {
|
|
571
596
|
var currentPluginState = syncedBlockPluginKey.getState(state);
|
|
572
597
|
if (!currentPluginState) {
|
|
573
|
-
return
|
|
598
|
+
return _view2.DecorationSet.empty;
|
|
574
599
|
}
|
|
575
600
|
var selectionDecorationSet = currentPluginState.selectionDecorationSet,
|
|
576
601
|
statusDecorationSet = currentPluginState.statusDecorationSet,
|
|
@@ -598,7 +623,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
|
|
|
598
623
|
// omit selection decorations (matches old behaviour).
|
|
599
624
|
var statusDecorations = statusDecorationSet.find();
|
|
600
625
|
if (statusDecorations.length === 0) {
|
|
601
|
-
return hasFocus ? selectionDecorationSet :
|
|
626
|
+
return hasFocus ? selectionDecorationSet : _view2.DecorationSet.empty;
|
|
602
627
|
} else {
|
|
603
628
|
return hasFocus ? selectionDecorationSet.add(state.doc, statusDecorations) : statusDecorationSet;
|
|
604
629
|
}
|
|
@@ -618,22 +643,22 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
|
|
|
618
643
|
var dragDecorations = [];
|
|
619
644
|
state.doc.descendants(function (node, pos) {
|
|
620
645
|
if (node.type.name === 'bodiedSyncBlock' && isOffline) {
|
|
621
|
-
offlineDecorations.push(
|
|
646
|
+
offlineDecorations.push(_view2.Decoration.node(pos, pos + node.nodeSize, {
|
|
622
647
|
class: _syncBlock.SyncBlockStateCssClassName.disabledClassName
|
|
623
648
|
}));
|
|
624
649
|
}
|
|
625
650
|
if (_syncBlockStore.isSyncBlock(node) && isViewMode) {
|
|
626
|
-
viewModeDecorations.push(
|
|
651
|
+
viewModeDecorations.push(_view2.Decoration.node(pos, pos + node.nodeSize, {
|
|
627
652
|
class: _syncBlock.SyncBlockStateCssClassName.viewModeClassName
|
|
628
653
|
}));
|
|
629
654
|
}
|
|
630
655
|
if (node.type.name === 'bodiedSyncBlock' && _syncBlockStore.sourceManager.isPendingCreation(node.attrs.resourceId)) {
|
|
631
|
-
loadingDecorations.push(
|
|
656
|
+
loadingDecorations.push(_view2.Decoration.node(pos, pos + node.nodeSize, {
|
|
632
657
|
class: _syncBlock.SyncBlockStateCssClassName.creationLoadingClassName
|
|
633
658
|
}));
|
|
634
659
|
}
|
|
635
660
|
if (isDragging && _syncBlockStore.isSyncBlock(node)) {
|
|
636
|
-
dragDecorations.push(
|
|
661
|
+
dragDecorations.push(_view2.Decoration.node(pos, pos + node.nodeSize, {
|
|
637
662
|
class: _syncBlock.SyncBlockStateCssClassName.draggingClassName
|
|
638
663
|
}));
|
|
639
664
|
}
|
|
@@ -641,7 +666,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
|
|
|
641
666
|
if (api !== null && api !== void 0 && (_api$focus2 = api.focus) !== null && _api$focus2 !== void 0 && (_api$focus2 = _api$focus2.sharedState) !== null && _api$focus2 !== void 0 && (_api$focus2 = _api$focus2.currentState()) !== null && _api$focus2 !== void 0 && _api$focus2.hasFocus) {
|
|
642
667
|
return selectionDecorationSet.add(doc, offlineDecorations).add(doc, viewModeDecorations).add(doc, loadingDecorations).add(doc, dragDecorations);
|
|
643
668
|
} else {
|
|
644
|
-
return
|
|
669
|
+
return _view2.DecorationSet.empty.add(doc, offlineDecorations).add(doc, viewModeDecorations).add(doc, loadingDecorations).add(doc, dragDecorations);
|
|
645
670
|
}
|
|
646
671
|
}
|
|
647
672
|
},
|
|
@@ -660,19 +685,55 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
|
|
|
660
685
|
copy: function copy() {
|
|
661
686
|
ctx.markCopyEvent();
|
|
662
687
|
return false;
|
|
688
|
+
},
|
|
689
|
+
cut: function cut() {
|
|
690
|
+
if ((0, _platformFeatureFlags.fg)('platform_synced_block_patch_13')) {
|
|
691
|
+
ctx.consumeCutEvent();
|
|
692
|
+
ctx.markCutEvent();
|
|
693
|
+
}
|
|
694
|
+
return false;
|
|
663
695
|
}
|
|
664
696
|
},
|
|
697
|
+
transformPasted: function transformPasted(slice, _view) {
|
|
698
|
+
if (!(0, _platformFeatureFlags.fg)('platform_synced_block_patch_13')) {
|
|
699
|
+
return slice;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Defense against bodiedSyncBlock nodes arriving via paste
|
|
703
|
+
// (e.g. drag-and-drop, cut-paste, or browser-level clipboard
|
|
704
|
+
// operations that bypass the transformCopied handler).
|
|
705
|
+
// We cannot convert to a syncBlock reference here because we don't
|
|
706
|
+
// know the source page's parentId (generateResourceIdForReference
|
|
707
|
+
// would use the current page's parentId which is wrong).
|
|
708
|
+
// Instead, strip the bodiedSyncBlock wrapper and keep only the
|
|
709
|
+
// inner content to prevent createBlock being called with the
|
|
710
|
+
// wrong parentId.
|
|
711
|
+
return (0, _utils.mapSlice)(slice, function (node) {
|
|
712
|
+
if (node.type.name === 'bodiedSyncBlock' && node.attrs.resourceId) {
|
|
713
|
+
// Return the inner content without the sync block wrapper.
|
|
714
|
+
// This is the same behavior as when a partial selection of
|
|
715
|
+
// a bodiedSyncBlock is copied (see transformCopied line 937).
|
|
716
|
+
return node.content;
|
|
717
|
+
}
|
|
718
|
+
return node;
|
|
719
|
+
});
|
|
720
|
+
},
|
|
665
721
|
transformCopied: function transformCopied(slice, _ref9) {
|
|
666
722
|
var state = _ref9.state;
|
|
667
723
|
var pluginState = syncedBlockPluginKey.getState(state);
|
|
668
724
|
var syncBlockStore = pluginState === null || pluginState === void 0 ? void 0 : pluginState.syncBlockStore;
|
|
669
725
|
var schema = state.schema;
|
|
670
726
|
var isCopy = ctx.consumeCopyEvent();
|
|
671
|
-
|
|
727
|
+
var isSyncedBlockPatch13Enabled = (0, _platformFeatureFlags.fg)('platform_synced_block_patch_13');
|
|
728
|
+
var isCut = isSyncedBlockPatch13Enabled && ctx.consumeCutEvent();
|
|
729
|
+
if (!syncBlockStore || !isCopy && !isCut) {
|
|
672
730
|
return slice;
|
|
673
731
|
}
|
|
674
732
|
return (0, _utils.mapSlice)(slice, function (node) {
|
|
675
733
|
if (syncBlockStore.referenceManager.isReferenceBlock(node)) {
|
|
734
|
+
if (isCut) {
|
|
735
|
+
return node;
|
|
736
|
+
}
|
|
676
737
|
showCopiedFlag(api);
|
|
677
738
|
return node;
|
|
678
739
|
}
|
|
@@ -682,6 +743,9 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
|
|
|
682
743
|
if (!(0, _utils3.sliceFullyContainsNode)(slice, node)) {
|
|
683
744
|
return node.content;
|
|
684
745
|
}
|
|
746
|
+
if (isSyncedBlockPatch13Enabled && isCut) {
|
|
747
|
+
return node;
|
|
748
|
+
}
|
|
685
749
|
showCopiedFlag(api);
|
|
686
750
|
var newResourceId = syncBlockStore.referenceManager.generateResourceIdForReference(node.attrs.resourceId);
|
|
687
751
|
// Convert bodiedSyncBlock to syncBlock
|
|
@@ -11,6 +11,7 @@ import { ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/tra
|
|
|
11
11
|
import { DecorationSet, Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
12
12
|
import { convertPMNodesToSyncBlockNodes, rebaseTransaction } from '@atlaskit/editor-synced-block-provider';
|
|
13
13
|
import { getSourceProductFromResourceIdSafe } from '@atlaskit/editor-synced-block-provider/utils';
|
|
14
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
14
15
|
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
15
16
|
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
|
|
16
17
|
import { bodiedSyncBlockNodeView, bodiedSyncBlockNodeViewOld } from '../nodeviews/bodiedSyncedBlock';
|
|
@@ -129,6 +130,7 @@ const filterTransactionOnline = ({
|
|
|
129
130
|
// events this lets us triangulate cross-product paste behaviour without standing up a
|
|
130
131
|
// dedicated `cross_product_paste` event subject.
|
|
131
132
|
const isPaste = Boolean((_tr$getMeta = tr.getMeta('paste')) !== null && _tr$getMeta !== void 0 ? _tr$getMeta : tr.getMeta('uiEvent') === 'paste');
|
|
133
|
+
const isPasteOrDrop = isPaste || tr.getMeta('uiEvent') === 'drop';
|
|
132
134
|
syncBlockAdded.forEach(syncBlock => {
|
|
133
135
|
var _api$analytics2, _api$analytics2$actio;
|
|
134
136
|
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({
|
|
@@ -156,6 +158,16 @@ const filterTransactionOnline = ({
|
|
|
156
158
|
// After true is returned here and the node is created, we delete the node in the filterTransaction immediately, which cancels out the creation
|
|
157
159
|
return true;
|
|
158
160
|
}
|
|
161
|
+
|
|
162
|
+
// Defense-in-depth: if a bodiedSyncBlock arrives via paste or drag-and-drop,
|
|
163
|
+
// it may have bypassed transformCopied (e.g. cut, browser clipboard).
|
|
164
|
+
// Do not call createBlock — it would use this page's parentId which may
|
|
165
|
+
// be wrong. The bodiedSyncBlock content will still be inserted but will
|
|
166
|
+
// not be registered as a source block in Block Service, which is safer
|
|
167
|
+
// than creating a zombie block under the wrong ARI.
|
|
168
|
+
if (isPasteOrDrop && fg('platform_synced_block_patch_13')) {
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
159
171
|
handleBodiedSyncBlockCreation(bodiedSyncBlockAdded, state, api);
|
|
160
172
|
return true;
|
|
161
173
|
}
|
|
@@ -254,6 +266,7 @@ class SyncedBlockPluginContext {
|
|
|
254
266
|
current: undefined
|
|
255
267
|
});
|
|
256
268
|
_defineProperty(this, "_isCopyEvent", false);
|
|
269
|
+
_defineProperty(this, "_isCutEvent", false);
|
|
257
270
|
_defineProperty(this, "unpublishedFlagShown", new Set());
|
|
258
271
|
_defineProperty(this, "extensionFlagShown", new Set());
|
|
259
272
|
}
|
|
@@ -263,11 +276,19 @@ class SyncedBlockPluginContext {
|
|
|
263
276
|
markCopyEvent() {
|
|
264
277
|
this._isCopyEvent = true;
|
|
265
278
|
}
|
|
279
|
+
markCutEvent() {
|
|
280
|
+
this._isCutEvent = true;
|
|
281
|
+
}
|
|
266
282
|
consumeCopyEvent() {
|
|
267
283
|
const was = this._isCopyEvent;
|
|
268
284
|
this._isCopyEvent = false;
|
|
269
285
|
return was;
|
|
270
286
|
}
|
|
287
|
+
consumeCutEvent() {
|
|
288
|
+
const was = this._isCutEvent;
|
|
289
|
+
this._isCutEvent = false;
|
|
290
|
+
return was;
|
|
291
|
+
}
|
|
271
292
|
}
|
|
272
293
|
export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api) => {
|
|
273
294
|
const {
|
|
@@ -628,7 +649,38 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
|
|
|
628
649
|
copy: () => {
|
|
629
650
|
ctx.markCopyEvent();
|
|
630
651
|
return false;
|
|
652
|
+
},
|
|
653
|
+
cut: () => {
|
|
654
|
+
if (fg('platform_synced_block_patch_13')) {
|
|
655
|
+
ctx.consumeCutEvent();
|
|
656
|
+
ctx.markCutEvent();
|
|
657
|
+
}
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
},
|
|
661
|
+
transformPasted: (slice, _view) => {
|
|
662
|
+
if (!fg('platform_synced_block_patch_13')) {
|
|
663
|
+
return slice;
|
|
631
664
|
}
|
|
665
|
+
|
|
666
|
+
// Defense against bodiedSyncBlock nodes arriving via paste
|
|
667
|
+
// (e.g. drag-and-drop, cut-paste, or browser-level clipboard
|
|
668
|
+
// operations that bypass the transformCopied handler).
|
|
669
|
+
// We cannot convert to a syncBlock reference here because we don't
|
|
670
|
+
// know the source page's parentId (generateResourceIdForReference
|
|
671
|
+
// would use the current page's parentId which is wrong).
|
|
672
|
+
// Instead, strip the bodiedSyncBlock wrapper and keep only the
|
|
673
|
+
// inner content to prevent createBlock being called with the
|
|
674
|
+
// wrong parentId.
|
|
675
|
+
return mapSlice(slice, node => {
|
|
676
|
+
if (node.type.name === 'bodiedSyncBlock' && node.attrs.resourceId) {
|
|
677
|
+
// Return the inner content without the sync block wrapper.
|
|
678
|
+
// This is the same behavior as when a partial selection of
|
|
679
|
+
// a bodiedSyncBlock is copied (see transformCopied line 937).
|
|
680
|
+
return node.content;
|
|
681
|
+
}
|
|
682
|
+
return node;
|
|
683
|
+
});
|
|
632
684
|
},
|
|
633
685
|
transformCopied: (slice, {
|
|
634
686
|
state
|
|
@@ -639,11 +691,16 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
|
|
|
639
691
|
schema
|
|
640
692
|
} = state;
|
|
641
693
|
const isCopy = ctx.consumeCopyEvent();
|
|
642
|
-
|
|
694
|
+
const isSyncedBlockPatch13Enabled = fg('platform_synced_block_patch_13');
|
|
695
|
+
const isCut = isSyncedBlockPatch13Enabled && ctx.consumeCutEvent();
|
|
696
|
+
if (!syncBlockStore || !isCopy && !isCut) {
|
|
643
697
|
return slice;
|
|
644
698
|
}
|
|
645
699
|
return mapSlice(slice, node => {
|
|
646
700
|
if (syncBlockStore.referenceManager.isReferenceBlock(node)) {
|
|
701
|
+
if (isCut) {
|
|
702
|
+
return node;
|
|
703
|
+
}
|
|
647
704
|
showCopiedFlag(api);
|
|
648
705
|
return node;
|
|
649
706
|
}
|
|
@@ -653,6 +710,9 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
|
|
|
653
710
|
if (!sliceFullyContainsNode(slice, node)) {
|
|
654
711
|
return node.content;
|
|
655
712
|
}
|
|
713
|
+
if (isSyncedBlockPatch13Enabled && isCut) {
|
|
714
|
+
return node;
|
|
715
|
+
}
|
|
656
716
|
showCopiedFlag(api);
|
|
657
717
|
const newResourceId = syncBlockStore.referenceManager.generateResourceIdForReference(node.attrs.resourceId);
|
|
658
718
|
// Convert bodiedSyncBlock to syncBlock
|
|
@@ -19,6 +19,7 @@ import { ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/tra
|
|
|
19
19
|
import { DecorationSet, Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
20
20
|
import { convertPMNodesToSyncBlockNodes, rebaseTransaction } from '@atlaskit/editor-synced-block-provider';
|
|
21
21
|
import { getSourceProductFromResourceIdSafe } from '@atlaskit/editor-synced-block-provider/utils';
|
|
22
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
22
23
|
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
23
24
|
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
|
|
24
25
|
import { bodiedSyncBlockNodeView, bodiedSyncBlockNodeViewOld } from '../nodeviews/bodiedSyncedBlock';
|
|
@@ -146,6 +147,7 @@ var filterTransactionOnline = function filterTransactionOnline(_ref3) {
|
|
|
146
147
|
// events this lets us triangulate cross-product paste behaviour without standing up a
|
|
147
148
|
// dedicated `cross_product_paste` event subject.
|
|
148
149
|
var isPaste = Boolean((_tr$getMeta = tr.getMeta('paste')) !== null && _tr$getMeta !== void 0 ? _tr$getMeta : tr.getMeta('uiEvent') === 'paste');
|
|
150
|
+
var isPasteOrDrop = isPaste || tr.getMeta('uiEvent') === 'drop';
|
|
149
151
|
syncBlockAdded.forEach(function (syncBlock) {
|
|
150
152
|
var _api$analytics2;
|
|
151
153
|
api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || (_api$analytics2 = _api$analytics2.actions) === null || _api$analytics2 === void 0 || _api$analytics2.fireAnalyticsEvent({
|
|
@@ -173,6 +175,16 @@ var filterTransactionOnline = function filterTransactionOnline(_ref3) {
|
|
|
173
175
|
// After true is returned here and the node is created, we delete the node in the filterTransaction immediately, which cancels out the creation
|
|
174
176
|
return true;
|
|
175
177
|
}
|
|
178
|
+
|
|
179
|
+
// Defense-in-depth: if a bodiedSyncBlock arrives via paste or drag-and-drop,
|
|
180
|
+
// it may have bypassed transformCopied (e.g. cut, browser clipboard).
|
|
181
|
+
// Do not call createBlock — it would use this page's parentId which may
|
|
182
|
+
// be wrong. The bodiedSyncBlock content will still be inserted but will
|
|
183
|
+
// not be registered as a source block in Block Service, which is safer
|
|
184
|
+
// than creating a zombie block under the wrong ARI.
|
|
185
|
+
if (isPasteOrDrop && fg('platform_synced_block_patch_13')) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
176
188
|
handleBodiedSyncBlockCreation(bodiedSyncBlockAdded, state, api);
|
|
177
189
|
return true;
|
|
178
190
|
}
|
|
@@ -271,6 +283,7 @@ var SyncedBlockPluginContext = /*#__PURE__*/function () {
|
|
|
271
283
|
current: undefined
|
|
272
284
|
});
|
|
273
285
|
_defineProperty(this, "_isCopyEvent", false);
|
|
286
|
+
_defineProperty(this, "_isCutEvent", false);
|
|
274
287
|
_defineProperty(this, "unpublishedFlagShown", new Set());
|
|
275
288
|
_defineProperty(this, "extensionFlagShown", new Set());
|
|
276
289
|
}
|
|
@@ -284,6 +297,11 @@ var SyncedBlockPluginContext = /*#__PURE__*/function () {
|
|
|
284
297
|
value: function markCopyEvent() {
|
|
285
298
|
this._isCopyEvent = true;
|
|
286
299
|
}
|
|
300
|
+
}, {
|
|
301
|
+
key: "markCutEvent",
|
|
302
|
+
value: function markCutEvent() {
|
|
303
|
+
this._isCutEvent = true;
|
|
304
|
+
}
|
|
287
305
|
}, {
|
|
288
306
|
key: "consumeCopyEvent",
|
|
289
307
|
value: function consumeCopyEvent() {
|
|
@@ -291,6 +309,13 @@ var SyncedBlockPluginContext = /*#__PURE__*/function () {
|
|
|
291
309
|
this._isCopyEvent = false;
|
|
292
310
|
return was;
|
|
293
311
|
}
|
|
312
|
+
}, {
|
|
313
|
+
key: "consumeCutEvent",
|
|
314
|
+
value: function consumeCutEvent() {
|
|
315
|
+
var was = this._isCutEvent;
|
|
316
|
+
this._isCutEvent = false;
|
|
317
|
+
return was;
|
|
318
|
+
}
|
|
294
319
|
}]);
|
|
295
320
|
}();
|
|
296
321
|
export var createPlugin = function createPlugin(options, pmPluginFactoryParams, syncBlockStore, api) {
|
|
@@ -653,19 +678,55 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
|
|
|
653
678
|
copy: function copy() {
|
|
654
679
|
ctx.markCopyEvent();
|
|
655
680
|
return false;
|
|
681
|
+
},
|
|
682
|
+
cut: function cut() {
|
|
683
|
+
if (fg('platform_synced_block_patch_13')) {
|
|
684
|
+
ctx.consumeCutEvent();
|
|
685
|
+
ctx.markCutEvent();
|
|
686
|
+
}
|
|
687
|
+
return false;
|
|
656
688
|
}
|
|
657
689
|
},
|
|
690
|
+
transformPasted: function transformPasted(slice, _view) {
|
|
691
|
+
if (!fg('platform_synced_block_patch_13')) {
|
|
692
|
+
return slice;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Defense against bodiedSyncBlock nodes arriving via paste
|
|
696
|
+
// (e.g. drag-and-drop, cut-paste, or browser-level clipboard
|
|
697
|
+
// operations that bypass the transformCopied handler).
|
|
698
|
+
// We cannot convert to a syncBlock reference here because we don't
|
|
699
|
+
// know the source page's parentId (generateResourceIdForReference
|
|
700
|
+
// would use the current page's parentId which is wrong).
|
|
701
|
+
// Instead, strip the bodiedSyncBlock wrapper and keep only the
|
|
702
|
+
// inner content to prevent createBlock being called with the
|
|
703
|
+
// wrong parentId.
|
|
704
|
+
return mapSlice(slice, function (node) {
|
|
705
|
+
if (node.type.name === 'bodiedSyncBlock' && node.attrs.resourceId) {
|
|
706
|
+
// Return the inner content without the sync block wrapper.
|
|
707
|
+
// This is the same behavior as when a partial selection of
|
|
708
|
+
// a bodiedSyncBlock is copied (see transformCopied line 937).
|
|
709
|
+
return node.content;
|
|
710
|
+
}
|
|
711
|
+
return node;
|
|
712
|
+
});
|
|
713
|
+
},
|
|
658
714
|
transformCopied: function transformCopied(slice, _ref9) {
|
|
659
715
|
var state = _ref9.state;
|
|
660
716
|
var pluginState = syncedBlockPluginKey.getState(state);
|
|
661
717
|
var syncBlockStore = pluginState === null || pluginState === void 0 ? void 0 : pluginState.syncBlockStore;
|
|
662
718
|
var schema = state.schema;
|
|
663
719
|
var isCopy = ctx.consumeCopyEvent();
|
|
664
|
-
|
|
720
|
+
var isSyncedBlockPatch13Enabled = fg('platform_synced_block_patch_13');
|
|
721
|
+
var isCut = isSyncedBlockPatch13Enabled && ctx.consumeCutEvent();
|
|
722
|
+
if (!syncBlockStore || !isCopy && !isCut) {
|
|
665
723
|
return slice;
|
|
666
724
|
}
|
|
667
725
|
return mapSlice(slice, function (node) {
|
|
668
726
|
if (syncBlockStore.referenceManager.isReferenceBlock(node)) {
|
|
727
|
+
if (isCut) {
|
|
728
|
+
return node;
|
|
729
|
+
}
|
|
669
730
|
showCopiedFlag(api);
|
|
670
731
|
return node;
|
|
671
732
|
}
|
|
@@ -675,6 +736,9 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
|
|
|
675
736
|
if (!sliceFullyContainsNode(slice, node)) {
|
|
676
737
|
return node.content;
|
|
677
738
|
}
|
|
739
|
+
if (isSyncedBlockPatch13Enabled && isCut) {
|
|
740
|
+
return node;
|
|
741
|
+
}
|
|
678
742
|
showCopiedFlag(api);
|
|
679
743
|
var newResourceId = syncBlockStore.referenceManager.generateResourceIdForReference(node.attrs.resourceId);
|
|
680
744
|
// Convert bodiedSyncBlock to syncBlock
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-synced-block",
|
|
3
|
-
"version": "8.4.
|
|
3
|
+
"version": "8.4.4",
|
|
4
4
|
"description": "SyncedBlock plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@atlaskit/adf-schema": "^52.14.0",
|
|
32
32
|
"@atlaskit/button": "23.11.8",
|
|
33
|
-
"@atlaskit/dropdown-menu": "16.9.
|
|
33
|
+
"@atlaskit/dropdown-menu": "16.9.5",
|
|
34
34
|
"@atlaskit/editor-json-transformer": "^8.32.0",
|
|
35
35
|
"@atlaskit/editor-plugin-analytics": "^10.1.0",
|
|
36
36
|
"@atlaskit/editor-plugin-block-menu": "^9.2.0",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"date-fns": "^2.17.0"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
|
-
"@atlaskit/editor-common": "^114.
|
|
67
|
+
"@atlaskit/editor-common": "^114.47.0",
|
|
68
68
|
"react": "^18.2.0",
|
|
69
69
|
"react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
|
|
70
70
|
},
|