@atlaskit/editor-plugin-block-menu 6.0.27 → 6.0.29
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 +16 -0
- package/dist/cjs/editor-commands/transformNode.js +6 -3
- package/dist/cjs/pm-plugins/experiences/block-menu-experiences.js +102 -59
- package/dist/cjs/pm-plugins/experiences/experience-check-utils.js +147 -0
- package/dist/cjs/ui/delete-button.js +3 -1
- package/dist/cjs/ui/move-down.js +3 -1
- package/dist/cjs/ui/move-up.js +3 -1
- package/dist/es2019/editor-commands/transformNode.js +6 -3
- package/dist/es2019/pm-plugins/experiences/block-menu-experiences.js +100 -59
- package/dist/es2019/pm-plugins/experiences/experience-check-utils.js +125 -0
- package/dist/es2019/ui/delete-button.js +3 -1
- package/dist/es2019/ui/move-down.js +4 -2
- package/dist/es2019/ui/move-up.js +4 -2
- package/dist/esm/editor-commands/transformNode.js +6 -3
- package/dist/esm/pm-plugins/experiences/block-menu-experiences.js +102 -58
- package/dist/esm/pm-plugins/experiences/experience-check-utils.js +140 -0
- package/dist/esm/ui/delete-button.js +3 -1
- package/dist/esm/ui/move-down.js +4 -2
- package/dist/esm/ui/move-up.js +4 -2
- package/dist/types/pm-plugins/experiences/experience-check-utils.d.ts +62 -0
- package/dist/types-ts4.5/pm-plugins/experiences/experience-check-utils.d.ts +62 -0
- package/package.json +3 -3
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { bind } from 'bind-event-listener';
|
|
2
|
-
import { ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
|
|
3
|
-
import {
|
|
2
|
+
import { ACTION, ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
|
|
3
|
+
import { BLOCK_MENU_ACTION_TEST_ID } from '@atlaskit/editor-common/block-menu';
|
|
4
|
+
import { Experience, EXPERIENCE_ID, ExperienceCheckDomMutation, ExperienceCheckTimeout, getPopupContainerFromEditorView } from '@atlaskit/editor-common/experiences';
|
|
4
5
|
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
5
6
|
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
7
|
+
import { getParentDOMAtSelection, handleDeleteDomMutation, handleMenuOpenDomMutation, handleMoveDomMutation, isBlockMenuVisible, isDragHandleElement } from './experience-check-utils';
|
|
6
8
|
const TIMEOUT_DURATION = 1000;
|
|
7
9
|
const pluginKey = new PluginKey('blockMenuExperiences');
|
|
8
10
|
const START_METHOD = {
|
|
@@ -17,13 +19,14 @@ export const getBlockMenuExperiencesPlugin = ({
|
|
|
17
19
|
refs,
|
|
18
20
|
dispatchAnalyticsEvent
|
|
19
21
|
}) => {
|
|
20
|
-
let
|
|
21
|
-
let
|
|
22
|
+
let popupTargetEl;
|
|
23
|
+
let editorView;
|
|
22
24
|
const getPopupsTarget = () => {
|
|
23
|
-
if (!
|
|
24
|
-
|
|
25
|
+
if (!popupTargetEl) {
|
|
26
|
+
var _editorView;
|
|
27
|
+
popupTargetEl = refs.popupsMountPoint || getPopupContainerFromEditorView((_editorView = editorView) === null || _editorView === void 0 ? void 0 : _editorView.dom);
|
|
25
28
|
}
|
|
26
|
-
return
|
|
29
|
+
return popupTargetEl;
|
|
27
30
|
};
|
|
28
31
|
const blockMenuOpenExperience = new Experience(EXPERIENCE_ID.MENU_OPEN, {
|
|
29
32
|
actionSubjectId: ACTION_SUBJECT_ID.BLOCK_MENU,
|
|
@@ -31,16 +34,7 @@ export const getBlockMenuExperiencesPlugin = ({
|
|
|
31
34
|
checks: [new ExperienceCheckTimeout({
|
|
32
35
|
durationMs: TIMEOUT_DURATION
|
|
33
36
|
}), new ExperienceCheckDomMutation({
|
|
34
|
-
onDomMutation:
|
|
35
|
-
mutations
|
|
36
|
-
}) => {
|
|
37
|
-
if (mutations.some(isBlockMenuAddedInMutation)) {
|
|
38
|
-
return {
|
|
39
|
-
status: 'success'
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
return undefined;
|
|
43
|
-
},
|
|
37
|
+
onDomMutation: handleMenuOpenDomMutation,
|
|
44
38
|
observeConfig: () => ({
|
|
45
39
|
target: getPopupsTarget(),
|
|
46
40
|
options: {
|
|
@@ -49,26 +43,87 @@ export const getBlockMenuExperiencesPlugin = ({
|
|
|
49
43
|
})
|
|
50
44
|
})]
|
|
51
45
|
});
|
|
46
|
+
const actionObserveConfig = () => ({
|
|
47
|
+
target: getParentDOMAtSelection(editorView),
|
|
48
|
+
options: {
|
|
49
|
+
childList: true
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const blockMoveUpExperience = new Experience(EXPERIENCE_ID.MENU_ACTION, {
|
|
53
|
+
action: ACTION.MOVED,
|
|
54
|
+
actionSubjectId: ACTION_SUBJECT_ID.MOVE_UP_BLOCK,
|
|
55
|
+
dispatchAnalyticsEvent,
|
|
56
|
+
checks: [new ExperienceCheckTimeout({
|
|
57
|
+
durationMs: TIMEOUT_DURATION
|
|
58
|
+
}), new ExperienceCheckDomMutation({
|
|
59
|
+
onDomMutation: handleMoveDomMutation,
|
|
60
|
+
observeConfig: actionObserveConfig
|
|
61
|
+
})]
|
|
62
|
+
});
|
|
63
|
+
const blockMoveDownExperience = new Experience(EXPERIENCE_ID.MENU_ACTION, {
|
|
64
|
+
action: ACTION.MOVED,
|
|
65
|
+
actionSubjectId: ACTION_SUBJECT_ID.MOVE_DOWN_BLOCK,
|
|
66
|
+
dispatchAnalyticsEvent,
|
|
67
|
+
checks: [new ExperienceCheckTimeout({
|
|
68
|
+
durationMs: TIMEOUT_DURATION
|
|
69
|
+
}), new ExperienceCheckDomMutation({
|
|
70
|
+
onDomMutation: handleMoveDomMutation,
|
|
71
|
+
observeConfig: actionObserveConfig
|
|
72
|
+
})]
|
|
73
|
+
});
|
|
74
|
+
const blockDeleteExperience = new Experience(EXPERIENCE_ID.MENU_ACTION, {
|
|
75
|
+
action: ACTION.DELETED,
|
|
76
|
+
actionSubjectId: ACTION_SUBJECT_ID.DELETE_BLOCK,
|
|
77
|
+
dispatchAnalyticsEvent,
|
|
78
|
+
checks: [new ExperienceCheckTimeout({
|
|
79
|
+
durationMs: TIMEOUT_DURATION
|
|
80
|
+
}), new ExperienceCheckDomMutation({
|
|
81
|
+
onDomMutation: handleDeleteDomMutation,
|
|
82
|
+
observeConfig: actionObserveConfig
|
|
83
|
+
})]
|
|
84
|
+
});
|
|
85
|
+
const handleMenuOpened = method => {
|
|
86
|
+
// Don't start if block menu is already visible
|
|
87
|
+
if (isBlockMenuVisible(getPopupsTarget())) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
blockMenuOpenExperience.start({
|
|
91
|
+
method
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
const handleItemActioned = target => {
|
|
95
|
+
const button = target.closest('button[data-testid]');
|
|
96
|
+
if (!button || !(button instanceof HTMLButtonElement) || button.disabled || button.getAttribute('aria-disabled') === 'true') {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const testId = button.dataset.testid;
|
|
100
|
+
if (!testId) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
switch (testId) {
|
|
104
|
+
case BLOCK_MENU_ACTION_TEST_ID.MOVE_UP:
|
|
105
|
+
blockMoveUpExperience.start();
|
|
106
|
+
break;
|
|
107
|
+
case BLOCK_MENU_ACTION_TEST_ID.MOVE_DOWN:
|
|
108
|
+
blockMoveDownExperience.start();
|
|
109
|
+
break;
|
|
110
|
+
case BLOCK_MENU_ACTION_TEST_ID.DELETE:
|
|
111
|
+
blockDeleteExperience.start();
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
52
115
|
const unbindClickListener = bind(document, {
|
|
53
116
|
type: 'click',
|
|
54
117
|
listener: event => {
|
|
55
|
-
if (!(event.target instanceof Element)) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
118
|
const target = event.target;
|
|
59
|
-
|
|
60
|
-
// Check if the click is on a drag handle
|
|
61
|
-
if (!isDragHandleElement(target)) {
|
|
119
|
+
if (!(target instanceof HTMLElement)) {
|
|
62
120
|
return;
|
|
63
121
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
122
|
+
if (isDragHandleElement(target)) {
|
|
123
|
+
handleMenuOpened(START_METHOD.DRAG_HANDLE_CLICK);
|
|
124
|
+
} else {
|
|
125
|
+
handleItemActioned(target);
|
|
68
126
|
}
|
|
69
|
-
blockMenuOpenExperience.start({
|
|
70
|
-
method: START_METHOD.DRAG_HANDLE_CLICK
|
|
71
|
-
});
|
|
72
127
|
},
|
|
73
128
|
options: {
|
|
74
129
|
capture: true
|
|
@@ -77,20 +132,14 @@ export const getBlockMenuExperiencesPlugin = ({
|
|
|
77
132
|
const unbindKeydownListener = bind(document, {
|
|
78
133
|
type: 'keydown',
|
|
79
134
|
listener: event => {
|
|
80
|
-
|
|
135
|
+
const target = event.target;
|
|
136
|
+
if (!(target instanceof HTMLElement)) {
|
|
81
137
|
return;
|
|
82
138
|
}
|
|
83
|
-
const target = event.target;
|
|
84
139
|
|
|
85
140
|
// Check if Enter or Space is pressed on a drag handle
|
|
86
141
|
if ((event.key === 'Enter' || event.key === ' ') && isDragHandleElement(target)) {
|
|
87
|
-
|
|
88
|
-
if (isBlockMenuVisible(getPopupsTarget())) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
blockMenuOpenExperience.start({
|
|
92
|
-
method: START_METHOD.KEYBOARD
|
|
93
|
-
});
|
|
142
|
+
handleMenuOpened(START_METHOD.KEYBOARD);
|
|
94
143
|
}
|
|
95
144
|
|
|
96
145
|
// Abort on Escape key if block menu is not yet visible
|
|
@@ -106,35 +155,27 @@ export const getBlockMenuExperiencesPlugin = ({
|
|
|
106
155
|
});
|
|
107
156
|
return new SafePlugin({
|
|
108
157
|
key: pluginKey,
|
|
109
|
-
view:
|
|
110
|
-
|
|
158
|
+
view: view => {
|
|
159
|
+
editorView = view;
|
|
111
160
|
return {
|
|
112
161
|
destroy: () => {
|
|
113
162
|
blockMenuOpenExperience.abort({
|
|
114
163
|
reason: ABORT_REASON.EDITOR_DESTROYED
|
|
115
164
|
});
|
|
165
|
+
blockMoveUpExperience.abort({
|
|
166
|
+
reason: ABORT_REASON.EDITOR_DESTROYED
|
|
167
|
+
});
|
|
168
|
+
blockMoveDownExperience.abort({
|
|
169
|
+
reason: ABORT_REASON.EDITOR_DESTROYED
|
|
170
|
+
});
|
|
171
|
+
blockDeleteExperience.abort({
|
|
172
|
+
reason: ABORT_REASON.EDITOR_DESTROYED
|
|
173
|
+
});
|
|
174
|
+
editorView = undefined;
|
|
116
175
|
unbindClickListener();
|
|
117
176
|
unbindKeydownListener();
|
|
118
177
|
}
|
|
119
178
|
};
|
|
120
179
|
}
|
|
121
180
|
});
|
|
122
|
-
};
|
|
123
|
-
const isBlockMenuAddedInMutation = ({
|
|
124
|
-
type,
|
|
125
|
-
addedNodes
|
|
126
|
-
}) => {
|
|
127
|
-
return type === 'childList' && [...addedNodes].some(isBlockMenuWithinNode);
|
|
128
|
-
};
|
|
129
|
-
const isBlockMenuWithinNode = node => {
|
|
130
|
-
return popupWithNestedElement(node, '[data-testid="editor-block-menu"]') !== null;
|
|
131
|
-
};
|
|
132
|
-
const isDragHandleElement = element => {
|
|
133
|
-
return !!(element !== null && element !== void 0 && element.closest('[data-editor-block-ctrl-drag-handle]'));
|
|
134
|
-
};
|
|
135
|
-
const isBlockMenuVisible = popupsTarget => {
|
|
136
|
-
if (!popupsTarget) {
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
return popupWithNestedElement(popupsTarget, '[data-testid="editor-block-menu"]') !== null;
|
|
140
181
|
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { popupWithNestedElement } from '@atlaskit/editor-common/experiences';
|
|
2
|
+
/**
|
|
3
|
+
* Checks if the given element or any of its ancestors is a drag handle element.
|
|
4
|
+
*
|
|
5
|
+
* @param element - The DOM element to check.
|
|
6
|
+
* @returns True if the element is a drag handle, false otherwise.
|
|
7
|
+
*/
|
|
8
|
+
export const isDragHandleElement = element => {
|
|
9
|
+
return !!(element !== null && element !== void 0 && element.closest('[data-editor-block-ctrl-drag-handle]'));
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Checks if the block menu is currently visible within the provided popups target element.
|
|
14
|
+
*
|
|
15
|
+
* @param popupsTarget - The container element for popups.
|
|
16
|
+
* @returns True if the block menu is visible, false otherwise.
|
|
17
|
+
*/
|
|
18
|
+
export const isBlockMenuVisible = popupsTarget => {
|
|
19
|
+
if (!popupsTarget) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return popupWithNestedElement(popupsTarget, '[data-testid="editor-block-menu"]') !== null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Gets the parent DOM element at the starting position of the current selection
|
|
27
|
+
* from the provided editor view.
|
|
28
|
+
*
|
|
29
|
+
* @param editorView - The editor view from which to get the parent DOM element
|
|
30
|
+
* @returns The parent HTMLElement at the selection start, or null if not found
|
|
31
|
+
*/
|
|
32
|
+
export const getParentDOMAtSelection = editorView => {
|
|
33
|
+
if (!editorView) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const {
|
|
37
|
+
selection
|
|
38
|
+
} = editorView.state;
|
|
39
|
+
const from = selection.from;
|
|
40
|
+
const nodeDOM = editorView.nodeDOM(from);
|
|
41
|
+
if (nodeDOM instanceof HTMLElement) {
|
|
42
|
+
return nodeDOM.parentElement;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
};
|
|
46
|
+
const isBlockMenuAddedInMutation = ({
|
|
47
|
+
type,
|
|
48
|
+
addedNodes
|
|
49
|
+
}) => {
|
|
50
|
+
return type === 'childList' && [...addedNodes].some(isBlockMenuWithinNode);
|
|
51
|
+
};
|
|
52
|
+
const isBlockMenuWithinNode = node => {
|
|
53
|
+
return popupWithNestedElement(node, '[data-testid="editor-block-menu"]') !== null;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Handles DOM mutations to determine if the block menu was opened
|
|
58
|
+
*
|
|
59
|
+
* This function looks for mutations that indicate the block menu
|
|
60
|
+
* has been added to the DOM.
|
|
61
|
+
*
|
|
62
|
+
* @param mutations - The list of DOM mutations to evaluate
|
|
63
|
+
* @returns An ExperienceCheckResult indicating success if the menu was opened, otherwise undefined
|
|
64
|
+
*/
|
|
65
|
+
export const handleMenuOpenDomMutation = ({
|
|
66
|
+
mutations
|
|
67
|
+
}) => {
|
|
68
|
+
// Look for a mutation that added the block menu
|
|
69
|
+
for (const mutation of mutations) {
|
|
70
|
+
if (isBlockMenuAddedInMutation(mutation)) {
|
|
71
|
+
return {
|
|
72
|
+
status: 'success'
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Handles DOM mutations to determine if a move action was performed
|
|
81
|
+
*
|
|
82
|
+
* Move actions typically produce two mutations: one where nodes are removed
|
|
83
|
+
* from their original location, and another where the same number of nodes are
|
|
84
|
+
* added to a new location. This function checks for that pattern.
|
|
85
|
+
*
|
|
86
|
+
* @param mutations - The list of DOM mutations to evaluate
|
|
87
|
+
* @returns An ExperienceCheckResult indicating success if a move was detected, otherwise undefined
|
|
88
|
+
*/
|
|
89
|
+
export const handleMoveDomMutation = ({
|
|
90
|
+
mutations
|
|
91
|
+
}) => {
|
|
92
|
+
const removeMutation = mutations.find(m => m.type === 'childList' && m.removedNodes.length > 0);
|
|
93
|
+
const addMutation = mutations.find(m => m.type === 'childList' && m.addedNodes.length > 0);
|
|
94
|
+
if (removeMutation && addMutation && removeMutation.removedNodes.length === addMutation.addedNodes.length) {
|
|
95
|
+
return {
|
|
96
|
+
status: 'success'
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Handles DOM mutations to determine if a delete action was performed
|
|
104
|
+
*
|
|
105
|
+
* Delete actions typically produce a single mutation where nodes are removed
|
|
106
|
+
* from the DOM without any corresponding additions. This function checks for
|
|
107
|
+
* that specific pattern.
|
|
108
|
+
*
|
|
109
|
+
* @param mutations - The list of DOM mutations to evaluate
|
|
110
|
+
* @returns An ExperienceCheckResult indicating success if a delete was detected, otherwise undefined
|
|
111
|
+
*/
|
|
112
|
+
export const handleDeleteDomMutation = ({
|
|
113
|
+
mutations
|
|
114
|
+
}) => {
|
|
115
|
+
// Delete action produces a single childList mutation with only removedNodes
|
|
116
|
+
const childListMutations = mutations.filter(m => m.type === 'childList');
|
|
117
|
+
|
|
118
|
+
// Check for at least one mutation with removedNodes but no addedNodes
|
|
119
|
+
if (childListMutations.some(m => m.removedNodes.length > 0 && m.addedNodes.length === 0)) {
|
|
120
|
+
return {
|
|
121
|
+
status: 'success'
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useCallback, useEffect } from 'react';
|
|
2
2
|
import { injectIntl, useIntl } from 'react-intl-next';
|
|
3
3
|
import { ACTION, ACTION_SUBJECT, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
|
|
4
|
+
import { BLOCK_MENU_ACTION_TEST_ID } from '@atlaskit/editor-common/block-menu';
|
|
4
5
|
import { blockMenuMessages } from '@atlaskit/editor-common/messages';
|
|
5
6
|
import { deleteSelectedRange, getSourceNodesFromSelectionRange } from '@atlaskit/editor-common/selection';
|
|
6
7
|
import { ToolbarDropdownItem } from '@atlaskit/editor-toolbar';
|
|
@@ -101,7 +102,8 @@ const DeleteDropdownItemContent = ({
|
|
|
101
102
|
color: "var(--ds-icon-danger, #C9372C)",
|
|
102
103
|
label: ""
|
|
103
104
|
}),
|
|
104
|
-
onClick: onClick
|
|
105
|
+
onClick: onClick,
|
|
106
|
+
testId: BLOCK_MENU_ACTION_TEST_ID.DELETE
|
|
105
107
|
}, /*#__PURE__*/React.createElement(Text, {
|
|
106
108
|
as: "span",
|
|
107
109
|
color: "color.text.danger"
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { injectIntl, useIntl } from 'react-intl-next';
|
|
3
3
|
import { getDocument } from '@atlaskit/browser-apis';
|
|
4
4
|
import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
|
|
5
|
+
import { BLOCK_MENU_ACTION_TEST_ID } from '@atlaskit/editor-common/block-menu';
|
|
5
6
|
import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
|
|
6
7
|
import { blockMenuMessages as messages } from '@atlaskit/editor-common/messages';
|
|
7
8
|
import { DIRECTION } from '@atlaskit/editor-common/types';
|
|
@@ -69,7 +70,8 @@ const MoveDownDropdownItemContent = ({
|
|
|
69
70
|
elemBefore: /*#__PURE__*/React.createElement(ArrowDownIcon, {
|
|
70
71
|
label: ""
|
|
71
72
|
}),
|
|
72
|
-
isDisabled: !canMoveDown
|
|
73
|
+
isDisabled: !canMoveDown,
|
|
74
|
+
testId: BLOCK_MENU_ACTION_TEST_ID.MOVE_DOWN
|
|
73
75
|
}, formatMessage(messages.moveDownBlock));
|
|
74
76
|
};
|
|
75
77
|
export const MoveDownDropdownItem = injectIntl(MoveDownDropdownItemContent);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { injectIntl, useIntl } from 'react-intl-next';
|
|
3
3
|
import { getDocument } from '@atlaskit/browser-apis';
|
|
4
4
|
import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
|
|
5
|
+
import { BLOCK_MENU_ACTION_TEST_ID } from '@atlaskit/editor-common/block-menu';
|
|
5
6
|
import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
|
|
6
7
|
import { blockMenuMessages as messages } from '@atlaskit/editor-common/messages';
|
|
7
8
|
import { DIRECTION } from '@atlaskit/editor-common/types';
|
|
@@ -67,7 +68,8 @@ const MoveUpDropdownItemContent = ({
|
|
|
67
68
|
elemBefore: /*#__PURE__*/React.createElement(ArrowUpIcon, {
|
|
68
69
|
label: ""
|
|
69
70
|
}),
|
|
70
|
-
isDisabled: !canMoveUp
|
|
71
|
+
isDisabled: !canMoveUp,
|
|
72
|
+
testId: BLOCK_MENU_ACTION_TEST_ID.MOVE_UP
|
|
71
73
|
}, formatMessage(messages.moveUpBlock));
|
|
72
74
|
};
|
|
73
75
|
export const MoveUpDropdownItem = injectIntl(MoveUpDropdownItemContent);
|
|
@@ -31,6 +31,9 @@ export var transformNode = function transformNode(api) {
|
|
|
31
31
|
var typeName = node.type.name;
|
|
32
32
|
sourceNodeTypes[typeName] = (sourceNodeTypes[typeName] || 0) + 1;
|
|
33
33
|
});
|
|
34
|
+
|
|
35
|
+
// Check if source node is empty paragraph or heading
|
|
36
|
+
var isEmptyLine = sourceNodes.length === 1 && (sourceNodes[0].type === nodes.paragraph || sourceNodes[0].type === nodes.heading) && (sourceNodes[0].content.size === 0 || sourceNodes[0].textContent.trim() === '');
|
|
34
37
|
var resultNodes = convertNodesToTargetType({
|
|
35
38
|
sourceNodes: sourceNodes,
|
|
36
39
|
targetNodeType: targetType,
|
|
@@ -69,12 +72,12 @@ export var transformNode = function transformNode(api) {
|
|
|
69
72
|
}
|
|
70
73
|
stopMeasure(measureId, function (duration, startTime) {
|
|
71
74
|
var _api$analytics;
|
|
72
|
-
api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || (_api$analytics = _api$analytics.actions) === null || _api$analytics === void 0 || _api$analytics.
|
|
75
|
+
api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || (_api$analytics = _api$analytics.actions) === null || _api$analytics === void 0 || _api$analytics.attachAnalyticsEvent({
|
|
73
76
|
action: ACTION.TRANSFORMED,
|
|
74
77
|
actionSubject: ACTION_SUBJECT.ELEMENT,
|
|
75
78
|
attributes: {
|
|
76
79
|
duration: duration,
|
|
77
|
-
|
|
80
|
+
isEmptyLine: isEmptyLine,
|
|
78
81
|
isNested: isNested,
|
|
79
82
|
sourceNodesCount: sourceNodes.length,
|
|
80
83
|
sourceNodesCountByType: sourceNodeTypes,
|
|
@@ -85,7 +88,7 @@ export var transformNode = function transformNode(api) {
|
|
|
85
88
|
inputMethod: INPUT_METHOD.BLOCK_MENU
|
|
86
89
|
},
|
|
87
90
|
eventType: EVENT_TYPE.TRACK
|
|
88
|
-
});
|
|
91
|
+
})(tr);
|
|
89
92
|
});
|
|
90
93
|
return tr;
|
|
91
94
|
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
2
1
|
import { bind } from 'bind-event-listener';
|
|
3
|
-
import { ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
|
|
4
|
-
import {
|
|
2
|
+
import { ACTION, ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
|
|
3
|
+
import { BLOCK_MENU_ACTION_TEST_ID } from '@atlaskit/editor-common/block-menu';
|
|
4
|
+
import { Experience, EXPERIENCE_ID, ExperienceCheckDomMutation, ExperienceCheckTimeout, getPopupContainerFromEditorView } from '@atlaskit/editor-common/experiences';
|
|
5
5
|
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
6
6
|
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
7
|
+
import { getParentDOMAtSelection, handleDeleteDomMutation, handleMenuOpenDomMutation, handleMoveDomMutation, isBlockMenuVisible, isDragHandleElement } from './experience-check-utils';
|
|
7
8
|
var TIMEOUT_DURATION = 1000;
|
|
8
9
|
var pluginKey = new PluginKey('blockMenuExperiences');
|
|
9
10
|
var START_METHOD = {
|
|
@@ -17,13 +18,14 @@ var ABORT_REASON = {
|
|
|
17
18
|
export var getBlockMenuExperiencesPlugin = function getBlockMenuExperiencesPlugin(_ref) {
|
|
18
19
|
var refs = _ref.refs,
|
|
19
20
|
dispatchAnalyticsEvent = _ref.dispatchAnalyticsEvent;
|
|
20
|
-
var
|
|
21
|
-
var
|
|
21
|
+
var popupTargetEl;
|
|
22
|
+
var editorView;
|
|
22
23
|
var getPopupsTarget = function getPopupsTarget() {
|
|
23
|
-
if (!
|
|
24
|
-
|
|
24
|
+
if (!popupTargetEl) {
|
|
25
|
+
var _editorView;
|
|
26
|
+
popupTargetEl = refs.popupsMountPoint || getPopupContainerFromEditorView((_editorView = editorView) === null || _editorView === void 0 ? void 0 : _editorView.dom);
|
|
25
27
|
}
|
|
26
|
-
return
|
|
28
|
+
return popupTargetEl;
|
|
27
29
|
};
|
|
28
30
|
var blockMenuOpenExperience = new Experience(EXPERIENCE_ID.MENU_OPEN, {
|
|
29
31
|
actionSubjectId: ACTION_SUBJECT_ID.BLOCK_MENU,
|
|
@@ -31,15 +33,7 @@ export var getBlockMenuExperiencesPlugin = function getBlockMenuExperiencesPlugi
|
|
|
31
33
|
checks: [new ExperienceCheckTimeout({
|
|
32
34
|
durationMs: TIMEOUT_DURATION
|
|
33
35
|
}), new ExperienceCheckDomMutation({
|
|
34
|
-
onDomMutation:
|
|
35
|
-
var mutations = _ref2.mutations;
|
|
36
|
-
if (mutations.some(isBlockMenuAddedInMutation)) {
|
|
37
|
-
return {
|
|
38
|
-
status: 'success'
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
return undefined;
|
|
42
|
-
},
|
|
36
|
+
onDomMutation: handleMenuOpenDomMutation,
|
|
43
37
|
observeConfig: function observeConfig() {
|
|
44
38
|
return {
|
|
45
39
|
target: getPopupsTarget(),
|
|
@@ -50,26 +44,89 @@ export var getBlockMenuExperiencesPlugin = function getBlockMenuExperiencesPlugi
|
|
|
50
44
|
}
|
|
51
45
|
})]
|
|
52
46
|
});
|
|
47
|
+
var actionObserveConfig = function actionObserveConfig() {
|
|
48
|
+
return {
|
|
49
|
+
target: getParentDOMAtSelection(editorView),
|
|
50
|
+
options: {
|
|
51
|
+
childList: true
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
var blockMoveUpExperience = new Experience(EXPERIENCE_ID.MENU_ACTION, {
|
|
56
|
+
action: ACTION.MOVED,
|
|
57
|
+
actionSubjectId: ACTION_SUBJECT_ID.MOVE_UP_BLOCK,
|
|
58
|
+
dispatchAnalyticsEvent: dispatchAnalyticsEvent,
|
|
59
|
+
checks: [new ExperienceCheckTimeout({
|
|
60
|
+
durationMs: TIMEOUT_DURATION
|
|
61
|
+
}), new ExperienceCheckDomMutation({
|
|
62
|
+
onDomMutation: handleMoveDomMutation,
|
|
63
|
+
observeConfig: actionObserveConfig
|
|
64
|
+
})]
|
|
65
|
+
});
|
|
66
|
+
var blockMoveDownExperience = new Experience(EXPERIENCE_ID.MENU_ACTION, {
|
|
67
|
+
action: ACTION.MOVED,
|
|
68
|
+
actionSubjectId: ACTION_SUBJECT_ID.MOVE_DOWN_BLOCK,
|
|
69
|
+
dispatchAnalyticsEvent: dispatchAnalyticsEvent,
|
|
70
|
+
checks: [new ExperienceCheckTimeout({
|
|
71
|
+
durationMs: TIMEOUT_DURATION
|
|
72
|
+
}), new ExperienceCheckDomMutation({
|
|
73
|
+
onDomMutation: handleMoveDomMutation,
|
|
74
|
+
observeConfig: actionObserveConfig
|
|
75
|
+
})]
|
|
76
|
+
});
|
|
77
|
+
var blockDeleteExperience = new Experience(EXPERIENCE_ID.MENU_ACTION, {
|
|
78
|
+
action: ACTION.DELETED,
|
|
79
|
+
actionSubjectId: ACTION_SUBJECT_ID.DELETE_BLOCK,
|
|
80
|
+
dispatchAnalyticsEvent: dispatchAnalyticsEvent,
|
|
81
|
+
checks: [new ExperienceCheckTimeout({
|
|
82
|
+
durationMs: TIMEOUT_DURATION
|
|
83
|
+
}), new ExperienceCheckDomMutation({
|
|
84
|
+
onDomMutation: handleDeleteDomMutation,
|
|
85
|
+
observeConfig: actionObserveConfig
|
|
86
|
+
})]
|
|
87
|
+
});
|
|
88
|
+
var handleMenuOpened = function handleMenuOpened(method) {
|
|
89
|
+
// Don't start if block menu is already visible
|
|
90
|
+
if (isBlockMenuVisible(getPopupsTarget())) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
blockMenuOpenExperience.start({
|
|
94
|
+
method: method
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
var handleItemActioned = function handleItemActioned(target) {
|
|
98
|
+
var button = target.closest('button[data-testid]');
|
|
99
|
+
if (!button || !(button instanceof HTMLButtonElement) || button.disabled || button.getAttribute('aria-disabled') === 'true') {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
var testId = button.dataset.testid;
|
|
103
|
+
if (!testId) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
switch (testId) {
|
|
107
|
+
case BLOCK_MENU_ACTION_TEST_ID.MOVE_UP:
|
|
108
|
+
blockMoveUpExperience.start();
|
|
109
|
+
break;
|
|
110
|
+
case BLOCK_MENU_ACTION_TEST_ID.MOVE_DOWN:
|
|
111
|
+
blockMoveDownExperience.start();
|
|
112
|
+
break;
|
|
113
|
+
case BLOCK_MENU_ACTION_TEST_ID.DELETE:
|
|
114
|
+
blockDeleteExperience.start();
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
53
118
|
var unbindClickListener = bind(document, {
|
|
54
119
|
type: 'click',
|
|
55
120
|
listener: function listener(event) {
|
|
56
|
-
if (!(event.target instanceof Element)) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
121
|
var target = event.target;
|
|
60
|
-
|
|
61
|
-
// Check if the click is on a drag handle
|
|
62
|
-
if (!isDragHandleElement(target)) {
|
|
122
|
+
if (!(target instanceof HTMLElement)) {
|
|
63
123
|
return;
|
|
64
124
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
125
|
+
if (isDragHandleElement(target)) {
|
|
126
|
+
handleMenuOpened(START_METHOD.DRAG_HANDLE_CLICK);
|
|
127
|
+
} else {
|
|
128
|
+
handleItemActioned(target);
|
|
69
129
|
}
|
|
70
|
-
blockMenuOpenExperience.start({
|
|
71
|
-
method: START_METHOD.DRAG_HANDLE_CLICK
|
|
72
|
-
});
|
|
73
130
|
},
|
|
74
131
|
options: {
|
|
75
132
|
capture: true
|
|
@@ -78,20 +135,14 @@ export var getBlockMenuExperiencesPlugin = function getBlockMenuExperiencesPlugi
|
|
|
78
135
|
var unbindKeydownListener = bind(document, {
|
|
79
136
|
type: 'keydown',
|
|
80
137
|
listener: function listener(event) {
|
|
81
|
-
|
|
138
|
+
var target = event.target;
|
|
139
|
+
if (!(target instanceof HTMLElement)) {
|
|
82
140
|
return;
|
|
83
141
|
}
|
|
84
|
-
var target = event.target;
|
|
85
142
|
|
|
86
143
|
// Check if Enter or Space is pressed on a drag handle
|
|
87
144
|
if ((event.key === 'Enter' || event.key === ' ') && isDragHandleElement(target)) {
|
|
88
|
-
|
|
89
|
-
if (isBlockMenuVisible(getPopupsTarget())) {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
blockMenuOpenExperience.start({
|
|
93
|
-
method: START_METHOD.KEYBOARD
|
|
94
|
-
});
|
|
145
|
+
handleMenuOpened(START_METHOD.KEYBOARD);
|
|
95
146
|
}
|
|
96
147
|
|
|
97
148
|
// Abort on Escape key if block menu is not yet visible
|
|
@@ -107,34 +158,27 @@ export var getBlockMenuExperiencesPlugin = function getBlockMenuExperiencesPlugi
|
|
|
107
158
|
});
|
|
108
159
|
return new SafePlugin({
|
|
109
160
|
key: pluginKey,
|
|
110
|
-
view: function view(
|
|
111
|
-
|
|
161
|
+
view: function view(_view) {
|
|
162
|
+
editorView = _view;
|
|
112
163
|
return {
|
|
113
164
|
destroy: function destroy() {
|
|
114
165
|
blockMenuOpenExperience.abort({
|
|
115
166
|
reason: ABORT_REASON.EDITOR_DESTROYED
|
|
116
167
|
});
|
|
168
|
+
blockMoveUpExperience.abort({
|
|
169
|
+
reason: ABORT_REASON.EDITOR_DESTROYED
|
|
170
|
+
});
|
|
171
|
+
blockMoveDownExperience.abort({
|
|
172
|
+
reason: ABORT_REASON.EDITOR_DESTROYED
|
|
173
|
+
});
|
|
174
|
+
blockDeleteExperience.abort({
|
|
175
|
+
reason: ABORT_REASON.EDITOR_DESTROYED
|
|
176
|
+
});
|
|
177
|
+
editorView = undefined;
|
|
117
178
|
unbindClickListener();
|
|
118
179
|
unbindKeydownListener();
|
|
119
180
|
}
|
|
120
181
|
};
|
|
121
182
|
}
|
|
122
183
|
});
|
|
123
|
-
};
|
|
124
|
-
var isBlockMenuAddedInMutation = function isBlockMenuAddedInMutation(_ref3) {
|
|
125
|
-
var type = _ref3.type,
|
|
126
|
-
addedNodes = _ref3.addedNodes;
|
|
127
|
-
return type === 'childList' && _toConsumableArray(addedNodes).some(isBlockMenuWithinNode);
|
|
128
|
-
};
|
|
129
|
-
var isBlockMenuWithinNode = function isBlockMenuWithinNode(node) {
|
|
130
|
-
return popupWithNestedElement(node, '[data-testid="editor-block-menu"]') !== null;
|
|
131
|
-
};
|
|
132
|
-
var isDragHandleElement = function isDragHandleElement(element) {
|
|
133
|
-
return !!(element !== null && element !== void 0 && element.closest('[data-editor-block-ctrl-drag-handle]'));
|
|
134
|
-
};
|
|
135
|
-
var isBlockMenuVisible = function isBlockMenuVisible(popupsTarget) {
|
|
136
|
-
if (!popupsTarget) {
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
return popupWithNestedElement(popupsTarget, '[data-testid="editor-block-menu"]') !== null;
|
|
140
184
|
};
|