@atlaskit/editor-plugin-block-controls 2.26.0 → 2.26.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 (52) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/cjs/editor-commands/move-node.js +79 -43
  3. package/dist/cjs/editor-commands/move-to-layout.js +23 -11
  4. package/dist/cjs/editor-commands/show-drag-handle.js +89 -3
  5. package/dist/cjs/pm-plugins/decorations-anchor.js +5 -10
  6. package/dist/cjs/pm-plugins/decorations-common.js +8 -10
  7. package/dist/cjs/pm-plugins/decorations-drag-handle.js +4 -19
  8. package/dist/cjs/pm-plugins/decorations-drop-target.js +8 -25
  9. package/dist/cjs/pm-plugins/main.js +28 -8
  10. package/dist/cjs/pm-plugins/utils/analytics.js +66 -0
  11. package/dist/cjs/pm-plugins/utils/selection.js +22 -2
  12. package/dist/cjs/ui/drag-handle.js +38 -10
  13. package/dist/es2019/editor-commands/move-node.js +76 -40
  14. package/dist/es2019/editor-commands/move-to-layout.js +23 -11
  15. package/dist/es2019/editor-commands/show-drag-handle.js +88 -3
  16. package/dist/es2019/pm-plugins/decorations-anchor.js +6 -11
  17. package/dist/es2019/pm-plugins/decorations-common.js +7 -9
  18. package/dist/es2019/pm-plugins/decorations-drag-handle.js +10 -25
  19. package/dist/es2019/pm-plugins/decorations-drop-target.js +11 -30
  20. package/dist/es2019/pm-plugins/main.js +24 -6
  21. package/dist/es2019/pm-plugins/utils/{fire-analytics.js → analytics.js} +31 -3
  22. package/dist/es2019/pm-plugins/utils/selection.js +22 -1
  23. package/dist/es2019/ui/drag-handle.js +34 -4
  24. package/dist/esm/editor-commands/move-node.js +80 -44
  25. package/dist/esm/editor-commands/move-to-layout.js +23 -11
  26. package/dist/esm/editor-commands/show-drag-handle.js +88 -2
  27. package/dist/esm/pm-plugins/decorations-anchor.js +6 -11
  28. package/dist/esm/pm-plugins/decorations-common.js +7 -9
  29. package/dist/esm/pm-plugins/decorations-drag-handle.js +4 -19
  30. package/dist/esm/pm-plugins/decorations-drop-target.js +8 -25
  31. package/dist/esm/pm-plugins/main.js +27 -7
  32. package/dist/esm/pm-plugins/utils/{fire-analytics.js → analytics.js} +32 -3
  33. package/dist/esm/pm-plugins/utils/selection.js +21 -1
  34. package/dist/esm/ui/drag-handle.js +37 -5
  35. package/dist/types/blockControlsPluginType.d.ts +1 -0
  36. package/dist/types/editor-commands/move-to-layout.d.ts +1 -0
  37. package/dist/types/editor-commands/show-drag-handle.d.ts +1 -1
  38. package/dist/types/pm-plugins/decorations-common.d.ts +1 -0
  39. package/dist/types/pm-plugins/main.d.ts +1 -0
  40. package/dist/types/pm-plugins/utils/analytics.d.ts +12 -0
  41. package/dist/types/pm-plugins/utils/selection.d.ts +9 -0
  42. package/dist/types-ts4.5/blockControlsPluginType.d.ts +1 -0
  43. package/dist/types-ts4.5/editor-commands/move-to-layout.d.ts +1 -0
  44. package/dist/types-ts4.5/editor-commands/show-drag-handle.d.ts +1 -1
  45. package/dist/types-ts4.5/pm-plugins/decorations-common.d.ts +1 -0
  46. package/dist/types-ts4.5/pm-plugins/main.d.ts +1 -0
  47. package/dist/types-ts4.5/pm-plugins/utils/analytics.d.ts +12 -0
  48. package/dist/types-ts4.5/pm-plugins/utils/selection.d.ts +9 -0
  49. package/package.json +10 -7
  50. package/dist/cjs/pm-plugins/utils/fire-analytics.js +0 -36
  51. package/dist/types/pm-plugins/utils/fire-analytics.d.ts +0 -5
  52. package/dist/types-ts4.5/pm-plugins/utils/fire-analytics.d.ts +0 -5
@@ -4,9 +4,9 @@ import { Fragment, Node as PMNode } from '@atlaskit/editor-prosemirror/model';
4
4
  import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
5
5
  import { fg } from '@atlaskit/platform-feature-flags';
6
6
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
7
+ import { fireInsertLayoutAnalytics, attachMoveNodeAnalytics, getMultiSelectAnalyticsAttributes } from '../pm-plugins/utils/analytics';
7
8
  import { isFragmentOfType, containsNodeOfType } from '../pm-plugins/utils/check-fragment';
8
9
  import { maxLayoutColumnSupported } from '../pm-plugins/utils/consts';
