@atlaskit/editor-plugin-synced-block 10.1.1 → 10.1.3

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,21 @@
1
1
  # @atlaskit/editor-plugin-synced-block
2
2
 
3
+ ## 10.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
9
+ ## 10.1.2
10
+
11
+ ### Patch Changes
12
+
13
+ - [`70c2b083643f1`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/70c2b083643f1) -
14
+ [ux] Fix creating a synced block from the block menu or toolbar so the caret is placed inside the
15
+ new block and the editor is re-focused, matching the slash-command behaviour. Previously the
16
+ cursor was left outside the newly created synced block.
17
+ - Updated dependencies
18
+
3
19
  ## 10.1.1
4
20
 
5
21
  ### Patch Changes
package/compass.yml CHANGED
@@ -24,6 +24,9 @@ customFields:
24
24
  - name: Department
25
25
  type: text
26
26
  value: Eng - Editor Autonomy - Jenga
27
+ - name: Trusted Reviewer Teams
28
+ type: text
29
+ value: ari:cloud:identity::team/cf6a670b-5252-4c68-a769-9207de366beb
27
30
  - name: Technical Owner
28
31
  type: user
29
32
  value: ari:cloud:identity::user/712020:8ef57ce4-d614-485e-8764-89301a16b906 # Casper Tiedemann
@@ -19,6 +19,61 @@ var _types = require("../types");
19
19
  var _utils3 = require("./utils");
20
20
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
21
21
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
22
+ /**
23
+ * Place the caret on the first editable position inside the newly created bodied
24
+ * sync block, identified by its unique localId.
25
+ *
26
+ * - Empty selection: lands inside the block's empty paragraph.
27
+ * - Converted content: lands at the start of the first editable child.
28
+ *
29
+ * `createSyncedBlock` builds the node but neither `safeInsert` (empty case) nor
30
+ * `replaceWith` (convert case) leaves the selection inside the new block, so
31
+ * without this the caret ends up outside/adjacent to the block after creation
32
+ * from the block menu or toolbar (EDITOR-7949). The typeahead path avoids this
33
+ * because `typeAheadInsert` positions the caret for us.
34
+ *
35
+ * Note: when created from the block menu, block-controls' selection preservation
36
+ * is active and will restore a whole-node NodeSelection over this caret. The
37
+ * caller (block-menu item) is responsible for calling `stopPreservingSelection`
38
+ * in the same flow so the caret survives — mirroring the block-menu delete item.
39
+ */
40
+ var placeCaretInsideBodiedSyncBlock = function placeCaretInsideBodiedSyncBlock(tr, localId) {
41
+ var bodiedSyncBlockType = tr.doc.type.schema.nodes.bodiedSyncBlock;
42
+ var blockPos;
43
+ tr.doc.descendants(function (node, pos) {
44
+ if (blockPos !== undefined) {
45
+ return false;
46
+ }
47
+ if (node.type === bodiedSyncBlockType && node.attrs.localId === localId) {
48
+ blockPos = pos;
49
+ return false;
50
+ }
51
+ return true;
52
+ });
53
+ if (blockPos === undefined) {
54
+ return tr;
55
+ }
56
+
57
+ // Find the first valid text position at or after the block's start. `findFrom`
58
+ // with textOnly=true descends into the first editable child (empty paragraph or
59
+ // start of the converted content), which is exactly what we want for both cases.
60
+ var selection = _state.Selection.findFrom(tr.doc.resolve(blockPos), 1, true);
61
+ if (selection) {
62
+ tr.setSelection(selection).scrollIntoView();
63
+ } else {
64
+ // Fallback: `bodiedSyncBlock` is always created with a paragraph as its first
65
+ // child, so a text position should always be found above. This guards against
66
+ // future schema changes where the first child isn't immediately text-editable
67
+ // — place the caret just inside the block rather than leaving it outside.
68
+ // `blockPos + 1` is the position immediately after the block's opening token,
69
+ // which is a node boundary rather than a valid text position, so use
70
+ // `TextSelection.near` to snap forward to the nearest selectable cursor. It
71
+ // also clamps to the document bounds internally, so no explicit end-of-doc
72
+ // check is required.
73
+ tr.setSelection(_state.TextSelection.near(tr.doc.resolve(blockPos + 1), 1)).scrollIntoView();
74
+ }
75
+ return tr;
76
+ };
22
77
  var createSyncedBlock = exports.createSyncedBlock = function createSyncedBlock(_ref) {
23
78
  var tr = _ref.tr,
24
79
  syncBlockStore = _ref.syncBlockStore,
@@ -49,6 +104,12 @@ var createSyncedBlock = exports.createSyncedBlock = function createSyncedBlock(_
49
104
  tr = typeAheadInsert(newBodiedSyncBlockNode);
50
105
  } else {
51
106
  tr = (0, _utils.safeInsert)(newBodiedSyncBlockNode)(tr).scrollIntoView();
107
+ // safeInsert does not move the selection into the new block, so place the
108
+ // caret inside the block's empty paragraph so typing continues inside the
109
+ // synced block (EDITOR-7949).
110
+ if ((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_4')) {
111
+ tr = placeCaretInsideBodiedSyncBlock(tr, newBodiedSyncBlockNode.attrs.localId);
112
+ }
52
113
  }
53
114
  } else {
54
115
  var conversionInfo = (0, _utils2.canBeConvertedToSyncBlock)(tr.selection);
@@ -79,9 +140,14 @@ var createSyncedBlock = exports.createSyncedBlock = function createSyncedBlock(_
79
140
  return false;
80
141
  }
81
142
  tr.replaceWith(conversionInfo.from, conversionInfo.to, _newBodiedSyncBlockNode).scrollIntoView();
82
-
83
- // set selection to the start of the previous selection for the position taken up by the start of the new synced block
84
- tr.setSelection(_state.TextSelection.create(tr.doc, conversionInfo.from));
143
+ if ((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_4')) {
144
+ // Place the caret on the first editable position inside the converted
145
+ // content so typing continues inside the synced block (EDITOR-7949).
146
+ tr = placeCaretInsideBodiedSyncBlock(tr, _newBodiedSyncBlockNode.attrs.localId);
147
+ } else {
148
+ // set selection to the start of the previous selection for the position taken up by the start of the new synced block
149
+ tr.setSelection(_state.TextSelection.create(tr.doc, conversionInfo.from));
150
+ }
85
151
  }
86
152
  return tr;
87
153
  };
@@ -13,6 +13,7 @@ var _messages = require("@atlaskit/editor-common/messages");
13
13
  var _editorPluginConnectivity = require("@atlaskit/editor-plugin-connectivity");
14
14
  var _editorToolbar = require("@atlaskit/editor-toolbar");
15
15
  var _blockSynced = _interopRequireDefault(require("@atlaskit/icon-lab/core/block-synced"));
16
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
16
17
  var _utils = require("../pm-plugins/utils/utils");
17
18
  var _types = require("../types");
18
19
  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); }
