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

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.28
4
+
5
+ ### Patch Changes
6
+
7
+ - [`957d9e1880c62`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/957d9e1880c62) -
8
+ EDITOR-3806 Block menu action experience tracking
9
+ - Updated dependencies
10
+
3
11
  ## 6.0.27
4
12
 
5
13
  ### Patch Changes
@@ -1,16 +1,16 @@
1
1
  "use strict";
2
2
 
3
- var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
3
  Object.defineProperty(exports, "__esModule", {
5
4
  value: true
6
5
  });
7
6
  exports.getBlockMenuExperiencesPlugin = void 0;
8
- var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
7
  var _bindEventListener = require("bind-event-listener");
10
8
  var _analytics = require("@atlaskit/editor-common/analytics");
9
+ var _blockMenu = require("@atlaskit/editor-common/block-menu");
11
10
  var _experiences = require("@atlaskit/editor-common/experiences");
12
11
  var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
13
12
  var _state = require("@atlaskit/editor-prosemirror/state");
13
+ var _experienceCheckUtils = require("./experience-check-utils");
14
14
  var TIMEOUT_DURATION = 1000;
15
15
  var pluginKey = new _state.PluginKey('blockMenuExperiences');
16
16
  var START_METHOD = {
@@ -24,13 +24,14 @@ var ABORT_REASON = {
24
24
  var getBlockMenuExperiencesPlugin = exports.getBlockMenuExperiencesPlugin = function getBlockMenuExperiencesPlugin(_ref) {
25
25
  var refs = _ref.refs,
26
26
  dispatchAnalyticsEvent = _ref.dispatchAnalyticsEvent;
27
- var targetEl;
28
- var editorViewEl;
27
+ var popupTargetEl;
28
+ var editorView;
29
29
  var getPopupsTarget = function getPopupsTarget() {
30
- if (!targetEl) {
31
- targetEl = refs.popupsMountPoint || (0, _experiences.getPopupContainerFromEditorView)(editorViewEl);
30
+ if (!popupTargetEl) {
31
+ var _editorView;
32
+ popupTargetEl = refs.popupsMountPoint || (0, _experiences.getPopupContainerFromEditorView)((_editorView = editorView) === null || _editorView === void 0 ? void 0 : _editorView.dom);
32
33
  }
33
- return targetEl;
34
+ return popupTargetEl;
34
35
  };
35
36
  var blockMenuOpenExperience = new _experiences.Experience(_experiences.EXPERIENCE_ID.MENU_OPEN, {
36
37
  actionSubjectId: _analytics.ACTION_SUBJECT_ID.BLOCK_MENU,
@@ -38,15 +39,7 @@ var getBlockMenuExperiencesPlugin = exports.getBlockMenuExperiencesPlugin = func
38
39
  checks: [new _experiences.ExperienceCheckTimeout({
39
40
  durationMs: TIMEOUT_DURATION
40
41
  }), 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
- },
42
+ onDomMutation: _experienceCheckUtils.handleMenuOpenDomMutation,
50
43
  observeConfig: function observeConfig() {
51
44
  return {
52
45
  target: getPopupsTarget(),
@@ -57,26 +50,89 @@ var getBlockMenuExperiencesPlugin = exports.getBlockMenuExperiencesPlugin = func
57
50
  }
58
51
  })]
59
52
  });
53
+ var actionObserveConfig = function actionObserveConfig() {
54
+ return {
55
+ target: (0, _experienceCheckUtils.getParentDOMAtSelection)(editorView),
56
+ options: {
57
+ childList: true
58
+ }
59
+ };
60
+ };
61
+ var blockMoveUpExperience = new _experiences.Experience(_experiences.EXPERIENCE_ID.MENU_ACTION, {
62
+ action: _analytics.ACTION.MOVED,
63
+ actionSubjectId: _analytics.ACTION_SUBJECT_ID.MOVE_UP_BLOCK,
64
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
65
+ checks: [new _experiences.ExperienceCheckTimeout({
66
+ durationMs: TIMEOUT_DURATION
67
+ }), new _experiences.ExperienceCheckDomMutation({
68
+ onDomMutation: _experienceCheckUtils.handleMoveDomMutation,
69
+ observeConfig: actionObserveConfig
70
+ })]
71
+ });
72
+ var blockMoveDownExperience = new _experiences.Experience(_experiences.EXPERIENCE_ID.MENU_ACTION, {
73
+ action: _analytics.ACTION.MOVED,
74
+ actionSubjectId: _analytics.ACTION_SUBJECT_ID.MOVE_DOWN_BLOCK,
75
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
76
+ checks: [new _experiences.ExperienceCheckTimeout({
77
+ durationMs: TIMEOUT_DURATION
78
+ }), new _experiences.ExperienceCheckDomMutation({
79
+ onDomMutation: _experienceCheckUtils.handleMoveDomMutation,
80
+ observeConfig: actionObserveConfig
81
+ })]
82
+ });
83
+ var blockDeleteExperience = new _experiences.Experience(_experiences.EXPERIENCE_ID.MENU_ACTION, {
84
+ action: _analytics.ACTION.DELETED,
85
+ actionSubjectId: _analytics.ACTION_SUBJECT_ID.DELETE_BLOCK,
86
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
87
+ checks: [new _experiences.ExperienceCheckTimeout({
88
+ durationMs: TIMEOUT_DURATION
89
+ }), new _experiences.ExperienceCheckDomMutation({
90
+ onDomMutation: _experienceCheckUtils.handleDeleteDomMutation,
91
+ observeConfig: actionObserveConfig
92
+ })]
93
+ });
94
+ var handleMenuOpened = function handleMenuOpened(method) {
95
+ // Don't start if block menu is already visible
96
+ if ((0, _experienceCheckUtils.isBlockMenuVisible)(getPopupsTarget())) {
97
+ return;
98
+ }
99
+ blockMenuOpenExperience.start({
100
+ method: method
101
+ });
102
+ };
103
+ var handleItemActioned = function handleItemActioned(target) {
104
+ var button = target.closest('button[data-testid]');
105
+ if (!button || !(button instanceof HTMLButtonElement) || button.disabled || button.getAttribute('aria-disabled') === 'true') {
106
+ return;
107
+ }
108
+ var testId = button.dataset.testid;
109
+ if (!testId) {
110
+ return;
111
+ }
112
+ switch (testId) {
113
+ case _blockMenu.BLOCK_MENU_ACTION_TEST_ID.MOVE_UP:
114
+ blockMoveUpExperience.start();
115
+ break;
116
+ case _blockMenu.BLOCK_MENU_ACTION_TEST_ID.MOVE_DOWN:
117
+ blockMoveDownExperience.start();
118
+ break;
119
+ case _blockMenu.BLOCK_MENU_ACTION_TEST_ID.DELETE:
120
+ blockDeleteExperience.start();
121
+ break;
122
+ }
123
+ };
60
124
  var unbindClickListener = (0, _bindEventListener.bind)(document, {
61
125
  type: 'click',
62
126
  listener: function listener(event) {
63
- if (!(event.target instanceof Element)) {
64
- return;
65
- }
66
127
  var target = event.target;
67
-
68
- // Check if the click is on a drag handle
69
- if (!isDragHandleElement(target)) {
128
+ if (!(target instanceof HTMLElement)) {
70
129
  return;
71
130
  }
72
-
73
- // Don't start if block menu is already visible
74
- if (isBlockMenuVisible(getPopupsTarget())) {
75
- return;
131
+ if ((0, _experienceCheckUtils.isDragHandleElement)(target)) {
132
+ handleMenuOpened(START_METHOD.DRAG_HANDLE_CLICK);
133
+ } else {
134
+ handleItemActioned(target);
76
135
  }
77
- blockMenuOpenExperience.start({
78
- method: START_METHOD.DRAG_HANDLE_CLICK
79
- });
80
136
  },
81
137
  options: {
82
138
  capture: true
@@ -85,24 +141,18 @@ var getBlockMenuExperiencesPlugin = exports.getBlockMenuExperiencesPlugin = func
85
141
  var unbindKeydownListener = (0, _bindEventListener.bind)(document, {
86
142
  type: 'keydown',
87
143
  listener: function listener(event) {
88
- if (!(event.target instanceof Element)) {
144
+ var target = event.target;
145
+ if (!(target instanceof HTMLElement)) {
89
146
  return;
90
147
  }
91
- var target = event.target;
92
148
 
93
149
  // 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
- });
150
+ if ((event.key === 'Enter' || event.key === ' ') && (0, _experienceCheckUtils.isDragHandleElement)(target)) {
151
+ handleMenuOpened(START_METHOD.KEYBOARD);
102
152
  }
103
153
 
104
154
  // Abort on Escape key if block menu is not yet visible
105
- if (event.key === 'Escape' && !isBlockMenuVisible(getPopupsTarget())) {
155
+ if (event.key === 'Escape' && !(0, _experienceCheckUtils.isBlockMenuVisible)(getPopupsTarget())) {
106
156
  blockMenuOpenExperience.abort({
107
157
  reason: ABORT_REASON.USER_CANCELED
108
158
  });
@@ -114,34 +164,27 @@ var getBlockMenuExperiencesPlugin = exports.getBlockMenuExperiencesPlugin = func
114
164
  });
115
165
  return new _safePlugin.SafePlugin({
116
166
  key: pluginKey,
117
- view: function view(editorView) {
118
- editorViewEl = editorView.dom;
167
+ view: function view(_view) {
168
+ editorView = _view;
119
169
  return {
120
170
  destroy: function destroy() {
121
171
  blockMenuOpenExperience.abort({
122
172
  reason: ABORT_REASON.EDITOR_DESTROYED
123
173
  });
174
+ blockMoveUpExperience.abort({
175
+ reason: ABORT_REASON.EDITOR_DESTROYED
176
+ });
177
+ blockMoveDownExperience.abort({
178
+ reason: ABORT_REASON.EDITOR_DESTROYED
179
+ });
180
+ blockDeleteExperience.abort({
181
+ reason: ABORT_REASON.EDITOR_DESTROYED
182
+ });
183
+ editorView = undefined;
124
184
  unbindClickListener();
125
185
  unbindKeydownListener();
126
186
  }
127
187
  };
128
188
  }
129
189
  });
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
190
  };
@@ -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.isDragHandleElement = exports.isBlockMenuVisible = exports.handleMoveDomMutation = exports.handleMenuOpenDomMutation = exports.handleDeleteDomMutation = exports.getParentDOMAtSelection = void 0;
8
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
+ var _experiences = require("@atlaskit/editor-common/experiences");
10
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
11
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
12
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
13
+ /**
14
+ * Checks if the given element or any of its ancestors is a drag handle element.
15
+ *
16
+ * @param element - The DOM element to check.
17
+ * @returns True if the element is a drag handle, false otherwise.
18
+ */
19
+ var isDragHandleElement = exports.isDragHandleElement = function isDragHandleElement(element) {
20
+ return !!(element !== null && element !== void 0 && element.closest('[data-editor-block-ctrl-drag-handle]'));
21
+ };
22
+
23
+ /**
24
+ * Checks if the block menu is currently visible within the provided popups target element.
25
+ *
26
+ * @param popupsTarget - The container element for popups.
27
+ * @returns True if the block menu is visible, false otherwise.
28
+ */
29
+ var isBlockMenuVisible = exports.isBlockMenuVisible = function isBlockMenuVisible(popupsTarget) {
30
+ if (!popupsTarget) {
31
+ return false;
32
+ }
33
+ return (0, _experiences.popupWithNestedElement)(popupsTarget, '[data-testid="editor-block-menu"]') !== null;
34
+ };
35
+
36
+ /**
37
+ * Gets the parent DOM element at the starting position of the current selection
38
+ * from the provided editor view.
39
+ *
40
+ * @param editorView - The editor view from which to get the parent DOM element
41
+ * @returns The parent HTMLElement at the selection start, or null if not found
42
+ */
43
+ var getParentDOMAtSelection = exports.getParentDOMAtSelection = function getParentDOMAtSelection(editorView) {
44
+ if (!editorView) {
45
+ return null;
46
+ }
47
+ var selection = editorView.state.selection;
48
+ var from = selection.from;
49
+ var nodeDOM = editorView.nodeDOM(from);
50
+ if (nodeDOM instanceof HTMLElement) {
51
+ return nodeDOM.parentElement;
52
+ }
53
+ return null;
54
+ };
55
+ var isBlockMenuAddedInMutation = function isBlockMenuAddedInMutation(_ref) {
56
+ var type = _ref.type,
57
+ addedNodes = _ref.addedNodes;
58
+ return type === 'childList' && (0, _toConsumableArray2.default)(addedNodes).some(isBlockMenuWithinNode);
59
+ };
60
+ var isBlockMenuWithinNode = function isBlockMenuWithinNode(node) {
61
+ return (0, _experiences.popupWithNestedElement)(node, '[data-testid="editor-block-menu"]') !== null;
62
+ };
63
+
64
+ /**
65
+ * Handles DOM mutations to determine if the block menu was opened
66
+ *
67
+ * This function looks for mutations that indicate the block menu
68
+ * has been added to the DOM.
69
+ *
70
+ * @param mutations - The list of DOM mutations to evaluate
71
+ * @returns An ExperienceCheckResult indicating success if the menu was opened, otherwise undefined
72
+ */
73
+ var handleMenuOpenDomMutation = exports.handleMenuOpenDomMutation = function handleMenuOpenDomMutation(_ref2) {
74
+ var mutations = _ref2.mutations;
75
+ // Look for a mutation that added the block menu
76
+ var _iterator = _createForOfIteratorHelper(mutations),
77
+ _step;
78
+ try {
79
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
80
+ var mutation = _step.value;
81
+ if (isBlockMenuAddedInMutation(mutation)) {
82
+ return {
83
+ status: 'success'
84
+ };
85
+ }
86
+ }
87
+ } catch (err) {
88
+ _iterator.e(err);
89
+ } finally {
90
+ _iterator.f();
91
+ }
92
+ return undefined;
93
+ };
94
+
95
+ /**
96
+ * Handles DOM mutations to determine if a move action was performed
97
+ *
98
+ * Move actions typically produce two mutations: one where nodes are removed
99
+ * from their original location, and another where the same number of nodes are
100
+ * added to a new location. This function checks for that pattern.
101
+ *
102
+ * @param mutations - The list of DOM mutations to evaluate
103
+ * @returns An ExperienceCheckResult indicating success if a move was detected, otherwise undefined
104
+ */
105
+ var handleMoveDomMutation = exports.handleMoveDomMutation = function handleMoveDomMutation(_ref3) {
106
+ var mutations = _ref3.mutations;
107
+ var removeMutation = mutations.find(function (m) {
108
+ return m.type === 'childList' && m.removedNodes.length > 0;
109
+ });
110
+ var addMutation = mutations.find(function (m) {
111
+ return m.type === 'childList' && m.addedNodes.length > 0;
112
+ });
113
+ if (removeMutation && addMutation && removeMutation.removedNodes.length === addMutation.addedNodes.length) {
114
+ return {
115
+ status: 'success'
116
+ };
117
+ }
118
+ return undefined;
119
+ };
120
+
121
+ /**
122
+ * Handles DOM mutations to determine if a delete action was performed
123
+ *
124
+ * Delete actions typically produce a single mutation where nodes are removed
125
+ * from the DOM without any corresponding additions. This function checks for
126
+ * that specific pattern.
127
+ *
128
+ * @param mutations - The list of DOM mutations to evaluate
129
+ * @returns An ExperienceCheckResult indicating success if a delete was detected, otherwise undefined
130
+ */
131
+ var handleDeleteDomMutation = exports.handleDeleteDomMutation = function handleDeleteDomMutation(_ref4) {
132
+ var mutations = _ref4.mutations;
133
+ // Delete action produces a single childList mutation with only removedNodes
134
+ var childListMutations = mutations.filter(function (m) {
135
+ return m.type === 'childList';
136
+ });
137
+
138
+ // Check for at least one mutation with removedNodes but no addedNodes
139
+ if (childListMutations.some(function (m) {
140
+ return m.removedNodes.length > 0 && m.addedNodes.length === 0;
141
+ })) {
142
+ return {
143
+ status: 'success'
144
+ };
145
+ }
146
+ return undefined;
147
+ };
@@ -9,6 +9,7 @@ exports.DeleteDropdownItem = void 0;
9
9
  var _react = _interopRequireWildcard(require("react"));
10
10
  var _reactIntlNext = require("react-intl-next");
11
11
  var _analytics = require("@atlaskit/editor-common/analytics");
12
+ var _blockMenu = require("@atlaskit/editor-common/block-menu");
12
13
  var _messages = require("@atlaskit/editor-common/messages");
13
14
  var _selection = require("@atlaskit/editor-common/selection");
14
15
  var _editorToolbar = require("@atlaskit/editor-toolbar");
@@ -105,7 +106,8 @@ var DeleteDropdownItemContent = function DeleteDropdownItemContent(_ref) {
105
106
  color: "var(--ds-icon-danger, #C9372C)",
106
107
  label: ""
107
108
  }),
108
- onClick: onClick
109
+ onClick: onClick,
110
+ testId: _blockMenu.BLOCK_MENU_ACTION_TEST_ID.DELETE
109
111
  }, /*#__PURE__*/_react.default.createElement(_text.default, {
110
112
  as: "span",
111
113
  color: "color.text.danger"
@@ -10,6 +10,7 @@ var _react = _interopRequireWildcard(require("react"));
10
10
  var _reactIntlNext = require("react-intl-next");
11
11
  var _browserApis = require("@atlaskit/browser-apis");
12
12
  var _analytics = require("@atlaskit/editor-common/analytics");
13
+ var _blockMenu = require("@atlaskit/editor-common/block-menu");
13
14
  var _hooks = require("@atlaskit/editor-common/hooks");
14
15
  var _messages = require("@atlaskit/editor-common/messages");
15
16
  var _types = require("@atlaskit/editor-common/types");
@@ -72,7 +73,8 @@ var MoveDownDropdownItemContent = function MoveDownDropdownItemContent(_ref) {
72
73
  elemBefore: /*#__PURE__*/_react.default.createElement(_arrowDown.default, {
73
74
  label: ""
74
75
  }),
75
- isDisabled: !canMoveDown
76
+ isDisabled: !canMoveDown,
77
+ testId: _blockMenu.BLOCK_MENU_ACTION_TEST_ID.MOVE_DOWN
76
78
  }, formatMessage(_messages.blockMenuMessages.moveDownBlock));
77
79
  };
78
80
  var MoveDownDropdownItem = exports.MoveDownDropdownItem = (0, _reactIntlNext.injectIntl)(MoveDownDropdownItemContent);
@@ -10,6 +10,7 @@ var _react = _interopRequireWildcard(require("react"));
10
10
  var _reactIntlNext = require("react-intl-next");
11
11
  var _browserApis = require("@atlaskit/browser-apis");
12
12
  var _analytics = require("@atlaskit/editor-common/analytics");
13
+ var _blockMenu = require("@atlaskit/editor-common/block-menu");
13
14
  var _hooks = require("@atlaskit/editor-common/hooks");
14
15
  var _messages = require("@atlaskit/editor-common/messages");
15
16
  var _types = require("@atlaskit/editor-common/types");
@@ -70,7 +71,8 @@ var MoveUpDropdownItemContent = function MoveUpDropdownItemContent(_ref) {
70
71
  elemBefore: /*#__PURE__*/_react.default.createElement(_arrowUp.default, {
71
72
  label: ""
72
73
  }),
73
- isDisabled: !canMoveUp
74
+ isDisabled: !canMoveUp,
75
+ testId: _blockMenu.BLOCK_MENU_ACTION_TEST_ID.MOVE_UP
74
76
  }, formatMessage(_messages.blockMenuMessages.moveUpBlock));
75
77
  };
76
78
  var MoveUpDropdownItem = exports.MoveUpDropdownItem = (0, _reactIntlNext.injectIntl)(MoveUpDropdownItemContent);
@@ -1,8 +1,10 @@
1
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';
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 targetEl;
21
- let editorViewEl;
22
+ let popupTargetEl;
23
+ let editorView;
22
24
  const getPopupsTarget = () => {
23
- if (!targetEl) {
24
- targetEl = refs.popupsMountPoint || getPopupContainerFromEditorView(editorViewEl);
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 targetEl;
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
- // Don't start if block menu is already visible
66
- if (isBlockMenuVisible(getPopupsTarget())) {
67
- return;
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
- if (!(event.target instanceof Element)) {
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
- // 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
- });
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: editorView => {
110
- editorViewEl = editorView.dom;
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
  };