@atlaskit/editor-plugin-block-controls 7.11.0 → 7.11.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.
Files changed (29) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/editor-commands/move-node-with-block-menu.js +30 -26
  3. package/dist/cjs/editor-commands/move-node.js +28 -9
  4. package/dist/cjs/editor-commands/utils/move-node-utils.js +2 -2
  5. package/dist/cjs/pm-plugins/main.js +4 -1
  6. package/dist/cjs/pm-plugins/selection-preservation/pm-plugin.js +16 -19
  7. package/dist/cjs/pm-plugins/utils/getSelection.js +74 -26
  8. package/dist/cjs/ui/drag-handle.js +4 -1
  9. package/dist/es2019/editor-commands/move-node-with-block-menu.js +33 -27
  10. package/dist/es2019/editor-commands/move-node.js +28 -9
  11. package/dist/es2019/editor-commands/utils/move-node-utils.js +2 -2
  12. package/dist/es2019/pm-plugins/main.js +3 -0
  13. package/dist/es2019/pm-plugins/selection-preservation/pm-plugin.js +15 -17
  14. package/dist/es2019/pm-plugins/utils/getSelection.js +73 -25
  15. package/dist/es2019/ui/drag-handle.js +5 -2
  16. package/dist/esm/editor-commands/move-node-with-block-menu.js +31 -27
  17. package/dist/esm/editor-commands/move-node.js +30 -11
  18. package/dist/esm/editor-commands/utils/move-node-utils.js +2 -2
  19. package/dist/esm/pm-plugins/main.js +3 -0
  20. package/dist/esm/pm-plugins/selection-preservation/pm-plugin.js +16 -19
  21. package/dist/esm/pm-plugins/utils/getSelection.js +73 -25
  22. package/dist/esm/ui/drag-handle.js +5 -2
  23. package/dist/types/blockControlsPluginType.d.ts +1 -0
  24. package/dist/types/pm-plugins/main.d.ts +2 -1
  25. package/dist/types/pm-plugins/utils/getSelection.d.ts +28 -3
  26. package/dist/types-ts4.5/blockControlsPluginType.d.ts +1 -0
  27. package/dist/types-ts4.5/pm-plugins/main.d.ts +2 -1
  28. package/dist/types-ts4.5/pm-plugins/utils/getSelection.d.ts +28 -3
  29. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @atlaskit/editor-plugin-block-controls
2
2
 
3
+ ## 7.11.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [`d555d4bd26286`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/d555d4bd26286) -
8
+ EDITOR-3565 fix block menu multi-select move actions
9
+ - Updated dependencies
10
+
11
+ ## 7.11.1
12
+
13
+ ### Patch Changes
14
+
15
+ - [`00c08e3995cb2`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/00c08e3995cb2) -
16
+ Clean up platform_editor_block_menu_empty_line
17
+ - Updated dependencies
18
+
3
19
  ## 7.11.0
4
20
 
5
21
  ### Minor Changes
@@ -6,10 +6,20 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.moveNodeWithBlockMenu = void 0;
7
7
  var _analytics = require("@atlaskit/editor-common/analytics");
8
8
  var _types = require("@atlaskit/editor-common/types");
9
- var _state = require("@atlaskit/editor-prosemirror/state");
10
9
  var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
11
10
  var _moveNode = require("./move-node");
12
11
  var _moveNodeUtils = require("./utils/move-node-utils");
12
+ var getSelectionToIndex = function getSelectionToIndex(fromIndex, $to, depth) {
13
+ var toIndex = $to.index(depth);
14
+ var toIndexAfter = $to.indexAfter(depth);
15
+
16
+ // If $to is at the start of a node (indexAfter === index), don't include that node
17
+ // This occurs when the preserved selection is outside of inline positions at node boundaries
18
+ if (toIndexAfter === toIndex && toIndex > fromIndex) {
19
+ return toIndex - 1;
20
+ }
21
+ return toIndex;
22
+ };
13
23
  var moveNodeWithBlockMenu = exports.moveNodeWithBlockMenu = function moveNodeWithBlockMenu(api, direction) {
14
24
  return function (_ref) {
15
25
  var _api$blockControls$sh;
@@ -26,33 +36,27 @@ var moveNodeWithBlockMenu = exports.moveNodeWithBlockMenu = function moveNodeWit
26
36
  schema: tr.doc.type.schema,
27
37
  resolve: tr.doc.resolve.bind(tr.doc)
28
38
  });
