@atlaskit/editor-plugin-synced-block 10.1.1 → 10.1.2
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 +10 -0
- package/compass.yml +3 -0
- package/dist/cjs/editor-commands/index.js +69 -3
- package/dist/cjs/ui/CreateSyncedBlockButton.js +36 -4
- package/dist/cjs/ui/CreateSyncedBlockDropdownItem.js +53 -11
- package/dist/es2019/editor-commands/index.js +70 -4
- package/dist/es2019/ui/CreateSyncedBlockButton.js +36 -3
- package/dist/es2019/ui/CreateSyncedBlockDropdownItem.js +49 -6
- package/dist/esm/editor-commands/index.js +70 -4
- package/dist/esm/ui/CreateSyncedBlockButton.js +36 -4
- package/dist/esm/ui/CreateSyncedBlockDropdownItem.js +53 -11
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-synced-block
|
|
2
2
|
|
|
3
|
+
## 10.1.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`70c2b083643f1`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/70c2b083643f1) -
|
|
8
|
+
[ux] Fix creating a synced block from the block menu or toolbar so the caret is placed inside the
|
|
9
|
+
new block and the editor is re-focused, matching the slash-command behaviour. Previously the
|
|
10
|
+
cursor was left outside the newly created synced block.
|
|
11
|
+
- Updated dependencies
|
|
12
|
+
|
|
3
13
|
## 10.1.1
|
|
4
14
|
|
|
5
15
|
### 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
|
-
|
|
84
|
-
|
|
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$
|
|
39
|
-
|
|
40
|
-
var
|
|
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$
|
|
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$
|
|
51
|
-
|
|
52
|
-
|
|
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(
|
|
72
|
-
var 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$
|
|
84
|
-
api === null || api === void 0 || (_api$
|
|
85
|
-
api === null || api === void 0 || (_api$
|
|
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(
|
|
103
|
-
var api =
|
|
104
|
-
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
|
-
|
|
80
|
-
|
|
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$
|
|
32
|
-
|
|
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$
|
|
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$
|
|
42
|
-
|
|
43
|
-
|
|
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$
|
|
78
|
-
api === null || api === void 0 ? void 0 : (_api$
|
|
79
|
-
api === null || api === void 0 ? void 0 : (_api$
|
|
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
|
-
|
|
77
|
-
|
|
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$
|
|
30
|
-
|
|
31
|
-
var
|
|
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$
|
|
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$
|
|
42
|
-
|
|
43
|
-
|
|
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(
|
|
63
|
-
var 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$
|
|
75
|
-
api === null || api === void 0 || (_api$
|
|
76
|
-
api === null || api === void 0 || (_api$
|
|
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(
|
|
94
|
-
var api =
|
|
95
|
-
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.
|
|
3
|
+
"version": "10.1.2",
|
|
4
4
|
"description": "SyncedBlock plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -46,7 +46,7 @@
|
|
|
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": "^
|
|
49
|
+
"@atlaskit/tmp-editor-statsig": "^120.0.0",
|
|
50
50
|
"@atlaskit/tokens": "15.3.0",
|
|
51
51
|
"@atlaskit/tooltip": "^23.1.0",
|
|
52
52
|
"@atlaskit/visually-hidden": "^4.1.0",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"date-fns": "^2.17.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@atlaskit/editor-common": "^116.
|
|
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
|
},
|