@atlaskit/editor-plugin-synced-block 23.0.3 → 24.0.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,24 @@
1
1
  # @atlaskit/editor-plugin-synced-block
2
2
 
3
+ ## 24.0.0
4
+
5
+ ### Patch Changes
6
+
7
+ - [`8de064ccdb113`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/8de064ccdb113) -
8
+ Prevent native editing of reference synced block content and error messages and synchronize native
9
+ reference text selection with ProseMirror so normal editor deletion handles the containing
10
+ reference and preserving text selection and link interactions behind the
11
+ platform_editor_blocks_patch_11 experiment.
12
+ - Updated dependencies
13
+
14
+ ## 23.0.4
15
+
16
+ ### Patch Changes
17
+
18
+ - [`59547e8338ae0`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/59547e8338ae0) -
19
+ Clean up feature gate `platform_editor_blocks_patch_8`
20
+ - Updated dependencies
21
+
3
22
  ## 23.0.3
4
23
 
5
24
  ### Patch Changes
@@ -16,7 +16,6 @@ var _state = require("@atlaskit/editor-prosemirror/state");
16
16
  var _utils = require("@atlaskit/editor-prosemirror/utils");
17
17
  var _utils2 = require("@atlaskit/editor-synced-block-provider/utils");
18
18
  var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
19
- var _fg = require("@atlaskit/platform-feature-flags/fg");
20
19
  var _main = require("../pm-plugins/main");
21
20
  var _utils3 = require("../pm-plugins/utils/utils");
22
21
  var _types = require("../types");
@@ -140,7 +139,7 @@ var createSyncedBlock = exports.createSyncedBlock = function createSyncedBlock(_
140
139
  }))).sort();
141
140
  }
142
141
  var _attrs = syncBlockStore.sourceManager.generateBodiedSyncBlockAttrs();
