@atlaskit/editor-plugin-block-menu 6.0.26 → 6.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @atlaskit/editor-plugin-block-menu
2
2
 
3
+ ## 6.0.27
4
+
5
+ ### Patch Changes
6
+
7
+ - [`4cafa94a73e1e`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/4cafa94a73e1e) -
8
+ Add delete track event for block menu
9
+ - Updated dependencies
10
+
3
11
  ## 6.0.26
4
12
 
5
13
  ### Patch Changes
@@ -10,6 +10,7 @@ var _editorActions = require("./editor-actions");
10
10
  var _isTransformToTargetDisabled = require("./editor-actions/isTransformToTargetDisabled");
11
11
  var _formatNode2 = require("./editor-commands/formatNode");
12
12
  var _transformNode2 = require("./editor-commands/transformNode");
13
+ var _blockMenuExperiences = require("./pm-plugins/experiences/block-menu-experiences");
13
14
  var _keymap = require("./pm-plugins/keymap");
14
15
  var _main = require("./pm-plugins/main");
15
16
  var _blockMenu = _interopRequireDefault(require("./ui/block-menu"));
@@ -24,6 +25,7 @@ var blockMenuPlugin = exports.blockMenuPlugin = function blockMenuPlugin(_ref) {
24
25
  api: api,
25
26
  config: config
26
27
  }));
28
+ var refs = {};
27
29
  return {
28
30
  name: 'blockMenu',
29
31
  pmPlugins: function pmPlugins() {
@@ -37,6 +39,17 @@ var blockMenuPlugin = exports.blockMenuPlugin = function blockMenuPlugin(_ref) {
37
39
  plugin: function plugin() {
38
40
  return (0, _keymap.keymapPlugin)(api, config);
39
41
  }
42
+ }, {
43
+ name: 'blockMenuExperiences',
44
+ plugin: function plugin() {
45
+ return (0, _blockMenuExperiences.getBlockMenuExperiencesPlugin)({
46
+ refs: refs,
47
+ dispatchAnalyticsEvent: function dispatchAnalyticsEvent(payload) {
48
+ var _api$analytics;
49
+ return api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || (_api$analytics = _api$analytics.actions) === null || _api$analytics === void 0 ? void 0 : _api$analytics.fireAnalyticsEvent(payload);
50
+ }
51
+ });
52
+ }
40
53
  }];
41
54
  },