29
- if (currentNodePos > -1) {
30
- var $currentNodePos = tr.doc.resolve(currentNodePos);
31
- var nodeAfterPos = $currentNodePos.posAtIndex($currentNodePos.index() + 1);
32
- var moveToPos = direction === _types.DIRECTION.UP ? (0, _moveNodeUtils.getPosWhenMoveNodeUp)($currentNodePos, currentNodePos) : (0, _moveNodeUtils.getPosWhenMoveNodeDown)({
33
- $currentNodePos: $currentNodePos,
34
- nodeAfterPos: nodeAfterPos,
35
- tr: tr
36
- });
37
-
38
- // only move the node if the destination is at the same depth, not support moving a nested node to a parent node
39
- var shouldMoveNode = (0, _moveNodeUtils.getShouldMoveNode)({
40
- currentNodePos: currentNodePos,
41
- moveToPos: moveToPos,
42
- tr: tr
43
- });
44
- if (shouldMoveNode) {
45
- (0, _moveNode.moveNode)(api)(currentNodePos, moveToPos, _analytics.INPUT_METHOD.BLOCK_MENU)({
39
+ var $from = selection.$from,
40
+ $to = selection.$to;
41
+ var depth = tr.doc.resolve(currentNodePos).depth;
42
+ var fromIndex = $from.index(depth);
43
+ if (direction === _types.DIRECTION.UP) {
44
+ if (fromIndex > 0) {
45
+ var moveToPos = $from.posAtIndex(fromIndex - 1, depth);
46
+ return (0, _moveNode.moveNode)(api)(currentNodePos, moveToPos, _analytics.INPUT_METHOD.BLOCK_MENU)({
47
+ tr: tr
48
+ });
49
+ }
50
+ } else {
51
+ // selectionToIndex is the index of the last node in the selection
52
+ var selectionToIndex = getSelectionToIndex(fromIndex, $to, depth);
53
+ // Adding 2 so we jump over the next node to the position after it
54
+ var moveToIndex = selectionToIndex + 2;
55
+ if (moveToIndex <= $to.node(depth).childCount) {
56
+ var _moveToPos = $to.posAtIndex(moveToIndex, depth);
57
+ return (0, _moveNode.moveNode)(api)(currentNodePos, _moveToPos, _analytics.INPUT_METHOD.BLOCK_MENU)({
46
58
  tr: tr
47
59
  });
48
- if (tr.selection.empty && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true) && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_empty_line', 'isEnabled', true)) {
49
- var nodeAtCurrentPos = tr.doc.nodeAt(currentNodePos);
50
- var nodeAfter = tr.doc.nodeAt(moveToPos);
51
- var isConsecutiveEmptyLineMove = (nodeAtCurrentPos === null || nodeAtCurrentPos === void 0 ? void 0 : nodeAtCurrentPos.content.size) === 0 && (nodeAfter === null || nodeAfter === void 0 ? void 0 : nodeAfter.content.size) === 0;
52
- var cursorPos = direction === _types.DIRECTION.UP || direction === _types.DIRECTION.DOWN && isConsecutiveEmptyLineMove ? moveToPos : moveToPos - 1;
53
- tr.setSelection(_state.TextSelection.create(tr.doc, cursorPos));
54
- }
55
- tr.scrollIntoView();
56
60
  }
57
61
  }
58
62
  return tr;
@@ -313,7 +313,7 @@ var moveNode = exports.moveNode = function moveNode(api) {
313
313
  var inputMethod = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : _analytics.INPUT_METHOD.DRAG_AND_DROP;
314
314
  var formatMessage = arguments.length > 3 ? arguments[3] : undefined;
315
315
  return function (_ref7) {
316
- var _convertedNodeSlice, _api$blockControls$co, _api$accessibilityUti;
316
+ var _api$blockControls$sh, _convertedNodeSlice, _api$accessibilityUti;
317
317
  var tr = _ref7.tr;
318
318
  if (!api || start < 0 || to < 0) {
319
319
  return tr;
@@ -332,13 +332,22 @@ var moveNode = exports.moveNode = function moveNode(api) {
332
332
  tr: tr
333
333
  });
334
334
  }
335
- var slicePosition = (0, _selection2.getSelectedSlicePosition)(start, tr, api);
336
- if (isMultiSelect) {
337
- sliceFrom = slicePosition.from;
338
- sliceTo = slicePosition.to;
335
+ var preservedSelection = (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true) ? api === null || api === void 0 || (_api$blockControls$sh = api.blockControls.sharedState.currentState()) === null || _api$blockControls$sh === void 0 ? void 0 : _api$blockControls$sh.preservedSelection : undefined;
336
+ if (preservedSelection) {
337
+ var $from = tr.doc.resolve(Math.min(start, preservedSelection.from));
338
+ var expandedRange = $from.blockRange(preservedSelection.$to);
339
+ sliceFrom = expandedRange ? expandedRange.start : preservedSelection.from;
340
+ sliceTo = expandedRange ? expandedRange.end : preservedSelection.to;
339
341
  var attributes = (0, _analytics2.getMultiSelectAnalyticsAttributes)(tr, sliceFrom, sliceTo);
340
342
  hasSelectedMultipleNodes = attributes.hasSelectedMultipleNodes;
341
343
  sourceNodeTypes = attributes.nodeTypes;
344
+ } else if (isMultiSelect) {
345
+ var slicePosition = (0, _selection2.getSelectedSlicePosition)(start, tr, api);
346
+ sliceFrom = slicePosition.from;
347
+ sliceTo = slicePosition.to;
348
+ var _attributes = (0, _analytics2.getMultiSelectAnalyticsAttributes)(tr, sliceFrom, sliceTo);
349
+ hasSelectedMultipleNodes = _attributes.hasSelectedMultipleNodes;
350
+ sourceNodeTypes = _attributes.nodeTypes;
342
351
  } else {
343
352
  var _handleNode$nodeSize;
344
353
  var size = (_handleNode$nodeSize = handleNode === null || handleNode === void 0 ? void 0 : handleNode.nodeSize) !== null && _handleNode$nodeSize !== void 0 ? _handleNode$nodeSize : 1;
@@ -414,12 +423,22 @@ var moveNode = exports.moveNode = function moveNode(api) {
414
423
  tr.insert(mappedTo, convertedNode);
415
424
  }
416
425
  var sliceSize = sliceTo - sliceFrom;
417
- tr = inputMethod === _analytics.INPUT_METHOD.DRAG_AND_DROP ? (0, _getSelection.setCursorPositionAtMovedNode)(tr, mappedTo, api) : isMultiSelect ? (_api$blockControls$co = api === null || api === void 0 ? void 0 : api.blockControls.commands.setMultiSelectPositions(mappedTo, mappedTo + sliceSize)({
418
- tr: tr
419
- })) !== null && _api$blockControls$co !== void 0 ? _api$blockControls$co : tr : (0, _getSelection.selectNode)(tr, mappedTo, handleNode.type.name, api);
426
+ if (inputMethod === _analytics.INPUT_METHOD.DRAG_AND_DROP) {
427
+ tr = (0, _getSelection.setCursorPositionAtMovedNode)(tr, mappedTo, api);
428
+ } else if (preservedSelection) {
429
+ // do nothing here to allow the selection preservation plugin to handle mapping the selection
430
+ } else if (isMultiSelect) {
431
+ var _api$blockControls$co;
432
+ tr = (_api$blockControls$co = api === null || api === void 0 ? void 0 : api.blockControls.commands.setMultiSelectPositions(mappedTo, mappedTo + sliceSize)({
433
+ tr: tr
434
+ })) !== null && _api$blockControls$co !== void 0 ? _api$blockControls$co : tr;
435
+ } else {
436
+ tr = (0, _getSelection.selectNode)(tr, mappedTo, handleNode.type.name, api);
437
+ }
420
438
  var currMeta = tr.getMeta(_main.key);
421
439
  tr.setMeta(_main.key, _objectSpread(_objectSpread({}, currMeta), {}, {
422
- nodeMoved: true
440
+ nodeMoved: true,
441
+ nodeMovedOffset: mappedTo - sliceFrom
423
442
  }));
424
443
  api === null || api === void 0 || api.core.actions.focus();
425
444
  var $mappedTo = tr.doc.resolve(mappedTo);
@@ -14,7 +14,7 @@ var getCurrentNodePosFromDragHandleSelection = exports.getCurrentNodePosFromDrag
14
14
  schema = _ref.schema,
15
15
  resolve = _ref.resolve;
16
16
  var currentNodePos = -1;
17
- if (selection.empty && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true) && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_empty_line', 'isEnabled', true)) {
17
+ if (selection.empty && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true)) {
18
18
  currentNodePos = selection.$from.pos;
19
19
  }
20
20
  if ((0, _utils.isTableSelected)(selection)) {
@@ -57,7 +57,7 @@ var getPosWhenMoveNodeDown = exports.getPosWhenMoveNodeDown = function getPosWhe
57
57
  return -1;
58
58
  }
59
59
  var nodeAfter = tr.doc.nodeAt(nodeAfterPos);
60
- if ((0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true) && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_empty_line', 'isEnabled', true)) {
60
+ if ((0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true)) {
61
61
  var nodeAtCurrentPos = tr.doc.nodeAt($currentNodePos.pos);
62
62
  // if move empty line down to another empty line, move to the position of the next empty line
63
63
  if ((nodeAtCurrentPos === null || nodeAtCurrentPos === void 0 ? void 0 : nodeAtCurrentPos.content.size) === 0 && nodeAtCurrentPos.type.name !== 'extension' && (nodeAfter === null || nodeAfter === void 0 ? void 0 : nodeAfter.content.size) === 0 && nodeAfter.type.name !== 'extension') {
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.key = exports.getDecorations = exports.createPlugin = exports.apply = void 0;
7
+ exports.key = exports.getDecorations = exports.getBlockControlsMeta = exports.createPlugin = exports.apply = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
9
  var _rafSchd = _interopRequireDefault(require("raf-schd"));
10
10
  var _analytics = require("@atlaskit/editor-common/analytics");
@@ -891,4 +891,7 @@ var createPlugin = exports.createPlugin = function createPlugin(api, getIntl, no
891
891
  };
892
892
  }
893
893
  });
894
+ };
895
+ var getBlockControlsMeta = exports.getBlockControlsMeta = function getBlockControlsMeta(tr) {
896
+ return tr.getMeta(key);
894
897
  };
@@ -9,6 +9,9 @@ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/de
9
9
  var _monitoring = require("@atlaskit/editor-common/monitoring");
10
10
  var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
11
11
  var _state = require("@atlaskit/editor-prosemirror/state");
12
+ var _transform = require("@atlaskit/editor-prosemirror/transform");
13
+ var _main = require("../main");
14
+ var _getSelection = require("../utils/getSelection");
12
15
  var _editorCommands = require("./editor-commands");
13
16
  var _pluginKey = require("./plugin-key");
14
17
  var _utils = require("./utils");
@@ -96,15 +99,22 @@ var createSelectionPreservationPlugin = exports.createSelectionPreservationPlugi
96
99
  });
97
100
  };
98
101
  var mapSelection = function mapSelection(selection, tr) {
102
+ var _ref = (0, _main.getBlockControlsMeta)(tr) || {},
103
+ nodeMoved = _ref.nodeMoved,
104
+ nodeMovedOffset = _ref.nodeMovedOffset;
105
+ var mapping = nodeMoved && typeof nodeMovedOffset === 'number' ? new _transform.Mapping([new _transform.StepMap([0, 0, nodeMovedOffset])]) : tr.mapping;
99
106
  if (selection instanceof _state.TextSelection) {
100
- var from = tr.mapping.map(selection.from);
101
- var to = tr.mapping.map(selection.to);
107
+ var from = mapping.map(selection.from);
108
+ var to = mapping.map(selection.to);
102
109
 
103
110
  // expand the text selection range to block boundaries, so as document changes occur the
104
111
  // selection always includes whole nodes
105
- var _expandToBlockRange = expandToBlockRange(tr.doc.resolve(from), tr.doc.resolve(to)),
106
- $from = _expandToBlockRange.$from,
107
- $to = _expandToBlockRange.$to;
112
+ var expanded = (0, _getSelection.expandToBlockRange)(tr.doc.resolve(from), tr.doc.resolve(to));
113
+
114
+ // collapse the expanded range to a valid selection range
115
+ var _collapseToSelectionR = (0, _getSelection.collapseToSelectionRange)(expanded.$from, expanded.$to),
116
+ $from = _collapseToSelectionR.$from,
117
+ $to = _collapseToSelectionR.$to;
108
118
 
109
119
  // stop preserving if preserved selection becomes invalid or collapsed to a cursor
110
120
  // e.g. after deleting the selection
@@ -114,21 +124,8 @@ var mapSelection = function mapSelection(selection, tr) {
114
124
  return new _state.TextSelection($from, $to);
115
125
  }
116
126
  try {
117
- return selection.map(tr.doc, tr.mapping);
127
+ return selection.map(tr.doc, mapping);
118
128
  } catch (_unused) {
119
129
  return undefined;
120
130
  }
121
- };
122
- var expandToBlockRange = function expandToBlockRange($from, $to) {
123
- var range = $from.blockRange($to);
124
- if (!range) {
125
- return {
126
- $from: $from,
127
- $to: $to
128
- };
129
- }
130
- return {
131
- $from: $from.doc.resolve(range.start),
132
- $to: $to.doc.resolve(range.end)
133
- };
134
131
  };
@@ -3,21 +3,21 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.setCursorPositionAtMovedNode = exports.selectNode = exports.rootTaskListDepth = exports.rootListDepth = exports.isNodeWithCodeBlock = exports.isHandleCorrelatedToSelection = exports.getSelection = exports.getInlineNodePos = void 0;
6
+ exports.setCursorPositionAtMovedNode = exports.selectNode = exports.rootTaskListDepth = exports.rootListDepth = exports.isNodeWithCodeBlock = exports.isHandleCorrelatedToSelection = exports.getSelection = exports.getInlineNodePos = exports.expandToBlockRange = exports.collapseToSelectionRange = void 0;
7
7
  var _selection2 = require("@atlaskit/editor-common/selection");
8
8
  var _toolbarFlagCheck = require("@atlaskit/editor-common/toolbar-flag-check");
9
9
  var _state = require("@atlaskit/editor-prosemirror/state");
10
10
  var _utils = require("@atlaskit/editor-prosemirror/utils");
11
11
  var _utils2 = require("@atlaskit/editor-tables/utils");
12
12
  var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
13
- var getInlineNodePos = exports.getInlineNodePos = function getInlineNodePos(tr, start, nodeSize) {
14
- var $startPos = tr.doc.resolve(start);
13
+ var getInlineNodePos = exports.getInlineNodePos = function getInlineNodePos(doc, start, nodeSize) {
14
+ var $startPos = doc.resolve(start);
15
15
  // To trigger the annotation floating toolbar for non-selectable node, we need to select inline nodes
16
16
  // Find the first inline node in the node
17
17
  var inlineNodePos = start;
18
18
  var foundInlineNode = false;
19
19
  var inlineNodeEndPos = 0;
20
- tr.doc.nodesBetween($startPos.pos, $startPos.pos + nodeSize, function (n, pos) {
20
+ doc.nodesBetween($startPos.pos, $startPos.pos + nodeSize, function (n, pos) {
21
21
  if (n.isInline) {
22
22
  inlineNodeEndPos = pos + n.nodeSize;
23
23
  }
@@ -42,10 +42,10 @@ var isNodeWithCodeBlock = exports.isNodeWithCodeBlock = function isNodeWithCodeB
42
42
  });
43
43
  return hasCodeBlock;
44
44
  };
45
- var isNodeWithMediaOrExtension = function isNodeWithMediaOrExtension(tr, start, nodeSize) {
46
- var $startPos = tr.doc.resolve(start);
45
+ var isNodeWithMediaOrExtension = function isNodeWithMediaOrExtension(doc, start, nodeSize) {
46
+ var $startPos = doc.resolve(start);
47
47
  var hasMediaOrExtension = false;
48
- tr.doc.nodesBetween($startPos.pos, $startPos.pos + nodeSize, function (n) {
48
+ doc.nodesBetween($startPos.pos, $startPos.pos + nodeSize, function (n) {
49
49
  if (['media', 'extension'].includes(n.type.name)) {
50
50
  hasMediaOrExtension = true;
51
51
  }
@@ -58,8 +58,8 @@ var oldGetSelection = function oldGetSelection(tr, start) {
58
58
  var nodeSize = node ? node.nodeSize : 1;
59
59
  var $startPos = tr.doc.resolve(start);
60
60
  var nodeName = node === null || node === void 0 ? void 0 : node.type.name;
61
- var isBlockQuoteWithMediaOrExtension = nodeName === 'blockquote' && isNodeWithMediaOrExtension(tr, start, nodeSize);
62
- var isListWithMediaOrExtension = nodeName === 'bulletList' && isNodeWithMediaOrExtension(tr, start, nodeSize) || nodeName === 'orderedList' && isNodeWithMediaOrExtension(tr, start, nodeSize);
61
+ var isBlockQuoteWithMediaOrExtension = nodeName === 'blockquote' && isNodeWithMediaOrExtension(tr.doc, start, nodeSize);
62
+ var isListWithMediaOrExtension = nodeName === 'bulletList' && isNodeWithMediaOrExtension(tr.doc, start, nodeSize) || nodeName === 'orderedList' && isNodeWithMediaOrExtension(tr.doc, start, nodeSize);
63
63
  if (isNodeSelection && nodeName !== 'blockquote' || isListWithMediaOrExtension || isBlockQuoteWithMediaOrExtension ||
64
64
  // decisionList/layoutColumn node is not selectable, but we want to select the whole node not just text
65
65
  ['decisionList', 'layoutColumn'].includes(nodeName || '')) {
@@ -75,55 +75,55 @@ var oldGetSelection = function oldGetSelection(tr, start) {
75
75
  } else if (nodeName === 'taskList') {
76
76
  return _state.TextSelection.create(tr.doc, start, start + nodeSize);
77
77
  } else {
78
- var _getInlineNodePos = getInlineNodePos(tr, start, nodeSize),
78
+ var _getInlineNodePos = getInlineNodePos(tr.doc, start, nodeSize),
79
79
  inlineNodePos = _getInlineNodePos.inlineNodePos,
80
80
  inlineNodeEndPos = _getInlineNodePos.inlineNodeEndPos;
81
81
  return new _state.TextSelection(tr.doc.resolve(inlineNodePos), tr.doc.resolve(inlineNodeEndPos));
82
82
  }
83
83
  };
84
- var newGetSelection = function newGetSelection(tr, start) {
85
- var node = tr.doc.nodeAt(start);
84
+ var newGetSelection = function newGetSelection(doc, selectionEmpty, start) {
85
+ var node = doc.nodeAt(start);
86
86
  var isNodeSelection = node && _state.NodeSelection.isSelectable(node);
87
87
  var nodeSize = node ? node.nodeSize : 1;
88
88
  var nodeName = node === null || node === void 0 ? void 0 : node.type.name;
89
89
 
90
90
  // this is a fix for empty paragraph selection - put first to avoid any extra work
91
- if (nodeName === 'paragraph' && tr.selection.empty && (node === null || node === void 0 ? void 0 : node.childCount) === 0 && !(0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true)) {
91
+ if (nodeName === 'paragraph' && selectionEmpty && (node === null || node === void 0 ? void 0 : node.childCount) === 0 && !(0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true)) {
92
92
  return false;
93
93
  }
94
- var isParagraphHeadingEmpty = ['paragraph', 'heading'].includes(nodeName || '') && tr.selection.empty && (node === null || node === void 0 ? void 0 : node.childCount) === 0;
94
+ var isParagraphHeadingEmpty = ['paragraph', 'heading'].includes(nodeName || '') && selectionEmpty && (node === null || node === void 0 ? void 0 : node.childCount) === 0;
95
95
  var isBlockQuoteEmpty = nodeName === 'blockquote' && (node === null || node === void 0 ? void 0 : node.textContent) === '';
96
- var isListEmpty = ['orderedList', 'bulletList', 'taskList'].includes(nodeName || '') && tr.selection.empty && (node === null || node === void 0 ? void 0 : node.textContent) === '';
96
+ var isListEmpty = ['orderedList', 'bulletList', 'taskList'].includes(nodeName || '') && selectionEmpty && (node === null || node === void 0 ? void 0 : node.textContent) === '';
97
97
 
98
98
  // if block menu and empty line format menu are enabled,
99
99
  // we want to set the selection to avoid the selection goes to the top of the document
100
- if ((isParagraphHeadingEmpty || isBlockQuoteEmpty || isListEmpty) && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true) && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_empty_line', 'isEnabled', true)) {
101
- return _state.TextSelection.create(tr.doc, start + 1, start + 1);
100
+ if ((isParagraphHeadingEmpty || isBlockQuoteEmpty || isListEmpty) && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true)) {
101
+ return _state.TextSelection.create(doc, start + 1, start + 1);
102
102
  }
103
- var isBlockQuoteWithMediaOrExtension = nodeName === 'blockquote' && isNodeWithMediaOrExtension(tr, start, nodeSize);
104
- var isListWithMediaOrExtension = nodeName === 'bulletList' && isNodeWithMediaOrExtension(tr, start, nodeSize) || nodeName === 'orderedList' && isNodeWithMediaOrExtension(tr, start, nodeSize);
103
+ var isBlockQuoteWithMediaOrExtension = nodeName === 'blockquote' && isNodeWithMediaOrExtension(doc, start, nodeSize);
104
+ var isListWithMediaOrExtension = nodeName === 'bulletList' && isNodeWithMediaOrExtension(doc, start, nodeSize) || nodeName === 'orderedList' && isNodeWithMediaOrExtension(doc, start, nodeSize);
105
105
  if (isNodeSelection && nodeName !== 'blockquote' || isListWithMediaOrExtension || isBlockQuoteWithMediaOrExtension ||
106
106
  // decisionList/layoutColumn node is not selectable, but we want to select the whole node not just text
107
107
  ['decisionList', 'layoutColumn'].includes(nodeName || '') || nodeName === 'mediaGroup' && typeof (node === null || node === void 0 ? void 0 : node.childCount) === 'number' && (node === null || node === void 0 ? void 0 : node.childCount) > 1) {
108
- return new _state.NodeSelection(tr.doc.resolve(start));
108
+ return new _state.NodeSelection(doc.resolve(start));
109
109
  }
110
110
 
111
111
  // if mediaGroup only has a single child, we want to select the child
112
112
  if (nodeName === 'mediaGroup') {
113
- var $mediaStartPos = tr.doc.resolve(start + 1);
113
+ var $mediaStartPos = doc.resolve(start + 1);
114
114
  return new _state.NodeSelection($mediaStartPos);
115
115
  }
116
116
  if (nodeName === 'taskList' && !(0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true)) {
117
- return _state.TextSelection.create(tr.doc, start, start + nodeSize);
117
+ return _state.TextSelection.create(doc, start, start + nodeSize);
118
118
  }
119
- var _getInlineNodePos2 = getInlineNodePos(tr, start, nodeSize),
119
+ var _getInlineNodePos2 = getInlineNodePos(doc, start, nodeSize),
120
120
  inlineNodePos = _getInlineNodePos2.inlineNodePos,
121
121
  inlineNodeEndPos = _getInlineNodePos2.inlineNodeEndPos;
122
- return new _state.TextSelection(tr.doc.resolve(inlineNodePos), tr.doc.resolve(inlineNodeEndPos));
122
+ return new _state.TextSelection(doc.resolve(inlineNodePos), doc.resolve(inlineNodeEndPos));
123
123
  };
124
124
  var getSelection = exports.getSelection = function getSelection(tr, start, api) {
125
125
  if ((0, _toolbarFlagCheck.areToolbarFlagsEnabled)(Boolean(api === null || api === void 0 ? void 0 : api.toolbar))) {
126
- return newGetSelection(tr, start);
126
+ return newGetSelection(tr.doc, tr.selection.empty, start);
127
127
  }
128
128
  return oldGetSelection(tr, start);
129
129
  };
@@ -158,7 +158,7 @@ var setCursorPositionAtMovedNode = exports.setCursorPositionAtMovedNode = functi
158
158
  tr.setSelection(_selection);
159
159
  return tr;
160
160
  }
161
- var _getInlineNodePos3 = getInlineNodePos(tr, start, nodeSize),
161
+ var _getInlineNodePos3 = getInlineNodePos(tr.doc, start, nodeSize),
162
162
  inlineNodeEndPos = _getInlineNodePos3.inlineNodeEndPos;
163
163
  selection = new _state.TextSelection(tr.doc.resolve(inlineNodeEndPos));
164
164
  tr.setSelection(selection);
@@ -220,4 +220,52 @@ var rootTaskListDepth = exports.rootTaskListDepth = function rootTaskListDepth(t
220
220
  }
221
221
  }
222
222
  return depth;
223
+ };
224
+
225
+ /**
226
+ * This expands the given $from and $to resolved positions to the block boundaries
227
+ * spanning all nodes in the range up to the nearest common ancestor.
228
+ *
229
+ * @param $from The resolved start position
230
+ * @param $to The resolved end position
231
+ * @returns An object containing the expanded $from and $to resolved positions
232
+ */
233
+ var expandToBlockRange = exports.expandToBlockRange = function expandToBlockRange($from, $to) {
234
+ var range = $from.blockRange($to);
235
+ if (!range) {
236
+ return {
237
+ $from: $from,
238
+ $to: $to
239
+ };
240
+ }
241
+ return {
242
+ $from: $from.doc.resolve(range.start),
243
+ $to: $to.doc.resolve(range.end)
244
+ };
245
+ };
246
+
247
+ /**
248
+ * Collapses the given $from and $to resolved positions to the nearest valid selection range.
249
+ *
250
+ * Will retract the from and to positions to nearest inline positions at node boundaries only if needed.
251
+ *
252
+ * @param $from the resolved start position
253
+ * @param $to the resolved end position
254
+ * @returns An object containing the collapsed $from and $to resolved positions
255
+ */
256
+ var collapseToSelectionRange = exports.collapseToSelectionRange = function collapseToSelectionRange($from, $to) {
257
+ var resolvedRangeEnd = $from.doc.resolve($to.pos);
258
+
259
+ // Get the selections that would be made for the first and last node in the range
260
+ // We re-use the getSelection logic as it already handles various node types and edge cases
261
+ var firstNodeSelection = newGetSelection($from.doc, $from.pos === $to.pos, $from.pos);
262
+ var lastNodeSize = resolvedRangeEnd.nodeBefore ? resolvedRangeEnd.nodeBefore.nodeSize : 0;
263
+ var lastNodeStartPos = resolvedRangeEnd.pos - lastNodeSize;
264
+ var lastNodeSelection = newGetSelection($from.doc, $from.pos === $to.pos, lastNodeStartPos);
265
+
266
+ // Return a selection spanning from the start of the first node selection to the end of the last node selection
267
+ return {
268
+ $from: $from.doc.resolve(firstNodeSelection ? firstNodeSelection.from : $from.pos),
269
+ $to: $to.doc.resolve(lastNodeSelection ? lastNodeSelection.to : $to.pos)
270
+ };
223
271
  };
@@ -437,7 +437,10 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref2) {
437
437
 
438
438
  // Set selection to expanded selection range if it encompases the clicked drag handle
439
439
  if (range && isPosWithinRange(startPos, range) && isMultiNodeRange(range)) {
440
- tr.setSelection(_state.TextSelection.create(tr.doc, Math.min(selection.from, range.start), Math.max(selection.to, range.end)));
440
+ var collapsed = (0, _getSelection.collapseToSelectionRange)(tr.doc.resolve(range.start), tr.doc.resolve(range.end));
441
+
442
+ // Then create a selection from the start of the first node to the end of the last node
443
+ tr.setSelection(_state.TextSelection.create(tr.doc, Math.min(selection.from, collapsed ? collapsed.$from.pos : range.start), Math.max(selection.to, collapsed ? collapsed.$to.pos : range.end)));
441
444
  } else {
442
445
  // Select the clicked drag handle's node only
443
446
  tr = (0, _getSelection.selectNode)(tr, startPos, nodeType, api);
@@ -1,9 +1,19 @@
1
1
  import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
2
2
  import { DIRECTION } from '@atlaskit/editor-common/types';
3
- import { TextSelection } from '@atlaskit/editor-prosemirror/state';
4
3
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
5
4
  import { moveNode } from './move-node';
6
- import { getCurrentNodePosFromDragHandleSelection, getPosWhenMoveNodeDown, getPosWhenMoveNodeUp, getShouldMoveNode } from './utils/move-node-utils';
5
+ import { getCurrentNodePosFromDragHandleSelection } from './utils/move-node-utils';
6
+ const getSelectionToIndex = (fromIndex, $to, depth) => {
7
+ const toIndex = $to.index(depth);
8
+ const toIndexAfter = $to.indexAfter(depth);
9
+
10
+ // If $to is at the start of a node (indexAfter === index), don't include that node
11
+ // This occurs when the preserved selection is outside of inline positions at node boundaries
12
+ if (toIndexAfter === toIndex && toIndex > fromIndex) {
13
+ return toIndex - 1;
14
+ }
15
+ return toIndex;
16
+ };
7
17
  export const moveNodeWithBlockMenu = (api, direction) => {
8
18
  return ({
9
19
  tr
@@ -21,33 +31,29 @@ export const moveNodeWithBlockMenu = (api, direction) => {
21
31
  schema: tr.doc.type.schema,
22
32
  resolve: tr.doc.resolve.bind(tr.doc)
23
33
  });
24
- if (currentNodePos > -1) {
25
- const $currentNodePos = tr.doc.resolve(currentNodePos);
26
- const nodeAfterPos = $currentNodePos.posAtIndex($currentNodePos.index() + 1);
27
- const moveToPos = direction === DIRECTION.UP ? getPosWhenMoveNodeUp($currentNodePos, currentNodePos) : getPosWhenMoveNodeDown({
28
- $currentNodePos,
29
- nodeAfterPos,
30
- tr
31
- });
32
-
33
- // only move the node if the destination is at the same depth, not support moving a nested node to a parent node
34
- const shouldMoveNode = getShouldMoveNode({
35
- currentNodePos,
36
- moveToPos,
37
- tr
38
- });
39
- if (shouldMoveNode) {
40
- moveNode(api)(currentNodePos, moveToPos, INPUT_METHOD.BLOCK_MENU)({
34
+ const {
35
+ $from,
36
+ $to
37
+ } = selection;
38
+ const depth = tr.doc.resolve(currentNodePos).depth;
39
+ const fromIndex = $from.index(depth);
40
+ if (direction === DIRECTION.UP) {
41
+ if (fromIndex > 0) {
42
+ const moveToPos = $from.posAtIndex(fromIndex - 1, depth);
43
+ return moveNode(api)(currentNodePos, moveToPos, INPUT_METHOD.BLOCK_MENU)({
44
+ tr
45
+ });
46
+ }
47
+ } else {
48
+ // selectionToIndex is the index of the last node in the selection
49
+ const selectionToIndex = getSelectionToIndex(fromIndex, $to, depth);
50
+ // Adding 2 so we jump over the next node to the position after it
51
+ const moveToIndex = selectionToIndex + 2;
52
+ if (moveToIndex <= $to.node(depth).childCount) {
53
+ const moveToPos = $to.posAtIndex(moveToIndex, depth);
54
+ return moveNode(api)(currentNodePos, moveToPos, INPUT_METHOD.BLOCK_MENU)({
41
55
  tr
42
56
  });
43
- if (tr.selection.empty && expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true) && expValEqualsNoExposure('platform_editor_block_menu_empty_line', 'isEnabled', true)) {
44
- const nodeAtCurrentPos = tr.doc.nodeAt(currentNodePos);
45
- const nodeAfter = tr.doc.nodeAt(moveToPos);
46
- const isConsecutiveEmptyLineMove = (nodeAtCurrentPos === null || nodeAtCurrentPos === void 0 ? void 0 : nodeAtCurrentPos.content.size) === 0 && (nodeAfter === null || nodeAfter === void 0 ? void 0 : nodeAfter.content.size) === 0;
47
- const cursorPos = direction === DIRECTION.UP || direction === DIRECTION.DOWN && isConsecutiveEmptyLineMove ? moveToPos : moveToPos - 1;
48
- tr.setSelection(TextSelection.create(tr.doc, cursorPos));
49
- }
50
- tr.scrollIntoView();
51
57
  }
52
58
  }
53
59
  return tr;
@@ -6,7 +6,7 @@ import { transformSliceNestedExpandToExpand } from '@atlaskit/editor-common/tran
6
6
  import { DIRECTION } from '@atlaskit/editor-common/types';
7
7
  import { isEmptyParagraph } from '@atlaskit/editor-common/utils';
8
8
  import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
9
- import { Selection, NodeSelection } from '@atlaskit/editor-prosemirror/state';
9
+ import { NodeSelection, Selection } from '@atlaskit/editor-prosemirror/state';
10
10
  import { findChildrenByType, findParentNodeOfType, findParentNodeOfTypeClosestToPos } from '@atlaskit/editor-prosemirror/utils';
11
11
  import { findTable, isInTable, isTableSelected } from '@atlaskit/editor-tables/utils';
12
12
  import { fg } from '@atlaskit/platform-feature-flags';
@@ -19,7 +19,7 @@ import { selectNode, setCursorPositionAtMovedNode } from '../pm-plugins/utils/ge
19
19
  import { removeFromSource } from '../pm-plugins/utils/remove-from-source';
20
20
  import { getSelectedSlicePosition } from '../pm-plugins/utils/selection';
21
21
  import { getInsertLayoutStep, updateSelection } from '../pm-plugins/utils/update-selection';
22
- import { canMoveNodeToIndex, isInsideTable, transformSliceExpandToNestedExpand, transformFragmentExpandToNestedExpand } from '../pm-plugins/utils/validation';
22
+ import { canMoveNodeToIndex, isInsideTable, transformFragmentExpandToNestedExpand, transformSliceExpandToNestedExpand } from '../pm-plugins/utils/validation';
23
23
  import { getPosWhenMoveNodeDown, getPosWhenMoveNodeUp } from './utils/move-node-utils';
24
24
 
25
25
  /**
@@ -315,7 +315,7 @@ export const moveNodeViaShortcut = (api, direction, formatMessage) => {
315
315
  export const moveNode = api => (start, to, inputMethod = INPUT_METHOD.DRAG_AND_DROP, formatMessage) => ({
316
316
  tr
317
317
  }) => {
318
- var _convertedNodeSlice, _api$blockControls$co2, _api$accessibilityUti;
318
+ var _api$blockControls$sh, _convertedNodeSlice, _api$accessibilityUti;
319
319
  if (!api || start < 0 || to < 0) {
320
320
  return tr;
321
321
  }
@@ -333,8 +333,17 @@ export const moveNode = api => (start, to, inputMethod = INPUT_METHOD.DRAG_AND_D
333
333
  tr
334
334
  });
335
335
  }
336
- const slicePosition = getSelectedSlicePosition(start, tr, api);
337
- if (isMultiSelect) {
336
+ const preservedSelection = expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true) ? api === null || api === void 0 ? void 0 : (_api$blockControls$sh = api.blockControls.sharedState.currentState()) === null || _api$blockControls$sh === void 0 ? void 0 : _api$blockControls$sh.preservedSelection : undefined;
337
+ if (preservedSelection) {
338
+ const $from = tr.doc.resolve(Math.min(start, preservedSelection.from));
339
+ const expandedRange = $from.blockRange(preservedSelection.$to);
340
+ sliceFrom = expandedRange ? expandedRange.start : preservedSelection.from;
341
+ sliceTo = expandedRange ? expandedRange.end : preservedSelection.to;
342
+ const attributes = getMultiSelectAnalyticsAttributes(tr, sliceFrom, sliceTo);
343
+ hasSelectedMultipleNodes = attributes.hasSelectedMultipleNodes;
344
+ sourceNodeTypes = attributes.nodeTypes;
345
+ } else if (isMultiSelect) {
346
+ const slicePosition = getSelectedSlicePosition(start, tr, api);
338
347
  sliceFrom = slicePosition.from;
339
348
  sliceTo = slicePosition.to;
340
349
  const attributes = getMultiSelectAnalyticsAttributes(tr, sliceFrom, sliceTo);
@@ -414,13 +423,23 @@ export const moveNode = api => (start, to, inputMethod = INPUT_METHOD.DRAG_AND_D
414
423
  tr.insert(mappedTo, convertedNode);
415
424
  }
416
425
  const sliceSize = sliceTo - sliceFrom;
417
- tr = inputMethod === INPUT_METHOD.DRAG_AND_DROP ? setCursorPositionAtMovedNode(tr, mappedTo, api) : isMultiSelect ? (_api$blockControls$co2 = api === null || api === void 0 ? void 0 : api.blockControls.commands.setMultiSelectPositions(mappedTo, mappedTo + sliceSize)({
418
- tr
419
- })) !== null && _api$blockControls$co2 !== void 0 ? _api$blockControls$co2 : tr : selectNode(tr, mappedTo, handleNode.type.name, api);
426
+ if (inputMethod === INPUT_METHOD.DRAG_AND_DROP) {
427
+ tr = setCursorPositionAtMovedNode(tr, mappedTo, api);
428
+ } else if (preservedSelection) {
429
+ // do nothing here to allow the selection preservation plugin to handle mapping the selection
430
+ } else if (isMultiSelect) {
431
+ var _api$blockControls$co2;
432
+ tr = (_api$blockControls$co2 = api === null || api === void 0 ? void 0 : api.blockControls.commands.setMultiSelectPositions(mappedTo, mappedTo + sliceSize)({
433
+ tr
434
+ })) !== null && _api$blockControls$co2 !== void 0 ? _api$blockControls$co2 : tr;
435
+ } else {
436
+ tr = selectNode(tr, mappedTo, handleNode.type.name, api);
437
+ }
420
438
  const currMeta = tr.getMeta(key);
421
439
  tr.setMeta(key, {
422
440
  ...currMeta,
423
- nodeMoved: true
441
+ nodeMoved: true,
442
+ nodeMovedOffset: mappedTo - sliceFrom
424
443
  });
425
444
  api === null || api === void 0 ? void 0 : api.core.actions.focus();
426
445
  const $mappedTo = tr.doc.resolve(mappedTo);
@@ -9,7 +9,7 @@ export const getCurrentNodePosFromDragHandleSelection = ({
9
9
  resolve
10
10
  }) => {
11
11
  let currentNodePos = -1;
12
- if (selection.empty && expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true) && expValEqualsNoExposure('platform_editor_block_menu_empty_line', 'isEnabled', true)) {
12
+ if (selection.empty && expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true)) {
13
13
  currentNodePos = selection.$from.pos;
14
14
  }
15
15
  if (isTableSelected(selection)) {
@@ -53,7 +53,7 @@ export const getPosWhenMoveNodeDown = ({
53
53
  return -1;
54
54
  }
55
55
  const nodeAfter = tr.doc.nodeAt(nodeAfterPos);
56
- if (expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true) && expValEqualsNoExposure('platform_editor_block_menu_empty_line', 'isEnabled', true)) {
56
+ if (expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true)) {
57
57
  const nodeAtCurrentPos = tr.doc.nodeAt($currentNodePos.pos);
58
58
  // if move empty line down to another empty line, move to the position of the next empty line
59
59
  if ((nodeAtCurrentPos === null || nodeAtCurrentPos === void 0 ? void 0 : nodeAtCurrentPos.content.size) === 0 && nodeAtCurrentPos.type.name !== 'extension' && (nodeAfter === null || nodeAfter === void 0 ? void 0 : nodeAfter.content.size) === 0 && nodeAfter.type.name !== 'extension') {