@atlaskit/editor-plugin-block-controls 2.19.2 → 2.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @atlaskit/editor-plugin-block-controls
2
2
 
3
+ ## 2.21.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#102469](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/102469)
8
+ [`bb834fe685b5a`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/bb834fe685b5a) -
9
+ ED-25924 makes drop hints appear more consistent
10
+
11
+ ## 2.20.0
12
+
13
+ ### Minor Changes
14
+
15
+ - [#102564](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/102564)
16
+ [`078168e470e88`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/078168e470e88) -
17
+ [ux] Add initial multi-select
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies
22
+
3
23
  ## 2.19.2
4
24
 
5
25
  ### Patch Changes
@@ -165,37 +165,55 @@ var moveNode = exports.moveNode = function moveNode(api) {
165
165
  // eslint-disable-next-line @typescript-eslint/max-params
166
166
  = arguments.length > 3 ? arguments[3] : undefined;
167
167
  return function (_ref4) {
168
- var _node$nodeSize;
169
168
  var tr = _ref4.tr;
170
- var node = tr.doc.nodeAt(start);
171
- var resolvedNode = tr.doc.resolve(start);
172
- if (!node) {
169
+ var isMultiSelect = (0, _experiments.editorExperiment)('platform_editor_element_drag_and_drop_multiselect', true, {
170
+ exposure: true
171
+ });
172
+ var selection = tr.selection;
173
+ var selectionFrom = selection.$from.pos;
174
+ var selectionTo = selection.$to.pos;
175
+ var handleNode = tr.doc.nodeAt(start);
176
+ if (!handleNode) {
173
177
  return tr;
174
178
  }
179
+ var sliceFrom = start;
180
+ var sliceTo;
181
+ if (isMultiSelect) {
182
+ var _handleNode$nodeSize;
183
+ // //If the handle position sits within the Editor selection, we will move all nodes that sit in that selection
184
+ var useSelection = sliceFrom >= selectionFrom - 1 && sliceFrom <= selectionTo;
185
+ sliceFrom = useSelection ? selectionFrom : start;
186
+ var handleSize = (_handleNode$nodeSize = handleNode === null || handleNode === void 0 ? void 0 : handleNode.nodeSize) !== null && _handleNode$nodeSize !== void 0 ? _handleNode$nodeSize : 1;
187
+ var handleEnd = sliceFrom + handleSize;
188
+ sliceTo = useSelection ? selectionTo : handleEnd;
189
+ } else {
190
+ var _handleNode$nodeSize2;
191
+ var size = (_handleNode$nodeSize2 = handleNode === null || handleNode === void 0 ? void 0 : handleNode.nodeSize) !== null && _handleNode$nodeSize2 !== void 0 ? _handleNode$nodeSize2 : 1;
192
+ sliceTo = sliceFrom + size;
193
+ }
175
194
  var _tr$doc$type$schema$n = tr.doc.type.schema.nodes,
176
195
  expand = _tr$doc$type$schema$n.expand,
177
196
  nestedExpand = _tr$doc$type$schema$n.nestedExpand;
178
- var size = (_node$nodeSize = node === null || node === void 0 ? void 0 : node.nodeSize) !== null && _node$nodeSize !== void 0 ? _node$nodeSize : 1;
179
- var end = start + size;
180
- var $from = tr.doc.resolve(start);
181
197
  var $to = tr.doc.resolve(to);
198
+ var $handlePos = tr.doc.resolve(start);
182
199
  var mappedTo;
183
200
  if ((0, _experiments.editorExperiment)('nested-dnd', true)) {
184
- var nodeCopy = tr.doc.slice(start, end, false); // cut the content
201
+ var nodeCopy = tr.doc.slice(sliceFrom, sliceTo, false); // cut the content
185
202
  var destType = $to.node().type;
186
203
  var destParent = $to.node($to.depth);
187
- var sourceNode = $from.nodeAfter;
204
+ var sourceNode = $handlePos.nodeAfter;
188
205
 
206
+ //TODO: Does this need to be updated with new selection logic above? ^
189
207
  // Move a layout column to top level
190
- if (sourceNode && isDragLayoutColumnToTopLevel($from, $to)) {
208
+ if (sourceNode && isDragLayoutColumnToTopLevel($handlePos, $to)) {
191
209
  // need update after we support single column layout.
192
210
  var fragment = _model.Fragment.from(sourceNode.content);
193
- (0, _removeFromSource.removeFromSource)(tr, $from);
211
+ (0, _removeFromSource.removeFromSource)(tr, $handlePos);
194
212
  var _mappedTo = tr.mapping.map(to);
195
213
  tr.insert(_mappedTo, fragment).setSelection(_state.Selection.near(tr.doc.resolve(_mappedTo))).scrollIntoView();
196
214
  return tr;
197
215
  }
198
- if (!(0, _validation.canMoveNodeToIndex)(destParent, $to.index(), $from.node().child($from.index()), $to)) {
216
+ if (!(0, _validation.canMoveNodeToIndex)(destParent, $to.index(), $handlePos.node().child($handlePos.index()), $to)) {
199
217
  return tr;
200
218
  }
201
219
  var convertedNodeSlice = transformSourceSlice(nodeCopy, destType);
@@ -203,7 +221,7 @@ var moveNode = exports.moveNode = function moveNode(api) {
203
221
  if (!convertedNode) {
204
222
  return tr;
205
223
  }
206
- tr.delete(start, end); // delete the content from the original position
224
+ tr.delete(sliceFrom, sliceTo); // delete the content from the original position
207
225
  mappedTo = tr.mapping.map(to);
208
226
  var isDestNestedLoneEmptyParagraph = destParent.type.name !== 'doc' && destParent.childCount === 1 && (0, _utils.isEmptyParagraph)($to.nodeAfter);
209
227
  if (convertedNodeSlice && isDestNestedLoneEmptyParagraph) {
@@ -214,12 +232,12 @@ var moveNode = exports.moveNode = function moveNode(api) {
214
232
  tr.insert(mappedTo, convertedNode);
215
233
  }
216
234
  } else {
217
- var _nodeCopy = tr.doc.content.cut(start, end); // cut the content
218
- tr.delete(start, end); // delete the content from the original position
235
+ var _nodeCopy = tr.doc.content.cut(sliceFrom, sliceTo); // cut the content
236
+ tr.delete(sliceFrom, sliceTo); // delete the content from the original position
219
237
  mappedTo = tr.mapping.map(to);
220
238
  tr.insert(mappedTo, _nodeCopy); // insert the content at the new position
221
239
  }
222
- tr = inputMethod === _analytics.INPUT_METHOD.DRAG_AND_DROP ? (0, _getSelection.setCursorPositionAtMovedNode)(tr, mappedTo) : (0, _getSelection.selectNode)(tr, mappedTo, node.type.name);
240
+ tr = inputMethod === _analytics.INPUT_METHOD.DRAG_AND_DROP ? (0, _getSelection.setCursorPositionAtMovedNode)(tr, mappedTo) : (0, _getSelection.selectNode)(tr, mappedTo, handleNode.type.name);
223
241
  tr.setMeta(_main.key, {
224
242
  nodeMoved: true
225
243
  });
@@ -234,7 +252,7 @@ var moveNode = exports.moveNode = function moveNode(api) {
234
252
  }
235
253
  }
236
254
  if ((0, _experiments.editorExperiment)('advanced_layouts', true)) {
237
- (0, _fireAnalytics.attachMoveNodeAnalytics)(tr, inputMethod, resolvedNode.depth, node.type.name, $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.depth, $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.parent.type.name, $from.sameParent($mappedTo), api);
255
+ (0, _fireAnalytics.attachMoveNodeAnalytics)(tr, inputMethod, $handlePos.depth, handleNode.type.name, $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.depth, $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.parent.type.name, $handlePos.sameParent($mappedTo), api);
238
256
  } else {
239
257
  var _api$analytics;
240
258
  api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.attachAnalyticsEvent({
@@ -243,8 +261,8 @@ var moveNode = exports.moveNode = function moveNode(api) {
243
261
  actionSubject: _analytics.ACTION_SUBJECT.ELEMENT,
244
262
  actionSubjectId: _analytics.ACTION_SUBJECT_ID.ELEMENT_DRAG_HANDLE,
245
263
  attributes: _objectSpread({
246
- nodeDepth: resolvedNode.depth,
247
- nodeType: node.type.name,
264
+ nodeDepth: $handlePos.depth,
265
+ nodeType: handleNode.type.name,
248
266
  destinationNodeDepth: $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.depth,
249
267
  destinationNodeType: $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.parent.type.name
250
268
  }, (0, _platformFeatureFlags.fg)('platform_editor_element_drag_and_drop_ed_23873') && {
@@ -254,7 +272,7 @@ var moveNode = exports.moveNode = function moveNode(api) {
254
272
  }
255
273
  if ((0, _platformFeatureFlags.fg)('platform_editor_element_drag_and_drop_ed_23873')) {
256
274
  var _api$accessibilityUti;
257
- var movedMessage = to > start ? _messages.blockControlsMessages.movedDown : _messages.blockControlsMessages.movedup;
275
+ var movedMessage = to > sliceFrom ? _messages.blockControlsMessages.movedDown : _messages.blockControlsMessages.movedup;
258
276
  api === null || api === void 0 || (_api$accessibilityUti = api.accessibilityUtils) === null || _api$accessibilityUti === void 0 || _api$accessibilityUti.actions.ariaNotify(formatMessage ? formatMessage(movedMessage) : movedMessage.defaultMessage, {
259
277
  priority: 'important'
260
278
  });
@@ -11,6 +11,7 @@ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/creat
11
11
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
12
12
  var _events = require("events");
13
13
  var _react = require("react");
14
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
14
15
  var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
15
16
  var ActiveAnchorTracker = exports.ActiveAnchorTracker = /*#__PURE__*/function () {
16
17
  function ActiveAnchorTracker() {
@@ -19,6 +20,11 @@ var ActiveAnchorTracker = exports.ActiveAnchorTracker = /*#__PURE__*/function ()
19
20
  this.emitter = new _events.EventEmitter();
20
21
  }
21
22
  return (0, _createClass2.default)(ActiveAnchorTracker, [{
23
+ key: "getActiveAnchor",
24
+ value: function getActiveAnchor() {
25
+ return this.lastActiveAnchor;
26
+ }
27
+ }, {
22
28
  key: "subscribe",
23
29
  value: function subscribe(anchorName, callback) {
24
30
  if (this.emitter) {
@@ -68,6 +74,9 @@ var useActiveAnchorTracker = exports.useActiveAnchorTracker = function useActive
68
74
  (0, _react.useEffect)(function () {
69
75
  if (activeAnchorTracker && anchorName && (0, _experiments.editorExperiment)('advanced_layouts', true)) {
70
76
  activeAnchorTracker.subscribe(anchorName, onActive);
77
+ if (activeAnchorTracker.getActiveAnchor() === anchorName && (0, _platformFeatureFlags.fg)('platform_editor_advanced_layouts_post_fix_patch_3')) {
78
+ setIsActive(true);
79
+ }
71
80
  var unsubscribe = function unsubscribe() {
72
81
  activeAnchorTracker.unsubscribe(anchorName, onActive);
73
82
  };
@@ -225,10 +225,37 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref) {
225
225
  api === null || api === void 0 || (_api$core5 = api.core) === null || _api$core5 === void 0 || _api$core5.actions.execute(function (_ref6) {
226
226
  var _api$blockControls, _api$analytics3;
227
227
  var tr = _ref6.tr;
228
+ var isMultiSelect = (0, _experiments.editorExperiment)('platform_editor_element_drag_and_drop_multiselect', true, {
229
+ exposure: true
230
+ });
231
+ var selectionStart = start;
232
+ if (isMultiSelect) {
233
+ var selection = tr.selection;
234
+ var selectionFrom = selection.$from.pos;
235
+ var selectionTo = selection.$to.pos;
236
+ var $selectionFrom = tr.doc.resolve(selectionFrom);
237
+ var $selectionTo = tr.doc.resolve(selectionTo);
238
+ selectionStart = $selectionFrom.start();
239
+ var selectionEnd = $selectionTo.end();
240
+ var handlePos = getPos();
241
+ if (typeof handlePos !== 'number') {
242
+ return tr;
243
+ }
244
+ var posBeforeNode = $selectionFrom.pos ? $selectionFrom.start() - 1 : $selectionFrom.pos;
245
+ var shouldExpandSelection = handlePos >= posBeforeNode && handlePos <= selectionEnd;
246
+ if (shouldExpandSelection) {
247
+ //TODO: What happens if not a text selection?
248
+ var newSelection = _state.TextSelection.create(tr.doc, selectionStart, selectionEnd);
249
+ tr.setSelection(newSelection);
250
+ } else {
251
+ var _$selectionFrom = tr.doc.resolve(handlePos + 1);
252
+ (0, _getSelection.selectNode)(tr, handlePos, _$selectionFrom.node().type.name);
253
+ }
254
+ }
228
255
  api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || _api$blockControls.commands.setNodeDragged(getPos, anchorName, nodeType)({
229
256
  tr: tr
230
257
  });
231
- var resolvedMovingNode = tr.doc.resolve(start);
258
+ var resolvedMovingNode = tr.doc.resolve(selectionStart);
232
259
  var maybeNode = resolvedMovingNode.nodeAfter;
233
260
  tr.setMeta('scrollIntoView', false);
234
261
  api === null || api === void 0 || (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 || _api$analytics3.actions.attachAnalyticsEvent({
@@ -160,37 +160,55 @@ export const moveNode = api => (start, to, inputMethod = INPUT_METHOD.DRAG_AND_D
160
160
  ) => ({
161
161
  tr
162
162
  }) => {
163
- var _node$nodeSize;
164
- const node = tr.doc.nodeAt(start);
165
- const resolvedNode = tr.doc.resolve(start);
166
- if (!node) {
163
+ const isMultiSelect = editorExperiment('platform_editor_element_drag_and_drop_multiselect', true, {
164
+ exposure: true
165
+ });
166
+ const selection = tr.selection;
167
+ const selectionFrom = selection.$from.pos;
168
+ const selectionTo = selection.$to.pos;
169
+ const handleNode = tr.doc.nodeAt(start);
170
+ if (!handleNode) {
167
171
  return tr;
168
172
  }
173
+ let sliceFrom = start;
174
+ let sliceTo;
175
+ if (isMultiSelect) {
176
+ var _handleNode$nodeSize;
177
+ // //If the handle position sits within the Editor selection, we will move all nodes that sit in that selection
178
+ const useSelection = sliceFrom >= selectionFrom - 1 && sliceFrom <= selectionTo;
179
+ sliceFrom = useSelection ? selectionFrom : start;
180
+ const handleSize = (_handleNode$nodeSize = handleNode === null || handleNode === void 0 ? void 0 : handleNode.nodeSize) !== null && _handleNode$nodeSize !== void 0 ? _handleNode$nodeSize : 1;
181
+ const handleEnd = sliceFrom + handleSize;
182
+ sliceTo = useSelection ? selectionTo : handleEnd;
183
+ } else {
184
+ var _handleNode$nodeSize2;
185
+ const size = (_handleNode$nodeSize2 = handleNode === null || handleNode === void 0 ? void 0 : handleNode.nodeSize) !== null && _handleNode$nodeSize2 !== void 0 ? _handleNode$nodeSize2 : 1;
186
+ sliceTo = sliceFrom + size;
187
+ }
169
188
  const {
170
189
  expand,
171
190
  nestedExpand
172
191
  } = tr.doc.type.schema.nodes;
173
- const size = (_node$nodeSize = node === null || node === void 0 ? void 0 : node.nodeSize) !== null && _node$nodeSize !== void 0 ? _node$nodeSize : 1;
174
- const end = start + size;
175
- const $from = tr.doc.resolve(start);
176
192
  const $to = tr.doc.resolve(to);
193
+ const $handlePos = tr.doc.resolve(start);
177
194
  let mappedTo;
178
195
  if (editorExperiment('nested-dnd', true)) {
179
- const nodeCopy = tr.doc.slice(start, end, false); // cut the content
196
+ const nodeCopy = tr.doc.slice(sliceFrom, sliceTo, false); // cut the content
180
197
  const destType = $to.node().type;
181
198
  const destParent = $to.node($to.depth);
182
- const sourceNode = $from.nodeAfter;
199
+ const sourceNode = $handlePos.nodeAfter;
183
200
 
201
+ //TODO: Does this need to be updated with new selection logic above? ^
184
202
  // Move a layout column to top level
185
- if (sourceNode && isDragLayoutColumnToTopLevel($from, $to)) {
203
+ if (sourceNode && isDragLayoutColumnToTopLevel($handlePos, $to)) {
186
204
  // need update after we support single column layout.
187
205
  const fragment = Fragment.from(sourceNode.content);
188
- removeFromSource(tr, $from);
206
+ removeFromSource(tr, $handlePos);
189
207
  const mappedTo = tr.mapping.map(to);
190
208
  tr.insert(mappedTo, fragment).setSelection(Selection.near(tr.doc.resolve(mappedTo))).scrollIntoView();
191
209
  return tr;
192
210
  }
193
- if (!canMoveNodeToIndex(destParent, $to.index(), $from.node().child($from.index()), $to)) {
211
+ if (!canMoveNodeToIndex(destParent, $to.index(), $handlePos.node().child($handlePos.index()), $to)) {
194
212
  return tr;
195
213
  }
196
214
  const convertedNodeSlice = transformSourceSlice(nodeCopy, destType);
@@ -198,7 +216,7 @@ export const moveNode = api => (start, to, inputMethod = INPUT_METHOD.DRAG_AND_D
198
216
  if (!convertedNode) {
199
217
  return tr;
200
218
  }
201
- tr.delete(start, end); // delete the content from the original position
219
+ tr.delete(sliceFrom, sliceTo); // delete the content from the original position
202
220
  mappedTo = tr.mapping.map(to);
203
221
  const isDestNestedLoneEmptyParagraph = destParent.type.name !== 'doc' && destParent.childCount === 1 && isEmptyParagraph($to.nodeAfter);
204
222
  if (convertedNodeSlice && isDestNestedLoneEmptyParagraph) {
@@ -209,12 +227,12 @@ export const moveNode = api => (start, to, inputMethod = INPUT_METHOD.DRAG_AND_D
209
227
  tr.insert(mappedTo, convertedNode);
210
228
  }
211
229
  } else {
212
- const nodeCopy = tr.doc.content.cut(start, end); // cut the content
213
- tr.delete(start, end); // delete the content from the original position
230
+ const nodeCopy = tr.doc.content.cut(sliceFrom, sliceTo); // cut the content
231
+ tr.delete(sliceFrom, sliceTo); // delete the content from the original position
214
232
  mappedTo = tr.mapping.map(to);
215
233
  tr.insert(mappedTo, nodeCopy); // insert the content at the new position
216
234
  }
217
- tr = inputMethod === INPUT_METHOD.DRAG_AND_DROP ? setCursorPositionAtMovedNode(tr, mappedTo) : selectNode(tr, mappedTo, node.type.name);
235
+ tr = inputMethod === INPUT_METHOD.DRAG_AND_DROP ? setCursorPositionAtMovedNode(tr, mappedTo) : selectNode(tr, mappedTo, handleNode.type.name);
218
236
  tr.setMeta(key, {
219
237
  nodeMoved: true
220
238
  });
@@ -229,7 +247,7 @@ export const moveNode = api => (start, to, inputMethod = INPUT_METHOD.DRAG_AND_D
229
247
  }
230
248
  }
231
249
  if (editorExperiment('advanced_layouts', true)) {
232
- attachMoveNodeAnalytics(tr, inputMethod, resolvedNode.depth, node.type.name, $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.depth, $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.parent.type.name, $from.sameParent($mappedTo), api);
250
+ attachMoveNodeAnalytics(tr, inputMethod, $handlePos.depth, handleNode.type.name, $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.depth, $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.parent.type.name, $handlePos.sameParent($mappedTo), api);
233
251
  } else {
234
252
  var _api$analytics;
235
253
  api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions.attachAnalyticsEvent({
@@ -238,8 +256,8 @@ export const moveNode = api => (start, to, inputMethod = INPUT_METHOD.DRAG_AND_D
238
256
  actionSubject: ACTION_SUBJECT.ELEMENT,
239
257
  actionSubjectId: ACTION_SUBJECT_ID.ELEMENT_DRAG_HANDLE,
240
258
  attributes: {
241
- nodeDepth: resolvedNode.depth,
242
- nodeType: node.type.name,
259
+ nodeDepth: $handlePos.depth,
260
+ nodeType: handleNode.type.name,
243
261
  destinationNodeDepth: $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.depth,
244
262
  destinationNodeType: $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.parent.type.name,
245
263
  ...(fg('platform_editor_element_drag_and_drop_ed_23873') && {
@@ -250,7 +268,7 @@ export const moveNode = api => (start, to, inputMethod = INPUT_METHOD.DRAG_AND_D
250
268
  }
251
269
  if (fg('platform_editor_element_drag_and_drop_ed_23873')) {
252
270
  var _api$accessibilityUti;
253
- const movedMessage = to > start ? blockControlsMessages.movedDown : blockControlsMessages.movedup;
271
+ const movedMessage = to > sliceFrom ? blockControlsMessages.movedDown : blockControlsMessages.movedup;
254
272
  api === null || api === void 0 ? void 0 : (_api$accessibilityUti = api.accessibilityUtils) === null || _api$accessibilityUti === void 0 ? void 0 : _api$accessibilityUti.actions.ariaNotify(formatMessage ? formatMessage(movedMessage) : movedMessage.defaultMessage, {
255
273
  priority: 'important'
256
274
  });
@@ -1,12 +1,16 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import { EventEmitter } from 'events';
3
3
  import { useCallback, useEffect, useState } from 'react';
4
+ import { fg } from '@atlaskit/platform-feature-flags';
4
5
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
5
6
  export class ActiveAnchorTracker {
6
7
  constructor() {
7
8
  _defineProperty(this, "lastActiveAnchor", '');
8
9
  this.emitter = new EventEmitter();
9
10
  }
11
+ getActiveAnchor() {
12
+ return this.lastActiveAnchor;
13
+ }
10
14
  subscribe(anchorName, callback) {
11
15
  if (this.emitter) {
12
16
  this.emitter.on(anchorName, callback);
@@ -46,6 +50,9 @@ export const useActiveAnchorTracker = (anchorName, activeAnchorTracker = default
46
50
  useEffect(() => {
47
51
  if (activeAnchorTracker && anchorName && editorExperiment('advanced_layouts', true)) {
48
52
  activeAnchorTracker.subscribe(anchorName, onActive);
53
+ if (activeAnchorTracker.getActiveAnchor() === anchorName && fg('platform_editor_advanced_layouts_post_fix_patch_3')) {
54
+ setIsActive(true);
55
+ }
49
56
  const unsubscribe = () => {
50
57
  activeAnchorTracker.unsubscribe(anchorName, onActive);
51
58
  };
@@ -208,10 +208,37 @@ export const DragHandle = ({
208
208
  tr
209
209
  }) => {
210
210
  var _api$blockControls, _api$analytics3;
211
+ const isMultiSelect = editorExperiment('platform_editor_element_drag_and_drop_multiselect', true, {
212
+ exposure: true
213
+ });
214
+ let selectionStart = start;
215
+ if (isMultiSelect) {
216
+ const selection = tr.selection;
217
+ const selectionFrom = selection.$from.pos;
218
+ const selectionTo = selection.$to.pos;
219
+ const $selectionFrom = tr.doc.resolve(selectionFrom);
220
+ const $selectionTo = tr.doc.resolve(selectionTo);
221
+ selectionStart = $selectionFrom.start();
222
+ const selectionEnd = $selectionTo.end();
223
+ const handlePos = getPos();
224
+ if (typeof handlePos !== 'number') {
225
+ return tr;
226
+ }
227
+ const posBeforeNode = $selectionFrom.pos ? $selectionFrom.start() - 1 : $selectionFrom.pos;
228
+ const shouldExpandSelection = handlePos >= posBeforeNode && handlePos <= selectionEnd;
229
+ if (shouldExpandSelection) {
230
+ //TODO: What happens if not a text selection?
231
+ const newSelection = TextSelection.create(tr.doc, selectionStart, selectionEnd);
232
+ tr.setSelection(newSelection);
233
+ } else {
234
+ const $selectionFrom = tr.doc.resolve(handlePos + 1);
235
+ selectNode(tr, handlePos, $selectionFrom.node().type.name);
236
+ }
237
+ }
211
238
  api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.commands.setNodeDragged(getPos, anchorName, nodeType)({
212
239
  tr
213
240
  });
214
- const resolvedMovingNode = tr.doc.resolve(start);
241
+ const resolvedMovingNode = tr.doc.resolve(selectionStart);
215
242
  const maybeNode = resolvedMovingNode.nodeAfter;
216
243
  tr.setMeta('scrollIntoView', false);
217
244
  api === null || api === void 0 ? void 0 : (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 ? void 0 : _api$analytics3.actions.attachAnalyticsEvent({
@@ -159,37 +159,55 @@ export var moveNode = function moveNode(api) {
159
159
  // eslint-disable-next-line @typescript-eslint/max-params
160
160
  = arguments.length > 3 ? arguments[3] : undefined;
161
161
  return function (_ref4) {
162
- var _node$nodeSize;
163
162
  var tr = _ref4.tr;
164
- var node = tr.doc.nodeAt(start);
165
- var resolvedNode = tr.doc.resolve(start);
166
- if (!node) {
163
+ var isMultiSelect = editorExperiment('platform_editor_element_drag_and_drop_multiselect', true, {
164
+ exposure: true
165
+ });
166
+ var selection = tr.selection;
167
+ var selectionFrom = selection.$from.pos;
168
+ var selectionTo = selection.$to.pos;
169
+ var handleNode = tr.doc.nodeAt(start);
170
+ if (!handleNode) {
167
171
  return tr;
168
172
  }
173
+ var sliceFrom = start;
174
+ var sliceTo;
175
+ if (isMultiSelect) {
176
+ var _handleNode$nodeSize;
177
+ // //If the handle position sits within the Editor selection, we will move all nodes that sit in that selection
178
+ var useSelection = sliceFrom >= selectionFrom - 1 && sliceFrom <= selectionTo;
179
+ sliceFrom = useSelection ? selectionFrom : start;
180
+ var handleSize = (_handleNode$nodeSize = handleNode === null || handleNode === void 0 ? void 0 : handleNode.nodeSize) !== null && _handleNode$nodeSize !== void 0 ? _handleNode$nodeSize : 1;
181
+ var handleEnd = sliceFrom + handleSize;
182
+ sliceTo = useSelection ? selectionTo : handleEnd;
183
+ } else {
184
+ var _handleNode$nodeSize2;
185
+ var size = (_handleNode$nodeSize2 = handleNode === null || handleNode === void 0 ? void 0 : handleNode.nodeSize) !== null && _handleNode$nodeSize2 !== void 0 ? _handleNode$nodeSize2 : 1;
186
+ sliceTo = sliceFrom + size;
187
+ }
169
188
  var _tr$doc$type$schema$n = tr.doc.type.schema.nodes,
170
189
  expand = _tr$doc$type$schema$n.expand,
171
190
  nestedExpand = _tr$doc$type$schema$n.nestedExpand;
172
- var size = (_node$nodeSize = node === null || node === void 0 ? void 0 : node.nodeSize) !== null && _node$nodeSize !== void 0 ? _node$nodeSize : 1;
173
- var end = start + size;
174
- var $from = tr.doc.resolve(start);
175
191
  var $to = tr.doc.resolve(to);
192
+ var $handlePos = tr.doc.resolve(start);
176
193
  var mappedTo;
177
194
  if (editorExperiment('nested-dnd', true)) {
178
- var nodeCopy = tr.doc.slice(start, end, false); // cut the content
195
+ var nodeCopy = tr.doc.slice(sliceFrom, sliceTo, false); // cut the content
179
196
  var destType = $to.node().type;
180
197
  var destParent = $to.node($to.depth);
181
- var sourceNode = $from.nodeAfter;
198
+ var sourceNode = $handlePos.nodeAfter;
182
199
 
200
+ //TODO: Does this need to be updated with new selection logic above? ^
183
201
  // Move a layout column to top level
184
- if (sourceNode && isDragLayoutColumnToTopLevel($from, $to)) {
202
+ if (sourceNode && isDragLayoutColumnToTopLevel($handlePos, $to)) {
185
203
  // need update after we support single column layout.
186
204
  var fragment = Fragment.from(sourceNode.content);
187
- removeFromSource(tr, $from);
205
+ removeFromSource(tr, $handlePos);
188
206
  var _mappedTo = tr.mapping.map(to);
189
207
  tr.insert(_mappedTo, fragment).setSelection(Selection.near(tr.doc.resolve(_mappedTo))).scrollIntoView();
190
208
  return tr;
191
209
  }
192
- if (!canMoveNodeToIndex(destParent, $to.index(), $from.node().child($from.index()), $to)) {
210
+ if (!canMoveNodeToIndex(destParent, $to.index(), $handlePos.node().child($handlePos.index()), $to)) {
193
211
  return tr;
194
212
  }
195
213
  var convertedNodeSlice = transformSourceSlice(nodeCopy, destType);
@@ -197,7 +215,7 @@ export var moveNode = function moveNode(api) {
197
215
  if (!convertedNode) {
198
216
  return tr;
199
217
  }
200
- tr.delete(start, end); // delete the content from the original position
218
+ tr.delete(sliceFrom, sliceTo); // delete the content from the original position
201
219
  mappedTo = tr.mapping.map(to);
202
220
  var isDestNestedLoneEmptyParagraph = destParent.type.name !== 'doc' && destParent.childCount === 1 && isEmptyParagraph($to.nodeAfter);
203
221
  if (convertedNodeSlice && isDestNestedLoneEmptyParagraph) {
@@ -208,12 +226,12 @@ export var moveNode = function moveNode(api) {
208
226
  tr.insert(mappedTo, convertedNode);
209
227
  }
210
228
  } else {
211
- var _nodeCopy = tr.doc.content.cut(start, end); // cut the content
212
- tr.delete(start, end); // delete the content from the original position
229
+ var _nodeCopy = tr.doc.content.cut(sliceFrom, sliceTo); // cut the content
230
+ tr.delete(sliceFrom, sliceTo); // delete the content from the original position
213
231
  mappedTo = tr.mapping.map(to);
214
232
  tr.insert(mappedTo, _nodeCopy); // insert the content at the new position
215
233
  }
216
- tr = inputMethod === INPUT_METHOD.DRAG_AND_DROP ? setCursorPositionAtMovedNode(tr, mappedTo) : selectNode(tr, mappedTo, node.type.name);
234
+ tr = inputMethod === INPUT_METHOD.DRAG_AND_DROP ? setCursorPositionAtMovedNode(tr, mappedTo) : selectNode(tr, mappedTo, handleNode.type.name);
217
235
  tr.setMeta(key, {
218
236
  nodeMoved: true
219
237
  });
@@ -228,7 +246,7 @@ export var moveNode = function moveNode(api) {
228
246
  }
229
247
  }
230
248
  if (editorExperiment('advanced_layouts', true)) {
231
- attachMoveNodeAnalytics(tr, inputMethod, resolvedNode.depth, node.type.name, $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.depth, $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.parent.type.name, $from.sameParent($mappedTo), api);
249
+ attachMoveNodeAnalytics(tr, inputMethod, $handlePos.depth, handleNode.type.name, $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.depth, $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.parent.type.name, $handlePos.sameParent($mappedTo), api);
232
250
  } else {
233
251
  var _api$analytics;
234
252
  api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.attachAnalyticsEvent({
@@ -237,8 +255,8 @@ export var moveNode = function moveNode(api) {
237
255
  actionSubject: ACTION_SUBJECT.ELEMENT,
238
256
  actionSubjectId: ACTION_SUBJECT_ID.ELEMENT_DRAG_HANDLE,
239
257
  attributes: _objectSpread({
240
- nodeDepth: resolvedNode.depth,
241
- nodeType: node.type.name,
258
+ nodeDepth: $handlePos.depth,
259
+ nodeType: handleNode.type.name,
242
260
  destinationNodeDepth: $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.depth,
243
261
  destinationNodeType: $mappedTo === null || $mappedTo === void 0 ? void 0 : $mappedTo.parent.type.name
244
262
  }, fg('platform_editor_element_drag_and_drop_ed_23873') && {
@@ -248,7 +266,7 @@ export var moveNode = function moveNode(api) {
248
266
  }
249
267
  if (fg('platform_editor_element_drag_and_drop_ed_23873')) {
250
268
  var _api$accessibilityUti;
251
- var movedMessage = to > start ? blockControlsMessages.movedDown : blockControlsMessages.movedup;
269
+ var movedMessage = to > sliceFrom ? blockControlsMessages.movedDown : blockControlsMessages.movedup;
252
270
  api === null || api === void 0 || (_api$accessibilityUti = api.accessibilityUtils) === null || _api$accessibilityUti === void 0 || _api$accessibilityUti.actions.ariaNotify(formatMessage ? formatMessage(movedMessage) : movedMessage.defaultMessage, {
253
271
  priority: 'important'
254
272
  });
@@ -4,6 +4,7 @@ import _createClass from "@babel/runtime/helpers/createClass";
4
4
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
5
5
  import { EventEmitter } from 'events';
6
6
  import { useCallback, useEffect, useState } from 'react';
7
+ import { fg } from '@atlaskit/platform-feature-flags';
7
8
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
8
9
  export var ActiveAnchorTracker = /*#__PURE__*/function () {
9
10
  function ActiveAnchorTracker() {
@@ -12,6 +13,11 @@ export var ActiveAnchorTracker = /*#__PURE__*/function () {
12
13
  this.emitter = new EventEmitter();
13
14
  }
14
15
  return _createClass(ActiveAnchorTracker, [{
16
+ key: "getActiveAnchor",
17
+ value: function getActiveAnchor() {
18
+ return this.lastActiveAnchor;
19
+ }
20
+ }, {
15
21
  key: "subscribe",
16
22
  value: function subscribe(anchorName, callback) {
17
23
  if (this.emitter) {
@@ -63,6 +69,9 @@ export var useActiveAnchorTracker = function useActiveAnchorTracker(anchorName)
63
69
  useEffect(function () {
64
70
  if (activeAnchorTracker && anchorName && editorExperiment('advanced_layouts', true)) {
65
71
  activeAnchorTracker.subscribe(anchorName, onActive);
72
+ if (activeAnchorTracker.getActiveAnchor() === anchorName && fg('platform_editor_advanced_layouts_post_fix_patch_3')) {
73
+ setIsActive(true);
74
+ }
66
75
  var unsubscribe = function unsubscribe() {
67
76
  activeAnchorTracker.unsubscribe(anchorName, onActive);
68
77
  };
@@ -216,10 +216,37 @@ export var DragHandle = function DragHandle(_ref) {
216
216
  api === null || api === void 0 || (_api$core5 = api.core) === null || _api$core5 === void 0 || _api$core5.actions.execute(function (_ref6) {
217
217
  var _api$blockControls, _api$analytics3;
218
218
  var tr = _ref6.tr;
219
+ var isMultiSelect = editorExperiment('platform_editor_element_drag_and_drop_multiselect', true, {
220
+ exposure: true
221
+ });
222
+ var selectionStart = start;
223
+ if (isMultiSelect) {
224
+ var selection = tr.selection;
225
+ var selectionFrom = selection.$from.pos;
226
+ var selectionTo = selection.$to.pos;
227
+ var $selectionFrom = tr.doc.resolve(selectionFrom);
228
+ var $selectionTo = tr.doc.resolve(selectionTo);
229
+ selectionStart = $selectionFrom.start();
230
+ var selectionEnd = $selectionTo.end();
231
+ var handlePos = getPos();
232
+ if (typeof handlePos !== 'number') {
233
+ return tr;
234
+ }
235
+ var posBeforeNode = $selectionFrom.pos ? $selectionFrom.start() - 1 : $selectionFrom.pos;
236
+ var shouldExpandSelection = handlePos >= posBeforeNode && handlePos <= selectionEnd;
237
+ if (shouldExpandSelection) {
238
+ //TODO: What happens if not a text selection?
239
+ var newSelection = TextSelection.create(tr.doc, selectionStart, selectionEnd);
240
+ tr.setSelection(newSelection);
241
+ } else {
242
+ var _$selectionFrom = tr.doc.resolve(handlePos + 1);
243
+ selectNode(tr, handlePos, _$selectionFrom.node().type.name);
244
+ }
245
+ }
219
246
  api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || _api$blockControls.commands.setNodeDragged(getPos, anchorName, nodeType)({
220
247
  tr: tr
221
248
  });
222
- var resolvedMovingNode = tr.doc.resolve(start);
249
+ var resolvedMovingNode = tr.doc.resolve(selectionStart);
223
250
  var maybeNode = resolvedMovingNode.nodeAfter;
224
251
  tr.setMeta('scrollIntoView', false);
225
252
  api === null || api === void 0 || (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 || _api$analytics3.actions.attachAnalyticsEvent({
@@ -6,6 +6,7 @@ export declare class ActiveAnchorTracker {
6
6
  emitter: EventEmitter | null;
7
7
  lastActiveAnchor: string;
8
8
  constructor();
9
+ getActiveAnchor(): string;
9
10
  subscribe(anchorName: string, callback: (isActive: boolean) => void): void;
10
11
  unsubscribe(anchorName: string, callback: (isActive: boolean) => void): void;
11
12
  emit(anchorName: string): void;
@@ -6,6 +6,7 @@ export declare class ActiveAnchorTracker {
6
6
  emitter: EventEmitter | null;
7
7
  lastActiveAnchor: string;
8
8
  constructor();
9
+ getActiveAnchor(): string;
9
10
  subscribe(anchorName: string, callback: (isActive: boolean) => void): void;
10
11
  unsubscribe(anchorName: string, callback: (isActive: boolean) => void): void;
11
12
  emit(anchorName: string): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-block-controls",
3
- "version": "2.19.2",
3
+ "version": "2.21.0",
4
4
  "description": "Block controls plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -31,7 +31,7 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@atlaskit/adf-schema": "^46.1.0",
34
- "@atlaskit/editor-common": "^99.1.0",
34
+ "@atlaskit/editor-common": "^99.3.0",
35
35
  "@atlaskit/editor-plugin-accessibility-utils": "^1.2.0",
36
36
  "@atlaskit/editor-plugin-analytics": "^1.10.0",
37
37
  "@atlaskit/editor-plugin-editor-disabled": "^1.3.0",
@@ -41,14 +41,14 @@
41
41
  "@atlaskit/editor-prosemirror": "6.2.1",
42
42
  "@atlaskit/editor-shared-styles": "^3.2.0",
43
43
  "@atlaskit/editor-tables": "^2.8.0",
44
- "@atlaskit/icon": "^23.3.0",
44
+ "@atlaskit/icon": "^23.4.0",
45
45
  "@atlaskit/platform-feature-flags": "^0.3.0",
46
46
  "@atlaskit/pragmatic-drag-and-drop": "^1.4.0",
47
47
  "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^2.1.0",
48
48
  "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "^1.1.0",
49
49
  "@atlaskit/primitives": "^13.3.0",
50
50
  "@atlaskit/theme": "^14.0.0",
51
- "@atlaskit/tmp-editor-statsig": "^2.34.0",
51
+ "@atlaskit/tmp-editor-statsig": "^2.35.0",
52
52
  "@atlaskit/tokens": "^3.0.0",
53
53
  "@atlaskit/tooltip": "^19.0.0",
54
54
  "@babel/runtime": "^7.0.0",