42
55
  actions: {
@@ -94,6 +107,7 @@ var blockMenuPlugin = exports.blockMenuPlugin = function blockMenuPlugin(_ref) {
94
107
  popupsMountPoint = _ref2.popupsMountPoint,
95
108
  popupsBoundariesElement = _ref2.popupsBoundariesElement,
96
109
  popupsScrollableElement = _ref2.popupsScrollableElement;
110
+ refs.popupsMountPoint = popupsMountPoint || undefined;
97
111
  return /*#__PURE__*/_react.default.createElement(_blockMenuProvider.BlockMenuProvider, {
98
112
  api: api,
99
113
  editorView: editorView
@@ -84,6 +84,7 @@ var transformNode = exports.transformNode = function transformNode(api) {
84
84
  isNested: isNested,
85
85
  sourceNodesCount: sourceNodes.length,
86
86
  sourceNodesCountByType: sourceNodeTypes,
87
+ sourceNodeType: sourceNodes.length === 1 ? sourceNodes[0].type.name : 'multiple',
87
88
  startTime: startTime,
88
89
  targetNodeType: targetType.name,
89
90
  outputNodesCount: content.length,
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.getBlockMenuExperiencesPlugin = void 0;
8
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
+ var _bindEventListener = require("bind-event-listener");
10
+ var _analytics = require("@atlaskit/editor-common/analytics");
11
+ var _experiences = require("@atlaskit/editor-common/experiences");
12
+ var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
13
+ var _state = require("@atlaskit/editor-prosemirror/state");
14
+ var TIMEOUT_DURATION = 1000;
15
+ var pluginKey = new _state.PluginKey('blockMenuExperiences');
16
+ var START_METHOD = {
17
+ DRAG_HANDLE_CLICK: 'dragHandleClick',
18
+ KEYBOARD: 'keyboard'
19
+ };
20
+ var ABORT_REASON = {
21
+ USER_CANCELED: 'userCanceled',
22
+ EDITOR_DESTROYED: 'editorDestroyed'
23
+ };
24
+ var getBlockMenuExperiencesPlugin = exports.getBlockMenuExperiencesPlugin = function getBlockMenuExperiencesPlugin(_ref) {
25
+ var refs = _ref.refs,
26
+ dispatchAnalyticsEvent = _ref.dispatchAnalyticsEvent;
27
+ var targetEl;
28
+ var editorViewEl;
29
+ var getPopupsTarget = function getPopupsTarget() {
30
+ if (!targetEl) {
31
+ targetEl = refs.popupsMountPoint || (0, _experiences.getPopupContainerFromEditorView)(editorViewEl);
32
+ }
33
+ return targetEl;
34
+ };
35
+ var blockMenuOpenExperience = new _experiences.Experience(_experiences.EXPERIENCE_ID.MENU_OPEN, {
36
+ actionSubjectId: _analytics.ACTION_SUBJECT_ID.BLOCK_MENU,
37
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
38
+ checks: [new _experiences.ExperienceCheckTimeout({
39
+ durationMs: TIMEOUT_DURATION
40
+ }), new _experiences.ExperienceCheckDomMutation({
41
+ onDomMutation: function onDomMutation(_ref2) {
42
+ var mutations = _ref2.mutations;
43
+ if (mutations.some(isBlockMenuAddedInMutation)) {
44
+ return {
45
+ status: 'success'
46
+ };
47
+ }
48
+ return undefined;
49
+ },
50
+ observeConfig: function observeConfig() {
51
+ return {
52
+ target: getPopupsTarget(),
53
+ options: {
54
+ childList: true
55
+ }
56
+ };
57
+ }
58
+ })]
59
+ });
60
+ var unbindClickListener = (0, _bindEventListener.bind)(document, {
61
+ type: 'click',
62
+ listener: function listener(event) {
63
+ if (!(event.target instanceof Element)) {
64
+ return;
65
+ }
66
+ var target = event.target;
67
+
68
+ // Check if the click is on a drag handle
69
+ if (!isDragHandleElement(target)) {
70
+ return;
71
+ }
72
+
73
+ // Don't start if block menu is already visible
74
+ if (isBlockMenuVisible(getPopupsTarget())) {
75
+ return;
76
+ }
77
+ blockMenuOpenExperience.start({
78
+ method: START_METHOD.DRAG_HANDLE_CLICK
79
+ });
80
+ },
81
+ options: {
82
+ capture: true
83
+ }
84
+ });
85
+ var unbindKeydownListener = (0, _bindEventListener.bind)(document, {
86
+ type: 'keydown',
87
+ listener: function listener(event) {
88
+ if (!(event.target instanceof Element)) {
89
+ return;
90
+ }
91
+ var target = event.target;
92
+
93
+ // Check if Enter or Space is pressed on a drag handle
94
+ if ((event.key === 'Enter' || event.key === ' ') && isDragHandleElement(target)) {
95
+ // Don't start if block menu is already visible
96
+ if (isBlockMenuVisible(getPopupsTarget())) {
97
+ return;
98
+ }
99
+ blockMenuOpenExperience.start({
100
+ method: START_METHOD.KEYBOARD
101
+ });
102
+ }
103
+
104
+ // Abort on Escape key if block menu is not yet visible
105
+ if (event.key === 'Escape' && !isBlockMenuVisible(getPopupsTarget())) {
106
+ blockMenuOpenExperience.abort({
107
+ reason: ABORT_REASON.USER_CANCELED
108
+ });
109
+ }
110
+ },
111
+ options: {
112
+ capture: true
113
+ }
114
+ });
115
+ return new _safePlugin.SafePlugin({
116
+ key: pluginKey,
117
+ view: function view(editorView) {
118
+ editorViewEl = editorView.dom;
119
+ return {
120
+ destroy: function destroy() {
121
+ blockMenuOpenExperience.abort({
122
+ reason: ABORT_REASON.EDITOR_DESTROYED
123
+ });
124
+ unbindClickListener();
125
+ unbindKeydownListener();
126
+ }
127
+ };
128
+ }
129
+ });
130
+ };
131
+ var isBlockMenuAddedInMutation = function isBlockMenuAddedInMutation(_ref3) {
132
+ var type = _ref3.type,
133
+ addedNodes = _ref3.addedNodes;
134
+ return type === 'childList' && (0, _toConsumableArray2.default)(addedNodes).some(isBlockMenuWithinNode);
135
+ };
136
+ var isBlockMenuWithinNode = function isBlockMenuWithinNode(node) {
137
+ return (0, _experiences.popupWithNestedElement)(node, '[data-testid="editor-block-menu"]') !== null;
138
+ };
139
+ var isDragHandleElement = function isDragHandleElement(element) {
140
+ return !!(element !== null && element !== void 0 && element.closest('[data-editor-block-ctrl-drag-handle]'));
141
+ };
142
+ var isBlockMenuVisible = function isBlockMenuVisible(popupsTarget) {
143
+ if (!popupsTarget) {
144
+ return false;
145
+ }
146
+ return (0, _experiences.popupWithNestedElement)(popupsTarget, '[data-testid="editor-block-menu"]') !== null;
147
+ };
@@ -34,7 +34,30 @@ var DeleteDropdownItemContent = function DeleteDropdownItemContent(_ref) {
34
34
  eventType: _analytics.EVENT_TYPE.UI
35
35
  };
36
36
  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(payload)(tr);
37
- (0, _selection.deleteSelectedRange)(tr, api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.sharedState.currentState()) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.preservedSelection);
37
+
38
+ // Extract node information before deletion
39
+ var preservedSelection = api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.sharedState.currentState()) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.preservedSelection;
40
+ var selection = preservedSelection || tr.selection;
41
+ var sourceNodes = (0, _selection.getSourceNodesFromSelectionRange)(tr, selection);
42
+ var nodeCount = sourceNodes.length;
43
+
44
+ // Fire node deletion analytics event if nodes are being deleted
45
+ if (nodeCount > 0) {
46
+ var _api$analytics2;
47
+ var nodeType = sourceNodes.length === 1 ? sourceNodes[0].type.name : 'multiple';
48
+ var nodeDeletedPayload = {
49
+ action: _analytics.ACTION.DELETED,
50
+ actionSubject: _analytics.ACTION_SUBJECT.ELEMENT,
51
+ attributes: {
52
+ inputMethod: _analytics.INPUT_METHOD.BLOCK_MENU,
53
+ nodeType: nodeType,
54
+ nodeCount: nodeCount
55
+ },
56
+ eventType: _analytics.EVENT_TYPE.TRACK
57
+ };
58
+ api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || (_api$analytics2 = _api$analytics2.actions) === null || _api$analytics2 === void 0 || _api$analytics2.attachAnalyticsEvent(nodeDeletedPayload)(tr);
59
+ }
60
+ (0, _selection.deleteSelectedRange)(tr, preservedSelection);
38
61
  api === null || api === void 0 || (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || (_api$blockControls2 = _api$blockControls2.commands) === null || _api$blockControls2 === void 0 || _api$blockControls2.toggleBlockMenu({
39
62
  closeMenu: true
40
63
  })({
@@ -3,6 +3,7 @@ import { createBlockMenuRegistry } from './editor-actions';
3
3
  import { isTransformToTargetDisabled } from './editor-actions/isTransformToTargetDisabled';
4
4
  import { formatNode } from './editor-commands/formatNode';
5
5
  import { transformNode } from './editor-commands/transformNode';
6
+ import { getBlockMenuExperiencesPlugin } from './pm-plugins/experiences/block-menu-experiences';
6
7
  import { keymapPlugin } from './pm-plugins/keymap';
7
8
  import { blockMenuPluginKey, createPlugin } from './pm-plugins/main';
8
9
  import BlockMenu from './ui/block-menu';
@@ -18,6 +19,7 @@ export const blockMenuPlugin = ({
18
19
  api,
19
20
  config
20
21
  }));
22
+ const refs = {};
21
23
  return {
22
24
  name: 'blockMenu',
23
25
  pmPlugins() {
@@ -27,6 +29,15 @@ export const blockMenuPlugin = ({
27
29
  }, {
28
30
  name: 'blockMenuKeymap',
29
31
  plugin: () => keymapPlugin(api, config)
32
+ }, {
33
+ name: 'blockMenuExperiences',
34
+ plugin: () => getBlockMenuExperiencesPlugin({
35
+ refs,
36
+ dispatchAnalyticsEvent: payload => {
37
+ var _api$analytics, _api$analytics$action;
38
+ 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.fireAnalyticsEvent(payload);
39
+ }
40
+ })
30
41
  }];
31
42
  },
32
43
  actions: {
@@ -85,6 +96,7 @@ export const blockMenuPlugin = ({
85
96
  popupsBoundariesElement,
86
97
  popupsScrollableElement
87
98
  }) {
99
+ refs.popupsMountPoint = popupsMountPoint || undefined;
88
100
  return /*#__PURE__*/React.createElement(BlockMenuProvider, {
89
101
  api: api,
90
102
  editorView: editorView
@@ -76,6 +76,7 @@ export const transformNode = api => (targetType, metadata) => ({
76
76
  isNested,
77
77
  sourceNodesCount: sourceNodes.length,
78
78
  sourceNodesCountByType: sourceNodeTypes,
79
+ sourceNodeType: sourceNodes.length === 1 ? sourceNodes[0].type.name : 'multiple',
79
80
  startTime,
80
81
  targetNodeType: targetType.name,
81
82
  outputNodesCount: content.length,
@@ -0,0 +1,140 @@
1
+ import { bind } from 'bind-event-listener';
2
+ import { ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
3
+ import { Experience, EXPERIENCE_ID, ExperienceCheckDomMutation, ExperienceCheckTimeout, getPopupContainerFromEditorView, popupWithNestedElement } from '@atlaskit/editor-common/experiences';
4
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
5
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
6
+ const TIMEOUT_DURATION = 1000;
7
+ const pluginKey = new PluginKey('blockMenuExperiences');
8
+ const START_METHOD = {
9
+ DRAG_HANDLE_CLICK: 'dragHandleClick',
10
+ KEYBOARD: 'keyboard'
11
+ };
12
+ const ABORT_REASON = {
13
+ USER_CANCELED: 'userCanceled',
14
+ EDITOR_DESTROYED: 'editorDestroyed'
15
+ };
16
+ export const getBlockMenuExperiencesPlugin = ({
17
+ refs,
18
+ dispatchAnalyticsEvent
19
+ }) => {
20
+ let targetEl;
21
+ let editorViewEl;
22
+ const getPopupsTarget = () => {
23
+ if (!targetEl) {
24
+ targetEl = refs.popupsMountPoint || getPopupContainerFromEditorView(editorViewEl);
25
+ }
26
+ return targetEl;
27
+ };
28
+ const blockMenuOpenExperience = new Experience(EXPERIENCE_ID.MENU_OPEN, {
29
+ actionSubjectId: ACTION_SUBJECT_ID.BLOCK_MENU,
30
+ dispatchAnalyticsEvent,
31
+ checks: [new ExperienceCheckTimeout({
32
+ durationMs: TIMEOUT_DURATION
33
+ }), new ExperienceCheckDomMutation({
34
+ onDomMutation: ({
35
+ mutations
36
+ }) => {
37
+ if (mutations.some(isBlockMenuAddedInMutation)) {
38
+ return {
39
+ status: 'success'
40
+ };
41
+ }
42
+ return undefined;
43
+ },
44
+ observeConfig: () => ({
45
+ target: getPopupsTarget(),
46
+ options: {
47
+ childList: true
48
+ }
49
+ })
50
+ })]
51
+ });
52
+ const unbindClickListener = bind(document, {
53
+ type: 'click',
54
+ listener: event => {
55
+ if (!(event.target instanceof Element)) {
56
+ return;
57
+ }
58
+ const target = event.target;
59
+
60
+ // Check if the click is on a drag handle
61
+ if (!isDragHandleElement(target)) {
62
+ return;
63
+ }
64
+
65
+ // Don't start if block menu is already visible
66
+ if (isBlockMenuVisible(getPopupsTarget())) {
67
+ return;
68
+ }
69
+ blockMenuOpenExperience.start({
70
+ method: START_METHOD.DRAG_HANDLE_CLICK
71
+ });
72
+ },
73
+ options: {
74
+ capture: true
75
+ }
76
+ });
77
+ const unbindKeydownListener = bind(document, {
78
+ type: 'keydown',
79
+ listener: event => {
80
+ if (!(event.target instanceof Element)) {
81
+ return;
82
+ }
83
+ const target = event.target;
84
+
85
+ // Check if Enter or Space is pressed on a drag handle
86
+ if ((event.key === 'Enter' || event.key === ' ') && isDragHandleElement(target)) {
87
+ // Don't start if block menu is already visible
88
+ if (isBlockMenuVisible(getPopupsTarget())) {
89
+ return;
90
+ }
91
+ blockMenuOpenExperience.start({
92
+ method: START_METHOD.KEYBOARD
93
+ });
94
+ }
95
+
96
+ // Abort on Escape key if block menu is not yet visible
97
+ if (event.key === 'Escape' && !isBlockMenuVisible(getPopupsTarget())) {
98
+ blockMenuOpenExperience.abort({
99
+ reason: ABORT_REASON.USER_CANCELED
100
+ });
101
+ }
102
+ },
103
+ options: {
104
+ capture: true
105
+ }
106
+ });
107
+ return new SafePlugin({
108
+ key: pluginKey,
109
+ view: editorView => {
110
+ editorViewEl = editorView.dom;
111
+ return {
112
+ destroy: () => {
113
+ blockMenuOpenExperience.abort({
114
+ reason: ABORT_REASON.EDITOR_DESTROYED
115
+ });
116
+ unbindClickListener();
117
+ unbindKeydownListener();
118
+ }
119
+ };
120
+ }
121
+ });
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
+ };
@@ -1,8 +1,8 @@
1
1
  import React, { useCallback, useEffect } from 'react';
2
2
  import { injectIntl, useIntl } from 'react-intl-next';
3
- import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
3
+ import { ACTION, ACTION_SUBJECT, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
4
4
  import { blockMenuMessages } from '@atlaskit/editor-common/messages';
5
- import { deleteSelectedRange } from '@atlaskit/editor-common/selection';
5
+ import { deleteSelectedRange, getSourceNodesFromSelectionRange } from '@atlaskit/editor-common/selection';
6
6
  import { ToolbarDropdownItem } from '@atlaskit/editor-toolbar';
7
7
  import DeleteIcon from '@atlaskit/icon/core/delete';
8
8
  import { Box } from '@atlaskit/primitives/box';
@@ -28,7 +28,30 @@ const DeleteDropdownItemContent = ({
28
28
  eventType: EVENT_TYPE.UI
29
29
  };
30
30
  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(payload)(tr);
31
- deleteSelectedRange(tr, api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : (_api$blockControls$sh = _api$blockControls.sharedState.currentState()) === null || _api$blockControls$sh === void 0 ? void 0 : _api$blockControls$sh.preservedSelection);
31
+
32
+ // Extract node information before deletion
33
+ const preservedSelection = api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : (_api$blockControls$sh = _api$blockControls.sharedState.currentState()) === null || _api$blockControls$sh === void 0 ? void 0 : _api$blockControls$sh.preservedSelection;
34
+ const selection = preservedSelection || tr.selection;
35
+ const sourceNodes = getSourceNodesFromSelectionRange(tr, selection);
36
+ const nodeCount = sourceNodes.length;
37
+
38
+ // Fire node deletion analytics event if nodes are being deleted
39
+ if (nodeCount > 0) {
40
+ var _api$analytics2, _api$analytics2$actio;
41
+ const nodeType = sourceNodes.length === 1 ? sourceNodes[0].type.name : 'multiple';
42
+ const nodeDeletedPayload = {
43
+ action: ACTION.DELETED,
44
+ actionSubject: ACTION_SUBJECT.ELEMENT,
45
+ attributes: {
46
+ inputMethod: INPUT_METHOD.BLOCK_MENU,
47
+ nodeType,
48
+ nodeCount
49
+ },
50
+ eventType: EVENT_TYPE.TRACK
51
+ };
52
+ 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(nodeDeletedPayload)(tr);
53
+ }
54
+ deleteSelectedRange(tr, preservedSelection);
32
55
  api === null || api === void 0 ? void 0 : (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 ? void 0 : (_api$blockControls2$c = _api$blockControls2.commands) === null || _api$blockControls2$c === void 0 ? void 0 : _api$blockControls2$c.toggleBlockMenu({
33
56
  closeMenu: true
34
57
  })({
@@ -3,6 +3,7 @@ import { createBlockMenuRegistry } from './editor-actions';
3
3
  import { isTransformToTargetDisabled } from './editor-actions/isTransformToTargetDisabled';
4
4
  import { formatNode as _formatNode } from './editor-commands/formatNode';
5
5
  import { transformNode as _transformNode } from './editor-commands/transformNode';
6
+ import { getBlockMenuExperiencesPlugin } from './pm-plugins/experiences/block-menu-experiences';
6
7
  import { keymapPlugin } from './pm-plugins/keymap';
7
8
  import { blockMenuPluginKey, createPlugin } from './pm-plugins/main';
8
9
  import BlockMenu from './ui/block-menu';
@@ -17,6 +18,7 @@ export var blockMenuPlugin = function blockMenuPlugin(_ref) {
17
18
  api: api,
18
19
  config: config
19
20
  }));
21
+ var refs = {};
20
22
  return {
21
23
  name: 'blockMenu',
22
24
  pmPlugins: function pmPlugins() {
@@ -30,6 +32,17 @@ export var blockMenuPlugin = function blockMenuPlugin(_ref) {
30
32
  plugin: function plugin() {
31
33
  return keymapPlugin(api, config);
32
34
  }
35
+ }, {
36
+ name: 'blockMenuExperiences',
37
+ plugin: function plugin() {
38
+ return getBlockMenuExperiencesPlugin({
39
+ refs: refs,
40
+ dispatchAnalyticsEvent: function dispatchAnalyticsEvent(payload) {
41
+ var _api$analytics;
42
+ return api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || (_api$analytics = _api$analytics.actions) === null || _api$analytics === void 0 ? void 0 : _api$analytics.fireAnalyticsEvent(payload);
43
+ }
44
+ });
45
+ }
33
46
  }];
34
47
  },
35
48
  actions: {
@@ -87,6 +100,7 @@ export var blockMenuPlugin = function blockMenuPlugin(_ref) {
87
100
  popupsMountPoint = _ref2.popupsMountPoint,
88
101
  popupsBoundariesElement = _ref2.popupsBoundariesElement,
89
102
  popupsScrollableElement = _ref2.popupsScrollableElement;
103
+ refs.popupsMountPoint = popupsMountPoint || undefined;
90
104
  return /*#__PURE__*/React.createElement(BlockMenuProvider, {
91
105
  api: api,
92
106
  editorView: editorView
@@ -78,6 +78,7 @@ export var transformNode = function transformNode(api) {
78
78
  isNested: isNested,
79
79
  sourceNodesCount: sourceNodes.length,
80
80
  sourceNodesCountByType: sourceNodeTypes,
81
+ sourceNodeType: sourceNodes.length === 1 ? sourceNodes[0].type.name : 'multiple',
81
82
  startTime: startTime,
82
83
  targetNodeType: targetType.name,
83
84
  outputNodesCount: content.length,
@@ -0,0 +1,140 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
+ import { bind } from 'bind-event-listener';
3
+ import { ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
4
+ import { Experience, EXPERIENCE_ID, ExperienceCheckDomMutation, ExperienceCheckTimeout, getPopupContainerFromEditorView, popupWithNestedElement } from '@atlaskit/editor-common/experiences';
5
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
6
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
7
+ var TIMEOUT_DURATION = 1000;
8
+ var pluginKey = new PluginKey('blockMenuExperiences');
9
+ var START_METHOD = {
10
+ DRAG_HANDLE_CLICK: 'dragHandleClick',
11
+ KEYBOARD: 'keyboard'
12
+ };
13
+ var ABORT_REASON = {
14
+ USER_CANCELED: 'userCanceled',
15
+ EDITOR_DESTROYED: 'editorDestroyed'
16
+ };
17
+ export var getBlockMenuExperiencesPlugin = function getBlockMenuExperiencesPlugin(_ref) {
18
+ var refs = _ref.refs,
19
+ dispatchAnalyticsEvent = _ref.dispatchAnalyticsEvent;
20
+ var targetEl;
21
+ var editorViewEl;
22
+ var getPopupsTarget = function getPopupsTarget() {
23
+ if (!targetEl) {
24
+ targetEl = refs.popupsMountPoint || getPopupContainerFromEditorView(editorViewEl);
25
+ }
26
+ return targetEl;
27
+ };
28
+ var blockMenuOpenExperience = new Experience(EXPERIENCE_ID.MENU_OPEN, {
29
+ actionSubjectId: ACTION_SUBJECT_ID.BLOCK_MENU,
30
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
31
+ checks: [new ExperienceCheckTimeout({
32
+ durationMs: TIMEOUT_DURATION
33
+ }), new ExperienceCheckDomMutation({
34
+ onDomMutation: function onDomMutation(_ref2) {
35
+ var mutations = _ref2.mutations;
36
+ if (mutations.some(isBlockMenuAddedInMutation)) {
37
+ return {
38
+ status: 'success'
39
+ };
40
+ }
41
+ return undefined;
42
+ },
43
+ observeConfig: function observeConfig() {
44
+ return {
45
+ target: getPopupsTarget(),
46
+ options: {
47
+ childList: true
48
+ }
49
+ };
50
+ }
51
+ })]
52
+ });
53
+ var unbindClickListener = bind(document, {
54
+ type: 'click',
55
+ listener: function listener(event) {
56
+ if (!(event.target instanceof Element)) {
57
+ return;
58
+ }
59
+ var target = event.target;
60
+
61
+ // Check if the click is on a drag handle
62
+ if (!isDragHandleElement(target)) {
63
+ return;
64
+ }
65
+
66
+ // Don't start if block menu is already visible
67
+ if (isBlockMenuVisible(getPopupsTarget())) {
68
+ return;
69
+ }
70
+ blockMenuOpenExperience.start({
71
+ method: START_METHOD.DRAG_HANDLE_CLICK
72
+ });
73
+ },
74
+ options: {
75
+ capture: true
76
+ }
77
+ });
78
+ var unbindKeydownListener = bind(document, {
79
+ type: 'keydown',
80
+ listener: function listener(event) {
81
+ if (!(event.target instanceof Element)) {
82
+ return;
83
+ }
84
+ var target = event.target;
85
+
86
+ // Check if Enter or Space is pressed on a drag handle
87
+ if ((event.key === 'Enter' || event.key === ' ') && isDragHandleElement(target)) {
88
+ // Don't start if block menu is already visible
89
+ if (isBlockMenuVisible(getPopupsTarget())) {
90
+ return;
91
+ }
92
+ blockMenuOpenExperience.start({
93
+ method: START_METHOD.KEYBOARD
94
+ });
95
+ }
96
+
97
+ // Abort on Escape key if block menu is not yet visible
98
+ if (event.key === 'Escape' && !isBlockMenuVisible(getPopupsTarget())) {
99
+ blockMenuOpenExperience.abort({
100
+ reason: ABORT_REASON.USER_CANCELED
101
+ });
102
+ }
103
+ },
104
+ options: {
105
+ capture: true
106
+ }
107
+ });
108
+ return new SafePlugin({
109
+ key: pluginKey,
110
+ view: function view(editorView) {
111
+ editorViewEl = editorView.dom;
112
+ return {
113
+ destroy: function destroy() {
114
+ blockMenuOpenExperience.abort({
115
+ reason: ABORT_REASON.EDITOR_DESTROYED
116
+ });
117
+ unbindClickListener();
118
+ unbindKeydownListener();
119
+ }
120
+ };
121
+ }
122
+ });
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
+ };
@@ -1,8 +1,8 @@
1
1
  import React, { useCallback, useEffect } from 'react';
2
2
  import { injectIntl, useIntl } from 'react-intl-next';
3
- import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
3
+ import { ACTION, ACTION_SUBJECT, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
4
4
  import { blockMenuMessages } from '@atlaskit/editor-common/messages';
5
- import { deleteSelectedRange } from '@atlaskit/editor-common/selection';
5
+ import { deleteSelectedRange, getSourceNodesFromSelectionRange } from '@atlaskit/editor-common/selection';
6
6
  import { ToolbarDropdownItem } from '@atlaskit/editor-toolbar';
7
7
  import DeleteIcon from '@atlaskit/icon/core/delete';
8
8
  import { Box } from '@atlaskit/primitives/box';
@@ -25,7 +25,30 @@ var DeleteDropdownItemContent = function DeleteDropdownItemContent(_ref) {
25
25
  eventType: EVENT_TYPE.UI
26
26
  };
27
27
  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(payload)(tr);
28
- deleteSelectedRange(tr, api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.sharedState.currentState()) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.preservedSelection);
28
+
29
+ // Extract node information before deletion
30
+ var preservedSelection = api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.sharedState.currentState()) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.preservedSelection;
31
+ var selection = preservedSelection || tr.selection;
32
+ var sourceNodes = getSourceNodesFromSelectionRange(tr, selection);
33
+ var nodeCount = sourceNodes.length;
34
+
35
+ // Fire node deletion analytics event if nodes are being deleted
36
+ if (nodeCount > 0) {
37
+ var _api$analytics2;
38
+ var nodeType = sourceNodes.length === 1 ? sourceNodes[0].type.name : 'multiple';
39
+ var nodeDeletedPayload = {
40
+ action: ACTION.DELETED,
41
+ actionSubject: ACTION_SUBJECT.ELEMENT,
42
+ attributes: {
43
+ inputMethod: INPUT_METHOD.BLOCK_MENU,
44
+ nodeType: nodeType,
45
+ nodeCount: nodeCount
46
+ },
47
+ eventType: EVENT_TYPE.TRACK
48
+ };
49
+ api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || (_api$analytics2 = _api$analytics2.actions) === null || _api$analytics2 === void 0 || _api$analytics2.attachAnalyticsEvent(nodeDeletedPayload)(tr);
50
+ }
51
+ deleteSelectedRange(tr, preservedSelection);
29
52
  api === null || api === void 0 || (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || (_api$blockControls2 = _api$blockControls2.commands) === null || _api$blockControls2 === void 0 || _api$blockControls2.toggleBlockMenu({
30
53
  closeMenu: true
31
54
  })({
@@ -0,0 +1,10 @@
1
+ import { type DispatchAnalyticsEvent } from '@atlaskit/editor-common/analytics';
2
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
3
+ type ExperienceOptions = {
4
+ dispatchAnalyticsEvent: DispatchAnalyticsEvent;
5
+ refs: {
6
+ popupsMountPoint?: HTMLElement;
7
+ };
8
+ };
9
+ export declare const getBlockMenuExperiencesPlugin: ({ refs, dispatchAnalyticsEvent, }: ExperienceOptions) => SafePlugin<any>;
10
+ export {};
@@ -0,0 +1,10 @@
1
+ import { type DispatchAnalyticsEvent } from '@atlaskit/editor-common/analytics';
2
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
3
+ type ExperienceOptions = {
4
+ dispatchAnalyticsEvent: DispatchAnalyticsEvent;
5
+ refs: {
6
+ popupsMountPoint?: HTMLElement;
7
+ };
8
+ };
9
+ export declare const getBlockMenuExperiencesPlugin: ({ refs, dispatchAnalyticsEvent, }: ExperienceOptions) => SafePlugin<any>;
10
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-block-menu",
3
- "version": "6.0.26",
3
+ "version": "6.0.27",
4
4
  "description": "BlockMenu plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -46,9 +46,10 @@
46
46
  "@atlaskit/platform-feature-flags-react": "^0.4.0",
47
47
  "@atlaskit/primitives": "^17.1.0",
48
48
  "@atlaskit/prosemirror-history": "^0.2.0",
49
- "@atlaskit/tmp-editor-statsig": "^17.0.0",
49
+ "@atlaskit/tmp-editor-statsig": "^17.2.0",
50
50
  "@atlaskit/tokens": "^10.1.0",
51
- "@babel/runtime": "^7.0.0"
51
+ "@babel/runtime": "^7.0.0",
52
+ "bind-event-listener": "^3.0.0"
52
53
  },
53
54
  "peerDependencies": {
54
55
  "@atlaskit/editor-common": "^111.9.0",