@atlaskit/editor-plugin-block-controls 2.21.5 → 2.21.7

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 (50) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/cjs/blockControlsPlugin.js +0 -2
  3. package/dist/cjs/editor-commands/move-node.js +2 -5
  4. package/dist/cjs/editor-commands/move-to-layout.js +186 -60
  5. package/dist/cjs/pm-plugins/decorations-anchor.js +1 -6
  6. package/dist/cjs/pm-plugins/decorations-drag-handle.js +1 -4
  7. package/dist/cjs/pm-plugins/decorations-drop-target.js +35 -20
  8. package/dist/cjs/pm-plugins/main.js +2 -10
  9. package/dist/cjs/pm-plugins/utils/check-fragment.js +26 -0
  10. package/dist/cjs/pm-plugins/utils/drag-handle-positions.js +1 -4
  11. package/dist/cjs/pm-plugins/utils/fire-analytics.js +1 -4
  12. package/dist/cjs/pm-plugins/utils/inline-drop-target.js +1 -4
  13. package/dist/cjs/pm-plugins/utils/remove-from-source.js +15 -6
  14. package/dist/cjs/pm-plugins/utils/update-column-widths.js +1 -4
  15. package/dist/cjs/pm-plugins/utils/validation.js +28 -3
  16. package/dist/es2019/blockControlsPlugin.js +0 -2
  17. package/dist/es2019/editor-commands/move-node.js +2 -5
  18. package/dist/es2019/editor-commands/move-to-layout.js +171 -50
  19. package/dist/es2019/pm-plugins/decorations-anchor.js +1 -6
  20. package/dist/es2019/pm-plugins/decorations-drag-handle.js +1 -4
  21. package/dist/es2019/pm-plugins/decorations-drop-target.js +36 -21
  22. package/dist/es2019/pm-plugins/main.js +2 -10
  23. package/dist/es2019/pm-plugins/utils/check-fragment.js +20 -0
  24. package/dist/es2019/pm-plugins/utils/drag-handle-positions.js +1 -4
  25. package/dist/es2019/pm-plugins/utils/fire-analytics.js +1 -4
  26. package/dist/es2019/pm-plugins/utils/inline-drop-target.js +1 -4
  27. package/dist/es2019/pm-plugins/utils/remove-from-source.js +15 -6
  28. package/dist/es2019/pm-plugins/utils/update-column-widths.js +1 -4
  29. package/dist/es2019/pm-plugins/utils/validation.js +27 -3
  30. package/dist/esm/blockControlsPlugin.js +0 -2
  31. package/dist/esm/editor-commands/move-node.js +2 -5
  32. package/dist/esm/editor-commands/move-to-layout.js +187 -61
  33. package/dist/esm/pm-plugins/decorations-anchor.js +1 -6
  34. package/dist/esm/pm-plugins/decorations-drag-handle.js +1 -4
  35. package/dist/esm/pm-plugins/decorations-drop-target.js +36 -21
  36. package/dist/esm/pm-plugins/main.js +2 -10
  37. package/dist/esm/pm-plugins/utils/check-fragment.js +20 -0
  38. package/dist/esm/pm-plugins/utils/drag-handle-positions.js +1 -4
  39. package/dist/esm/pm-plugins/utils/fire-analytics.js +1 -4
  40. package/dist/esm/pm-plugins/utils/inline-drop-target.js +1 -4
  41. package/dist/esm/pm-plugins/utils/remove-from-source.js +15 -6
  42. package/dist/esm/pm-plugins/utils/update-column-widths.js +1 -4
  43. package/dist/esm/pm-plugins/utils/validation.js +27 -3
  44. package/dist/types/pm-plugins/utils/check-fragment.d.ts +9 -0
  45. package/dist/types/pm-plugins/utils/remove-from-source.d.ts +1 -1
  46. package/dist/types/pm-plugins/utils/validation.d.ts +1 -0
  47. package/dist/types-ts4.5/pm-plugins/utils/check-fragment.d.ts +9 -0
  48. package/dist/types-ts4.5/pm-plugins/utils/remove-from-source.d.ts +1 -1
  49. package/dist/types-ts4.5/pm-plugins/utils/validation.d.ts +1 -0
  50. package/package.json +5 -5
@@ -7,16 +7,25 @@ exports.removeFromSource = void 0;
7
7
  var _model = require("@atlaskit/editor-prosemirror/model");
8
8
  var _utils = require("@atlaskit/editor-prosemirror/utils");
9
9
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
10
+ var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
11
+ var _checkFragment = require("./check-fragment");
10
12
  var _consts = require("./consts");
11
13
  var _updateColumnWidths = require("./update-column-widths");
