@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.
- package/CHANGELOG.md +18 -0
- package/dist/cjs/editor-commands/move-node.js +79 -43
- package/dist/cjs/editor-commands/move-to-layout.js +23 -11
- package/dist/cjs/editor-commands/show-drag-handle.js +89 -3
- package/dist/cjs/pm-plugins/decorations-anchor.js +5 -10
- package/dist/cjs/pm-plugins/decorations-common.js +8 -10
- package/dist/cjs/pm-plugins/decorations-drag-handle.js +4 -19
- package/dist/cjs/pm-plugins/decorations-drop-target.js +8 -25
- package/dist/cjs/pm-plugins/main.js +28 -8
- package/dist/cjs/pm-plugins/utils/analytics.js +66 -0
- package/dist/cjs/pm-plugins/utils/selection.js +22 -2
- package/dist/cjs/ui/drag-handle.js +38 -10
- package/dist/es2019/editor-commands/move-node.js +76 -40
- package/dist/es2019/editor-commands/move-to-layout.js +23 -11
- package/dist/es2019/editor-commands/show-drag-handle.js +88 -3
- package/dist/es2019/pm-plugins/decorations-anchor.js +6 -11
- package/dist/es2019/pm-plugins/decorations-common.js +7 -9
- package/dist/es2019/pm-plugins/decorations-drag-handle.js +10 -25
- package/dist/es2019/pm-plugins/decorations-drop-target.js +11 -30
- package/dist/es2019/pm-plugins/main.js +24 -6
- package/dist/es2019/pm-plugins/utils/{fire-analytics.js → analytics.js} +31 -3
- package/dist/es2019/pm-plugins/utils/selection.js +22 -1
- package/dist/es2019/ui/drag-handle.js +34 -4
- package/dist/esm/editor-commands/move-node.js +80 -44
- package/dist/esm/editor-commands/move-to-layout.js +23 -11
- package/dist/esm/editor-commands/show-drag-handle.js +88 -2
- package/dist/esm/pm-plugins/decorations-anchor.js +6 -11
- package/dist/esm/pm-plugins/decorations-common.js +7 -9
- package/dist/esm/pm-plugins/decorations-drag-handle.js +4 -19
- package/dist/esm/pm-plugins/decorations-drop-target.js +8 -25
- package/dist/esm/pm-plugins/main.js +27 -7
- package/dist/esm/pm-plugins/utils/{fire-analytics.js → analytics.js} +32 -3
- package/dist/esm/pm-plugins/utils/selection.js +21 -1
- package/dist/esm/ui/drag-handle.js +37 -5
- package/dist/types/blockControlsPluginType.d.ts +1 -0
- package/dist/types/editor-commands/move-to-layout.d.ts +1 -0
- package/dist/types/editor-commands/show-drag-handle.d.ts +1 -1
- package/dist/types/pm-plugins/decorations-common.d.ts +1 -0
- package/dist/types/pm-plugins/main.d.ts +1 -0
- package/dist/types/pm-plugins/utils/analytics.d.ts +12 -0
- package/dist/types/pm-plugins/utils/selection.d.ts +9 -0
- package/dist/types-ts4.5/blockControlsPluginType.d.ts +1 -0
- package/dist/types-ts4.5/editor-commands/move-to-layout.d.ts +1 -0
- package/dist/types-ts4.5/editor-commands/show-drag-handle.d.ts +1 -1
- package/dist/types-ts4.5/pm-plugins/decorations-common.d.ts +1 -0
- package/dist/types-ts4.5/pm-plugins/main.d.ts +1 -0
- package/dist/types-ts4.5/pm-plugins/utils/analytics.d.ts +12 -0
- package/dist/types-ts4.5/pm-plugins/utils/selection.d.ts +9 -0
- package/package.json +10 -7
- package/dist/cjs/pm-plugins/utils/fire-analytics.js +0 -36
- package/dist/types/pm-plugins/utils/fire-analytics.d.ts +0 -5
- 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
|
-
|
|
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 (
|
|
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 {
|
|
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
|
-
|
|
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']:
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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$
|
|
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$
|
|
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$
|
|
553
|
+
var _key$getState3;
|
|
536
554
|
const {
|
|
537
555
|
state,
|
|
538
556
|
dispatch
|
|
539
557
|
} = view;
|
|
540
|
-
if ((_key$
|
|
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
|
-
|
|
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
|
-
} = (
|
|
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
|
};
|