9
- import { fireInsertLayoutAnalytics, attachMoveNodeAnalytics } from '../pm-plugins/utils/fire-analytics';
10
10
  import { removeFromSource } from '../pm-plugins/utils/remove-from-source';
11
11
  import { getMultiSelectionIfPosInside } from '../pm-plugins/utils/selection';
12
12
  import { updateColumnWidths } from '../pm-plugins/utils/update-column-widths';
@@ -42,9 +42,14 @@ const createNewLayout = (schema, layoutContents) => {
42
42
  const moveToExistingLayout = (toLayout, toLayoutPos, sourceContent, from, to, tr, $originalFrom, $originalTo, api, selectMovedNode) => {
43
43
  const isSameLayout = isInSameLayout($originalFrom, $originalTo);
44
44
  let sourceContentEndPos = -1;
45
- if (editorExperiment('platform_editor_element_drag_and_drop_multiselect', true)) {
45
+ const isMultiSelect = editorExperiment('platform_editor_element_drag_and_drop_multiselect', true);
46
+ let sourceNodeTypes, hasSelectedMultipleNodes;
47
+ if (isMultiSelect) {
46
48
  if (sourceContent instanceof Fragment) {
47
49
  sourceContentEndPos = from + sourceContent.size;
50
+ const attributes = getMultiSelectAnalyticsAttributes(tr, from, sourceContentEndPos);
51
+ hasSelectedMultipleNodes = attributes.hasSelectedMultipleNodes;
52
+ sourceNodeTypes = attributes.nodeTypes;
48
53
  }
49
54
  } else {
50
55
  if (sourceContent instanceof PMNode) {
@@ -63,7 +68,7 @@ const moveToExistingLayout = (toLayout, toLayoutPos, sourceContent, from, to, tr
63
68
  if (!fg('platform_editor_advanced_layouts_post_fix_patch_1') || selectMovedNode) {
64
69
  tr.setSelection(new NodeSelection(tr.doc.resolve(mappedTo))).scrollIntoView();
65
70
  }
66
- attachMoveNodeAnalytics(tr, INPUT_METHOD.DRAG_AND_DROP, $originalFrom.depth, ((_$originalFrom$nodeAf = $originalFrom.nodeAfter) === null || _$originalFrom$nodeAf === void 0 ? void 0 : _$originalFrom$nodeAf.type.name) || '', 1, 'layoutSection', true, api);
71
+ attachMoveNodeAnalytics(tr, INPUT_METHOD.DRAG_AND_DROP, $originalFrom.depth, ((_$originalFrom$nodeAf = $originalFrom.nodeAfter) === null || _$originalFrom$nodeAf === void 0 ? void 0 : _$originalFrom$nodeAf.type.name) || '', 1, 'layoutSection', true, api, sourceNodeTypes, hasSelectedMultipleNodes);
67
72
  } else if (toLayout.childCount < maxLayoutColumnSupported()) {
68
73
  var _$originalFrom$nodeAf2;
69
74
  if (fg('platform_editor_advanced_layouts_post_fix_patch_1')) {
@@ -74,7 +79,7 @@ const moveToExistingLayout = (toLayout, toLayoutPos, sourceContent, from, to, tr
74
79
  const mappedFrom = tr.mapping.map(from);
75
80
  removeFromSource(tr, tr.doc.resolve(mappedFrom), tr.mapping.map(sourceContentEndPos));
76
81
  }
77
- 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);
82
+ 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, sourceNodeTypes, hasSelectedMultipleNodes);
78
83
  }
79
84
  return tr;
80
85
  };
@@ -160,7 +165,7 @@ const insertToDestination = (tr, to, sourceContent, toLayout, toLayoutPos) => {
160
165
  * Check if the node at `from` can be moved to node at `to` to create/expand a layout.
161
166
  * Returns the source and destination nodes and positions if it's a valid move, otherwise, undefined
162
167
  */
163
- const canMoveToLayout = (api, from, to, tr) => {
168
+ const canMoveToLayout = (api, from, to, tr, moveNodeAtCursorPos) => {
164
169
  if (from === to) {
165
170
  return;
166
171
  }
@@ -192,12 +197,12 @@ const canMoveToLayout = (api, from, to, tr) => {
192
197
  let sourceContent = $from.nodeAfter;
193
198
  let sourceFrom = from;
194
199
  let sourceTo = from + sourceContent.nodeSize;
195
- if (isMultiSelect) {
200
+ if (isMultiSelect && !moveNodeAtCursorPos) {
196
201
  const {
197
202
  anchor,
198
203
  head
199
204
  } = getMultiSelectionIfPosInside(api, from);
200
- if (anchor && head) {
205
+ if (anchor !== undefined && head !== undefined) {
201
206
  sourceFrom = Math.min(anchor, head);
202
207
  sourceTo = Math.max(anchor, head);
203
208
  sourceContent = tr.doc.slice(sourceFrom, sourceTo).content;
@@ -249,7 +254,7 @@ const getBreakoutMode = (content, breakout) => {
249
254
  if (editorExperiment('platform_editor_element_drag_and_drop_multiselect', true)) {
250
255
  if (content instanceof PMNode) {
251
256
  var _content$marks$find;
252
- return (_content$marks$find = content.marks.find(m => m.type === breakout)) === null || _content$marks$find === void 0 ? void 0 : _content$marks$find.attrs;
257
+ return (_content$marks$find = content.marks.find(m => m.type === breakout)) === null || _content$marks$find === void 0 ? void 0 : _content$marks$find.attrs.mode;
253
258
  } else if (content instanceof Fragment) {
254
259
  // Find the first breakout mode in the fragment
255
260
  let firstBreakoutMode;
@@ -280,7 +285,7 @@ export const moveToLayout = api => (from, to, options) => ({
280
285
  if (!api) {
281
286
  return tr;
282
287
  }
283
- const canMove = canMoveToLayout(api, from, to, tr);
288
+ const canMove = canMoveToLayout(api, from, to, tr, options === null || options === void 0 ? void 0 : options.moveNodeAtCursorPos);
284
289
  if (!canMove) {
285
290
  return tr;
286
291
  }
@@ -308,6 +313,7 @@ export const moveToLayout = api => (from, to, options) => ({
308
313
  if (!fromContentWithoutBreakout) {
309
314
  return tr;
310
315
  }
316
+ const isMultiSelect = editorExperiment('platform_editor_element_drag_and_drop_multiselect', true);
311
317
  if (toNode.type === layoutSection) {
312
318
  const toPos = options !== null && options !== void 0 && options.moveToEnd ? to + toNode.nodeSize - 1 : to + 1;
313
319
  return moveToExistingLayout(toNode, to, fromContentWithoutBreakout, $sourceFrom.pos, toPos, tr, $sourceFrom, $to, api, options === null || options === void 0 ? void 0 : options.selectMovedNode);
@@ -325,7 +331,7 @@ export const moveToLayout = api => (from, to, options) => ({
325
331
  // resolve again the source node after node updated (remove breakout marks)
326
332
  toNodeWithoutBreakout = tr.doc.resolve(to).nodeAfter || toNode;
327
333
  }
328
- if (editorExperiment('platform_editor_element_drag_and_drop_multiselect', true)) {
334
+ if (isMultiSelect) {
329
335
  if (isFragmentOfType(fromContentWithoutBreakout, 'layoutColumn') && fromContentWithoutBreakout.firstChild) {
330
336
  fromContentWithoutBreakout = fromContentWithoutBreakout.firstChild.content;
331
337
  }
@@ -337,6 +343,12 @@ export const moveToLayout = api => (from, to, options) => ({
337
343
  const layoutContents = options !== null && options !== void 0 && options.moveToEnd ? [toNodeWithoutBreakout, fromContentWithoutBreakout] : [fromContentWithoutBreakout, toNodeWithoutBreakout];
338
344
  const newLayout = createNewLayout(tr.doc.type.schema, layoutContents);
339
345
  if (newLayout) {
346
+ let sourceNodeTypes, hasSelectedMultipleNodes;
347
+ if (isMultiSelect) {
348
+ const attributes = getMultiSelectAnalyticsAttributes(tr, $sourceFrom.pos, sourceTo);
349
+ hasSelectedMultipleNodes = attributes.hasSelectedMultipleNodes;
350
+ sourceNodeTypes = attributes.nodeTypes;
351
+ }
340
352
  tr = removeFromSource(tr, $sourceFrom, sourceTo);
341
353
  const mappedTo = tr.mapping.map(to);
342
354
  tr.delete(mappedTo, mappedTo + toNodeWithoutBreakout.nodeSize).insert(mappedTo, newLayout);
@@ -346,7 +358,7 @@ export const moveToLayout = api => (from, to, options) => ({
346
358
  breakoutMode && tr.setNodeMarkup(mappedTo, newLayout.type, newLayout.attrs, [breakout.create({
347
359
  mode: breakoutMode
348
360
  })]);
349
- fireInsertLayoutAnalytics(tr, api);
361
+ fireInsertLayoutAnalytics(tr, api, sourceNodeTypes, hasSelectedMultipleNodes);
350
362
  }
351
363
  return tr;
352
364
  }
@@ -1,8 +1,11 @@
1
+ import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
1
2
  import { isInTable } from '@atlaskit/editor-tables/utils';
3
+ import { fg } from '@atlaskit/platform-feature-flags';
2
4
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
3
- import { key } from '../pm-plugins/main';
5
+ import { findNodeDecs } from '../pm-plugins/decorations-anchor';
6
+ import { getDecorations, key } from '../pm-plugins/main';
4
7
  import { getNestedNodePosition } from '../pm-plugins/utils/getNestedNodePosition';
5
- export const showDragHandleAtSelection = (api, shouldFocusParentNode) => (state, _, view) => {
8
+ const showDragHandleAtSelectionOld = (api, shouldFocusParentNode) => (state, _, view) => {
6
9
  const {
7
10
  $from
8
11
  } = state.selection;
@@ -63,4 +66,86 @@ export const showDragHandleAtSelection = (api, shouldFocusParentNode) => (state,
63
66
  }
64
67
  }
65
68
  return false;
66
- };
69
+ };
70
+ const findParentPosForHandle = state => {
71
+ var _activeNode$handleOpt2;
72
+ const {
73
+ selection: {
74
+ $from
75
+ }
76
+ } = state;
77
+ const {
78
+ activeNode
79
+ } = key.getState(state) || {};
80
+
81
+ // if a node handle is already focused, return the parent pos of that node (with focused handle)
82
+ if (activeNode && (_activeNode$handleOpt2 = activeNode.handleOptions) !== null && _activeNode$handleOpt2 !== void 0 && _activeNode$handleOpt2.isFocused) {
83
+ const $activeNodePos = state.doc.resolve(activeNode.pos);
84
+
85
+ // if the handle is at the top level already, do nothing
86
+ if ($activeNodePos.depth === 0) {
87
+ return undefined;
88
+ }
89
+ return $activeNodePos.before();
90
+ }
91
+
92
+ // if we are in second level of nested node, we should focus the node at level 1
93
+ if ($from.depth <= 1) {
94
+ return $from.before(1);
95
+ }
96
+
97
+ // if we are inside a table, we should focus the table's handle
98
+ const parentTableNode = findParentNodeOfType([state.schema.nodes.table])(state.selection);
99
+ if (parentTableNode) {
100
+ return parentTableNode.pos;
101
+ }
102
+
103
+ // else find closest parent node
104
+ return getNestedNodePosition(state);
105
+ };
106
+ const findNextAnchorDecoration = state => {
107
+ const decorations = getDecorations(state);
108
+ if (!decorations) {
109
+ return undefined;
110
+ }
111
+ const nextHandleNodePos = findParentPosForHandle(state);
112
+ if (nextHandleNodePos === undefined) {
113
+ return undefined;
114
+ }
115
+ const nextHandleNode = state.doc.nodeAt(nextHandleNodePos);
116
+ let nodeDecorations = nextHandleNode && findNodeDecs(decorations, nextHandleNodePos, nextHandleNodePos + nextHandleNode.nodeSize);
117
+ if (!nodeDecorations || nodeDecorations.length === 0) {
118
+ return undefined;
119
+ }
120
+
121
+ // ensure the decoration covers the position of the look up node
122
+ nodeDecorations = nodeDecorations.filter(decoration => {
123
+ return decoration.from <= nextHandleNodePos;
124
+ });
125
+ if (nodeDecorations.length === 0) {
126
+ return undefined;
127
+ }
128
+
129
+ // sort the decorations by the position of the node
130
+ // so we can find the closest decoration to the node
131
+ nodeDecorations.sort((a, b) => {
132
+ if (a.from === b.from) {
133
+ return a.to - b.to;
134
+ }
135
+ return b.from - a.from;
136
+ });
137
+
138
+ // return the closest decoration to the node
139
+ return nodeDecorations[0];
140
+ };
141
+ const showDragHandleAtSelectionNew = api => state => {
142
+ const decoration = findNextAnchorDecoration(state);
143
+ if (api && decoration) {
144
+ api.core.actions.execute(api.blockControls.commands.showDragHandleAt(decoration.from, decoration.spec.anchorName, decoration.spec.nodeTypeWithLevel, {
145
+ isFocused: true
146
+ }));
147
+ return true;
148
+ }
149
+ return false;
150
+ };
151
+ export const showDragHandleAtSelection = api => (state, dispatch, view) => editorExperiment('nested-dnd', true) && fg('platform_editor_advanced_layouts_a11y') ? showDragHandleAtSelectionNew(api)(state) : showDragHandleAtSelectionOld(api)(state, dispatch, view);
@@ -1,7 +1,7 @@
1
1
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
2
2
  import { fg } from '@atlaskit/platform-feature-flags';
3
3
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
4
- import { getNestedDepth, getNodeAnchor, TYPE_NODE_DEC } from './decorations-common';
4
+ import { getNestedDepth, getNodeAnchor, getNodeTypeWithLevel, TYPE_NODE_DEC } from './decorations-common';
5
5
  const IGNORE_NODES = ['tableCell', 'tableHeader', 'tableRow', 'listItem', 'caption', 'layoutColumn'];
6
6
  const IGNORE_NODES_NEXT = ['tableCell', 'tableHeader', 'tableRow', 'listItem', 'caption'];
7
7
  const IGNORE_NODE_DESCENDANTS = ['listItem', 'taskList', 'decisionList', 'mediaSingle'];
@@ -68,11 +68,10 @@ export const nodeDecorations = (newState, from, to) => {
68
68
  const ignore_nodes = editorExperiment('advanced_layouts', true) ? IGNORE_NODES_NEXT : IGNORE_NODES;
69
69
  newState.doc.nodesBetween(docFrom, docTo, (node, pos, parent, index) => {
70
70
  let depth = 0;
71
- let anchorName;
72
71
  const shouldDescend = shouldDescendIntoNode(node);
73
- anchorName = getNodeAnchor(node);
72
+ const anchorName = getNodeAnchor(node);
73
+ const nodeTypeWithLevel = getNodeTypeWithLevel(node);
74
74
  if (editorExperiment('nested-dnd', true)) {
75
- var _anchorName;
76
75
  // Doesn't descend into a node
77
76
  if (node.isInline) {
78
77
  return false;
@@ -81,22 +80,18 @@ export const nodeDecorations = (newState, from, to) => {
81
80
  if (shouldIgnoreNode(node, ignore_nodes, depth, parent)) {
82
81
  return shouldDescend; //skip over, don't consider it a valid depth
83
82
  }
84
- anchorName = (_anchorName = anchorName) !== null && _anchorName !== void 0 ? _anchorName : `--node-anchor-${node.type.name}-${pos}`;
85
- } else {
86
- var _anchorName2;
87
- anchorName = (_anchorName2 = anchorName) !== null && _anchorName2 !== void 0 ? _anchorName2 : `--node-anchor-${node.type.name}-${index}`;
88
83
  }
89
84
  const anchorStyles = `anchor-name: ${anchorName};`;
90
- const subType = node.attrs.level ? `-${node.attrs.level}` : '';
91
85
  decs.push(Decoration.node(pos, pos + node.nodeSize, {
92
86
  style: anchorStyles,
93
87
  ['data-drag-handler-anchor-name']: anchorName,
94
- ['data-drag-handler-node-type']: node.type.name + subType,
88
+ ['data-drag-handler-node-type']: nodeTypeWithLevel,
95
89
  ['data-drag-handler-anchor-depth']: `${depth}`
96
90
  }, {
97
91
  type: TYPE_NODE_DEC,
98
92
  anchorName,
99
- nodeType: node.type.name
93
+ nodeType: node.type.name,
94
+ nodeTypeWithLevel
100
95
  }));
101
96
  return shouldDescend && depth < getNestedDepth();
102
97
  });
@@ -1,7 +1,5 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
- import ReactDOM from 'react-dom';
3
2
  import uuid from 'uuid';
4
- import { fg } from '@atlaskit/platform-feature-flags';
5
3
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
6
4
  export const TYPE_DROP_TARGET_DEC = 'drop-target-decoration';
7
5
  export const TYPE_HANDLE_DEC = 'drag-handle';
@@ -11,6 +9,10 @@ export const getNodeAnchor = node => {
11
9
  const handleId = ObjHash.getForNode(node);
12
10
  return `--node-anchor-${node.type.name}-${handleId}`;
13
11
  };
12
+ export const getNodeTypeWithLevel = node => {
13
+ const subType = node.attrs.level ? `-${node.attrs.level}` : '';
14
+ return node.type.name + subType;
15
+ };
14
16
  class ObjHash {
15
17
  static getForNode(node) {
16
18
  if (this.caching.has(node)) {
@@ -27,13 +29,9 @@ export const unmountDecorations = (nodeViewPortalProviderAPI, selector, key) =>
27
29
  // as it was more responsive and causes less re-rendering
28
30
  const decorationsToRemove = document.querySelectorAll(`[${selector}="true"]`);
29
31
  decorationsToRemove.forEach(el => {
30
- if (fg('platform_editor_react18_plugin_portalprovider')) {
31
- const nodeKey = el.getAttribute(key);
32
- if (nodeKey) {
33
- nodeViewPortalProviderAPI.remove(nodeKey);
34
- }
35
- } else {
36
- ReactDOM.unmountComponentAtNode(el);
32
+ const nodeKey = el.getAttribute(key);
33
+ if (nodeKey) {
34
+ nodeViewPortalProviderAPI.remove(nodeKey);
37
35
  }
38
36
  });
39
37
  };
@@ -1,9 +1,7 @@
1
1
  import { createElement } from 'react';
2
2
  import { bind } from 'bind-event-listener';
3
- import ReactDOM from 'react-dom';
4
3
  import uuid from 'uuid';
5
4
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
6
- import { fg } from '@atlaskit/platform-feature-flags';
7
5
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
8
6
  import { DragHandle } from '../ui/drag-handle';
9
7
  import { TYPE_HANDLE_DEC, TYPE_NODE_DEC, unmountDecorations } from './decorations-common';
@@ -63,29 +61,16 @@ export const dragHandleDecoration = (api, formatMessage, pos, anchorName, nodeTy
63
61
  // There are times when global clear: "both" styles are applied to this decoration causing jumpiness
64
62
  // due to margins applied to other nodes eg. Headings
65
63
  element.style.clear = 'unset';
66
- if (fg('platform_editor_react18_plugin_portalprovider')) {
67
- nodeViewPortalProviderAPI.render(() => /*#__PURE__*/createElement(DragHandle, {
68
- view,
69
- api,
70
- formatMessage,
71
- getPos,
72
- anchorName,
73
- nodeType,
74
- handleOptions,
75
- isTopLevelNode
76
- }), element, key);
77
- } else {
78
- ReactDOM.render( /*#__PURE__*/createElement(DragHandle, {
79
- view,
80
- api,
81
- formatMessage,
82
- getPos,
83
- anchorName,
84
- nodeType,
85
- handleOptions,
86
- isTopLevelNode
87
- }), element);
88
- }
64
+ nodeViewPortalProviderAPI.render(() => /*#__PURE__*/createElement(DragHandle, {
65
+ view,
66
+ api,
67
+ formatMessage,
68
+ getPos,
69
+ anchorName,
70
+ nodeType,
71
+ handleOptions,
72
+ isTopLevelNode
73
+ }), element, key);
89
74
  return element;
90
75
  }, {
91
76
  side: -1,
@@ -1,9 +1,7 @@
1
1
  import { createElement } from 'react';
2
- import ReactDOM from 'react-dom';
3
2
  import uuid from 'uuid';
4
3
  import { isEmptyParagraph } from '@atlaskit/editor-common/utils';
5
4
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
6
- import { fg } from '@atlaskit/platform-feature-flags';
7
5
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
8
6
  import { nodeMargins } from '../ui/consts';
9
7
  import { DropTarget, EDITOR_BLOCK_CONTROLS_DROP_INDICATOR_GAP, EDITOR_BLOCK_CONTROLS_DROP_INDICATOR_OFFSET } from '../ui/drop-target';
@@ -92,21 +90,12 @@ export const createDropTargetDecoration = (pos, props, nodeViewPortalProviderAPI
92
90
  element.style.setProperty(EDITOR_BLOCK_CONTROLS_DROP_INDICATOR_OFFSET, `${offset}px`);
93
91
  element.style.setProperty(EDITOR_BLOCK_CONTROLS_DROP_INDICATOR_GAP, `${gap}px`);
94
92
  element.style.setProperty('display', 'block');
95
- if (fg('platform_editor_react18_plugin_portalprovider')) {
96
- nodeViewPortalProviderAPI.render(() => /*#__PURE__*/createElement(DropTarget, {
97
- ...props,
98
- getPos,
99
- anchorRectCache,
100
- isSameLayout
101
- }), element, key);
102
- } else {
103
- ReactDOM.render( /*#__PURE__*/createElement(DropTarget, {
104
- ...props,
105
- getPos,
106
- anchorRectCache,
107
- isSameLayout
108
- }), element);
109
- }
93
+ nodeViewPortalProviderAPI.render(() => /*#__PURE__*/createElement(DropTarget, {
94
+ ...props,
95
+ getPos,
96
+ anchorRectCache,
97
+ isSameLayout
98
+ }), element, key);
110
99
  return element;
111
100
  }, {
112
101
  type: TYPE_DROP_TARGET_DEC,
@@ -127,19 +116,11 @@ export const createLayoutDropTargetDecoration = (pos, props, nodeViewPortalProvi
127
116
  element.setAttribute('data-blocks-drop-target-container', 'true');
128
117
  element.setAttribute('data-blocks-drop-target-key', key);
129
118
  element.style.clear = 'unset';
130
- if (fg('platform_editor_react18_plugin_portalprovider')) {
131
- nodeViewPortalProviderAPI.render(() => /*#__PURE__*/createElement(DropTargetLayout, {
132
- ...props,
133
- getPos,
134
- anchorRectCache
135
- }), element, key);
136
- } else {
137
- ReactDOM.render( /*#__PURE__*/createElement(DropTargetLayout, {
138
- ...props,
139
- getPos,
140
- anchorRectCache
141
- }), element);
142
- }
119
+ nodeViewPortalProviderAPI.render(() => /*#__PURE__*/createElement(DropTargetLayout, {
120
+ ...props,
121
+ getPos,
122
+ anchorRectCache
123
+ }), element, key);
143
124
  return element;
144
125
  }, {
145
126
  type: TYPE_DROP_TARGET_DEC
@@ -18,7 +18,9 @@ import { dropTargetDecorations, findDropTargetDecs } from './decorations-drop-ta
18
18
  import { handleMouseOver } from './handle-mouse-over';
19
19
  import { boundKeydownHandler } from './keymap';
20
20
  import { defaultActiveAnchorTracker } from './utils/active-anchor-tracker';
21
+ import { getMultiSelectAnalyticsAttributes } from './utils/analytics';
21
22
  import { AnchorRectCache, isAnchorSupported } from './utils/anchor-utils';
23
+ import { getSelectedSlicePosition } from './utils/selection';
22
24
  import { getTrMetadata } from './utils/transactions';
23
25
  export const key = new PluginKey('blockControls');
24
26
  const EDITOR_BLOCKS_DRAG_INIT = 'Editor Blocks Drag Initialization Time';
@@ -73,7 +75,8 @@ const destroyFn = (api, editorView) => {
73
75
  api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({
74
76
  tr
75
77
  }) => {
76
- if (editorExperiment('platform_editor_element_drag_and_drop_multiselect', true)) {
78
+ const isMultiSelect = editorExperiment('platform_editor_element_drag_and_drop_multiselect', true);
79
+ if (isMultiSelect) {
77
80
  var _api$blockControls, _api$selection;
78
81
  const {
79
82
  multiSelectDnD
@@ -96,6 +99,13 @@ const destroyFn = (api, editorView) => {
96
99
  // if no drop targets are rendered, assume that drop is invalid
97
100
  if (location.current.dropTargets.length === 0) {
98
101
  var _api$analytics2;
102
+ let nodeTypes, hasSelectedMultipleNodes;
103
+ if (isMultiSelect && api) {
104
+ const position = getSelectedSlicePosition(start, tr, api);
105
+ const attributes = getMultiSelectAnalyticsAttributes(tr, position.from, position.to);
106
+ nodeTypes = attributes.nodeTypes;
107
+ hasSelectedMultipleNodes = attributes.hasSelectedMultipleNodes;
108
+ }
99
109
  const resolvedMovingNode = tr.doc.resolve(start);
100
110
  const maybeNode = resolvedMovingNode.nodeAfter;
101
111
  api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions.attachAnalyticsEvent({
@@ -105,7 +115,11 @@ const destroyFn = (api, editorView) => {
105
115
  actionSubjectId: ACTION_SUBJECT_ID.ELEMENT_DRAG_HANDLE,
106
116
  attributes: {
107
117
  nodeDepth: resolvedMovingNode.depth,
108
- nodeType: (maybeNode === null || maybeNode === void 0 ? void 0 : maybeNode.type.name) || ''
118
+ nodeType: (maybeNode === null || maybeNode === void 0 ? void 0 : maybeNode.type.name) || '',
119
+ ...(isMultiSelect && {
120
+ nodeTypes,
121
+ hasSelectedMultipleNodes
122
+ })
109
123
  }
110
124
  })(tr);
111
125
  }
@@ -131,6 +145,10 @@ const initialState = {
131
145
  isPMDragging: false,
132
146
  multiSelectDnD: undefined
133
147
  };
148
+ export const getDecorations = state => {
149
+ var _key$getState;
150
+ return (_key$getState = key.getState(state)) === null || _key$getState === void 0 ? void 0 : _key$getState.decorations;
151
+ };
134
152
  export const newApply = (api, formatMessage, tr, currentState, newState, flags, nodeViewPortalProviderAPI, anchorRectCache) => {
135
153
  var _meta$activeNode, _activeNode, _activeNode2, _meta$activeNode$hand, _meta$isDragging, _meta$isDragging2, _meta$editorHeight, _meta$editorWidthLeft, _meta$editorWidthRigh, _meta$isPMDragging, _meta$multiSelectDnD;
136
154
  let {
@@ -452,12 +470,12 @@ export const createPlugin = (api, getIntl, nodeViewPortalProviderAPI) => {
452
470
  },
453
471
  props: {
454
472
  decorations: state => {
455
- var _api$editorDisabled, _api$editorDisabled$s, _key$getState;
473
+ var _api$editorDisabled, _api$editorDisabled$s, _key$getState2;
456
474
  const isDisabled = api === null || api === void 0 ? void 0 : (_api$editorDisabled = api.editorDisabled) === null || _api$editorDisabled === void 0 ? void 0 : (_api$editorDisabled$s = _api$editorDisabled.sharedState.currentState()) === null || _api$editorDisabled$s === void 0 ? void 0 : _api$editorDisabled$s.editorDisabled;
457
475
  if (isDisabled) {
458
476
  return;
459
477
  }
460
- return (_key$getState = key.getState(state)) === null || _key$getState === void 0 ? void 0 : _key$getState.decorations;
478
+ return (_key$getState2 = key.getState(state)) === null || _key$getState2 === void 0 ? void 0 : _key$getState2.decorations;
461
479
  },
462
480
  handleDOMEvents: {
463
481
  drop(view, event) {
@@ -532,12 +550,12 @@ export const createPlugin = (api, getIntl, nodeViewPortalProviderAPI) => {
532
550
  }));
533
551
  },
534
552
  dragend(view) {
535
- var _key$getState2;
553
+ var _key$getState3;
536
554
  const {
537
555
  state,
538
556
  dispatch
539
557
  } = view;
540
- if ((_key$getState2 = key.getState(state)) !== null && _key$getState2 !== void 0 && _key$getState2.isPMDragging) {
558
+ if ((_key$getState3 = key.getState(state)) !== null && _key$getState3 !== void 0 && _key$getState3.isPMDragging) {
541
559
  dispatch(state.tr.setMeta(key, {
542
560
  isPMDragging: false
543
561
  }));
@@ -1,5 +1,6 @@
1
1
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
2
- export const attachMoveNodeAnalytics = (tr, inputMethod, fromDepth, fromNodeType, toDepth, toNodeType, isSameParent, api) => {
2
+ import { fg } from '@atlaskit/platform-feature-flags';
3
+ export const attachMoveNodeAnalytics = (tr, inputMethod, fromDepth, fromNodeType, toDepth, toNodeType, isSameParent, api, fromNodeTypes, hasSelectedMultipleNodes) => {
3
4
  var _api$analytics, _api$analytics$action;
4
5
  return api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : (_api$analytics$action = _api$analytics.actions) === null || _api$analytics$action === void 0 ? void 0 : _api$analytics$action.attachAnalyticsEvent({
5
6
  eventType: EVENT_TYPE.TRACK,
@@ -9,6 +10,8 @@ export const attachMoveNodeAnalytics = (tr, inputMethod, fromDepth, fromNodeType
9
10
  attributes: {
10
11
  nodeDepth: fromDepth,
11
12
  nodeType: fromNodeType,
13
+ nodeTypes: fromNodeTypes,
14
+ hasSelectedMultipleNodes,
12
15
  destinationNodeDepth: toDepth,
13
16
  destinationNodeType: toNodeType,
14
17
  isSameParent: isSameParent,
@@ -16,15 +19,40 @@ export const attachMoveNodeAnalytics = (tr, inputMethod, fromDepth, fromNodeType
16
19
  }
17
20
  })(tr);
18
21
  };
19
- export const fireInsertLayoutAnalytics = (tr, api) => {
22
+ export const fireInsertLayoutAnalytics = (tr, api, nodeTypes, hasSelectedMultipleNodes) => {
20
23
  var _api$analytics2, _api$analytics2$actio;
21
24
  api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : (_api$analytics2$actio = _api$analytics2.actions) === null || _api$analytics2$actio === void 0 ? void 0 : _api$analytics2$actio.attachAnalyticsEvent({
22
25
  action: ACTION.INSERTED,
23
26
  actionSubject: ACTION_SUBJECT.DOCUMENT,
24
27
  actionSubjectId: ACTION_SUBJECT_ID.LAYOUT,
25
28
  attributes: {
26
- inputMethod: INPUT_METHOD.DRAG_AND_DROP
29
+ inputMethod: INPUT_METHOD.DRAG_AND_DROP,
30
+ nodeTypes,
31
+ hasSelectedMultipleNodes
27
32
  },
28
33
  eventType: EVENT_TYPE.TRACK
29
34
  })(tr);
35
+ };
36
+
37
+ /**
38
+ * Given a range, return distinctive types of node and whether there are multiple nodes in the range
39
+ */
40
+ export const getMultiSelectAnalyticsAttributes = (tr, anchor, head) => {
41
+ const nodeTypes = [];
42
+ const from = Math.min(anchor, head);
43
+ const to = Math.max(anchor, head);
44
+ tr.doc.nodesBetween(from, to, (node, pos) => {
45
+ if (pos < from) {
46
+ // ignore parent node
47
+ return true;
48
+ }
49
+ nodeTypes.push(node.type.name);
50
+
51
+ // only care about the top level (relatively in the range) nodes
52
+ return false;
53
+ });
54
+ return {
55
+ nodeTypes: fg('platform_editor_track_node_types') ? [...new Set(nodeTypes)].sort().join(',') : undefined,
56
+ hasSelectedMultipleNodes: nodeTypes.length > 1
57
+ };
30
58
  };
@@ -2,7 +2,7 @@ export const getMultiSelectionIfPosInside = (api, pos) => {
2
2
  var _api$blockControls;
3
3
  const {
4
4
  multiSelectDnD
5
- } = (api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.sharedState.currentState()) || {};
5
+ } = ((_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.sharedState.currentState()) || {};
6
6
  if (multiSelectDnD && multiSelectDnD.anchor >= 0 && multiSelectDnD.head >= 0) {
7
7
  const multiFrom = Math.min(multiSelectDnD.anchor, multiSelectDnD.head);
8
8
  const multiTo = Math.max(multiSelectDnD.anchor, multiSelectDnD.head);
@@ -14,4 +14,25 @@ export const getMultiSelectionIfPosInside = (api, pos) => {
14
14
  } : {};
15
15
  }
16
16
  return {};
17
+ };
18
+
19
+ /**
20
+ *
21
+ * @returns from and to positions of the selected content (after expansion)
22
+ */
23
+ export const getSelectedSlicePosition = (handlePos, tr, api) => {
24
+ var _activeNode$nodeSize;
25
+ const {
26
+ anchor,
27
+ head
28
+ } = getMultiSelectionIfPosInside(api, handlePos);
29
+ const inSelection = anchor !== undefined && head !== undefined;
30
+ const from = inSelection ? Math.min(anchor, head) : handlePos;
31
+ const activeNode = tr.doc.nodeAt(handlePos);
32
+ const activeNodeEndPos = handlePos + ((_activeNode$nodeSize = activeNode === null || activeNode === void 0 ? void 0 : activeNode.nodeSize) !== null && _activeNode$nodeSize !== void 0 ? _activeNode$nodeSize : 1);
33
+ const to = inSelection ? Math.max(anchor, head) : activeNodeEndPos;
34
+ return {
35
+ from,
36
+ to
37
+ };
17
38
  };