12
- var removeFromSource = exports.removeFromSource = function removeFromSource(tr, $from) {
13
- var sourceNode = $from.nodeAfter;
14
- var sourceParent = $from.parent;
15
- if (!sourceNode) {
14
+ var removeFromSource = exports.removeFromSource = function removeFromSource(tr, $from, to) {
15
+ var _sourceContent, _sourceContent2;
16
+ var sourceContent = $from.nodeAfter;
17
+ var isLayoutColumn = ((_sourceContent = sourceContent) === null || _sourceContent === void 0 ? void 0 : _sourceContent.type.name) === 'layoutColumn';
18
+ var sourceNodeEndPos = $from.pos + (((_sourceContent2 = sourceContent) === null || _sourceContent2 === void 0 ? void 0 : _sourceContent2.nodeSize) || 1);
19
+ if ((0, _experiments.editorExperiment)('platform_editor_element_drag_and_drop_multiselect', true)) {
20
+ sourceContent = tr.doc.slice($from.pos, to).content;
21
+ isLayoutColumn = (0, _checkFragment.isFragmentOfType)(sourceContent, 'layoutColumn');
22
+ sourceNodeEndPos = to === undefined ? $from.pos + sourceContent.size : to;
23
+ }
24
+ if (!sourceContent) {
16
25
  return tr;
17
26
  }
18
- var sourceNodeEndPos = $from.pos + sourceNode.nodeSize;
19
- if (sourceNode.type.name === 'layoutColumn') {
27
+ if (isLayoutColumn) {
28
+ var sourceParent = $from.parent;
20
29
  if (sourceParent.childCount === _consts.MIN_LAYOUT_COLUMN) {
21
30
  tr.delete($from.pos + 1, sourceNodeEndPos - 1);
22
31
 
@@ -5,10 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.updateColumnWidths = void 0;
7
7
  var _consts = require("../../ui/consts");
8
- var updateColumnWidths = exports.updateColumnWidths = function updateColumnWidths(tr, layoutNode, layoutNodePos, childCount
9
- // Ignored via go/ees005
10
- // eslint-disable-next-line @typescript-eslint/max-params
11
- ) {
8
+ var updateColumnWidths = exports.updateColumnWidths = function updateColumnWidths(tr, layoutNode, layoutNodePos, childCount) {
12
9
  var newColumnWidth = _consts.DEFAULT_COLUMN_DISTRIBUTIONS[childCount];
13
10
  if (newColumnWidth) {
14
11
  layoutNode.content.forEach(function (node, offset) {
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.canMoveNodeToIndex = canMoveNodeToIndex;
8
+ exports.canMoveSliceToIndex = canMoveSliceToIndex;
8
9
  exports.transformSliceExpandToNestedExpand = exports.transformExpandToNestedExpand = exports.memoizedTransformExpandToNestedExpand = exports.isNestedExpand = exports.isLayoutColumn = exports.isInsideTable = exports.isInSameLayout = exports.isExpand = exports.isDoc = void 0;
9
10
  var _memoizeOne = _interopRequireDefault(require("memoize-one"));
10
11
  var _nesting = require("@atlaskit/editor-common/nesting");
@@ -78,9 +79,6 @@ var memoizedTransformExpandToNestedExpand = exports.memoizedTransformExpandToNes
78
79
  return null;
79
80
  }
80
81
  });
81
-
82
- // Ignored via go/ees005
83
- // eslint-disable-next-line @typescript-eslint/max-params
84
82
  function canMoveNodeToIndex(destParent, indexIntoParent, srcNode, $destNodePos, destNode) {
85
83
  var srcNodeType = srcNode.type;
86
84
  var parentNodeType = destParent === null || destParent === void 0 ? void 0 : destParent.type.name;
@@ -129,4 +127,31 @@ function canMoveNodeToIndex(destParent, indexIntoParent, srcNode, $destNodePos,
129
127
  srcNodeType = srcNodeType.schema.nodes.expand;
130
128
  }
131
129
  return destParent.canReplaceWith(indexIntoParent, indexIntoParent, srcNodeType);
130
+ }
131
+ function canMoveSliceToIndex(slice, sliceFromPos, doc, destParent, indexIntoParent, $destNodePos, destNode) {
132
+ var canMoveNodes = true;
133
+ var nodesPos = [];
134
+ for (var i = 0; i < slice.content.childCount; i++) {
135
+ var node = slice.content.maybeChild(i);
136
+ if (i === 0) {
137
+ nodesPos[i] = sliceFromPos;
138
+ } else {
139
+ var _slice$content$maybeC;
140
+ nodesPos[i] = nodesPos[i - 1] + (((_slice$content$maybeC = slice.content.maybeChild(i - 1)) === null || _slice$content$maybeC === void 0 ? void 0 : _slice$content$maybeC.nodeSize) || 0);
141
+ }
142
+ if (node && node.isInline) {
143
+ // If the node is an inline node, we need to find the parent node
144
+ // as passing in them into canMoveNodeToIndex will return false
145
+ var $nodePos = doc.resolve(nodesPos[i]);
146
+ var parentNode = $nodePos.parent;
147
+ if (!parentNode || parentNode && !canMoveNodeToIndex(destParent, indexIntoParent, parentNode, $destNodePos, destNode)) {
148
+ canMoveNodes = false;
149
+ break;
150
+ }
151
+ } else if (node && !canMoveNodeToIndex(destParent, indexIntoParent, node, $destNodePos, destNode)) {
152
+ canMoveNodes = false;
153
+ break;
154
+ }
155
+ }
156
+ return canMoveNodes;
132
157
  }
@@ -20,8 +20,6 @@ export const blockControlsPlugin = ({
20
20
  commands: {
21
21
  moveNode: moveNode(api),
22
22
  moveToLayout: moveToLayout(api),
23
- // Ignored via go/ees005
24
- // eslint-disable-next-line @typescript-eslint/max-params
25
23
  showDragHandleAt: (pos, anchorName, nodeType, handleOptions) => ({
26
24
  tr
27
25
  }) => {
@@ -154,10 +154,7 @@ export const moveNodeViaShortcut = (api, direction, formatMessage) => {
154
154
  return false;
155
155
  };
156
156
  };
157
- export const moveNode = api => (start, to, inputMethod = INPUT_METHOD.DRAG_AND_DROP, formatMessage
158
- // Ignored via go/ees005
159
- // eslint-disable-next-line @typescript-eslint/max-params
160
- ) => ({
157
+ export const moveNode = api => (start, to, inputMethod = INPUT_METHOD.DRAG_AND_DROP, formatMessage) => ({
161
158
  tr
162
159
  }) => {
163
160
  const isMultiSelect = editorExperiment('platform_editor_element_drag_and_drop_multiselect', true, {
@@ -203,7 +200,7 @@ export const moveNode = api => (start, to, inputMethod = INPUT_METHOD.DRAG_AND_D
203
200
  if (sourceNode && isDragLayoutColumnToTopLevel($handlePos, $to)) {
204
201
  // need update after we support single column layout.
205
202
  const fragment = Fragment.from(sourceNode.content);
206
- removeFromSource(tr, $handlePos);
203
+ removeFromSource(tr, $handlePos, $handlePos.pos + sourceNode.nodeSize);
207
204
  const mappedTo = tr.mapping.map(to);
208
205
  tr.insert(mappedTo, fragment).setSelection(Selection.near(tr.doc.resolve(mappedTo))).scrollIntoView();
209
206
  return tr;
@@ -1,7 +1,9 @@
1
1
  import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
2
- import { Fragment } from '@atlaskit/editor-prosemirror/model';
2
+ import { Fragment, Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
3
  import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
4
4
  import { fg } from '@atlaskit/platform-feature-flags';
5
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
6
+ import { isFragmentOfType, containsNodeOfType } from '../pm-plugins/utils/check-fragment';
5
7
  import { maxLayoutColumnSupported } from '../pm-plugins/utils/consts';
6
8
  import { fireInsertLayoutAnalytics, attachMoveNodeAnalytics } from '../pm-plugins/utils/fire-analytics';
7
9
  import { removeFromSource } from '../pm-plugins/utils/remove-from-source';
@@ -33,17 +35,27 @@ const createNewLayout = (schema, layoutContents) => {
33
35
  }
34
36
  return null;
35
37
  };
36
- const moveToExistingLayout = (toLayout, toLayoutPos, sourceNode, from, to, tr, $originalFrom, $originalTo, api
37
- // Ignored via go/ees005
38
- // eslint-disable-next-line @typescript-eslint/max-params
39
- ) => {
38
+ const moveToExistingLayout = (toLayout, toLayoutPos, sourceContent, from, to, tr, $originalFrom, $originalTo, api) => {
40
39
  const isSameLayout = isInSameLayout($originalFrom, $originalTo);
40
+ let sourceContentEndPos = -1;
41
+ if (editorExperiment('platform_editor_element_drag_and_drop_multiselect', true)) {
42
+ if (sourceContent instanceof Fragment) {
43
+ sourceContentEndPos = from + sourceContent.size;
44
+ }
45
+ } else {
46
+ if (sourceContent instanceof PMNode) {
47
+ sourceContentEndPos = from + sourceContent.nodeSize;
48
+ }
49
+ }
50
+ if (sourceContentEndPos === -1) {
51
+ return tr;
52
+ }
41
53
  if (isSameLayout) {
42
54
  var _$originalFrom$nodeAf;
43
55
  // reorder columns
44
- tr.delete(from, from + sourceNode.nodeSize);
56
+ tr.delete(from, sourceContentEndPos);
45
57
  const mappedTo = tr.mapping.map(to);
46
- tr.insert(mappedTo, sourceNode);
58
+ tr.insert(mappedTo, sourceContent);
47
59
  if (!fg('platform_editor_advanced_layouts_post_fix_patch_1')) {
48
60
  tr.setSelection(new NodeSelection(tr.doc.resolve(mappedTo))).scrollIntoView();
49
61
  }
@@ -51,12 +63,12 @@ const moveToExistingLayout = (toLayout, toLayoutPos, sourceNode, from, to, tr, $
51
63
  } else if (toLayout.childCount < maxLayoutColumnSupported()) {
52
64
  var _$originalFrom$nodeAf2;
53
65
  if (fg('platform_editor_advanced_layouts_post_fix_patch_1')) {
54
- removeFromSource(tr, tr.doc.resolve(from));
55
- insertToDestinationNoWidthUpdate(tr, tr.mapping.map(to), sourceNode);
66
+ removeFromSource(tr, tr.doc.resolve(from), sourceContentEndPos);
67
+ insertToDestinationNoWidthUpdate(tr, tr.mapping.map(to), sourceContent);
56
68
  } else {
57
- insertToDestination(tr, to, sourceNode, toLayout, toLayoutPos);
69
+ insertToDestination(tr, to, sourceContent, toLayout, toLayoutPos);
58
70
  const mappedFrom = tr.mapping.map(from);
59
- removeFromSource(tr, tr.doc.resolve(mappedFrom));
71
+ removeFromSource(tr, tr.doc.resolve(mappedFrom), tr.mapping.map(sourceContentEndPos));
60
72
  }
61
73
  attachMoveNodeAnalytics(tr, INPUT_METHOD.DRAG_AND_DROP, $originalFrom.depth, ((_$originalFrom$nodeAf2 = $originalFrom.nodeAfter) === null || _$originalFrom$nodeAf2 === void 0 ? void 0 : _$originalFrom$nodeAf2.type.name) || '', 1, 'layoutSection', false, api);
62
74
  }
@@ -71,30 +83,57 @@ const moveToExistingLayout = (toLayout, toLayoutPos, sourceNode, from, to, tr, $
71
83
  * @param sourceNode
72
84
  * @returns
73
85
  */
74
- const insertToDestinationNoWidthUpdate = (tr, to, sourceNode) => {
86
+ const insertToDestinationNoWidthUpdate = (tr, to, sourceContent) => {
75
87
  const {
76
88
  layoutColumn
77
89
  } = tr.doc.type.schema.nodes || {};
78
- const content = layoutColumn.createChecked({
79
- width: 0
80
- }, sourceNode.type.name === 'layoutColumn' ? sourceNode.content : sourceNode);
81
- tr.insert(to, content);
90
+ let content = null;
91
+ if (editorExperiment('platform_editor_element_drag_and_drop_multiselect', true)) {
92
+ if (sourceContent instanceof Fragment) {
93
+ var _sourceFragment$first;
94
+ const sourceFragment = sourceContent;
95
+ content = layoutColumn.createChecked({
96
+ width: 0
97
+ }, isFragmentOfType(sourceFragment, 'layoutColumn') ? (_sourceFragment$first = sourceFragment.firstChild) === null || _sourceFragment$first === void 0 ? void 0 : _sourceFragment$first.content : sourceFragment);
98
+ }
99
+ } else {
100
+ if (sourceContent instanceof PMNode) {
101
+ const sourceNode = sourceContent;
102
+ content = layoutColumn.createChecked({
103
+ width: 0
104
+ }, sourceNode.type.name === 'layoutColumn' ? sourceNode.content : sourceNode);
105
+ }
106
+ }
107
+ if (content) {
108
+ tr.insert(to, content);
109
+ }
82
110
  return tr;
83
111
  };
84
- const insertToDestination = (tr, to, sourceNode, toLayout, toLayoutPos
85
- // Ignored via go/ees005
86
- // eslint-disable-next-line @typescript-eslint/max-params
87
- ) => {
112
+ const insertToDestination = (tr, to, sourceContent, toLayout, toLayoutPos) => {
88
113
  const {
89
114
  newColumnWidth
90
115
  } = updateColumnWidths(tr, toLayout, toLayoutPos, toLayout.childCount + 1) || {};
91
116
  const {
92
117
  layoutColumn
93
118
  } = tr.doc.type.schema.nodes || {};
94
- const content = layoutColumn.createChecked({
95
- width: newColumnWidth
96
- }, sourceNode.type.name === 'layoutColumn' ? sourceNode.content : sourceNode);
97
- tr.insert(to, content);
119
+ let content = null;
120
+ if (editorExperiment('platform_editor_element_drag_and_drop_multiselect', true)) {
121
+ if (sourceContent instanceof Fragment) {
122
+ var _sourceContent$firstC;
123
+ content = layoutColumn.createChecked({
124
+ width: newColumnWidth
125
+ }, isFragmentOfType(sourceContent, 'layoutColumn') ? (_sourceContent$firstC = sourceContent.firstChild) === null || _sourceContent$firstC === void 0 ? void 0 : _sourceContent$firstC.content : sourceContent);
126
+ }
127
+ } else {
128
+ if (sourceContent instanceof PMNode) {
129
+ content = layoutColumn.createChecked({
130
+ width: newColumnWidth
131
+ }, sourceContent.type.name === 'layoutColumn' ? sourceContent.content : sourceContent);
132
+ }
133
+ }
134
+ if (content) {
135
+ tr.insert(to, content);
136
+ }
98
137
  if (!fg('platform_editor_advanced_layouts_post_fix_patch_1')) {
99
138
  tr.setSelection(new NodeSelection(tr.doc.resolve(to))).scrollIntoView();
100
139
  }
@@ -126,20 +165,103 @@ const canMoveToLayout = (from, to, tr) => {
126
165
  return;
127
166
  }
128
167
  const $from = tr.doc.resolve(from);
168
+ const isMultiSelect = editorExperiment('platform_editor_element_drag_and_drop_multiselect', true, {
169
+ exposure: true
170
+ });
129
171
 
130
172
  // invalid from position or dragging a layout
131
173
  if (!$from.nodeAfter || $from.nodeAfter.type === layoutSection) {
132
174
  return;
133
175
  }
176
+ let sourceContent = $from.nodeAfter;
177
+ let sourceFrom = from;
178
+ let sourceTo = from + sourceContent.nodeSize;
179
+ if (isMultiSelect) {
180
+ var _tr$selection$$from$n;
181
+ // Selection often starts from the content of the node (e.g. to show text selection properly),
182
+ // so we need to trace back to the start of the node instead
183
+ const contentStartPos = tr.selection.$from.nodeAfter && ((_tr$selection$$from$n = tr.selection.$from.nodeAfter) === null || _tr$selection$$from$n === void 0 ? void 0 : _tr$selection$$from$n.type.name) !== 'text' ? tr.selection.$from.pos : tr.selection.$from.before();
184
+
185
+ // If the handle position sits within the Editor selection, we will move all nodes that sit in that selection
186
+ // handle position is the same as `from` position
187
+ const useSelection = from >= contentStartPos && from <= tr.selection.to;
188
+ if (useSelection) {
189
+ sourceFrom = contentStartPos;
190
+ sourceTo = tr.selection.to;
191
+ sourceContent = tr.doc.slice(sourceFrom, sourceTo).content;
192
+
193
+ // TODO: this might become expensive for large content, consider removing it if check has been done beforehand
194
+ if (containsNodeOfType(sourceContent, 'layoutSection')) {
195
+ return;
196
+ }
197
+ } else {
198
+ sourceContent = Fragment.from($from.nodeAfter);
199
+ }
200
+ }
134
201
  const toNode = $to.nodeAfter;
135
- const fromNode = $from.nodeAfter;
136
202
  return {
137
203
  toNode,
138
- fromNode,
139
- $from,
140
- $to
204
+ $to,
205
+ sourceContent,
206
+ $sourceFrom: tr.doc.resolve(sourceFrom),
207
+ sourceTo
141
208
  };
142
209
  };
210
+ const removeBreakoutMarks = (tr, $from, to) => {
211
+ let fromContentWithoutBreakout = $from.nodeAfter;
212
+ const {
213
+ breakout
214
+ } = tr.doc.type.schema.marks || {};
215
+ if (editorExperiment('platform_editor_element_drag_and_drop_multiselect', true)) {
216
+ tr.doc.nodesBetween($from.pos, to, (node, pos, parent) => {
217
+ // breakout doesn't exist on nested nodes
218
+ if ((parent === null || parent === void 0 ? void 0 : parent.type.name) === 'doc' && node.marks.some(m => m.type === breakout)) {
219
+ tr.removeNodeMark(pos, breakout);
220
+ }
221
+
222
+ // descending is not needed as breakout doesn't exist on nested nodes
223
+ return false;
224
+ });
225
+ // resolve again the source content after node updated (remove breakout marks)
226
+ fromContentWithoutBreakout = tr.doc.slice($from.pos, to).content;
227
+ } else {
228
+ if (breakout && $from.nodeAfter && $from.nodeAfter.marks.some(m => m.type === breakout)) {
229
+ tr.removeNodeMark($from.pos, breakout);
230
+ // resolve again the source node after node updated (remove breakout marks)
231
+ fromContentWithoutBreakout = tr.doc.resolve($from.pos).nodeAfter;
232
+ }
233
+ }
234
+ return fromContentWithoutBreakout;
235
+ };
236
+ const getBreakoutMode = (content, breakout) => {
237
+ if (editorExperiment('platform_editor_element_drag_and_drop_multiselect', true)) {
238
+ if (content instanceof PMNode) {
239
+ var _content$marks$find;
240
+ return (_content$marks$find = content.marks.find(m => m.type === breakout)) === null || _content$marks$find === void 0 ? void 0 : _content$marks$find.attrs;
241
+ } else if (content instanceof Fragment) {
242
+ // Find the first breakout mode in the fragment
243
+ let firstBreakoutMode;
244
+ for (let i = 0; i < content.childCount; i++) {
245
+ const child = content.child(i);
246
+ const breakoutMark = child.marks.find(m => m.type === breakout);
247
+ if (breakoutMark) {
248
+ firstBreakoutMode = breakoutMark.attrs.mode;
249
+ break;
250
+ }
251
+ }
252
+ return firstBreakoutMode;
253
+ }
254
+ } else {
255
+ // Without multi-select support, we can assume source content is of type PMNode
256
+ if (content instanceof PMNode) {
257
+ var _content$marks$find2;
258
+ return (_content$marks$find2 = content.marks.find(m => m.type === breakout)) === null || _content$marks$find2 === void 0 ? void 0 : _content$marks$find2.attrs.mode;
259
+ }
260
+ }
261
+ };
262
+
263
+ // TODO: As part of platform_editor_element_drag_and_drop_multiselect clean up,
264
+ // source content variable that has type of `PMNode | Fragment` should be updated to `Fragment` only
143
265
  export const moveToLayout = api => (from, to, options) => ({
144
266
  tr
145
267
  }) => {
@@ -149,9 +271,10 @@ export const moveToLayout = api => (from, to, options) => ({
149
271
  }
150
272
  const {
151
273
  toNode,
152
- fromNode,
153
- $from,
154
- $to
274
+ $to,
275
+ sourceContent,
276
+ $sourceFrom,
277
+ sourceTo
155
278
  } = canMove;
156
279
  const {
157
280
  layoutSection,
@@ -160,32 +283,24 @@ export const moveToLayout = api => (from, to, options) => ({
160
283
  const {
161
284
  breakout
162
285
  } = tr.doc.type.schema.marks || {};
163
- let fromNodeWithoutBreakout = fromNode;
164
- const getBreakoutMode = node => {
165
- var _node$marks$find;
166
- return (_node$marks$find = node.marks.find(m => m.type === breakout)) === null || _node$marks$find === void 0 ? void 0 : _node$marks$find.attrs.mode;
167
- };
286
+
168
287
  // get breakout mode from destination node,
169
288
  // if not found, get from source node,
170
- const breakoutMode = getBreakoutMode(toNode) || getBreakoutMode(fromNode);
289
+ const breakoutMode = getBreakoutMode(toNode, breakout) || getBreakoutMode(sourceContent, breakout);
171
290
 
172
- // remove breakout from node;
173
- if (breakout && $from.nodeAfter && $from.nodeAfter.marks.some(m => m.type === breakout)) {
174
- tr.removeNodeMark(from, breakout);
175
- // resolve again the source node after node updated (remove breakout marks)
176
- fromNodeWithoutBreakout = tr.doc.resolve(from).nodeAfter;
177
- }
178
- if (!fromNodeWithoutBreakout) {
291
+ // remove breakout from source content
292
+ let fromContentWithoutBreakout = removeBreakoutMarks(tr, $sourceFrom, sourceTo);
293
+ if (!fromContentWithoutBreakout) {
179
294
  return tr;
180
295
  }
181
296
  if (toNode.type === layoutSection) {
182
297
  const toPos = options !== null && options !== void 0 && options.moveToEnd ? to + toNode.nodeSize - 1 : to + 1;
183
- return moveToExistingLayout(toNode, to, fromNodeWithoutBreakout, from, toPos, tr, $from, $to, api);
298
+ return moveToExistingLayout(toNode, to, fromContentWithoutBreakout, $sourceFrom.pos, toPos, tr, $sourceFrom, $to, api);
184
299
  } else if (toNode.type === layoutColumn) {
185
300
  const toLayout = $to.parent;
186
301
  const toLayoutPos = to - $to.parentOffset - 1;
187
302
  const toPos = options !== null && options !== void 0 && options.moveToEnd ? to + toNode.nodeSize : to;
188
- return moveToExistingLayout(toLayout, toLayoutPos, fromNodeWithoutBreakout, from, toPos, tr, $from, $to, api);
303
+ return moveToExistingLayout(toLayout, toLayoutPos, fromContentWithoutBreakout, $sourceFrom.pos, toPos, tr, $sourceFrom, $to, api);
189
304
  } else {
190
305
  let toNodeWithoutBreakout = toNode;
191
306
 
@@ -195,13 +310,19 @@ export const moveToLayout = api => (from, to, options) => ({
195
310
  // resolve again the source node after node updated (remove breakout marks)
196
311
  toNodeWithoutBreakout = tr.doc.resolve(to).nodeAfter || toNode;
197
312
  }
198
- if (fromNodeWithoutBreakout.type.name === 'layoutColumn') {
199
- fromNodeWithoutBreakout = fromNodeWithoutBreakout.content;
313
+ if (editorExperiment('platform_editor_element_drag_and_drop_multiselect', true)) {
314
+ if (isFragmentOfType(fromContentWithoutBreakout, 'layoutColumn') && fromContentWithoutBreakout.firstChild) {
315
+ fromContentWithoutBreakout = fromContentWithoutBreakout.firstChild.content;
316
+ }
317
+ } else {
318
+ if (fromContentWithoutBreakout instanceof PMNode && fromContentWithoutBreakout.type.name === 'layoutColumn') {
319
+ fromContentWithoutBreakout = fromContentWithoutBreakout.content;
320
+ }
200
321
  }
201
- const layoutContents = options !== null && options !== void 0 && options.moveToEnd ? [toNodeWithoutBreakout, fromNodeWithoutBreakout] : [fromNodeWithoutBreakout, toNodeWithoutBreakout];
322
+ const layoutContents = options !== null && options !== void 0 && options.moveToEnd ? [toNodeWithoutBreakout, fromContentWithoutBreakout] : [fromContentWithoutBreakout, toNodeWithoutBreakout];
202
323
  const newLayout = createNewLayout(tr.doc.type.schema, layoutContents);
203
324
  if (newLayout) {
204
- tr = removeFromSource(tr, $from);
325
+ tr = removeFromSource(tr, $sourceFrom, sourceTo);
205
326
  const mappedTo = tr.mapping.map(to);
206
327
  tr.delete(mappedTo, mappedTo + toNodeWithoutBreakout.nodeSize).insert(mappedTo, newLayout);
207
328
  if (!fg('platform_editor_advanced_layouts_post_fix_patch_1')) {
@@ -19,10 +19,7 @@ export const shouldDescendIntoNode = node => {
19
19
  }
20
20
  return !IGNORE_NODE_DESCENDANTS.includes(node.type.name);
21
21
  };
22
- const shouldIgnoreNode = (node, ignore_nodes, depth, parent
23
- // Ignored via go/ees005
24
- // eslint-disable-next-line @typescript-eslint/max-params
25
- ) => {
22
+ const shouldIgnoreNode = (node, ignore_nodes, depth, parent) => {
26
23
  const isEmbedCard = node.type.name === 'embedCard';
27
24
  const isMediaSingle = node.type.name === 'mediaSingle';
28
25
  const isFirstTableRow = (parent === null || parent === void 0 ? void 0 : parent.type.name) === 'table' && depth === 1 && node === parent.firstChild && 'tableRow' === node.type.name && editorExperiment('advanced_layouts', true);
@@ -64,8 +61,6 @@ export const nodeDecorations = (newState, from, to) => {
64
61
  const docFrom = from === undefined || from < 0 ? 0 : from;
65
62
  const docTo = to === undefined || to > newState.doc.nodeSize - 2 ? newState.doc.nodeSize - 2 : to;
66
63
  const ignore_nodes = editorExperiment('advanced_layouts', true) ? IGNORE_NODES_NEXT : IGNORE_NODES;
67
- // Ignored via go/ees005
68
- // eslint-disable-next-line @typescript-eslint/max-params
69
64
  newState.doc.nodesBetween(docFrom, docTo, (node, pos, parent, index) => {
70
65
  let depth = 0;
71
66
  let anchorName;
@@ -20,10 +20,7 @@ export const emptyParagraphNodeDecorations = () => {
20
20
  export const findHandleDec = (decorations, from, to) => {
21
21
  return decorations.find(from, to, spec => spec.type === TYPE_HANDLE_DEC);
22
22
  };
23
- export const dragHandleDecoration = (api, formatMessage, pos, anchorName, nodeType, nodeViewPortalProviderAPI, handleOptions
24
- // Ignored via go/ees005
25
- // eslint-disable-next-line @typescript-eslint/max-params
26
- ) => {
23
+ export const dragHandleDecoration = (api, formatMessage, pos, anchorName, nodeType, nodeViewPortalProviderAPI, handleOptions) => {
27
24
  unmountDecorations(nodeViewPortalProviderAPI, 'data-blocks-drag-handle-container', 'data-blocks-drag-handle-key');
28
25
  let unbind;
29
26
  const key = uuid();
@@ -10,7 +10,7 @@ import { DropTarget, EDITOR_BLOCK_CONTROLS_DROP_INDICATOR_GAP, EDITOR_BLOCK_CONT
10
10
  import { DropTargetLayout } from '../ui/drop-target-layout';
11
11
  import { getNestedDepth, TYPE_DROP_TARGET_DEC, unmountDecorations } from './decorations-common';
12
12
  import { maxLayoutColumnSupported } from './utils/consts';
13
- import { canMoveNodeToIndex, isInSameLayout } from './utils/validation';
13
+ import { canMoveNodeToIndex, canMoveSliceToIndex, isInSameLayout } from './utils/validation';
14
14
  const IGNORE_NODES = ['tableCell', 'tableHeader', 'tableRow', 'layoutColumn', 'listItem', 'caption'];
15
15
  const PARENT_WITH_END_DROP_TARGET = ['tableCell', 'tableHeader', 'panel', 'layoutColumn', 'expand', 'nestedExpand', 'bodiedExtension'];
16
16
  const DISABLE_CHILD_DROP_TARGET = ['orderedList', 'bulletList'];
@@ -71,10 +71,7 @@ const getGapAndOffset = (prevNode, nextNode, parentNode) => {
71
71
  export const findDropTargetDecs = (decorations, from, to) => {
72
72
  return decorations.find(from, to, spec => spec.type === TYPE_DROP_TARGET_DEC);
73
73
  };
74
- export const createDropTargetDecoration = (pos, props, nodeViewPortalProviderAPI, side, anchorRectCache, isSameLayout
75
- // Ignored via go/ees005
76
- // eslint-disable-next-line @typescript-eslint/max-params
77
- ) => {
74
+ export const createDropTargetDecoration = (pos, props, nodeViewPortalProviderAPI, side, anchorRectCache, isSameLayout) => {
78
75
  const key = uuid();
79
76
  return Decoration.widget(pos, (_, getPosUnsafe) => {
80
77
  const getPos = () => {
@@ -116,10 +113,7 @@ export const createDropTargetDecoration = (pos, props, nodeViewPortalProviderAPI
116
113
  side
117
114
  });
118
115
  };
119
- export const createLayoutDropTargetDecoration = (pos, props, nodeViewPortalProviderAPI, anchorRectCache
120
- // Ignored via go/ees005
121
- // eslint-disable-next-line @typescript-eslint/max-params
122
- ) => {
116
+ export const createLayoutDropTargetDecoration = (pos, props, nodeViewPortalProviderAPI, anchorRectCache) => {
123
117
  const key = uuid();
124
118
  return Decoration.widget(pos, (_, getPosUnsafe) => {
125
119
  const getPos = () => {
@@ -151,10 +145,7 @@ export const createLayoutDropTargetDecoration = (pos, props, nodeViewPortalProvi
151
145
  type: TYPE_DROP_TARGET_DEC
152
146
  });
153
147
  };
154
- export const dropTargetDecorations = (newState, api, formatMessage, nodeViewPortalProviderAPI, activeNode, anchorRectCache, from, to
155
- // Ignored via go/ees005
156
- // eslint-disable-next-line @typescript-eslint/max-params
157
- ) => {
148
+ export const dropTargetDecorations = (newState, api, formatMessage, nodeViewPortalProviderAPI, activeNode, anchorRectCache, from, to) => {
158
149
  unmountDecorations(nodeViewPortalProviderAPI, 'data-blocks-drop-target-container', 'data-blocks-drop-target-key');
159
150
  const decs = [];
160
151
  const POS_END_OF_DOC = newState.doc.nodeSize - 2;
@@ -163,6 +154,9 @@ export const dropTargetDecorations = (newState, api, formatMessage, nodeViewPort
163
154
  const activeNodePos = activeNode === null || activeNode === void 0 ? void 0 : activeNode.pos;
164
155
  const $activeNodePos = typeof activeNodePos === 'number' && newState.doc.resolve(activeNodePos);
165
156
  const activePMNode = $activeNodePos && $activeNodePos.nodeAfter;
157
+ const isMultiSelect = editorExperiment('platform_editor_element_drag_and_drop_multiselect', true, {
158
+ exposure: true
159
+ });
166
160
  anchorRectCache === null || anchorRectCache === void 0 ? void 0 : anchorRectCache.clear();
167
161
  const prevNodeStack = [];
168
162
  const popNodeStack = depth => {
@@ -178,9 +172,6 @@ export const dropTargetDecorations = (newState, api, formatMessage, nodeViewPort
178
172
  prevNodeStack.push(node);
179
173
  };
180
174
  const isAdvancedLayoutsPreRelease2 = editorExperiment('advanced_layouts', true);
181
-
182
- // Ignored via go/ees005
183
- // eslint-disable-next-line @typescript-eslint/max-params
184
175
  newState.doc.nodesBetween(docFrom, docTo, (node, pos, parent, index) => {
185
176
  let depth = 0;
186
177
  // drop target deco at the end position
@@ -212,12 +203,36 @@ export const dropTargetDecorations = (newState, api, formatMessage, nodeViewPort
212
203
  pushNodeStack(node, depth);
213
204
  return shouldDescend(node); //skip over, don't consider it a valid depth
214
205
  }
215
- const canDrop = activePMNode && canMoveNodeToIndex(parent, index, activePMNode, $pos, node);
216
206
 
217
- //NOTE: This will block drop targets showing for nodes that are valid after transformation (i.e. expand -> nestedExpand)
218
- if (!canDrop) {
219
- pushNodeStack(node, depth);
220
- return false; //not valid pos, so nested not valid either
207
+ // When multi select is on, validate all the nodes in the selection instead of just the handle node
208
+ if (isMultiSelect) {
209
+ const selection = newState.selection;
210
+ const selectionFrom = selection.$from.pos;
211
+ const selectionTo = selection.$to.pos;
212
+ const handleInsideSelection = activeNodePos !== undefined && activeNodePos >= selectionFrom - 1 && activeNodePos <= selectionTo;
213
+ const selectionSlice = newState.doc.slice(selectionFrom, selectionTo, false);
214
+ const selectionSliceChildCount = selectionSlice.content.childCount;
215
+ let canDropSingleNode = true;
216
+ let canDropMultipleNodes = true;
217
+
218
+ // when there is only one node in the slice, use the same logic as when multi select is not on
219
+ if (selectionSliceChildCount > 1 && handleInsideSelection) {
220
+ canDropMultipleNodes = canMoveSliceToIndex(selectionSlice, selectionFrom, newState.doc, parent, index, $pos);
221
+ } else {
222
+ canDropSingleNode = !!(activePMNode && canMoveNodeToIndex(parent, index, activePMNode, $pos, node));
223
+ }
224
+ if (!canDropMultipleNodes || !canDropSingleNode) {
225
+ pushNodeStack(node, depth);
226
+ return false; //not valid pos, so nested not valid either
227
+ }
228
+ } else {
229
+ const canDrop = activePMNode && canMoveNodeToIndex(parent, index, activePMNode, $pos, node);
230
+
231
+ //NOTE: This will block drop targets showing for nodes that are valid after transformation (i.e. expand -> nestedExpand)
232
+ if (!canDrop) {
233
+ pushNodeStack(node, depth);
234
+ return false; //not valid pos, so nested not valid either
235
+ }
221
236
  }
222
237
  if (parent.lastChild === node && !isEmptyParagraph(node) && PARENT_WITH_END_DROP_TARGET.includes(parent.type.name)) {
223
238
  endPos = pos + node.nodeSize;
@@ -113,10 +113,7 @@ const initialState = {
113
113
  isDocSizeLimitEnabled: null,
114
114
  isPMDragging: false
115
115
  };
116
- export const newApply = (api, formatMessage, tr, currentState, newState, flags, nodeViewPortalProviderAPI, anchorRectCache
117
- // Ignored via go/ees005
118
- // eslint-disable-next-line @typescript-eslint/max-params
119
- ) => {
116
+ export const newApply = (api, formatMessage, tr, currentState, newState, flags, nodeViewPortalProviderAPI, anchorRectCache) => {
120
117
  var _meta$activeNode, _activeNode, _activeNode2, _meta$activeNode$hand, _meta$isDragging, _meta$isDragging2, _meta$editorHeight, _meta$editorWidthLeft, _meta$editorWidthRigh, _meta$isPMDragging;
121
118
  let {
122
119
  activeNode,
@@ -251,10 +248,7 @@ export const newApply = (api, formatMessage, tr, currentState, newState, flags,
251
248
  isPMDragging: (_meta$isPMDragging = meta === null || meta === void 0 ? void 0 : meta.isPMDragging) !== null && _meta$isPMDragging !== void 0 ? _meta$isPMDragging : isPMDragging
252
249
  };
253
250
  };
254
- export const oldApply = (api, formatMessage, tr, currentState, oldState, newState, flags, nodeViewPortalProviderAPI, anchorRectCache
255
- // Ignored via go/ees005
256
- // eslint-disable-next-line @typescript-eslint/max-params
257
- ) => {
251
+ export const oldApply = (api, formatMessage, tr, currentState, oldState, newState, flags, nodeViewPortalProviderAPI, anchorRectCache) => {
258
252
  var _meta$activeNode2, _meta$activeNode$hand2, _meta$activeNode8, _meta$isDragging4, _meta$editorHeight2, _meta$editorWidthLeft2, _meta$editorWidthRigh2, _meta$isPMDragging2;
259
253
  const {
260
254
  isNestedEnabled
@@ -420,8 +414,6 @@ export const createPlugin = (api, getIntl, nodeViewPortalProviderAPI) => {
420
414
  init() {
421
415
  return initialState;
422
416
  },
423
- // Ignored via go/ees005
424
- // eslint-disable-next-line @typescript-eslint/max-params
425
417
  apply(tr, currentState, oldState, newState) {
426
418
  if (isNestedEnabled) {
427
419
  return newApply(api, formatMessage, tr, currentState, newState, flags, nodeViewPortalProviderAPI, anchorRectCache);
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Check if the fragment has only one node of the specified type
3
+ */
4
+ export const isFragmentOfType = (fragment, type) => {
5
+ var _fragment$firstChild;
6
+ return fragment.childCount === 1 && ((_fragment$firstChild = fragment.firstChild) === null || _fragment$firstChild === void 0 ? void 0 : _fragment$firstChild.type.name) === type;
7
+ };
8
+
9
+ /**
10
+ * Check if the fragment contains at least a node of the specified type
11
+ */
12
+ export const containsNodeOfType = (fragment, type) => {
13
+ for (let i = 0; i < fragment.childCount; i++) {
14
+ const child = fragment.child(i);
15
+ if (child.type.name === type) {
16
+ return true;
17
+ }
18
+ }
19
+ return false;
20
+ };
@@ -34,10 +34,7 @@ export const getTopPosition = (dom, type) => {
34
34
  return `${dom.offsetTop}px`;
35
35
  }
36
36
  };
37
- export const getLeftPosition = (dom, type, innerContainer, macroInteractionUpdates, parentType
38
- // Ignored via go/ees005
39
- // eslint-disable-next-line @typescript-eslint/max-params
40
- ) => {
37
+ export const getLeftPosition = (dom, type, innerContainer, macroInteractionUpdates, parentType) => {
41
38
  if (!dom) {
42
39
  return 'auto';
43
40
  }