143
- var _newBodiedSyncBlockNode = bodiedSyncBlock.createAndFill(_attrs, conversionInfo.contentToInclude, (0, _fg.fg)('platform_editor_blocks_patch_8') && conversionInfo.breakoutMark ? [conversionInfo.breakoutMark] : undefined);
142
+ var _newBodiedSyncBlockNode = bodiedSyncBlock.createAndFill(_attrs, conversionInfo.contentToInclude, conversionInfo.breakoutMark ? [conversionInfo.breakoutMark] : undefined);
144
143
  if (!_newBodiedSyncBlockNode) {
145
144
  fireAnalyticsEvent === null || fireAnalyticsEvent === void 0 || fireAnalyticsEvent({
146
145
  action: _analytics.ACTION.ERROR,
@@ -18,6 +18,7 @@ var _errorBoundary = require("@atlaskit/editor-common/error-boundary");
18
18
  var _reactNodeView = _interopRequireDefault(require("@atlaskit/editor-common/react-node-view"));
19
19
  var _syncBlock = require("@atlaskit/editor-common/sync-block");
20
20
  var _state = require("@atlaskit/editor-prosemirror/state");
21
+ var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
21
22
  var _editorCommands = require("../editor-commands");
22
23
  var _SyncBlockRendererWrapper = require("../ui/SyncBlockRendererWrapper");
23
24
  var _SyncBlockSSRReactContextsProvider = require("../ui/SyncBlockSSRReactContextsProvider");
@@ -84,8 +85,9 @@ var SyncBlock = exports.SyncBlock = /*#__PURE__*/function (_ReactNodeView) {
84
85
  * through to the browser so that users can select and copy text within a
85
86
  * reference sync block.
86
87
  *
87
- * Events that originate inside the sync block content area (but not the label)
88
- * are stopped so ProseMirror does not intercept them for node-level selection.
88
+ * With patch_11, native selection changes select the containing node without taking focus.
89
+ * Without it, events inside the content area (but not the label) are stopped
90
+ * and mousedown explicitly selects the node.
89
91
  * This includes the full click-drag cycle (mousedown, mousemove, mouseup),
90
92
  * click, dblclick, selectstart and cut. The `cut` event is stopped because
91
93
  * mousedown explicitly sets a NodeSelection on the sync block — without
@@ -124,8 +126,16 @@ var SyncBlock = exports.SyncBlock = /*#__PURE__*/function (_ReactNodeView) {
124
126
  // NodeSelection), we let PM handle the copy event so the
125
127
  // "sync-block-copied" flag is set for the reference paste flow.
126
128
  if (eventType === 'copy') {
127
- var selection = window.getSelection();
128
- return !!(selection && selection.toString().length > 0);
129
+ var selection = this.view.dom.ownerDocument.getSelection();
130
+ if (!selection || selection.toString().length === 0) {
131
+ return false;
132
+ }
133
+ if (!(0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_blocks_patch_11')) {
134
+ return true;
135
+ }
136
+ // A native whole-node selection also contains text; only delegate renderer highlights.
137
+ var renderer = target.closest(".".concat(_syncBlock.SyncBlockSharedCssClassName.renderer));
138
+ return !!(renderer && renderer.contains(selection.anchorNode) && renderer.contains(selection.focusNode));
129
139
  }
130
140
 
131
141
  // For `cut`: when text is selected inside the renderer, stop the
@@ -150,6 +160,10 @@ var SyncBlock = exports.SyncBlock = /*#__PURE__*/function (_ReactNodeView) {
150
160
  return true;
151
161
  }
152
162
  if (STOPPED_EVENT_TYPES.includes(eventType)) {
163
+ if ((0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_blocks_patch_11')) {
164
+ // Preserve renderer focus; the selection listener tracks the containing node.
165
+ return true;
166
+ }
153
167
  // Ensure the syncBlock has a NodeSelection so the floating
154
168
  // toolbar is visible while the user interacts with the renderer.
155
169
  // stopEvent prevents PM from processing the mousedown, so we
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createReferenceSelectionView = void 0;
7
+ var _bindEventListener = require("bind-event-listener");
8
+ var _syncBlock = require("@atlaskit/editor-common/sync-block");
9
+ var _state = require("@atlaskit/editor-prosemirror/state");
10
+ var getSelectedReference = function getSelectedReference(view) {
11
+ var selection = view.dom.ownerDocument.getSelection();
12
+ var anchor = selection === null || selection === void 0 ? void 0 : selection.anchorNode;
13
+ var anchorElement = anchor instanceof Element ? anchor : anchor === null || anchor === void 0 ? void 0 : anchor.parentElement;
14
+ var renderer = anchorElement === null || anchorElement === void 0 ? void 0 : anchorElement.closest(".".concat(_syncBlock.SyncBlockSharedCssClassName.renderer));
15
+ if (!renderer || !(selection !== null && selection !== void 0 && selection.focusNode) || !renderer.contains(selection.focusNode)) {
16
+ return null;
17
+ }
18
+ var reference = renderer.closest(".".concat(_syncBlock.SyncBlockSharedCssClassName.prefix));
19
+ return reference && view.dom.contains(reference) ? reference : null;
20
+ };
21
+ var createReferenceSelectionView = exports.createReferenceSelectionView = function createReferenceSelectionView(view) {
22
+ var syncReferenceSelection = function syncReferenceSelection() {
23
+ var _doc$nodeAt;
24
+ if (!view.editable) {
25
+ return;
26
+ }
27
+ var reference = getSelectedReference(view);
28
+ if (!reference) {
29
+ return;
30
+ }
31
+ var pos = view.posAtDOM(reference, 0);
32
+ var _view$state = view.state,
33
+ doc = _view$state.doc,
34
+ schema = _view$state.schema,
35
+ selection = _view$state.selection;
36
+ if (((_doc$nodeAt = doc.nodeAt(pos)) === null || _doc$nodeAt === void 0 ? void 0 : _doc$nodeAt.type) !== schema.nodes.syncBlock || selection instanceof _state.NodeSelection && selection.from === pos) {
37
+ return;
38
+ }
39
+ // The selectable renderer owns native focus, so PM's observer ignores its selection.
40
+ // Track the containing atom without focusing the editor or clearing highlighted text.
41
+ view.dispatch(view.state.tr.setSelection(_state.NodeSelection.create(doc, pos)));
42
+ };
43
+ var unbind = (0, _bindEventListener.bind)(view.dom.ownerDocument, {
44
+ type: 'selectionchange',
45
+ listener: syncReferenceSelection
46
+ });
47
+ return {
48
+ destroy: unbind
49
+ };
50
+ };
@@ -21,13 +21,14 @@ var _editorPluginConnectivity = require("@atlaskit/editor-plugin-connectivity");
21
21
  var _model = require("@atlaskit/editor-prosemirror/model");
22
22
  var _state = require("@atlaskit/editor-prosemirror/state");
23
23
  var _transform = require("@atlaskit/editor-prosemirror/transform");
24
- var _view2 = require("@atlaskit/editor-prosemirror/view");
24
+ var _view3 = require("@atlaskit/editor-prosemirror/view");
25
25
  var _editorSyncedBlockProvider = require("@atlaskit/editor-synced-block-provider");
26
26
  var _utils2 = require("@atlaskit/editor-synced-block-provider/utils");
27
27
  var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
28
28
  var _bodiedSyncedBlock = require("../nodeviews/bodiedSyncedBlock");
29
29
  var _syncedBlock = require("../nodeviews/syncedBlock");
30
30
  var _types = require("../types");
31
+ var _createReferenceSelectionView = require("./createReferenceSelectionView");
31
32
  var _handleBodiedSyncBlockCreation = require("./utils/handle-bodied-sync-block-creation");
32
33
  var _handleBodiedSyncBlockRemoval = require("./utils/handle-bodied-sync-block-removal");
33
34
  var _hasSyncedBlocks = require("./utils/has-synced-blocks");
@@ -425,7 +426,7 @@ var buildStatusDecorations = function buildStatusDecorations(doc, syncBlockStore
425
426
  // Fast path: when all status flags are off and no creations are in flight,
426
427
  // no node can produce a decoration — skip the full doc traversal.
427
428
  if (!isOffline && !isViewMode && !isDragging && !syncBlockStore.sourceManager.hasPendingCreations()) {
428
- return _view2.DecorationSet.empty;
429
+ return _view3.DecorationSet.empty;
429
430
  }
430
431
  var offlineDecorations = [];
431
432
  var viewModeDecorations = [];
@@ -433,22 +434,22 @@ var buildStatusDecorations = function buildStatusDecorations(doc, syncBlockStore
433
434
  var dragDecorations = [];
434
435
  doc.descendants(function (node, pos) {
435
436
  if (node.type.name === 'bodiedSyncBlock' && isOffline) {
436
- offlineDecorations.push(_view2.Decoration.node(pos, pos + node.nodeSize, {
437
+ offlineDecorations.push(_view3.Decoration.node(pos, pos + node.nodeSize, {
437
438
  class: _syncBlock.SyncBlockStateCssClassName.disabledClassName
438
439
  }));
439
440
  }
440
441
  if (syncBlockStore.isSyncBlock(node) && isViewMode) {
441
- viewModeDecorations.push(_view2.Decoration.node(pos, pos + node.nodeSize, {
442
+ viewModeDecorations.push(_view3.Decoration.node(pos, pos + node.nodeSize, {
442
443
  class: _syncBlock.SyncBlockStateCssClassName.viewModeClassName
443
444
  }));
444
445
  }
445
446
  if (node.type.name === 'bodiedSyncBlock' && syncBlockStore.sourceManager.isPendingCreation(node.attrs.resourceId)) {
446
- loadingDecorations.push(_view2.Decoration.node(pos, pos + node.nodeSize, {
447
+ loadingDecorations.push(_view3.Decoration.node(pos, pos + node.nodeSize, {
447
448
  class: _syncBlock.SyncBlockStateCssClassName.creationLoadingClassName
448
449
  }));
449
450
  }
450
451
  if (isDragging && syncBlockStore.isSyncBlock(node)) {
451
- dragDecorations.push(_view2.Decoration.node(pos, pos + node.nodeSize, {
452
+ dragDecorations.push(_view3.Decoration.node(pos, pos + node.nodeSize, {
452
453
  class: _syncBlock.SyncBlockStateCssClassName.draggingClassName
453
454
  }));
454
455
  }
@@ -456,7 +457,7 @@ var buildStatusDecorations = function buildStatusDecorations(doc, syncBlockStore
456
457
  // only traverse the top-level node of the document, as syncBlock and bodiedSyncBlock are top-level nodes
457
458
  return false;
458
459
  });
459
- return _view2.DecorationSet.create(doc, [].concat(offlineDecorations, viewModeDecorations, loadingDecorations, dragDecorations));
460
+ return _view3.DecorationSet.create(doc, [].concat(offlineDecorations, viewModeDecorations, loadingDecorations, dragDecorations));
460
461
  };
461
462
 
462
463
  /**
@@ -715,7 +716,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
715
716
  // When the perf gate is ON and the doc has synced blocks we do a
716
717
  // single traversal here; afterwards `apply()` will map or rebuild
717
718
  // only when a status signal changes.
718
- var initStatusDecorationSet = docHasSyncedBlocks ? buildStatusDecorations(instance.doc, syncBlockStore, initIsOffline, initIsViewMode, initIsDragging) : _view2.DecorationSet.empty;
719
+ var initStatusDecorationSet = docHasSyncedBlocks ? buildStatusDecorations(instance.doc, syncBlockStore, initIsOffline, initIsViewMode, initIsDragging) : _view3.DecorationSet.empty;
719
720
  return {
720
721
  selectionDecorationSet: (0, _selectionDecorations.calculateDecorations)(instance.doc, instance.selection, instance.schema),
721
722
  activeFlag: false,
@@ -798,7 +799,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
798
799
  var nextIsDragging = prevDragging;
799
800
  if (!nextHasSyncedBlocks) {
800
801
  // No synced blocks → keep empty status decorations
801
- nextStatusDecorationSet = _view2.DecorationSet.empty;
802
+ nextStatusDecorationSet = _view3.DecorationSet.empty;
802
803
  } else {
803
804
  var _api$connectivity4, _api$editorViewMode3, _api$userIntent3;
804
805
  // Read current shared-state signals
@@ -879,7 +880,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
879
880
  var _api$focus$sharedStat, _api$focus;
880
881
  var currentPluginState = syncedBlockPluginKey.getState(state);
881
882
  if (!currentPluginState) {
882
- return _view2.DecorationSet.empty;
883
+ return _view3.DecorationSet.empty;
883
884
  }
884
885
  var selectionDecorationSet = currentPluginState.selectionDecorationSet,
885
886
  statusDecorationSet = currentPluginState.statusDecorationSet,
@@ -905,7 +906,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
905
906
  // omit selection decorations (matches old behaviour).
906
907
  var statusDecorations = statusDecorationSet.find();
907
908
  if (statusDecorations.length === 0) {
908
- return hasFocus ? selectionDecorationSet : _view2.DecorationSet.empty;
909
+ return hasFocus ? selectionDecorationSet : _view3.DecorationSet.empty;
909
910
  } else {
910
911
  return hasFocus ? selectionDecorationSet.add(state.doc, statusDecorations) : statusDecorationSet;
911
912
  }
@@ -1008,6 +1009,12 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
1008
1009
  return transformedSlice;
1009
1010
  }
1010
1011
  },
1012
+ view: function view(_view2) {
1013
+ if (!(0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_blocks_patch_11')) {
1014
+ return {};
1015
+ }
1016
+ return (0, _createReferenceSelectionView.createReferenceSelectionView)(_view2);
1017
+ },
1011
1018
  filterTransaction: function filterTransaction(tr, state) {
1012
1019
  var _api$editorViewMode4, _api$connectivity5;
1013
1020
  // Lazy-init: when no synced block currently exists in the doc and the
@@ -11,6 +11,7 @@ var _react = _interopRequireWildcard(require("react"));
11
11
  var _bindEventListener = require("bind-event-listener");
12
12
  var _syncBlock = require("@atlaskit/editor-common/sync-block");
13
13
  var _editorSyncedBlockProvider = require("@atlaskit/editor-synced-block-provider");
14
+ var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
14
15
  var _getUnpublishedSourceType = require("./getUnpublishedSourceType");
15
16
  var _SyncBlockLabel = require("./SyncBlockLabel");
16
17
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
@@ -66,6 +67,20 @@ var SyncBlockRendererWrapperComponent = function SyncBlockRendererWrapperCompone
66
67
  // ourselves. A native listener (rather than an onClick on the div) is used
67
68
  // so we delegate link clicks without making the container itself interactive.
68
69
  var rendererRef = (0, _react.useRef)(null);
70
+ var preventReferenceEditing = (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_blocks_patch_11');
71
+ (0, _react.useEffect)(function () {
72
+ var container = rendererRef.current;
73
+ if (!preventReferenceEditing || !container) {
74
+ return;
75
+ }
76
+ return (0, _bindEventListener.bind)(container, {
77
+ // Block native edits without intercepting ProseMirror's node deletion.
78
+ type: 'beforeinput',
79
+ listener: function listener(event) {
80
+ return event.preventDefault();
81
+ }
82
+ });
83
+ }, [preventReferenceEditing]);
69
84
  (0, _react.useEffect)(function () {
70
85
  var containerEl = rendererRef.current;
71
86
  if (!containerEl) {
@@ -7,7 +7,6 @@ import { NodeSelection, Selection, TextSelection } from '@atlaskit/editor-prosem
7
7
  import { findSelectedNodeOfType, removeParentNodeOfType, removeSelectedNode, safeInsert } from '@atlaskit/editor-prosemirror/utils';
8
8
  import { getSourceProductFromResourceIdSafe } from '@atlaskit/editor-synced-block-provider/utils';
9
9
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
10
- import { fg } from '@atlaskit/platform-feature-flags/fg';
11
10
  import { creationMetaKey, deleteMechanismMetaKey, syncedBlockPluginKey } from '../pm-plugins/main';
12
11
  import { canBeConvertedToSyncBlock, deferDispatch, findSyncBlock, findSyncBlockOrBodiedSyncBlock, isBodiedSyncBlockNode } from '../pm-plugins/utils/utils';
13
12
  import { FLAG_ID } from '../types';
@@ -133,7 +132,7 @@ export const createSyncedBlock = ({
133
132
  nodeTypes = [...new Set(getSourceNodesFromSelectionRange(tr, tr.selection).map(node => node.type.name))].sort();
134
133
  }
135
134
  const attrs = syncBlockStore.sourceManager.generateBodiedSyncBlockAttrs();
136
- const newBodiedSyncBlockNode = bodiedSyncBlock.createAndFill(attrs, conversionInfo.contentToInclude, fg('platform_editor_blocks_patch_8') && conversionInfo.breakoutMark ? [conversionInfo.breakoutMark] : undefined);
135
+ const newBodiedSyncBlockNode = bodiedSyncBlock.createAndFill(attrs, conversionInfo.contentToInclude, conversionInfo.breakoutMark ? [conversionInfo.breakoutMark] : undefined);
137
136
  if (!newBodiedSyncBlockNode) {
138
137
  fireAnalyticsEvent === null || fireAnalyticsEvent === void 0 ? void 0 : fireAnalyticsEvent({
139
138
  action: ACTION.ERROR,
@@ -7,6 +7,7 @@ import ReactNodeView from '@atlaskit/editor-common/react-node-view';
7
7
 
8
8
  import { SyncBlockSharedCssClassName, SyncBlockLabelSharedCssClassName, SyncBlockActionsProvider } from '@atlaskit/editor-common/sync-block';
9
9
  import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
10
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
10
11
  import { removeSyncedBlockAtPos } from '../editor-commands';
11
12
  import { SyncBlockRendererWrapper } from '../ui/SyncBlockRendererWrapper';
12
13
  import { SyncBlockSSRReactContextsProvider } from '../ui/SyncBlockSSRReactContextsProvider';
@@ -64,8 +65,9 @@ export class SyncBlock extends ReactNodeView {
64
65
  * through to the browser so that users can select and copy text within a
65
66
  * reference sync block.
66
67
  *
67
- * Events that originate inside the sync block content area (but not the label)
68
- * are stopped so ProseMirror does not intercept them for node-level selection.
68
+ * With patch_11, native selection changes select the containing node without taking focus.
69
+ * Without it, events inside the content area (but not the label) are stopped
70
+ * and mousedown explicitly selects the node.
69
71
  * This includes the full click-drag cycle (mousedown, mousemove, mouseup),
70
72
  * click, dblclick, selectstart and cut. The `cut` event is stopped because
71
73
  * mousedown explicitly sets a NodeSelection on the sync block — without
@@ -102,8 +104,16 @@ export class SyncBlock extends ReactNodeView {
102
104
  // NodeSelection), we let PM handle the copy event so the
103
105
  // "sync-block-copied" flag is set for the reference paste flow.
104
106
  if (eventType === 'copy') {
105
- const selection = window.getSelection();
106
- return !!(selection && selection.toString().length > 0);
107
+ const selection = this.view.dom.ownerDocument.getSelection();
108
+ if (!selection || selection.toString().length === 0) {
109
+ return false;
110
+ }
111
+ if (!isExperimentEnabled('platform_editor_blocks_patch_11')) {
112
+ return true;
113
+ }
114
+ // A native whole-node selection also contains text; only delegate renderer highlights.
115
+ const renderer = target.closest(`.${SyncBlockSharedCssClassName.renderer}`);
116
+ return !!(renderer && renderer.contains(selection.anchorNode) && renderer.contains(selection.focusNode));
107
117
  }
108
118
 
109
119
  // For `cut`: when text is selected inside the renderer, stop the
@@ -128,6 +138,10 @@ export class SyncBlock extends ReactNodeView {
128
138
  return true;
129
139
  }
130
140
  if (STOPPED_EVENT_TYPES.includes(eventType)) {
141
+ if (isExperimentEnabled('platform_editor_blocks_patch_11')) {
142
+ // Preserve renderer focus; the selection listener tracks the containing node.
143
+ return true;
144
+ }
131
145
  // Ensure the syncBlock has a NodeSelection so the floating
132
146
  // toolbar is visible while the user interacts with the renderer.
133
147
  // stopEvent prevents PM from processing the mousedown, so we
@@ -0,0 +1,45 @@
1
+ import { bind } from 'bind-event-listener';
2
+ import { SyncBlockSharedCssClassName } from '@atlaskit/editor-common/sync-block';
3
+ import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
4
+ const getSelectedReference = view => {
5
+ const selection = view.dom.ownerDocument.getSelection();
6
+ const anchor = selection === null || selection === void 0 ? void 0 : selection.anchorNode;
7
+ const anchorElement = anchor instanceof Element ? anchor : anchor === null || anchor === void 0 ? void 0 : anchor.parentElement;
8
+ const renderer = anchorElement === null || anchorElement === void 0 ? void 0 : anchorElement.closest(`.${SyncBlockSharedCssClassName.renderer}`);
9
+ if (!renderer || !(selection !== null && selection !== void 0 && selection.focusNode) || !renderer.contains(selection.focusNode)) {
10
+ return null;
11
+ }
12
+ const reference = renderer.closest(`.${SyncBlockSharedCssClassName.prefix}`);
13
+ return reference && view.dom.contains(reference) ? reference : null;
14
+ };
15
+ export const createReferenceSelectionView = view => {
16
+ const syncReferenceSelection = () => {
17
+ var _doc$nodeAt;
18
+ if (!view.editable) {
19
+ return;
20
+ }
21
+ const reference = getSelectedReference(view);
22
+ if (!reference) {
23
+ return;
24
+ }
25
+ const pos = view.posAtDOM(reference, 0);
26
+ const {
27
+ doc,
28
+ schema,
29
+ selection
30
+ } = view.state;
31
+ if (((_doc$nodeAt = doc.nodeAt(pos)) === null || _doc$nodeAt === void 0 ? void 0 : _doc$nodeAt.type) !== schema.nodes.syncBlock || selection instanceof NodeSelection && selection.from === pos) {
32
+ return;
33
+ }
34
+ // The selectable renderer owns native focus, so PM's observer ignores its selection.
35
+ // Track the containing atom without focusing the editor or clearing highlighted text.
36
+ view.dispatch(view.state.tr.setSelection(NodeSelection.create(doc, pos)));
37
+ };
38
+ const unbind = bind(view.dom.ownerDocument, {
39
+ type: 'selectionchange',
40
+ listener: syncReferenceSelection
41
+ });
42
+ return {
43
+ destroy: unbind
44
+ };
45
+ };
@@ -16,6 +16,7 @@ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-e
16
16
  import { bodiedSyncBlockNodeView } from '../nodeviews/bodiedSyncedBlock';
17
17
  import { SyncBlock as SyncBlockView } from '../nodeviews/syncedBlock';
18
18
  import { FLAG_ID } from '../types';
19
+ import { createReferenceSelectionView } from './createReferenceSelectionView';
19
20
  import { handleBodiedSyncBlockCreation } from './utils/handle-bodied-sync-block-creation';
20
21
  import { handleBodiedSyncBlockRemoval } from './utils/handle-bodied-sync-block-removal';
21
22
  import { hasSyncedBlocks } from './utils/has-synced-blocks';
@@ -948,6 +949,12 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
948
949
  return transformedSlice;
949
950
  }
950
951
  },
952
+ view: view => {
953
+ if (!isExperimentEnabled('platform_editor_blocks_patch_11')) {
954
+ return {};
955
+ }
956
+ return createReferenceSelectionView(view);
957
+ },
951
958
  filterTransaction: (tr, state) => {
952
959
  var _api$editorViewMode4, _api$editorViewMode4$, _api$connectivity5, _api$connectivity5$sh;
953
960
  // Lazy-init: when no synced block currently exists in the doc and the
@@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import { bind } from 'bind-event-listener';
3
3
  import { SyncBlockSharedCssClassName } from '@atlaskit/editor-common/sync-block';
4
4
  import { SyncBlockError, useFetchSyncBlockData, useFetchSyncBlockTitle } from '@atlaskit/editor-synced-block-provider';
5
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
5
6
  import { getUnpublishedSourceType } from './getUnpublishedSourceType';
6
7
  import { SyncBlockLabel } from './SyncBlockLabel';
7
8
  const SyncBlockRendererWrapperDataId = 'sync-block-plugin-renderer-wrapper';
@@ -54,6 +55,18 @@ const SyncBlockRendererWrapperComponent = ({
54
55
  // ourselves. A native listener (rather than an onClick on the div) is used
55
56
  // so we delegate link clicks without making the container itself interactive.
56
57
  const rendererRef = useRef(null);
58
+ const preventReferenceEditing = isExperimentEnabled('platform_editor_blocks_patch_11');
59
+ useEffect(() => {
60
+ const container = rendererRef.current;
61
+ if (!preventReferenceEditing || !container) {
62
+ return;
63
+ }
64
+ return bind(container, {
65
+ // Block native edits without intercepting ProseMirror's node deletion.
66
+ type: 'beforeinput',
67
+ listener: event => event.preventDefault()
68
+ });
69
+ }, [preventReferenceEditing]);
57
70
  useEffect(() => {
58
71
  const containerEl = rendererRef.current;
59
72
  if (!containerEl) {
@@ -11,7 +11,6 @@ import { NodeSelection, Selection, TextSelection } from '@atlaskit/editor-prosem
11
11
  import { findSelectedNodeOfType, removeParentNodeOfType, removeSelectedNode, safeInsert } from '@atlaskit/editor-prosemirror/utils';
12
12
  import { getSourceProductFromResourceIdSafe } from '@atlaskit/editor-synced-block-provider/utils';
13
13
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
14
- import { fg } from '@atlaskit/platform-feature-flags/fg';
15
14
  import { creationMetaKey, deleteMechanismMetaKey, syncedBlockPluginKey } from '../pm-plugins/main';
16
15
  import { canBeConvertedToSyncBlock, deferDispatch, findSyncBlock, findSyncBlockOrBodiedSyncBlock, isBodiedSyncBlockNode } from '../pm-plugins/utils/utils';
17
16
  import { FLAG_ID } from '../types';
@@ -133,7 +132,7 @@ export var createSyncedBlock = function createSyncedBlock(_ref) {
133
132
  }))).sort();
134
133
  }
135
134
  var _attrs = syncBlockStore.sourceManager.generateBodiedSyncBlockAttrs();
136
- var _newBodiedSyncBlockNode = bodiedSyncBlock.createAndFill(_attrs, conversionInfo.contentToInclude, fg('platform_editor_blocks_patch_8') && conversionInfo.breakoutMark ? [conversionInfo.breakoutMark] : undefined);
135
+ var _newBodiedSyncBlockNode = bodiedSyncBlock.createAndFill(_attrs, conversionInfo.contentToInclude, conversionInfo.breakoutMark ? [conversionInfo.breakoutMark] : undefined);
137
136
  if (!_newBodiedSyncBlockNode) {
138
137
  fireAnalyticsEvent === null || fireAnalyticsEvent === void 0 || fireAnalyticsEvent({
139
138
  action: ACTION.ERROR,
@@ -16,6 +16,7 @@ import ReactNodeView from '@atlaskit/editor-common/react-node-view';
16
16
 
17
17
  import { SyncBlockSharedCssClassName, SyncBlockLabelSharedCssClassName, SyncBlockActionsProvider } from '@atlaskit/editor-common/sync-block';
18
18
  import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
19
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
19
20
  import { removeSyncedBlockAtPos } from '../editor-commands';
20
21
  import { SyncBlockRendererWrapper } from '../ui/SyncBlockRendererWrapper';
21
22
  import { SyncBlockSSRReactContextsProvider } from '../ui/SyncBlockSSRReactContextsProvider';
@@ -80,8 +81,9 @@ export var SyncBlock = /*#__PURE__*/function (_ReactNodeView) {
80
81
  * through to the browser so that users can select and copy text within a
81
82
  * reference sync block.
82
83
  *
83
- * Events that originate inside the sync block content area (but not the label)
84
- * are stopped so ProseMirror does not intercept them for node-level selection.
84
+ * With patch_11, native selection changes select the containing node without taking focus.
85
+ * Without it, events inside the content area (but not the label) are stopped
86
+ * and mousedown explicitly selects the node.
85
87
  * This includes the full click-drag cycle (mousedown, mousemove, mouseup),
86
88
  * click, dblclick, selectstart and cut. The `cut` event is stopped because
87
89
  * mousedown explicitly sets a NodeSelection on the sync block — without
@@ -120,8 +122,16 @@ export var SyncBlock = /*#__PURE__*/function (_ReactNodeView) {
120
122
  // NodeSelection), we let PM handle the copy event so the
121
123
  // "sync-block-copied" flag is set for the reference paste flow.
122
124
  if (eventType === 'copy') {
123
- var selection = window.getSelection();
124
- return !!(selection && selection.toString().length > 0);
125
+ var selection = this.view.dom.ownerDocument.getSelection();
126
+ if (!selection || selection.toString().length === 0) {
127
+ return false;
128
+ }
129
+ if (!isExperimentEnabled('platform_editor_blocks_patch_11')) {
130
+ return true;
131
+ }
132
+ // A native whole-node selection also contains text; only delegate renderer highlights.
133
+ var renderer = target.closest(".".concat(SyncBlockSharedCssClassName.renderer));
134
+ return !!(renderer && renderer.contains(selection.anchorNode) && renderer.contains(selection.focusNode));
125
135
  }
126
136
 
127
137
  // For `cut`: when text is selected inside the renderer, stop the
@@ -146,6 +156,10 @@ export var SyncBlock = /*#__PURE__*/function (_ReactNodeView) {
146
156
  return true;
147
157
  }
148
158
  if (STOPPED_EVENT_TYPES.includes(eventType)) {
159
+ if (isExperimentEnabled('platform_editor_blocks_patch_11')) {
160
+ // Preserve renderer focus; the selection listener tracks the containing node.
161
+ return true;
162
+ }
149
163
  // Ensure the syncBlock has a NodeSelection so the floating
150
164
  // toolbar is visible while the user interacts with the renderer.
151
165
  // stopEvent prevents PM from processing the mousedown, so we
@@ -0,0 +1,44 @@
1
+ import { bind } from 'bind-event-listener';
2
+ import { SyncBlockSharedCssClassName } from '@atlaskit/editor-common/sync-block';
3
+ import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
4
+ var getSelectedReference = function getSelectedReference(view) {
5
+ var selection = view.dom.ownerDocument.getSelection();
6
+ var anchor = selection === null || selection === void 0 ? void 0 : selection.anchorNode;
7
+ var anchorElement = anchor instanceof Element ? anchor : anchor === null || anchor === void 0 ? void 0 : anchor.parentElement;
8
+ var renderer = anchorElement === null || anchorElement === void 0 ? void 0 : anchorElement.closest(".".concat(SyncBlockSharedCssClassName.renderer));
9
+ if (!renderer || !(selection !== null && selection !== void 0 && selection.focusNode) || !renderer.contains(selection.focusNode)) {
10
+ return null;
11
+ }
12
+ var reference = renderer.closest(".".concat(SyncBlockSharedCssClassName.prefix));
13
+ return reference && view.dom.contains(reference) ? reference : null;
14
+ };
15
+ export var createReferenceSelectionView = function createReferenceSelectionView(view) {
16
+ var syncReferenceSelection = function syncReferenceSelection() {
17
+ var _doc$nodeAt;
18
+ if (!view.editable) {
19
+ return;
20
+ }
21
+ var reference = getSelectedReference(view);
22
+ if (!reference) {
23
+ return;
24
+ }
25
+ var pos = view.posAtDOM(reference, 0);
26
+ var _view$state = view.state,
27
+ doc = _view$state.doc,
28
+ schema = _view$state.schema,
29
+ selection = _view$state.selection;
30
+ if (((_doc$nodeAt = doc.nodeAt(pos)) === null || _doc$nodeAt === void 0 ? void 0 : _doc$nodeAt.type) !== schema.nodes.syncBlock || selection instanceof NodeSelection && selection.from === pos) {
31
+ return;
32
+ }
33
+ // The selectable renderer owns native focus, so PM's observer ignores its selection.
34
+ // Track the containing atom without focusing the editor or clearing highlighted text.
35
+ view.dispatch(view.state.tr.setSelection(NodeSelection.create(doc, pos)));
36
+ };
37
+ var unbind = bind(view.dom.ownerDocument, {
38
+ type: 'selectionchange',
39
+ listener: syncReferenceSelection
40
+ });
41
+ return {
42
+ destroy: unbind
43
+ };
44
+ };
@@ -26,6 +26,7 @@ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-e
26
26
  import { bodiedSyncBlockNodeView } from '../nodeviews/bodiedSyncedBlock';
27
27
  import { SyncBlock as SyncBlockView } from '../nodeviews/syncedBlock';
28
28
  import { FLAG_ID } from '../types';
29
+ import { createReferenceSelectionView } from './createReferenceSelectionView';
29
30
  import { handleBodiedSyncBlockCreation } from './utils/handle-bodied-sync-block-creation';
30
31
  import { handleBodiedSyncBlockRemoval } from './utils/handle-bodied-sync-block-removal';
31
32
  import { hasSyncedBlocks } from './utils/has-synced-blocks';
@@ -1001,6 +1002,12 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
1001
1002
  return transformedSlice;
1002
1003
  }
1003
1004
  },
1005
+ view: function view(_view2) {
1006
+ if (!isExperimentEnabled('platform_editor_blocks_patch_11')) {
1007
+ return {};
1008
+ }
1009
+ return createReferenceSelectionView(_view2);
1010
+ },
1004
1011
  filterTransaction: function filterTransaction(tr, state) {
1005
1012
  var _api$editorViewMode4, _api$connectivity5;
1006
1013
  // Lazy-init: when no synced block currently exists in the doc and the
@@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
3
3
  import { bind } from 'bind-event-listener';
4
4
  import { SyncBlockSharedCssClassName } from '@atlaskit/editor-common/sync-block';
5
5
  import { SyncBlockError, useFetchSyncBlockData, useFetchSyncBlockTitle } from '@atlaskit/editor-synced-block-provider';
6
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
6
7
  import { getUnpublishedSourceType } from './getUnpublishedSourceType';
7
8
  import { SyncBlockLabel } from './SyncBlockLabel';
8
9
  var SyncBlockRendererWrapperDataId = 'sync-block-plugin-renderer-wrapper';
@@ -57,6 +58,20 @@ var SyncBlockRendererWrapperComponent = function SyncBlockRendererWrapperCompone
57
58
  // ourselves. A native listener (rather than an onClick on the div) is used
58
59
  // so we delegate link clicks without making the container itself interactive.
59
60
  var rendererRef = useRef(null);
61
+ var preventReferenceEditing = isExperimentEnabled('platform_editor_blocks_patch_11');
62
+ useEffect(function () {
63
+ var container = rendererRef.current;
64
+ if (!preventReferenceEditing || !container) {
65
+ return;
66
+ }
67
+ return bind(container, {
68
+ // Block native edits without intercepting ProseMirror's node deletion.
69
+ type: 'beforeinput',
70
+ listener: function listener(event) {
71
+ return event.preventDefault();
72
+ }
73
+ });
74
+ }, [preventReferenceEditing]);
60
75
  useEffect(function () {
61
76
  var containerEl = rendererRef.current;
62
77
  if (!containerEl) {
@@ -38,8 +38,9 @@ export declare class SyncBlock extends ReactNodeView<SyncBlockNodeViewProps> {
38
38
  * through to the browser so that users can select and copy text within a
39
39
  * reference sync block.
40
40
  *
41
- * Events that originate inside the sync block content area (but not the label)
42
- * are stopped so ProseMirror does not intercept them for node-level selection.
41
+ * With patch_11, native selection changes select the containing node without taking focus.
42
+ * Without it, events inside the content area (but not the label) are stopped
43
+ * and mousedown explicitly selects the node.
43
44
  * This includes the full click-drag cycle (mousedown, mousemove, mouseup),
44
45
  * click, dblclick, selectstart and cut. The `cut` event is stopped because
45
46
  * mousedown explicitly sets a NodeSelection on the sync block — without
@@ -0,0 +1,4 @@
1
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
2
+ export declare const createReferenceSelectionView: (view: EditorView) => {
3
+ destroy: () => void;
4
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-synced-block",
3
- "version": "23.0.3",
3
+ "version": "24.0.0",
4
4
  "description": "SyncedBlock plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -24,24 +24,24 @@
24
24
  "@atlaskit/button": "25.4.5",
25
25
  "@atlaskit/dropdown-menu": "21.0.0",
26
26
  "@atlaskit/editor-json-transformer": "^10.0.0",
27
- "@atlaskit/editor-plugin-analytics": "^25.0.0",
28
- "@atlaskit/editor-plugin-block-controls": "^26.0.0",
29
- "@atlaskit/editor-plugin-block-menu": "^24.0.0",
30
- "@atlaskit/editor-plugin-connectivity": "25.0.0",
31
- "@atlaskit/editor-plugin-content-format": "^19.0.0",
32
- "@atlaskit/editor-plugin-decorations": "^25.0.0",
33
- "@atlaskit/editor-plugin-editor-disabled": "^25.0.0",
34
- "@atlaskit/editor-plugin-editor-viewmode": "^27.0.0",
35
- "@atlaskit/editor-plugin-floating-toolbar": "^27.0.0",
36
- "@atlaskit/editor-plugin-focus": "^24.0.0",
37
- "@atlaskit/editor-plugin-history": "^25.0.0",
38
- "@atlaskit/editor-plugin-selection": "^25.0.0",
39
- "@atlaskit/editor-plugin-toolbar": "^22.0.0",
40
- "@atlaskit/editor-plugin-ui-control-registry": "^19.0.0",
41
- "@atlaskit/editor-plugin-user-intent": "^23.0.0",
27
+ "@atlaskit/editor-plugin-analytics": "^26.0.0",
28
+ "@atlaskit/editor-plugin-block-controls": "^27.0.0",
29
+ "@atlaskit/editor-plugin-block-menu": "^25.0.0",
30
+ "@atlaskit/editor-plugin-connectivity": "26.0.0",
31
+ "@atlaskit/editor-plugin-content-format": "^20.0.0",
32
+ "@atlaskit/editor-plugin-decorations": "^26.0.0",
33
+ "@atlaskit/editor-plugin-editor-disabled": "^26.0.0",
34
+ "@atlaskit/editor-plugin-editor-viewmode": "^28.0.0",
35
+ "@atlaskit/editor-plugin-floating-toolbar": "^28.0.0",
36
+ "@atlaskit/editor-plugin-focus": "^25.0.0",
37
+ "@atlaskit/editor-plugin-history": "^26.0.0",
38
+ "@atlaskit/editor-plugin-selection": "^26.0.0",
39
+ "@atlaskit/editor-plugin-toolbar": "^23.0.0",
40
+ "@atlaskit/editor-plugin-ui-control-registry": "^20.0.0",
41
+ "@atlaskit/editor-plugin-user-intent": "^24.0.0",
42
42
  "@atlaskit/editor-prosemirror": "^9.0.0",
43
43
  "@atlaskit/editor-shared-styles": "^4.3.0",
44
- "@atlaskit/editor-synced-block-provider": "^21.0.0",
44
+ "@atlaskit/editor-synced-block-provider": "^22.0.0",
45
45
  "@atlaskit/editor-toolbar": "^2.8.0",
46
46
  "@atlaskit/flag": "^19.0.0",
47
47
  "@atlaskit/icon": "38.0.2",
@@ -53,8 +53,8 @@
53
53
  "@atlaskit/platform-feature-flags": "^2.2.0",
54
54
  "@atlaskit/primitives": "^22.5.0",
55
55
  "@atlaskit/spinner": "20.4.4",
56
- "@atlaskit/tmp-editor-statsig": "^226.0.0",
57
- "@atlaskit/tokens": "20.3.0",
56
+ "@atlaskit/tmp-editor-statsig": "^227.0.0",
57
+ "@atlaskit/tokens": "20.4.0",
58
58
  "@atlaskit/tooltip": "^24.4.0",
59
59
  "@atlaskit/visually-hidden": "^4.4.0",
60
60
  "@babel/runtime": "^7.0.0",
@@ -65,7 +65,7 @@
65
65
  "memoize-one": "^6.0.0"
66
66
  },
67
67
  "peerDependencies": {
68
- "@atlaskit/editor-common": "^129.1.0",
68
+ "@atlaskit/editor-common": "^130.0.0",
69
69
  "react": "^18.2.0 || ^19.2.0",
70
70
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
71
71
  },
@@ -77,9 +77,6 @@
77
77
  "platform-feature-flags": {
78
78
  "confluence_fronend_labels_categorization_migration": {
79
79
  "type": "boolean"
80
- },
81
- "platform_editor_blocks_patch_8": {
82
- "type": "boolean"
83
80
  }
84
81
  }
85
82
  }