@@ -35,14 +36,45 @@ var CreateSyncedBlockButton = exports.CreateSyncedBlockButton = function CreateS
35
36
  var canInsertEmptyBlock = Boolean(selection === null || selection === void 0 ? void 0 : selection.empty);
36
37
  var isDisabled = Boolean((0, _editorPluginConnectivity.isOfflineMode)(mode) || !canBeConverted && !canInsertEmptyBlock);
37
38
  var onClick = (0, _react.useCallback)(function () {
38
- var _api$core, _api$core2;
39
- api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function (_ref2) {
40
- var tr = _ref2.tr;
39
+ var _api$core3, _api$core4;
40
+ if ((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_4')) {
41
+ var _api$core;
42
+ // Insert the synced block and stop preserving the selection in a single
43
+ // transaction so the caret that insertSyncedBlock places inside the new
44
+ // block survives (EDITOR-7949). The toolbar is not normally opened via the
45
+ // block menu, but stopping preservation is defensive and keeps behaviour
46
+ // consistent with the block-menu create item should preservation ever be
47
+ // active (e.g. a block is selected via block-controls). Then re-focus so
48
+ // the caret is active, but only when the transaction was actually
49
+ // dispatched — otherwise a failed insertion would still steal DOM focus
50
+ // into the editor (mirrors CreateSyncedBlockDropdownItem).
51
+ var dispatched = api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(function (_ref2) {
52
+ var _api$blockControls;
53
+ var tr = _ref2.tr;
54
+ var result = api === null || api === void 0 ? void 0 : api.syncedBlock.commands.insertSyncedBlock()({
55
+ tr: tr
56
+ });
57
+ if (!result) {
58
+ return null;
59
+ }
60
+ api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.commands) === null || _api$blockControls === void 0 || _api$blockControls.stopPreservingSelection()({
61
+ tr: tr
62
+ });
63
+ return tr;
64
+ });
65
+ if (dispatched) {
66
+ var _api$core2;
67
+ api === null || api === void 0 || (_api$core2 = api.core) === null || _api$core2 === void 0 || _api$core2.actions.focus();
68
+ }
69
+ return;
70
+ }
71
+ api === null || api === void 0 || (_api$core3 = api.core) === null || _api$core3 === void 0 || _api$core3.actions.execute(function (_ref3) {
72
+ var tr = _ref3.tr;
41
73
  return api === null || api === void 0 ? void 0 : api.syncedBlock.commands.insertSyncedBlock()({
42
74
  tr: tr
43
75
  });
44
76
  });
45
- api === null || api === void 0 || (_api$core2 = api.core) === null || _api$core2 === void 0 || _api$core2.actions.focus();
77
+ api === null || api === void 0 || (_api$core4 = api.core) === null || _api$core4 === void 0 || _api$core4.actions.focus();
46
78
  }, [api]);
47
79
  var message = intl.formatMessage(_messages.syncBlockMessages.syncBlockLabel);
48
80
  return /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarTooltip, {
@@ -47,9 +47,51 @@ var CreateSyncedBlockDropdownItem = function CreateSyncedBlockDropdownItem(_ref2
47
47
  return null;
48
48
  }
49
49
  var onClick = function onClick() {
50
- var _api$core, _api$core2, _api$blockControls;
51
- api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(api === null || api === void 0 ? void 0 : api.syncedBlock.commands.insertSyncedBlock());
52
- api === null || api === void 0 || (_api$core2 = api.core) === null || _api$core2 === void 0 || _api$core2.actions.execute(api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.commands) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.toggleBlockMenu({
50
+ var _api$core3, _api$core4, _api$blockControls3;
51
+ if ((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_4')) {
52
+ var _api$core;
53
+ // Insert the synced block, close the block menu, and stop preserving the
54
+ // selection — all in a single transaction, mirroring the block-menu delete
55
+ // item. Stopping preservation is required so the caret that
56
+ // insertSyncedBlock places inside the new block survives; otherwise
57
+ // block-controls restores a whole-node NodeSelection over it, hiding the
58
+ // caret (EDITOR-7949). Then re-focus the editor so the caret is active,
59
+ // but only when the transaction was actually dispatched — otherwise a
60
+ // failed insertion would still steal DOM focus into the editor.
61
+ var dispatched = api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(function (_ref3) {
62
+ var _api$blockControls, _api$blockControls2;
63
+ var tr = _ref3.tr;
64
+ // If the insertion fails, bail out without closing the menu or
65
+ // stopping selection preservation — otherwise we would dispatch an
66
+ // effectively-empty transaction and close the menu despite nothing
67
+ // having been inserted.
68
+ var result = api === null || api === void 0 ? void 0 : api.syncedBlock.commands.insertSyncedBlock()({
69
+ tr: tr
70
+ });
71
+ if (!result) {
72
+ return null;
73
+ }
74
+ api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.commands) === null || _api$blockControls === void 0 || _api$blockControls.toggleBlockMenu({
75
+ closeMenu: true
76
+ })({
77
+ tr: tr
78
+ });
79
+ api === null || api === void 0 || (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || (_api$blockControls2 = _api$blockControls2.commands) === null || _api$blockControls2 === void 0 || _api$blockControls2.stopPreservingSelection()({
80
+ tr: tr
81
+ });
82
+ return tr;
83
+ });
84
+ if (dispatched) {
85
+ var _api$core2;
86
+ api === null || api === void 0 || (_api$core2 = api.core) === null || _api$core2 === void 0 || _api$core2.actions.focus();
87
+ }
88
+ return;
89
+ }
90
+
91
+ // Legacy behaviour: insert then close the block menu as two separate executes
92
+ // (batching them caused selection collisions — EDITOR-2751).
93
+ api === null || api === void 0 || (_api$core3 = api.core) === null || _api$core3 === void 0 || _api$core3.actions.execute(api === null || api === void 0 ? void 0 : api.syncedBlock.commands.insertSyncedBlock());
94
+ api === null || api === void 0 || (_api$core4 = api.core) === null || _api$core4 === void 0 || _api$core4.actions.execute(api === null || api === void 0 || (_api$blockControls3 = api.blockControls) === null || _api$blockControls3 === void 0 || (_api$blockControls3 = _api$blockControls3.commands) === null || _api$blockControls3 === void 0 ? void 0 : _api$blockControls3.toggleBlockMenu({
53
95
  closeMenu: true
54
96
  }));
55
97
  };
@@ -68,8 +110,8 @@ var CreateSyncedBlockDropdownItem = function CreateSyncedBlockDropdownItem(_ref2
68
110
  elemAfterText: lozenge
69
111
  }, formatMessage(_messages.blockMenuMessages.syncBlock));
70
112
  };
71
- var CopySyncedBlockDropdownItem = function CopySyncedBlockDropdownItem(_ref3) {
72
- var api = _ref3.api;
113
+ var CopySyncedBlockDropdownItem = function CopySyncedBlockDropdownItem(_ref4) {
114
+ var api = _ref4.api;
73
115
  var _useIntl2 = (0, _reactIntl.useIntl)(),
74
116
  formatMessage = _useIntl2.formatMessage;
75
117
  var _useSharedPluginState2 = (0, _hooks.useSharedPluginStateWithSelector)(api, ['connectivity'], function (states) {
@@ -80,9 +122,9 @@ var CopySyncedBlockDropdownItem = function CopySyncedBlockDropdownItem(_ref3) {
80
122
  }),
81
123
  mode = _useSharedPluginState2.mode;
82
124
  var onClick = function onClick() {
83
- var _api$core3, _api$core4, _api$blockControls2;
84
- api === null || api === void 0 || (_api$core3 = api.core) === null || _api$core3 === void 0 || _api$core3.actions.execute(api === null || api === void 0 ? void 0 : api.syncedBlock.commands.copySyncedBlockReferenceToClipboard(_analytics.INPUT_METHOD.BLOCK_MENU));
85
- api === null || api === void 0 || (_api$core4 = api.core) === null || _api$core4 === void 0 || _api$core4.actions.execute(api === null || api === void 0 || (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || (_api$blockControls2 = _api$blockControls2.commands) === null || _api$blockControls2 === void 0 ? void 0 : _api$blockControls2.toggleBlockMenu({
125
+ var _api$core5, _api$core6, _api$blockControls4;
126
+ api === null || api === void 0 || (_api$core5 = api.core) === null || _api$core5 === void 0 || _api$core5.actions.execute(api === null || api === void 0 ? void 0 : api.syncedBlock.commands.copySyncedBlockReferenceToClipboard(_analytics.INPUT_METHOD.BLOCK_MENU));
127
+ api === null || api === void 0 || (_api$core6 = api.core) === null || _api$core6 === void 0 || _api$core6.actions.execute(api === null || api === void 0 || (_api$blockControls4 = api.blockControls) === null || _api$blockControls4 === void 0 || (_api$blockControls4 = _api$blockControls4.commands) === null || _api$blockControls4 === void 0 ? void 0 : _api$blockControls4.toggleBlockMenu({
86
128
  closeMenu: true
87
129
  }));
88
130
  };
@@ -99,9 +141,9 @@ var CopySyncedBlockDropdownItem = function CopySyncedBlockDropdownItem(_ref3) {
99
141
  elemAfterText: lozenge
100
142
  }, formatMessage(_messages.blockMenuMessages.copySyncedBlock));
101
143
  };
102
- var CreateOrCopySyncedBlockDropdownItem = exports.CreateOrCopySyncedBlockDropdownItem = function CreateOrCopySyncedBlockDropdownItem(_ref4) {
103
- var api = _ref4.api,
104
- enableSourceSyncedBlockCreation = _ref4.enableSourceSyncedBlockCreation;
144
+ var CreateOrCopySyncedBlockDropdownItem = exports.CreateOrCopySyncedBlockDropdownItem = function CreateOrCopySyncedBlockDropdownItem(_ref5) {
145
+ var api = _ref5.api,
146
+ enableSourceSyncedBlockCreation = _ref5.enableSourceSyncedBlockCreation;
105
147
  var _useSharedPluginState3 = (0, _hooks.useSharedPluginStateWithSelector)(api, ['blockControls'], function (states) {
106
148
  var _states$blockControls3, _states$blockControls4;
107
149
  return {
@@ -2,13 +2,68 @@ import { defaultSchema } from '@atlaskit/adf-schema/schema-default';
2
2
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
3
3
  import { copyDomNode, toDOM } from '@atlaskit/editor-common/copy-button';
4
4
  import { DOMSerializer, Fragment } from '@atlaskit/editor-prosemirror/model';
5
- import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
5
+ import { NodeSelection, Selection, TextSelection } from '@atlaskit/editor-prosemirror/state';
6
6
  import { findSelectedNodeOfType, removeParentNodeOfType, removeSelectedNode, safeInsert } from '@atlaskit/editor-prosemirror/utils';
7
7
  import { fg } from '@atlaskit/platform-feature-flags';
8
8
  import { deleteMechanismMetaKey, syncedBlockPluginKey } from '../pm-plugins/main';
9
9
  import { canBeConvertedToSyncBlock, deferDispatch, findSyncBlock, findSyncBlockOrBodiedSyncBlock, isBodiedSyncBlockNode } from '../pm-plugins/utils/utils';
10
10
  import { FLAG_ID } from '../types';
11
11
  import { findBodiedSyncBlockByLocalId, pasteSyncBlockHTMLContent } from './utils';
12
+ /**
13
+ * Place the caret on the first editable position inside the newly created bodied
14
+ * sync block, identified by its unique localId.
15
+ *
16
+ * - Empty selection: lands inside the block's empty paragraph.
17
+ * - Converted content: lands at the start of the first editable child.
18
+ *
19
+ * `createSyncedBlock` builds the node but neither `safeInsert` (empty case) nor
20
+ * `replaceWith` (convert case) leaves the selection inside the new block, so
21
+ * without this the caret ends up outside/adjacent to the block after creation
22
+ * from the block menu or toolbar (EDITOR-7949). The typeahead path avoids this
23
+ * because `typeAheadInsert` positions the caret for us.
24
+ *
25
+ * Note: when created from the block menu, block-controls' selection preservation
26
+ * is active and will restore a whole-node NodeSelection over this caret. The
27
+ * caller (block-menu item) is responsible for calling `stopPreservingSelection`
28
+ * in the same flow so the caret survives — mirroring the block-menu delete item.
29
+ */
30
+ const placeCaretInsideBodiedSyncBlock = (tr, localId) => {
31
+ const bodiedSyncBlockType = tr.doc.type.schema.nodes.bodiedSyncBlock;
32
+ let blockPos;
33
+ tr.doc.descendants((node, pos) => {
34
+ if (blockPos !== undefined) {
35
+ return false;
36
+ }
37
+ if (node.type === bodiedSyncBlockType && node.attrs.localId === localId) {
38
+ blockPos = pos;
39
+ return false;
40
+ }
41
+ return true;
42
+ });
43
+ if (blockPos === undefined) {
44
+ return tr;
45
+ }
46
+
47
+ // Find the first valid text position at or after the block's start. `findFrom`
48
+ // with textOnly=true descends into the first editable child (empty paragraph or
49
+ // start of the converted content), which is exactly what we want for both cases.
50
+ const selection = Selection.findFrom(tr.doc.resolve(blockPos), 1, true);
51
+ if (selection) {
52
+ tr.setSelection(selection).scrollIntoView();
53
+ } else {
54
+ // Fallback: `bodiedSyncBlock` is always created with a paragraph as its first
55
+ // child, so a text position should always be found above. This guards against
56
+ // future schema changes where the first child isn't immediately text-editable
57
+ // — place the caret just inside the block rather than leaving it outside.
58
+ // `blockPos + 1` is the position immediately after the block's opening token,
59
+ // which is a node boundary rather than a valid text position, so use
60
+ // `TextSelection.near` to snap forward to the nearest selectable cursor. It
61
+ // also clamps to the document bounds internally, so no explicit end-of-doc
62
+ // check is required.
63
+ tr.setSelection(TextSelection.near(tr.doc.resolve(blockPos + 1), 1)).scrollIntoView();
64
+ }
65
+ return tr;
66
+ };
12
67
  export const createSyncedBlock = ({
13
68
  tr,
14
69
  syncBlockStore,
@@ -45,6 +100,12 @@ export const createSyncedBlock = ({
45
100
  tr = typeAheadInsert(newBodiedSyncBlockNode);
46
101
  } else {
47
102
  tr = safeInsert(newBodiedSyncBlockNode)(tr).scrollIntoView();
103
+ // safeInsert does not move the selection into the new block, so place the
104
+ // caret inside the block's empty paragraph so typing continues inside the
105
+ // synced block (EDITOR-7949).
106
+ if (fg('platform_editor_blocks_patch_4')) {
107
+ tr = placeCaretInsideBodiedSyncBlock(tr, newBodiedSyncBlockNode.attrs.localId);
108
+ }
48
109
  }
49
110
  } else {
50
111
  const conversionInfo = canBeConvertedToSyncBlock(tr.selection);
@@ -75,9 +136,14 @@ export const createSyncedBlock = ({
75
136
  return false;
76
137
  }
77
138
  tr.replaceWith(conversionInfo.from, conversionInfo.to, newBodiedSyncBlockNode).scrollIntoView();
78
-
79
- // set selection to the start of the previous selection for the position taken up by the start of the new synced block
80
- tr.setSelection(TextSelection.create(tr.doc, conversionInfo.from));
139
+ if (fg('platform_editor_blocks_patch_4')) {
140
+ // Place the caret on the first editable position inside the converted
141
+ // content so typing continues inside the synced block (EDITOR-7949).
142
+ tr = placeCaretInsideBodiedSyncBlock(tr, newBodiedSyncBlockNode.attrs.localId);
143
+ } else {
144
+ // set selection to the start of the previous selection for the position taken up by the start of the new synced block
145
+ tr.setSelection(TextSelection.create(tr.doc, conversionInfo.from));
146
+ }
81
147
  }
82
148
  return tr;
83
149
  };
@@ -5,6 +5,7 @@ import { syncBlockMessages } from '@atlaskit/editor-common/messages';
5
5
  import { isOfflineMode } from '@atlaskit/editor-plugin-connectivity';
6
6
  import { ToolbarButton, ToolbarTooltip } from '@atlaskit/editor-toolbar';
7
7
  import BlockSyncedIcon from '@atlaskit/icon-lab/core/block-synced';
8
+ import { fg } from '@atlaskit/platform-feature-flags';
8
9
  import { canBeConvertedToSyncBlock } from '../pm-plugins/utils/utils';
9
10
  import { SYNCED_BLOCK_BUTTON_TEST_ID } from '../types';
10
11
  export const CreateSyncedBlockButton = ({
@@ -28,13 +29,45 @@ export const CreateSyncedBlockButton = ({
28
29
  const canInsertEmptyBlock = Boolean(selection === null || selection === void 0 ? void 0 : selection.empty);
29
30
  const isDisabled = Boolean(isOfflineMode(mode) || !canBeConverted && !canInsertEmptyBlock);
30
31
  const onClick = useCallback(() => {
31
- var _api$core, _api$core2;
32
- api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({
32
+ var _api$core3, _api$core4;
33
+ if (fg('platform_editor_blocks_patch_4')) {
34
+ var _api$core;
35
+ // Insert the synced block and stop preserving the selection in a single
36
+ // transaction so the caret that insertSyncedBlock places inside the new
37
+ // block survives (EDITOR-7949). The toolbar is not normally opened via the
38
+ // block menu, but stopping preservation is defensive and keeps behaviour
39
+ // consistent with the block-menu create item should preservation ever be
40
+ // active (e.g. a block is selected via block-controls). Then re-focus so
41
+ // the caret is active, but only when the transaction was actually
42
+ // dispatched — otherwise a failed insertion would still steal DOM focus
43
+ // into the editor (mirrors CreateSyncedBlockDropdownItem).
44
+ const dispatched = api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({
45
+ tr
46
+ }) => {
47
+ var _api$blockControls, _api$blockControls$co;
48
+ const result = api === null || api === void 0 ? void 0 : api.syncedBlock.commands.insertSyncedBlock()({
49
+ tr
50
+ });
51
+ if (!result) {
52
+ return null;
53
+ }
54
+ api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : (_api$blockControls$co = _api$blockControls.commands) === null || _api$blockControls$co === void 0 ? void 0 : _api$blockControls$co.stopPreservingSelection()({
55
+ tr
56
+ });
57
+ return tr;
58
+ });
59
+ if (dispatched) {
60
+ var _api$core2;
61
+ api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.focus();
62
+ }
63
+ return;
64
+ }
65
+ api === null || api === void 0 ? void 0 : (_api$core3 = api.core) === null || _api$core3 === void 0 ? void 0 : _api$core3.actions.execute(({
33
66
  tr
34
67
  }) => api === null || api === void 0 ? void 0 : api.syncedBlock.commands.insertSyncedBlock()({
35
68
  tr
36
69
  }));
37
- api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.focus();
70
+ api === null || api === void 0 ? void 0 : (_api$core4 = api.core) === null || _api$core4 === void 0 ? void 0 : _api$core4.actions.focus();
38
71
  }, [api]);
39
72
  const message = intl.formatMessage(syncBlockMessages.syncBlockLabel);
40
73
  return /*#__PURE__*/React.createElement(ToolbarTooltip, {
@@ -38,9 +38,52 @@ const CreateSyncedBlockDropdownItem = ({
38
38
  return null;
39
39
  }
40
40
  const onClick = () => {
41
- var _api$core, _api$core2, _api$blockControls, _api$blockControls$co;
42
- api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(api === null || api === void 0 ? void 0 : api.syncedBlock.commands.insertSyncedBlock());
43
- api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : (_api$blockControls$co = _api$blockControls.commands) === null || _api$blockControls$co === void 0 ? void 0 : _api$blockControls$co.toggleBlockMenu({
41
+ var _api$core3, _api$core4, _api$blockControls3, _api$blockControls3$c;
42
+ if (fg('platform_editor_blocks_patch_4')) {
43
+ var _api$core;
44
+ // Insert the synced block, close the block menu, and stop preserving the
45
+ // selection — all in a single transaction, mirroring the block-menu delete
46
+ // item. Stopping preservation is required so the caret that
47
+ // insertSyncedBlock places inside the new block survives; otherwise
48
+ // block-controls restores a whole-node NodeSelection over it, hiding the
49
+ // caret (EDITOR-7949). Then re-focus the editor so the caret is active,
50
+ // but only when the transaction was actually dispatched — otherwise a
51
+ // failed insertion would still steal DOM focus into the editor.
52
+ const dispatched = api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({
53
+ tr
54
+ }) => {
55
+ var _api$blockControls, _api$blockControls$co, _api$blockControls2, _api$blockControls2$c;
56
+ // If the insertion fails, bail out without closing the menu or
57
+ // stopping selection preservation — otherwise we would dispatch an
58
+ // effectively-empty transaction and close the menu despite nothing
59
+ // having been inserted.
60
+ const result = api === null || api === void 0 ? void 0 : api.syncedBlock.commands.insertSyncedBlock()({
61
+ tr
62
+ });
63
+ if (!result) {
64
+ return null;
65
+ }
66
+ api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : (_api$blockControls$co = _api$blockControls.commands) === null || _api$blockControls$co === void 0 ? void 0 : _api$blockControls$co.toggleBlockMenu({
67
+ closeMenu: true
68
+ })({
69
+ tr
70
+ });
71
+ api === null || api === void 0 ? void 0 : (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 ? void 0 : (_api$blockControls2$c = _api$blockControls2.commands) === null || _api$blockControls2$c === void 0 ? void 0 : _api$blockControls2$c.stopPreservingSelection()({
72
+ tr
73
+ });
74
+ return tr;
75
+ });
76
+ if (dispatched) {
77
+ var _api$core2;
78
+ api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.focus();
79
+ }
80
+ return;
81
+ }
82
+
83
+ // Legacy behaviour: insert then close the block menu as two separate executes
84
+ // (batching them caused selection collisions — EDITOR-2751).
85
+ api === null || api === void 0 ? void 0 : (_api$core3 = api.core) === null || _api$core3 === void 0 ? void 0 : _api$core3.actions.execute(api === null || api === void 0 ? void 0 : api.syncedBlock.commands.insertSyncedBlock());
86
+ api === null || api === void 0 ? void 0 : (_api$core4 = api.core) === null || _api$core4 === void 0 ? void 0 : _api$core4.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockControls3 = api.blockControls) === null || _api$blockControls3 === void 0 ? void 0 : (_api$blockControls3$c = _api$blockControls3.commands) === null || _api$blockControls3$c === void 0 ? void 0 : _api$blockControls3$c.toggleBlockMenu({
44
87
  closeMenu: true
45
88
  }));
46
89
  };
@@ -74,9 +117,9 @@ const CopySyncedBlockDropdownItem = ({
74
117
  };
75
118
  });
76
119
  const onClick = () => {
77
- var _api$core3, _api$core4, _api$blockControls2, _api$blockControls2$c;
78
- api === null || api === void 0 ? void 0 : (_api$core3 = api.core) === null || _api$core3 === void 0 ? void 0 : _api$core3.actions.execute(api === null || api === void 0 ? void 0 : api.syncedBlock.commands.copySyncedBlockReferenceToClipboard(INPUT_METHOD.BLOCK_MENU));
79
- api === null || api === void 0 ? void 0 : (_api$core4 = api.core) === null || _api$core4 === void 0 ? void 0 : _api$core4.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 ? void 0 : (_api$blockControls2$c = _api$blockControls2.commands) === null || _api$blockControls2$c === void 0 ? void 0 : _api$blockControls2$c.toggleBlockMenu({
120
+ var _api$core5, _api$core6, _api$blockControls4, _api$blockControls4$c;
121
+ api === null || api === void 0 ? void 0 : (_api$core5 = api.core) === null || _api$core5 === void 0 ? void 0 : _api$core5.actions.execute(api === null || api === void 0 ? void 0 : api.syncedBlock.commands.copySyncedBlockReferenceToClipboard(INPUT_METHOD.BLOCK_MENU));
122
+ api === null || api === void 0 ? void 0 : (_api$core6 = api.core) === null || _api$core6 === void 0 ? void 0 : _api$core6.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockControls4 = api.blockControls) === null || _api$blockControls4 === void 0 ? void 0 : (_api$blockControls4$c = _api$blockControls4.commands) === null || _api$blockControls4$c === void 0 ? void 0 : _api$blockControls4$c.toggleBlockMenu({
80
123
  closeMenu: true
81
124
  }));
82
125
  };
@@ -5,13 +5,68 @@ import { defaultSchema } from '@atlaskit/adf-schema/schema-default';
5
5
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
6
6
  import { copyDomNode, toDOM } from '@atlaskit/editor-common/copy-button';
7
7
  import { DOMSerializer, Fragment } from '@atlaskit/editor-prosemirror/model';
8
- import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
8
+ import { NodeSelection, Selection, TextSelection } from '@atlaskit/editor-prosemirror/state';
9
9
  import { findSelectedNodeOfType, removeParentNodeOfType, removeSelectedNode, safeInsert } from '@atlaskit/editor-prosemirror/utils';
10
10
  import { fg } from '@atlaskit/platform-feature-flags';
11
11
  import { deleteMechanismMetaKey, syncedBlockPluginKey } from '../pm-plugins/main';
12
12
  import { canBeConvertedToSyncBlock, deferDispatch, findSyncBlock, findSyncBlockOrBodiedSyncBlock, isBodiedSyncBlockNode } from '../pm-plugins/utils/utils';
13
13
  import { FLAG_ID } from '../types';
14
14
  import { findBodiedSyncBlockByLocalId, pasteSyncBlockHTMLContent } from './utils';
15
+ /**
16
+ * Place the caret on the first editable position inside the newly created bodied
17
+ * sync block, identified by its unique localId.
18
+ *
19
+ * - Empty selection: lands inside the block's empty paragraph.
20
+ * - Converted content: lands at the start of the first editable child.
21
+ *
22
+ * `createSyncedBlock` builds the node but neither `safeInsert` (empty case) nor
23
+ * `replaceWith` (convert case) leaves the selection inside the new block, so
24
+ * without this the caret ends up outside/adjacent to the block after creation
25
+ * from the block menu or toolbar (EDITOR-7949). The typeahead path avoids this
26
+ * because `typeAheadInsert` positions the caret for us.
27
+ *
28
+ * Note: when created from the block menu, block-controls' selection preservation
29
+ * is active and will restore a whole-node NodeSelection over this caret. The
30
+ * caller (block-menu item) is responsible for calling `stopPreservingSelection`
31
+ * in the same flow so the caret survives — mirroring the block-menu delete item.
32
+ */
33
+ var placeCaretInsideBodiedSyncBlock = function placeCaretInsideBodiedSyncBlock(tr, localId) {
34
+ var bodiedSyncBlockType = tr.doc.type.schema.nodes.bodiedSyncBlock;
35
+ var blockPos;
36
+ tr.doc.descendants(function (node, pos) {
37
+ if (blockPos !== undefined) {
38
+ return false;
39
+ }
40
+ if (node.type === bodiedSyncBlockType && node.attrs.localId === localId) {
41
+ blockPos = pos;
42
+ return false;
43
+ }
44
+ return true;
45
+ });
46
+ if (blockPos === undefined) {
47
+ return tr;
48
+ }
49
+
50
+ // Find the first valid text position at or after the block's start. `findFrom`
51
+ // with textOnly=true descends into the first editable child (empty paragraph or
52
+ // start of the converted content), which is exactly what we want for both cases.
53
+ var selection = Selection.findFrom(tr.doc.resolve(blockPos), 1, true);
54
+ if (selection) {
55
+ tr.setSelection(selection).scrollIntoView();
56
+ } else {
57
+ // Fallback: `bodiedSyncBlock` is always created with a paragraph as its first
58
+ // child, so a text position should always be found above. This guards against
59
+ // future schema changes where the first child isn't immediately text-editable
60
+ // — place the caret just inside the block rather than leaving it outside.
61
+ // `blockPos + 1` is the position immediately after the block's opening token,
62
+ // which is a node boundary rather than a valid text position, so use
63
+ // `TextSelection.near` to snap forward to the nearest selectable cursor. It
64
+ // also clamps to the document bounds internally, so no explicit end-of-doc
65
+ // check is required.
66
+ tr.setSelection(TextSelection.near(tr.doc.resolve(blockPos + 1), 1)).scrollIntoView();
67
+ }
68
+ return tr;
69
+ };
15
70
  export var createSyncedBlock = function createSyncedBlock(_ref) {
16
71
  var tr = _ref.tr,
17
72
  syncBlockStore = _ref.syncBlockStore,
@@ -42,6 +97,12 @@ export var createSyncedBlock = function createSyncedBlock(_ref) {
42
97
  tr = typeAheadInsert(newBodiedSyncBlockNode);
43
98
  } else {
44
99
  tr = safeInsert(newBodiedSyncBlockNode)(tr).scrollIntoView();
100
+ // safeInsert does not move the selection into the new block, so place the
101
+ // caret inside the block's empty paragraph so typing continues inside the
102
+ // synced block (EDITOR-7949).
103
+ if (fg('platform_editor_blocks_patch_4')) {
104
+ tr = placeCaretInsideBodiedSyncBlock(tr, newBodiedSyncBlockNode.attrs.localId);
105
+ }
45
106
  }
46
107
  } else {
47
108
  var conversionInfo = canBeConvertedToSyncBlock(tr.selection);
@@ -72,9 +133,14 @@ export var createSyncedBlock = function createSyncedBlock(_ref) {
72
133
  return false;
73
134
  }
74
135
  tr.replaceWith(conversionInfo.from, conversionInfo.to, _newBodiedSyncBlockNode).scrollIntoView();
75
-
76
- // set selection to the start of the previous selection for the position taken up by the start of the new synced block
77
- tr.setSelection(TextSelection.create(tr.doc, conversionInfo.from));
136
+ if (fg('platform_editor_blocks_patch_4')) {
137
+ // Place the caret on the first editable position inside the converted
138
+ // content so typing continues inside the synced block (EDITOR-7949).
139
+ tr = placeCaretInsideBodiedSyncBlock(tr, _newBodiedSyncBlockNode.attrs.localId);
140
+ } else {
141
+ // set selection to the start of the previous selection for the position taken up by the start of the new synced block
142
+ tr.setSelection(TextSelection.create(tr.doc, conversionInfo.from));
143
+ }
78
144
  }
79
145
  return tr;
80
146
  };
@@ -5,6 +5,7 @@ import { syncBlockMessages } from '@atlaskit/editor-common/messages';
5
5
  import { isOfflineMode } from '@atlaskit/editor-plugin-connectivity';
6
6
  import { ToolbarButton, ToolbarTooltip } from '@atlaskit/editor-toolbar';
7
7
  import BlockSyncedIcon from '@atlaskit/icon-lab/core/block-synced';
8
+ import { fg } from '@atlaskit/platform-feature-flags';
8
9
  import { canBeConvertedToSyncBlock } from '../pm-plugins/utils/utils';
9
10
  import { SYNCED_BLOCK_BUTTON_TEST_ID } from '../types';
10
11
  export var CreateSyncedBlockButton = function CreateSyncedBlockButton(_ref) {
@@ -26,14 +27,45 @@ export var CreateSyncedBlockButton = function CreateSyncedBlockButton(_ref) {
26
27
  var canInsertEmptyBlock = Boolean(selection === null || selection === void 0 ? void 0 : selection.empty);
27
28
  var isDisabled = Boolean(isOfflineMode(mode) || !canBeConverted && !canInsertEmptyBlock);
28
29
  var onClick = useCallback(function () {
29
- var _api$core, _api$core2;
30
- api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function (_ref2) {
31
- var tr = _ref2.tr;
30
+ var _api$core3, _api$core4;
31
+ if (fg('platform_editor_blocks_patch_4')) {
32
+ var _api$core;
33
+ // Insert the synced block and stop preserving the selection in a single
34
+ // transaction so the caret that insertSyncedBlock places inside the new
35
+ // block survives (EDITOR-7949). The toolbar is not normally opened via the
36
+ // block menu, but stopping preservation is defensive and keeps behaviour
37
+ // consistent with the block-menu create item should preservation ever be
38
+ // active (e.g. a block is selected via block-controls). Then re-focus so
39
+ // the caret is active, but only when the transaction was actually
40
+ // dispatched — otherwise a failed insertion would still steal DOM focus
41
+ // into the editor (mirrors CreateSyncedBlockDropdownItem).
42
+ var dispatched = api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(function (_ref2) {
43
+ var _api$blockControls;
44
+ var tr = _ref2.tr;
45
+ var result = api === null || api === void 0 ? void 0 : api.syncedBlock.commands.insertSyncedBlock()({
46
+ tr: tr
47
+ });
48
+ if (!result) {
49
+ return null;
50
+ }
51
+ api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.commands) === null || _api$blockControls === void 0 || _api$blockControls.stopPreservingSelection()({
52
+ tr: tr
53
+ });
54
+ return tr;
55
+ });
56
+ if (dispatched) {
57
+ var _api$core2;
58
+ api === null || api === void 0 || (_api$core2 = api.core) === null || _api$core2 === void 0 || _api$core2.actions.focus();
59
+ }
60
+ return;
61
+ }
62
+ api === null || api === void 0 || (_api$core3 = api.core) === null || _api$core3 === void 0 || _api$core3.actions.execute(function (_ref3) {
63
+ var tr = _ref3.tr;
32
64
  return api === null || api === void 0 ? void 0 : api.syncedBlock.commands.insertSyncedBlock()({
33
65
  tr: tr
34
66
  });
35
67
  });
36
- api === null || api === void 0 || (_api$core2 = api.core) === null || _api$core2 === void 0 || _api$core2.actions.focus();
68
+ api === null || api === void 0 || (_api$core4 = api.core) === null || _api$core4 === void 0 || _api$core4.actions.focus();
37
69
  }, [api]);
38
70
  var message = intl.formatMessage(syncBlockMessages.syncBlockLabel);
39
71
  return /*#__PURE__*/React.createElement(ToolbarTooltip, {
@@ -38,9 +38,51 @@ var CreateSyncedBlockDropdownItem = function CreateSyncedBlockDropdownItem(_ref2
38
38
  return null;
39
39
  }
40
40
  var onClick = function onClick() {
41
- var _api$core, _api$core2, _api$blockControls;
42
- api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(api === null || api === void 0 ? void 0 : api.syncedBlock.commands.insertSyncedBlock());
43
- api === null || api === void 0 || (_api$core2 = api.core) === null || _api$core2 === void 0 || _api$core2.actions.execute(api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.commands) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.toggleBlockMenu({
41
+ var _api$core3, _api$core4, _api$blockControls3;
42
+ if (fg('platform_editor_blocks_patch_4')) {
43
+ var _api$core;
44
+ // Insert the synced block, close the block menu, and stop preserving the
45
+ // selection — all in a single transaction, mirroring the block-menu delete
46
+ // item. Stopping preservation is required so the caret that
47
+ // insertSyncedBlock places inside the new block survives; otherwise
48
+ // block-controls restores a whole-node NodeSelection over it, hiding the
49
+ // caret (EDITOR-7949). Then re-focus the editor so the caret is active,
50
+ // but only when the transaction was actually dispatched — otherwise a
51
+ // failed insertion would still steal DOM focus into the editor.
52
+ var dispatched = api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(function (_ref3) {
53
+ var _api$blockControls, _api$blockControls2;
54
+ var tr = _ref3.tr;
55
+ // If the insertion fails, bail out without closing the menu or
56
+ // stopping selection preservation — otherwise we would dispatch an
57
+ // effectively-empty transaction and close the menu despite nothing
58
+ // having been inserted.
59
+ var result = api === null || api === void 0 ? void 0 : api.syncedBlock.commands.insertSyncedBlock()({
60
+ tr: tr
61
+ });
62
+ if (!result) {
63
+ return null;
64
+ }
65
+ api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.commands) === null || _api$blockControls === void 0 || _api$blockControls.toggleBlockMenu({
66
+ closeMenu: true
67
+ })({
68
+ tr: tr
69
+ });
70
+ api === null || api === void 0 || (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || (_api$blockControls2 = _api$blockControls2.commands) === null || _api$blockControls2 === void 0 || _api$blockControls2.stopPreservingSelection()({
71
+ tr: tr
72
+ });
73
+ return tr;
74
+ });
75
+ if (dispatched) {
76
+ var _api$core2;
77
+ api === null || api === void 0 || (_api$core2 = api.core) === null || _api$core2 === void 0 || _api$core2.actions.focus();
78
+ }
79
+ return;
80
+ }
81
+
82
+ // Legacy behaviour: insert then close the block menu as two separate executes
83
+ // (batching them caused selection collisions — EDITOR-2751).
84
+ api === null || api === void 0 || (_api$core3 = api.core) === null || _api$core3 === void 0 || _api$core3.actions.execute(api === null || api === void 0 ? void 0 : api.syncedBlock.commands.insertSyncedBlock());
85
+ api === null || api === void 0 || (_api$core4 = api.core) === null || _api$core4 === void 0 || _api$core4.actions.execute(api === null || api === void 0 || (_api$blockControls3 = api.blockControls) === null || _api$blockControls3 === void 0 || (_api$blockControls3 = _api$blockControls3.commands) === null || _api$blockControls3 === void 0 ? void 0 : _api$blockControls3.toggleBlockMenu({
44
86
  closeMenu: true
45
87
  }));
46
88
  };
@@ -59,8 +101,8 @@ var CreateSyncedBlockDropdownItem = function CreateSyncedBlockDropdownItem(_ref2
59
101
  elemAfterText: lozenge
60
102
  }, formatMessage(blockMenuMessages.syncBlock));
61
103
  };
62
- var CopySyncedBlockDropdownItem = function CopySyncedBlockDropdownItem(_ref3) {
63
- var api = _ref3.api;
104
+ var CopySyncedBlockDropdownItem = function CopySyncedBlockDropdownItem(_ref4) {
105
+ var api = _ref4.api;
64
106
  var _useIntl2 = useIntl(),
65
107
  formatMessage = _useIntl2.formatMessage;
66
108
  var _useSharedPluginState2 = useSharedPluginStateWithSelector(api, ['connectivity'], function (states) {
@@ -71,9 +113,9 @@ var CopySyncedBlockDropdownItem = function CopySyncedBlockDropdownItem(_ref3) {
71
113
  }),
72
114
  mode = _useSharedPluginState2.mode;
73
115
  var onClick = function onClick() {
74
- var _api$core3, _api$core4, _api$blockControls2;
75
- api === null || api === void 0 || (_api$core3 = api.core) === null || _api$core3 === void 0 || _api$core3.actions.execute(api === null || api === void 0 ? void 0 : api.syncedBlock.commands.copySyncedBlockReferenceToClipboard(INPUT_METHOD.BLOCK_MENU));
76
- api === null || api === void 0 || (_api$core4 = api.core) === null || _api$core4 === void 0 || _api$core4.actions.execute(api === null || api === void 0 || (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || (_api$blockControls2 = _api$blockControls2.commands) === null || _api$blockControls2 === void 0 ? void 0 : _api$blockControls2.toggleBlockMenu({
116
+ var _api$core5, _api$core6, _api$blockControls4;
117
+ api === null || api === void 0 || (_api$core5 = api.core) === null || _api$core5 === void 0 || _api$core5.actions.execute(api === null || api === void 0 ? void 0 : api.syncedBlock.commands.copySyncedBlockReferenceToClipboard(INPUT_METHOD.BLOCK_MENU));
118
+ api === null || api === void 0 || (_api$core6 = api.core) === null || _api$core6 === void 0 || _api$core6.actions.execute(api === null || api === void 0 || (_api$blockControls4 = api.blockControls) === null || _api$blockControls4 === void 0 || (_api$blockControls4 = _api$blockControls4.commands) === null || _api$blockControls4 === void 0 ? void 0 : _api$blockControls4.toggleBlockMenu({
77
119
  closeMenu: true
78
120
  }));
79
121
  };
@@ -90,9 +132,9 @@ var CopySyncedBlockDropdownItem = function CopySyncedBlockDropdownItem(_ref3) {
90
132
  elemAfterText: lozenge
91
133
  }, formatMessage(blockMenuMessages.copySyncedBlock));
92
134
  };
93
- export var CreateOrCopySyncedBlockDropdownItem = function CreateOrCopySyncedBlockDropdownItem(_ref4) {
94
- var api = _ref4.api,
95
- enableSourceSyncedBlockCreation = _ref4.enableSourceSyncedBlockCreation;
135
+ export var CreateOrCopySyncedBlockDropdownItem = function CreateOrCopySyncedBlockDropdownItem(_ref5) {
136
+ var api = _ref5.api,
137
+ enableSourceSyncedBlockCreation = _ref5.enableSourceSyncedBlockCreation;
96
138
  var _useSharedPluginState3 = useSharedPluginStateWithSelector(api, ['blockControls'], function (states) {
97
139
  var _states$blockControls3, _states$blockControls4;
98
140
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-synced-block",
3
- "version": "10.1.1",
3
+ "version": "10.1.3",
4
4
  "description": "SyncedBlock plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -46,8 +46,8 @@
46
46
  "@atlaskit/platform-feature-flags": "^2.0.0",
47
47
  "@atlaskit/primitives": "^20.3.0",
48
48
  "@atlaskit/spinner": "20.1.0",
49
- "@atlaskit/tmp-editor-statsig": "^119.0.0",
50
- "@atlaskit/tokens": "15.3.0",
49
+ "@atlaskit/tmp-editor-statsig": "^120.1.0",
50
+ "@atlaskit/tokens": "15.3.1",
51
51
  "@atlaskit/tooltip": "^23.1.0",
52
52
  "@atlaskit/visually-hidden": "^4.1.0",
53
53
  "@babel/runtime": "^7.0.0",
@@ -56,7 +56,7 @@
56
56
  "date-fns": "^2.17.0"
57
57
  },
58
58
  "peerDependencies": {
59
- "@atlaskit/editor-common": "^116.19.0",
59
+ "@atlaskit/editor-common": "^116.20.0",
60
60
  "react": "^18.2.0",
61
61
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
62
62
  },