@atlaskit/editor-plugin-block-menu 4.0.3 → 4.0.5

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,25 @@
1
1
  # @atlaskit/editor-plugin-block-menu
2
2
 
3
+ ## 4.0.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [`51d46145cda56`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/51d46145cda56) -
8
+ Adds additional attributes to Element Converted event and fires the event for the empty line
9
+ transforms
10
+ - Updated dependencies
11
+
12
+ ## 4.0.4
13
+
14
+ ### Patch Changes
15
+
16
+ - [`1eda79686167c`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/1eda79686167c) -
17
+ ED-29418: Fix empty code block convert to lists
18
+ - [`0778701e62192`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/0778701e62192) -
19
+ [ux] ED-29424 Focus first menu item when block menu is opened and remove decorations when delete
20
+ button unmounts
21
+ - Updated dependencies
22
+
3
23
  ## 4.0.3
4
24
 
5
25
  ### Patch Changes
@@ -37,8 +37,8 @@ var blockMenuPlugin = exports.blockMenuPlugin = function blockMenuPlugin(_ref) {
37
37
  }
38
38
  },
39
39
  commands: {
40
- formatNode: function formatNode(targetType) {
41
- return (0, _formatNode2.formatNode)(api)(targetType);
40
+ formatNode: function formatNode(targetType, analyticsAttrs) {
41
+ return (0, _formatNode2.formatNode)(api)(targetType, analyticsAttrs);
42
42
  }
43
43
  },
44
44
  getSharedState: function getSharedState(editorState) {
@@ -8,6 +8,7 @@ var _analytics = require("@atlaskit/editor-common/analytics");
8
8
  var _state = require("@atlaskit/editor-prosemirror/state");
9
9
  var _utils = require("@atlaskit/editor-prosemirror/utils");
10
10
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
11
+ var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
11
12
  var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
12
13
  var _selection = require("./selection");
13
14
  var _layoutTransforms = require("./transforms/layout-transforms");
@@ -92,9 +93,10 @@ var formatNodeSelectEmptyList = exports.formatNodeSelectEmptyList = function for
92
93
  * Formats the current node or selection to the specified target type
93
94
  * @param api - The editor API injection that provides access to analytics and other plugin actions
94
95
  * @param targetType - The target node type to convert to
96
+ * @param analyticsAttrs - Attributes required for formatNode analytics like: inputMethod and triggeredFrom
95
97
  */
96
98
  var formatNode = exports.formatNode = function formatNode(api) {
97
- return function (targetType) {
99
+ return function (targetType, analyticsAttrs) {
98
100
  return function (_ref) {
99
101
  var tr = _ref.tr;
100
102
  var selection = tr.selection;
@@ -123,9 +125,64 @@ var formatNode = exports.formatNode = function formatNode(api) {
123
125
  // can only select one list at a time, so we just need to find the first one
124
126
  if (listNodes.length > 0 && (0, _platformFeatureFlags.fg)('platform_editor_block_menu_patch_2')) {
125
127
  var firstChild = listNodes[0];
126
- return formatNodeSelectEmptyList(tr, targetType, firstChild, schema);
128
+ var newTr = formatNodeSelectEmptyList(tr, targetType, firstChild, schema);
129
+ if (newTr) {
130
+ var _api$analytics;
131
+ var sourceTypeName = firstChild.node.type.name;
132
+ 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({
133
+ action: _analytics.ACTION.CONVERTED,
134
+ actionSubject: _analytics.ACTION_SUBJECT.ELEMENT,
135
+ eventType: _analytics.EVENT_TYPE.TRACK,
136
+ attributes: {
137
+ from: sourceTypeName,
138
+ to: targetType,
139
+ inputMethod: (analyticsAttrs === null || analyticsAttrs === void 0 ? void 0 : analyticsAttrs.inputMethod) || _analytics.INPUT_METHOD.MOUSE,
140
+ triggeredFrom: (analyticsAttrs === null || analyticsAttrs === void 0 ? void 0 : analyticsAttrs.triggeredFrom) || _analytics.INPUT_METHOD.BLOCK_MENU,
141
+ conversionSource: 'emptyList'
142
+ }
143
+ })(newTr);
144
+ }
145
+ return newTr;
127
146
  } else {
128
- return formatNodeWhenSelectionEmpty(tr, targetType, nodePos, schema);
147
+ var _newTr = formatNodeWhenSelectionEmpty(tr, targetType, nodePos, schema);
148
+ var allowedNodes = [nodes.blockquote, nodes.panel, nodes.codeBlock];
149
+ if ((0, _expValEquals.expValEquals)('platform_editor_block_menu_layout_format', 'isEnabled', true)) {
150
+ allowedNodes.push(nodes.layoutSection);
151
+ }
152
+ if ((0, _expValEquals.expValEquals)('platform_editor_block_menu_expand_format', 'isEnabled', true)) {
153
+ allowedNodes.push(nodes.expand);
154
+ }
155
+ var _sourceTypeName = 'paragraph';
156
+ var conversionSource;
157
+ var containerNode = (0, _utils.findParentNodeOfType)(allowedNodes)(selection);
158
+ var paragraphOrHeading = (0, _utils.findParentNodeOfType)([nodes.heading, nodes.paragraph])(selection);
159
+ if (containerNode) {
160
+ // At the moment this branch is executed for converstions from an empty blockquote
161
+ _sourceTypeName = containerNode.node.type.name;
162
+ conversionSource = undefined; // could be 'emptyNode' or something else
163
+ } else if (paragraphOrHeading) {
164
+ _sourceTypeName = paragraphOrHeading.node.type.name;
165
+ if (_sourceTypeName === 'heading') {
166
+ _sourceTypeName = "heading".concat(paragraphOrHeading.node.attrs.level);
167
+ }
168
+ conversionSource = 'emptyLine';
169
+ }
170
+ if (_newTr) {
171
+ var _api$analytics2;
172
+ 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({
173
+ action: _analytics.ACTION.CONVERTED,
174
+ actionSubject: _analytics.ACTION_SUBJECT.ELEMENT,
175
+ eventType: _analytics.EVENT_TYPE.TRACK,
176
+ attributes: {
177
+ from: _sourceTypeName,
178
+ to: targetType,
179
+ inputMethod: (analyticsAttrs === null || analyticsAttrs === void 0 ? void 0 : analyticsAttrs.inputMethod) || _analytics.INPUT_METHOD.MOUSE,
180
+ triggeredFrom: (analyticsAttrs === null || analyticsAttrs === void 0 ? void 0 : analyticsAttrs.triggeredFrom) || _analytics.INPUT_METHOD.BLOCK_MENU,
181
+ conversionSource: conversionSource
182
+ }
183
+ })(_newTr);
184
+ }
185
+ return _newTr;
129
186
  }
130
187
  }
131
188
 
@@ -161,28 +218,29 @@ var formatNode = exports.formatNode = function formatNode(api) {
161
218
  }
162
219
  try {
163
220
  var _nodeToFormat$attrs;
164
- var newTr = (0, _transformNodeToTargetType.transformNodeToTargetType)(tr, nodeToFormat, nodePos, targetType);
165
- var sourceTypeName = nodeToFormat.type.name;
166
- if (sourceTypeName === 'heading' && (_nodeToFormat$attrs = nodeToFormat.attrs) !== null && _nodeToFormat$attrs !== void 0 && _nodeToFormat$attrs.level) {
167
- sourceTypeName = "heading".concat(nodeToFormat.attrs.level);
221
+ var _newTr2 = (0, _transformNodeToTargetType.transformNodeToTargetType)(tr, nodeToFormat, nodePos, targetType);
222
+ var _sourceTypeName2 = nodeToFormat.type.name;
223
+ if (_sourceTypeName2 === 'heading' && (_nodeToFormat$attrs = nodeToFormat.attrs) !== null && _nodeToFormat$attrs !== void 0 && _nodeToFormat$attrs.level) {
224
+ _sourceTypeName2 = "heading".concat(nodeToFormat.attrs.level);
168
225
  }
169
- if (newTr && sourceTypeName !== targetType) {
170
- var _api$analytics;
171
- 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({
226
+ if (_newTr2 && _sourceTypeName2 !== targetType) {
227
+ var _api$analytics3;
228
+ api === null || api === void 0 || (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 || (_api$analytics3 = _api$analytics3.actions) === null || _api$analytics3 === void 0 || _api$analytics3.attachAnalyticsEvent({
172
229
  action: _analytics.ACTION.CONVERTED,
173
230
  actionSubject: _analytics.ACTION_SUBJECT.ELEMENT,
174
231
  eventType: _analytics.EVENT_TYPE.TRACK,
175
232
  attributes: {
176
- from: sourceTypeName,
233
+ from: _sourceTypeName2,
177
234
  to: targetType,
178
- inputMethod: _analytics.INPUT_METHOD.BLOCK_MENU
235
+ inputMethod: (analyticsAttrs === null || analyticsAttrs === void 0 ? void 0 : analyticsAttrs.inputMethod) || _analytics.INPUT_METHOD.MOUSE,
236
+ triggeredFrom: (analyticsAttrs === null || analyticsAttrs === void 0 ? void 0 : analyticsAttrs.triggeredFrom) || _analytics.INPUT_METHOD.BLOCK_MENU
179
237
  }
180
- })(newTr);
238
+ })(_newTr2);
181
239
  }
182
- if (newTr && (0, _platformFeatureFlags.fg)('platform_editor_block_menu_selection_fix')) {
183
- return (0, _selection.setSelectionAfterTransform)(newTr, nodePos, targetType);
240
+ if (_newTr2 && (0, _platformFeatureFlags.fg)('platform_editor_block_menu_selection_fix')) {
241
+ return (0, _selection.setSelectionAfterTransform)(_newTr2, nodePos, targetType);
184
242
  }
185
- return newTr;
243
+ return _newTr2;
186
244
  } catch (_unused) {
187
245
  return null;
188
246
  }
@@ -8,6 +8,7 @@ exports.unwrapAndConvertToList = exports.unwrapAndConvertToBlockType = exports.t
8
8
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
9
  var _model = require("@atlaskit/editor-prosemirror/model");
10
10
  var _utils = require("@atlaskit/editor-prosemirror/utils");
11
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
11
12
  var _inlineNodeTransforms = require("./inline-node-transforms");
12
13
  var _utils2 = require("./utils");
13
14
  var convertInvalidNodeToValidNodeType = function convertInvalidNodeToValidNodeType(sourceContent, sourceNodeType, validNodeType, withMarks) {
@@ -203,7 +204,11 @@ var unwrapAndConvertToList = exports.unwrapAndConvertToList = function unwrapAnd
203
204
  heading = _schema$nodes2.heading;
204
205
  var isTargetTaskList = targetNodeType === taskList;
205
206
  var createListItemFromInline = function createListItemFromInline(content) {
206
- return isTargetTaskList ? taskItem.create(null, content) : listItem.create(null, paragraph.create(null, content));
207
+ if (!content && (0, _platformFeatureFlags.fg)('platform_editor_block_menu_patch_2')) {
208
+ return isTargetTaskList ? taskItem.create() : listItem.create(null, paragraph.create());
209
+ } else {
210
+ return isTargetTaskList ? taskItem.create(null, content) : listItem.create(null, paragraph.create(null, content));
211
+ }
207
212
  };
208
213
  var getInlineContent = function getInlineContent(textblock) {
209
214
  var inlineContent = [];
@@ -233,6 +238,15 @@ var unwrapAndConvertToList = exports.unwrapAndConvertToList = function unwrapAnd
233
238
  };
234
239
  if (sourceNode.type.name === 'codeBlock') {
235
240
  var codeText = sourceNode.textContent;
241
+ // check if code block only contains newline characters
242
+ // eslint-disable-next-line require-unicode-regexp
243
+ var isOnlyNewLines = function isOnlyNewLines(codeText) {
244
+ return codeText.replace(/\n/g, '').trim() === '';
245
+ };
246
+ if ((!codeText || isOnlyNewLines(codeText)) && (0, _platformFeatureFlags.fg)('platform_editor_block_menu_patch_2')) {
247
+ // Empty code block - create an empty list item
248
+ currentListItems.push(createListItemFromInline());
249
+ }
236
250
  if (codeText) {
237
251
  var lines = codeText.split('\n');
238
252
  // Remove empty lines
@@ -9,6 +9,7 @@ exports.BlockMenuRenderer = void 0;
9
9
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
10
10
  var _react = _interopRequireWildcard(require("react"));
11
11
  var _uiMenu = require("@atlaskit/editor-common/ui-menu");
12
+ var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
12
13
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
13
14
  var NoOp = function NoOp(props) {
14
15
  return null;
@@ -50,48 +51,51 @@ var BlockMenuRenderer = exports.BlockMenuRenderer = function BlockMenuRenderer(_
50
51
  var menuSections = getSortedNonNestedSections(components);
51
52
  var menuItems = components.filter(isMenuItem);
52
53
  var nestedMenus = components.filter(isNestedMenu);
53
- return /*#__PURE__*/_react.default.createElement(_uiMenu.ArrowKeyNavigationProvider, {
54
- type: _uiMenu.ArrowKeyNavigationType.MENU
55
- }, /*#__PURE__*/_react.default.createElement(_react.Fragment, null, menuSections.map(function (section) {
56
- // Get all items for the current section, including nested menus, and sort them by rank
57
- var currentSectionItemsSorted = getSortedChildren([].concat((0, _toConsumableArray2.default)(menuItems), (0, _toConsumableArray2.default)(nestedMenus)), section.key);
58
- if (currentSectionItemsSorted.length === 0) {
59
- return null;
60
- }
54
+ var renderMenu = function renderMenu() {
55
+ return /*#__PURE__*/_react.default.createElement(_react.Fragment, null, menuSections.map(function (section) {
56
+ // Get all items for the current section, including nested menus, and sort them by rank
57
+ var currentSectionItemsSorted = getSortedChildren([].concat((0, _toConsumableArray2.default)(menuItems), (0, _toConsumableArray2.default)(nestedMenus)), section.key);
58
+ if (currentSectionItemsSorted.length === 0) {
59
+ return null;
60
+ }
61
61
 
62
- // iterate over the current section items, if it is nested menu, get their children, sort them
63
- // if they are menu items, just render as they are sorted above
64
- var getChildrenWithNestedItems = function getChildrenWithNestedItems(items) {
65
- return items.map(function (item) {
66
- if (isNestedMenu(item)) {
67
- var sortedNestedSections = getSortedNestedSections(components, item.key);
68
- return sortedNestedSections.map(function (section) {
69
- var sortedNestedMenuItems = getSortedChildren(menuItems, section.key);
70
- var NestedMenuComponent = item.component || fallbacks.nestedMenu || NoOp;
71
- var NestedSection = section.component || fallbacks.section || NoOp;
72
- return /*#__PURE__*/_react.default.createElement(NestedMenuComponent, {
62
+ // iterate over the current section items, if it is nested menu, get their children, sort them
63
+ // if they are menu items, just render as they are sorted above
64
+ var getChildrenWithNestedItems = function getChildrenWithNestedItems(items) {
65
+ return items.map(function (item) {
66
+ if (isNestedMenu(item)) {
67
+ var sortedNestedSections = getSortedNestedSections(components, item.key);
68
+ return sortedNestedSections.map(function (section) {
69
+ var sortedNestedMenuItems = getSortedChildren(menuItems, section.key);
70
+ var NestedMenuComponent = item.component || fallbacks.nestedMenu || NoOp;
71
+ var NestedSection = section.component || fallbacks.section || NoOp;
72
+ return /*#__PURE__*/_react.default.createElement(NestedMenuComponent, {
73
+ key: item.key
74
+ }, /*#__PURE__*/_react.default.createElement(NestedSection, {
75
+ key: section.key
76
+ }, sortedNestedMenuItems.map(function (nestedItem) {
77
+ var NestedMenuItemComponent = nestedItem.component || fallbacks.item || NoOp;
78
+ return /*#__PURE__*/_react.default.createElement(NestedMenuItemComponent, {
79
+ key: nestedItem.key
80
+ });
81
+ })));
82
+ });
83
+ } else {
84
+ var ItemComponent = item.component || fallbacks.item || NoOp;
85
+ return /*#__PURE__*/_react.default.createElement(ItemComponent, {
73
86
  key: item.key
74
- }, /*#__PURE__*/_react.default.createElement(NestedSection, {
75
- key: section.key
76
- }, sortedNestedMenuItems.map(function (nestedItem) {
77
- var NestedMenuItemComponent = nestedItem.component || fallbacks.item || NoOp;
78
- return /*#__PURE__*/_react.default.createElement(NestedMenuItemComponent, {
79
- key: nestedItem.key
80
- });
81
- })));
82
- });
83
- } else {
84
- var ItemComponent = item.component || fallbacks.item || NoOp;
85
- return /*#__PURE__*/_react.default.createElement(ItemComponent, {
86
- key: item.key
87
- });
88
- }
89
- });
90
- };
91
- var children = getChildrenWithNestedItems(currentSectionItemsSorted);
92
- var SectionComponent = section.component || fallbacks.section || NoOp;
93
- return /*#__PURE__*/_react.default.createElement(SectionComponent, {
94
- key: section.key
95
- }, children);
96
- })));
87
+ });
88
+ }
89
+ });
90
+ };
91
+ var children = getChildrenWithNestedItems(currentSectionItemsSorted);
92
+ var SectionComponent = section.component || fallbacks.section || NoOp;
93
+ return /*#__PURE__*/_react.default.createElement(SectionComponent, {
94
+ key: section.key
95
+ }, children);
96
+ }));
97
+ };
98
+ return (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true) ? /*#__PURE__*/_react.default.createElement(_uiMenu.ArrowKeyNavigationProvider, {
99
+ type: _uiMenu.ArrowKeyNavigationType.MENU
100
+ }, renderMenu()) : renderMenu();
97
101
  };
@@ -64,18 +64,20 @@ var BlockMenu = function BlockMenu(_ref2) {
64
64
  boundariesElement = _ref2.boundariesElement,
65
65
  scrollableElement = _ref2.scrollableElement;
66
66
  var _useSharedPluginState = (0, _hooks.useSharedPluginStateWithSelector)(api, ['blockControls', 'userIntent'], function (states) {
67
- var _states$blockControls, _states$blockControls2, _states$blockControls3, _states$userIntentSta;
67
+ var _states$blockControls, _states$blockControls2, _states$blockControls3, _states$userIntentSta, _states$blockControls4;
68
68
  return {
69
69
  menuTriggerBy: (_states$blockControls = states.blockControlsState) === null || _states$blockControls === void 0 ? void 0 : _states$blockControls.menuTriggerBy,
70
70
  isSelectedViaDragHandle: (_states$blockControls2 = states.blockControlsState) === null || _states$blockControls2 === void 0 ? void 0 : _states$blockControls2.isSelectedViaDragHandle,
71
71
  isMenuOpen: (_states$blockControls3 = states.blockControlsState) === null || _states$blockControls3 === void 0 ? void 0 : _states$blockControls3.isMenuOpen,
72
- currentUserIntent: (_states$userIntentSta = states.userIntentState) === null || _states$userIntentSta === void 0 ? void 0 : _states$userIntentSta.currentUserIntent
72
+ currentUserIntent: (_states$userIntentSta = states.userIntentState) === null || _states$userIntentSta === void 0 ? void 0 : _states$userIntentSta.currentUserIntent,
73
+ openedViaKeyboard: (_states$blockControls4 = states.blockControlsState) === null || _states$blockControls4 === void 0 || (_states$blockControls4 = _states$blockControls4.blockMenuOptions) === null || _states$blockControls4 === void 0 ? void 0 : _states$blockControls4.openedViaKeyboard
73
74
  };
74
75
  }),
75
76
  menuTriggerBy = _useSharedPluginState.menuTriggerBy,
76
77
  isSelectedViaDragHandle = _useSharedPluginState.isSelectedViaDragHandle,
77
78
  isMenuOpen = _useSharedPluginState.isMenuOpen,
78
- currentUserIntent = _useSharedPluginState.currentUserIntent;
79
+ currentUserIntent = _useSharedPluginState.currentUserIntent,
80
+ openedViaKeyboard = _useSharedPluginState.openedViaKeyboard;
79
81
  var _useBlockMenu = (0, _blockMenuProvider.useBlockMenu)(),
80
82
  onDropdownOpenChanged = _useBlockMenu.onDropdownOpenChanged,
81
83
  fireAnalyticsEvent = _useBlockMenu.fireAnalyticsEvent;
@@ -86,7 +88,7 @@ var BlockMenu = function BlockMenu(_ref2) {
86
88
  // hasSelection true, always show block menu
87
89
  // hasSelection false, only show block menu when empty line experiment is enabled
88
90
  var shouldShowBlockMenuForEmptyLine = hasSelection || !hasSelection && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_empty_line', 'isEnabled', true);
89
- var selectedByShortcutORDragHandle = isSelectedViaDragHandle;
91
+ var selectedByShortcutORDragHandle = isSelectedViaDragHandle || openedViaKeyboard && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true);
90
92
  (0, _react.useEffect)(function () {
91
93
  var _api$userIntent;
92
94
  if (!isMenuOpen || !menuTriggerBy || !selectedByShortcutORDragHandle || !hasFocus || !shouldShowBlockMenuForEmptyLine || ['resizing', 'dragging'].includes(currentUserIntent || '')) {
@@ -149,7 +151,9 @@ var BlockMenu = function BlockMenu(_ref2) {
149
151
  preventOverflow: true // disables forced horizontal placement when forcePlacement is on, so fitWidth controls flipping
150
152
  ,
151
153
  stick: true,
152
- focusTrap: true,
154
+ focusTrap: (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true) ? openedViaKeyboard ? {
155
+ initialFocus: undefined
156
+ } : true : undefined,
153
157
  offset: [_styles.DRAG_HANDLE_WIDTH + DRAG_HANDLE_OFFSET_PADDING, 0]
154
158
  }, /*#__PURE__*/_react.default.createElement(BlockMenuContent, {
155
159
  api: api
@@ -78,10 +78,22 @@ var DeleteDropdownItemContent = function DeleteDropdownItemContent(_ref) {
78
78
  return tr;
79
79
  });
80
80
  }, [api, nodeTypes]);
81
- var onRemoveHoverDecoration = function onRemoveHoverDecoration() {
81
+ var onRemoveHoverDecoration = (0, _react.useCallback)(function () {
82
82
  var _api$decorations2, _api$decorations2$rem;
83
83
  api === null || api === void 0 || api.core.actions.execute(api === null || api === void 0 || (_api$decorations2 = api.decorations) === null || _api$decorations2 === void 0 || (_api$decorations2 = _api$decorations2.commands) === null || _api$decorations2 === void 0 || (_api$decorations2$rem = _api$decorations2.removeDecoration) === null || _api$decorations2$rem === void 0 ? void 0 : _api$decorations2$rem.call(_api$decorations2));
84
- };
84
+ }, [api]);
85
+ (0, _react.useEffect)(function () {
86
+ if (!(0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true)) {
87
+ return;
88
+ }
89
+ return function () {
90
+ if (!(0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true)) {
91
+ return;
92
+ }
93
+ // clean up hover decoration when unmounting
94
+ onRemoveHoverDecoration();
95
+ };
96
+ }, [onRemoveHoverDecoration]);
85
97
  var text = (0, _platformFeatureFlags.fg)('platform_editor_block_menu_patch_1') ? formatMessage(_messages.blockMenuMessages.deleteBlock) : formatMessage(_blockMenu.messages.deleteBlock);
86
98
  return /*#__PURE__*/_react.default.createElement(_box.Box, {
87
99
  onMouseEnter: onShowHoverDecoration,
@@ -31,8 +31,8 @@ export const blockMenuPlugin = ({
31
31
  }
32
32
  },
33
33
  commands: {
34
- formatNode: targetType => {
35
- return formatNode(api)(targetType);
34
+ formatNode: (targetType, analyticsAttrs) => {
35
+ return formatNode(api)(targetType, analyticsAttrs);
36
36
  }
37
37
  },
38
38
  getSharedState(editorState) {
@@ -2,6 +2,7 @@ import { ACTION, ACTION_SUBJECT, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/edit
2
2
  import { TextSelection } from '@atlaskit/editor-prosemirror/state';
3
3
  import { findParentNodeOfType, findSelectedNodeOfType, safeInsert as pmSafeInsert } from '@atlaskit/editor-prosemirror/utils';
4
4
  import { fg } from '@atlaskit/platform-feature-flags';
5
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
5
6
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
6
7
  import { setSelectionAfterTransform } from './selection';
7
8
  import { createDefaultLayoutSection } from './transforms/layout-transforms';
@@ -93,8 +94,9 @@ export const formatNodeSelectEmptyList = (tr, targetType, listNode, schema) => {
93
94
  * Formats the current node or selection to the specified target type
94
95
  * @param api - The editor API injection that provides access to analytics and other plugin actions
95
96
  * @param targetType - The target node type to convert to
97
+ * @param analyticsAttrs - Attributes required for formatNode analytics like: inputMethod and triggeredFrom
96
98
  */
97
- export const formatNode = api => targetType => {
99
+ export const formatNode = api => (targetType, analyticsAttrs) => {
98
100
  return ({
99
101
  tr
100
102
  }) => {
@@ -128,9 +130,64 @@ export const formatNode = api => targetType => {
128
130
  // can only select one list at a time, so we just need to find the first one
129
131
  if (listNodes.length > 0 && fg('platform_editor_block_menu_patch_2')) {
130
132
  const firstChild = listNodes[0];
131
- return formatNodeSelectEmptyList(tr, targetType, firstChild, schema);
133
+ const newTr = formatNodeSelectEmptyList(tr, targetType, firstChild, schema);
134
+ if (newTr) {
135
+ var _api$analytics, _api$analytics$action;
136
+ const sourceTypeName = firstChild.node.type.name;
137
+ 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({
138
+ action: ACTION.CONVERTED,
139
+ actionSubject: ACTION_SUBJECT.ELEMENT,
140
+ eventType: EVENT_TYPE.TRACK,
141
+ attributes: {
142
+ from: sourceTypeName,
143
+ to: targetType,
144
+ inputMethod: (analyticsAttrs === null || analyticsAttrs === void 0 ? void 0 : analyticsAttrs.inputMethod) || INPUT_METHOD.MOUSE,
145
+ triggeredFrom: (analyticsAttrs === null || analyticsAttrs === void 0 ? void 0 : analyticsAttrs.triggeredFrom) || INPUT_METHOD.BLOCK_MENU,
146
+ conversionSource: 'emptyList'
147
+ }
148
+ })(newTr);
149
+ }
150
+ return newTr;
132
151
  } else {
133
- return formatNodeWhenSelectionEmpty(tr, targetType, nodePos, schema);
152
+ const newTr = formatNodeWhenSelectionEmpty(tr, targetType, nodePos, schema);
153
+ const allowedNodes = [nodes.blockquote, nodes.panel, nodes.codeBlock];
154
+ if (expValEquals('platform_editor_block_menu_layout_format', 'isEnabled', true)) {
155
+ allowedNodes.push(nodes.layoutSection);
156
+ }
157
+ if (expValEquals('platform_editor_block_menu_expand_format', 'isEnabled', true)) {
158
+ allowedNodes.push(nodes.expand);
159
+ }
160
+ let sourceTypeName = 'paragraph';
161
+ let conversionSource;
162
+ const containerNode = findParentNodeOfType(allowedNodes)(selection);
163
+ const paragraphOrHeading = findParentNodeOfType([nodes.heading, nodes.paragraph])(selection);
164
+ if (containerNode) {
165
+ // At the moment this branch is executed for converstions from an empty blockquote
166
+ sourceTypeName = containerNode.node.type.name;
167
+ conversionSource = undefined; // could be 'emptyNode' or something else
168
+ } else if (paragraphOrHeading) {
169
+ sourceTypeName = paragraphOrHeading.node.type.name;
170
+ if (sourceTypeName === 'heading') {
171
+ sourceTypeName = `heading${paragraphOrHeading.node.attrs.level}`;
172
+ }
173
+ conversionSource = 'emptyLine';
174
+ }
175
+ if (newTr) {
176
+ var _api$analytics2, _api$analytics2$actio;
177
+ 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({
178
+ action: ACTION.CONVERTED,
179
+ actionSubject: ACTION_SUBJECT.ELEMENT,
180
+ eventType: EVENT_TYPE.TRACK,
181
+ attributes: {
182
+ from: sourceTypeName,
183
+ to: targetType,
184
+ inputMethod: (analyticsAttrs === null || analyticsAttrs === void 0 ? void 0 : analyticsAttrs.inputMethod) || INPUT_METHOD.MOUSE,
185
+ triggeredFrom: (analyticsAttrs === null || analyticsAttrs === void 0 ? void 0 : analyticsAttrs.triggeredFrom) || INPUT_METHOD.BLOCK_MENU,
186
+ conversionSource
187
+ }
188
+ })(newTr);
189
+ }
190
+ return newTr;
134
191
  }
135
192
  }
136
193
 
@@ -172,15 +229,16 @@ export const formatNode = api => targetType => {
172
229
  sourceTypeName = `heading${nodeToFormat.attrs.level}`;
173
230
  }
174
231
  if (newTr && sourceTypeName !== targetType) {
175
- var _api$analytics, _api$analytics$action;
176
- 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({
232
+ var _api$analytics3, _api$analytics3$actio;
233
+ api === null || api === void 0 ? void 0 : (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 ? void 0 : (_api$analytics3$actio = _api$analytics3.actions) === null || _api$analytics3$actio === void 0 ? void 0 : _api$analytics3$actio.attachAnalyticsEvent({
177
234
  action: ACTION.CONVERTED,
178
235
  actionSubject: ACTION_SUBJECT.ELEMENT,
179
236
  eventType: EVENT_TYPE.TRACK,
180
237
  attributes: {
181
238
  from: sourceTypeName,
182
239
  to: targetType,
183
- inputMethod: INPUT_METHOD.BLOCK_MENU
240
+ inputMethod: (analyticsAttrs === null || analyticsAttrs === void 0 ? void 0 : analyticsAttrs.inputMethod) || INPUT_METHOD.MOUSE,
241
+ triggeredFrom: (analyticsAttrs === null || analyticsAttrs === void 0 ? void 0 : analyticsAttrs.triggeredFrom) || INPUT_METHOD.BLOCK_MENU
184
242
  }
185
243
  })(newTr);
186
244
  }
@@ -1,5 +1,6 @@
1
1
  import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
2
2
  import { findChildrenByType } from '@atlaskit/editor-prosemirror/utils';
3
+ import { fg } from '@atlaskit/platform-feature-flags';
3
4
  import { getInlineNodeTextContent } from './inline-node-transforms';
4
5
  import { isBlockNodeType, isListNodeType, isContainerNodeType, isBlockNodeForExtraction, convertNodeToInlineContent, getContentSupportChecker, convertCodeBlockContentToParagraphs, filterMarksForTargetNodeType, getMarksWithBreakout } from './utils';
5
6
  const convertInvalidNodeToValidNodeType = (sourceContent, sourceNodeType, validNodeType, withMarks) => {
@@ -204,7 +205,11 @@ export const unwrapAndConvertToList = ({
204
205
  } = schema.nodes;
205
206
  const isTargetTaskList = targetNodeType === taskList;
206
207
  const createListItemFromInline = content => {
207
- return isTargetTaskList ? taskItem.create(null, content) : listItem.create(null, paragraph.create(null, content));
208
+ if (!content && fg('platform_editor_block_menu_patch_2')) {
209
+ return isTargetTaskList ? taskItem.create() : listItem.create(null, paragraph.create());
210
+ } else {
211
+ return isTargetTaskList ? taskItem.create(null, content) : listItem.create(null, paragraph.create(null, content));
212
+ }
208
213
  };
209
214
  const getInlineContent = textblock => {
210
215
  const inlineContent = [];
@@ -234,6 +239,13 @@ export const unwrapAndConvertToList = ({
234
239
  };
235
240
  if (sourceNode.type.name === 'codeBlock') {
236
241
  const codeText = sourceNode.textContent;
242
+ // check if code block only contains newline characters
243
+ // eslint-disable-next-line require-unicode-regexp
244
+ const isOnlyNewLines = codeText => codeText.replace(/\n/g, '').trim() === '';
245
+ if ((!codeText || isOnlyNewLines(codeText)) && fg('platform_editor_block_menu_patch_2')) {
246
+ // Empty code block - create an empty list item
247
+ currentListItems.push(createListItemFromInline());
248
+ }
237
249
  if (codeText) {
238
250
  const lines = codeText.split('\n');
239
251
  // Remove empty lines
@@ -1,5 +1,6 @@
1
1
  import React, { Fragment } from 'react';
2
2
  import { ArrowKeyNavigationProvider, ArrowKeyNavigationType } from '@atlaskit/editor-common/ui-menu';
3
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
3
4
  const NoOp = props => null;
4
5
  const isNonNestedMenuSection = component => {
5
6
  return component.type === 'block-menu-section' && !('parent' in component);
@@ -29,48 +30,51 @@ export const BlockMenuRenderer = ({
29
30
  const menuSections = getSortedNonNestedSections(components);
30
31
  const menuItems = components.filter(isMenuItem);
31
32
  const nestedMenus = components.filter(isNestedMenu);
32
- return /*#__PURE__*/React.createElement(ArrowKeyNavigationProvider, {
33
- type: ArrowKeyNavigationType.MENU
34
- }, /*#__PURE__*/React.createElement(Fragment, null, menuSections.map(section => {
35
- // Get all items for the current section, including nested menus, and sort them by rank
36
- const currentSectionItemsSorted = getSortedChildren([...menuItems, ...nestedMenus], section.key);
37
- if (currentSectionItemsSorted.length === 0) {
38
- return null;
39
- }
33
+ const renderMenu = () => {
34
+ return /*#__PURE__*/React.createElement(Fragment, null, menuSections.map(section => {
35
+ // Get all items for the current section, including nested menus, and sort them by rank
36
+ const currentSectionItemsSorted = getSortedChildren([...menuItems, ...nestedMenus], section.key);
37
+ if (currentSectionItemsSorted.length === 0) {
38
+ return null;
39
+ }
40
40
 
41
- // iterate over the current section items, if it is nested menu, get their children, sort them
42
- // if they are menu items, just render as they are sorted above
43
- const getChildrenWithNestedItems = items => {
44
- return items.map(item => {
45
- if (isNestedMenu(item)) {
46
- const sortedNestedSections = getSortedNestedSections(components, item.key);
47
- return sortedNestedSections.map(section => {
48
- const sortedNestedMenuItems = getSortedChildren(menuItems, section.key);
49
- const NestedMenuComponent = item.component || fallbacks.nestedMenu || NoOp;
50
- const NestedSection = section.component || fallbacks.section || NoOp;
51
- return /*#__PURE__*/React.createElement(NestedMenuComponent, {
41
+ // iterate over the current section items, if it is nested menu, get their children, sort them
42
+ // if they are menu items, just render as they are sorted above
43
+ const getChildrenWithNestedItems = items => {
44
+ return items.map(item => {
45
+ if (isNestedMenu(item)) {
46
+ const sortedNestedSections = getSortedNestedSections(components, item.key);
47
+ return sortedNestedSections.map(section => {
48
+ const sortedNestedMenuItems = getSortedChildren(menuItems, section.key);
49
+ const NestedMenuComponent = item.component || fallbacks.nestedMenu || NoOp;
50
+ const NestedSection = section.component || fallbacks.section || NoOp;
51
+ return /*#__PURE__*/React.createElement(NestedMenuComponent, {
52
+ key: item.key
53
+ }, /*#__PURE__*/React.createElement(NestedSection, {
54
+ key: section.key
55
+ }, sortedNestedMenuItems.map(nestedItem => {
56
+ const NestedMenuItemComponent = nestedItem.component || fallbacks.item || NoOp;
57
+ return /*#__PURE__*/React.createElement(NestedMenuItemComponent, {
58
+ key: nestedItem.key
59
+ });
60
+ })));
61
+ });
62
+ } else {
63
+ const ItemComponent = item.component || fallbacks.item || NoOp;
64
+ return /*#__PURE__*/React.createElement(ItemComponent, {
52
65
  key: item.key
53
- }, /*#__PURE__*/React.createElement(NestedSection, {
54
- key: section.key
55
- }, sortedNestedMenuItems.map(nestedItem => {
56
- const NestedMenuItemComponent = nestedItem.component || fallbacks.item || NoOp;
57
- return /*#__PURE__*/React.createElement(NestedMenuItemComponent, {
58
- key: nestedItem.key
59
- });
60
- })));
61
- });
62
- } else {
63
- const ItemComponent = item.component || fallbacks.item || NoOp;
64
- return /*#__PURE__*/React.createElement(ItemComponent, {
65
- key: item.key
66
- });
67
- }
68
- });
69
- };
70
- const children = getChildrenWithNestedItems(currentSectionItemsSorted);
71
- const SectionComponent = section.component || fallbacks.section || NoOp;
72
- return /*#__PURE__*/React.createElement(SectionComponent, {
73
- key: section.key
74
- }, children);
75
- })));
66
+ });
67
+ }
68
+ });
69
+ };
70
+ const children = getChildrenWithNestedItems(currentSectionItemsSorted);
71
+ const SectionComponent = section.component || fallbacks.section || NoOp;
72
+ return /*#__PURE__*/React.createElement(SectionComponent, {
73
+ key: section.key
74
+ }, children);
75
+ }));
76
+ };
77
+ return expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true) ? /*#__PURE__*/React.createElement(ArrowKeyNavigationProvider, {
78
+ type: ArrowKeyNavigationType.MENU
79
+ }, renderMenu()) : renderMenu();
76
80
  };
@@ -55,14 +55,16 @@ const BlockMenu = ({
55
55
  menuTriggerBy,
56
56
  isSelectedViaDragHandle,
57
57
  isMenuOpen,
58
- currentUserIntent
58
+ currentUserIntent,
59
+ openedViaKeyboard
59
60
  } = useSharedPluginStateWithSelector(api, ['blockControls', 'userIntent'], states => {
60
- var _states$blockControls, _states$blockControls2, _states$blockControls3, _states$userIntentSta;
61
+ var _states$blockControls, _states$blockControls2, _states$blockControls3, _states$userIntentSta, _states$blockControls4, _states$blockControls5;
61
62
  return {
62
63
  menuTriggerBy: (_states$blockControls = states.blockControlsState) === null || _states$blockControls === void 0 ? void 0 : _states$blockControls.menuTriggerBy,
63
64
  isSelectedViaDragHandle: (_states$blockControls2 = states.blockControlsState) === null || _states$blockControls2 === void 0 ? void 0 : _states$blockControls2.isSelectedViaDragHandle,
64
65
  isMenuOpen: (_states$blockControls3 = states.blockControlsState) === null || _states$blockControls3 === void 0 ? void 0 : _states$blockControls3.isMenuOpen,
65
- currentUserIntent: (_states$userIntentSta = states.userIntentState) === null || _states$userIntentSta === void 0 ? void 0 : _states$userIntentSta.currentUserIntent
66
+ currentUserIntent: (_states$userIntentSta = states.userIntentState) === null || _states$userIntentSta === void 0 ? void 0 : _states$userIntentSta.currentUserIntent,
67
+ openedViaKeyboard: (_states$blockControls4 = states.blockControlsState) === null || _states$blockControls4 === void 0 ? void 0 : (_states$blockControls5 = _states$blockControls4.blockMenuOptions) === null || _states$blockControls5 === void 0 ? void 0 : _states$blockControls5.openedViaKeyboard
66
68
  };
67
69
  });
68
70
  const {
@@ -76,7 +78,7 @@ const BlockMenu = ({
76
78
  // hasSelection true, always show block menu
77
79
  // hasSelection false, only show block menu when empty line experiment is enabled
78
80
  const shouldShowBlockMenuForEmptyLine = hasSelection || !hasSelection && expValEqualsNoExposure('platform_editor_block_menu_empty_line', 'isEnabled', true);
79
- const selectedByShortcutORDragHandle = isSelectedViaDragHandle;
81
+ const selectedByShortcutORDragHandle = isSelectedViaDragHandle || openedViaKeyboard && expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true);
80
82
  useEffect(() => {
81
83
  var _api$userIntent;
82
84
  if (!isMenuOpen || !menuTriggerBy || !selectedByShortcutORDragHandle || !hasFocus || !shouldShowBlockMenuForEmptyLine || ['resizing', 'dragging'].includes(currentUserIntent || '')) {
@@ -140,7 +142,9 @@ const BlockMenu = ({
140
142
  preventOverflow: true // disables forced horizontal placement when forcePlacement is on, so fitWidth controls flipping
141
143
  ,
142
144
  stick: true,
143
- focusTrap: true,
145
+ focusTrap: expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true) ? openedViaKeyboard ? {
146
+ initialFocus: undefined
147
+ } : true : undefined,
144
148
  offset: [DRAG_HANDLE_WIDTH + DRAG_HANDLE_OFFSET_PADDING, 0]
145
149
  }, /*#__PURE__*/React.createElement(BlockMenuContent, {
146
150
  api: api
@@ -1,4 +1,4 @@
1
- import React, { useCallback } from 'react';
1
+ import React, { useCallback, useEffect } from 'react';
2
2
  import { useIntl, injectIntl } from 'react-intl-next';
3
3
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
4
4
  import { messages } from '@atlaskit/editor-common/block-menu';
@@ -74,10 +74,22 @@ const DeleteDropdownItemContent = ({
74
74
  return tr;
75
75
  });
76
76
  }, [api, nodeTypes]);
77
- const onRemoveHoverDecoration = () => {
77
+ const onRemoveHoverDecoration = useCallback(() => {
78
78
  var _api$decorations2, _api$decorations2$com, _api$decorations2$com2;
79
79
  api === null || api === void 0 ? void 0 : api.core.actions.execute(api === null || api === void 0 ? void 0 : (_api$decorations2 = api.decorations) === null || _api$decorations2 === void 0 ? void 0 : (_api$decorations2$com = _api$decorations2.commands) === null || _api$decorations2$com === void 0 ? void 0 : (_api$decorations2$com2 = _api$decorations2$com.removeDecoration) === null || _api$decorations2$com2 === void 0 ? void 0 : _api$decorations2$com2.call(_api$decorations2$com));
80
- };
80
+ }, [api]);
81
+ useEffect(() => {
82
+ if (!expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true)) {
83
+ return;
84
+ }
85
+ return () => {
86
+ if (!expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true)) {
87
+ return;
88
+ }
89
+ // clean up hover decoration when unmounting
90
+ onRemoveHoverDecoration();
91
+ };
92
+ }, [onRemoveHoverDecoration]);
81
93
  const text = fg('platform_editor_block_menu_patch_1') ? formatMessage(blockMenuMessages.deleteBlock) : formatMessage(messages.deleteBlock);
82
94
  return /*#__PURE__*/React.createElement(Box, {
83
95
  onMouseEnter: onShowHoverDecoration,
@@ -30,8 +30,8 @@ export var blockMenuPlugin = function blockMenuPlugin(_ref) {
30
30
  }
31
31
  },
32
32
  commands: {
33
- formatNode: function formatNode(targetType) {
34
- return _formatNode(api)(targetType);
33
+ formatNode: function formatNode(targetType, analyticsAttrs) {
34
+ return _formatNode(api)(targetType, analyticsAttrs);
35
35
  }
36
36
  },
37
37
  getSharedState: function getSharedState(editorState) {
@@ -2,6 +2,7 @@ import { ACTION, ACTION_SUBJECT, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/edit
2
2
  import { TextSelection } from '@atlaskit/editor-prosemirror/state';
3
3
  import { findParentNodeOfType, findSelectedNodeOfType, safeInsert as pmSafeInsert } from '@atlaskit/editor-prosemirror/utils';
4
4
  import { fg } from '@atlaskit/platform-feature-flags';
5
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
5
6
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
6
7
  import { setSelectionAfterTransform } from './selection';
7
8
  import { createDefaultLayoutSection } from './transforms/layout-transforms';
@@ -87,9 +88,10 @@ export var formatNodeSelectEmptyList = function formatNodeSelectEmptyList(tr, ta
87
88
  * Formats the current node or selection to the specified target type
88
89
  * @param api - The editor API injection that provides access to analytics and other plugin actions
89
90
  * @param targetType - The target node type to convert to
91
+ * @param analyticsAttrs - Attributes required for formatNode analytics like: inputMethod and triggeredFrom
90
92
  */
91
93
  export var formatNode = function formatNode(api) {
92
- return function (targetType) {
94
+ return function (targetType, analyticsAttrs) {
93
95
  return function (_ref) {
94
96
  var tr = _ref.tr;
95
97
  var selection = tr.selection;
@@ -118,9 +120,64 @@ export var formatNode = function formatNode(api) {
118
120
  // can only select one list at a time, so we just need to find the first one
119
121
  if (listNodes.length > 0 && fg('platform_editor_block_menu_patch_2')) {
120
122
  var firstChild = listNodes[0];
121
- return formatNodeSelectEmptyList(tr, targetType, firstChild, schema);
123
+ var newTr = formatNodeSelectEmptyList(tr, targetType, firstChild, schema);
124
+ if (newTr) {
125
+ var _api$analytics;
126
+ var sourceTypeName = firstChild.node.type.name;
127
+ 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({
128
+ action: ACTION.CONVERTED,
129
+ actionSubject: ACTION_SUBJECT.ELEMENT,
130
+ eventType: EVENT_TYPE.TRACK,
131
+ attributes: {
132
+ from: sourceTypeName,
133
+ to: targetType,
134
+ inputMethod: (analyticsAttrs === null || analyticsAttrs === void 0 ? void 0 : analyticsAttrs.inputMethod) || INPUT_METHOD.MOUSE,
135
+ triggeredFrom: (analyticsAttrs === null || analyticsAttrs === void 0 ? void 0 : analyticsAttrs.triggeredFrom) || INPUT_METHOD.BLOCK_MENU,
136
+ conversionSource: 'emptyList'
137
+ }
138
+ })(newTr);
139
+ }
140
+ return newTr;
122
141
  } else {
123
- return formatNodeWhenSelectionEmpty(tr, targetType, nodePos, schema);
142
+ var _newTr = formatNodeWhenSelectionEmpty(tr, targetType, nodePos, schema);
143
+ var allowedNodes = [nodes.blockquote, nodes.panel, nodes.codeBlock];
144
+ if (expValEquals('platform_editor_block_menu_layout_format', 'isEnabled', true)) {
145
+ allowedNodes.push(nodes.layoutSection);
146
+ }
147
+ if (expValEquals('platform_editor_block_menu_expand_format', 'isEnabled', true)) {
148
+ allowedNodes.push(nodes.expand);
149
+ }
150
+ var _sourceTypeName = 'paragraph';
151
+ var conversionSource;
152
+ var containerNode = findParentNodeOfType(allowedNodes)(selection);
153
+ var paragraphOrHeading = findParentNodeOfType([nodes.heading, nodes.paragraph])(selection);
154
+ if (containerNode) {
155
+ // At the moment this branch is executed for converstions from an empty blockquote
156
+ _sourceTypeName = containerNode.node.type.name;
157
+ conversionSource = undefined; // could be 'emptyNode' or something else
158
+ } else if (paragraphOrHeading) {
159
+ _sourceTypeName = paragraphOrHeading.node.type.name;
160
+ if (_sourceTypeName === 'heading') {
161
+ _sourceTypeName = "heading".concat(paragraphOrHeading.node.attrs.level);
162
+ }
163
+ conversionSource = 'emptyLine';
164
+ }
165
+ if (_newTr) {
166
+ var _api$analytics2;
167
+ 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({
168
+ action: ACTION.CONVERTED,
169
+ actionSubject: ACTION_SUBJECT.ELEMENT,
170
+ eventType: EVENT_TYPE.TRACK,
171
+ attributes: {
172
+ from: _sourceTypeName,
173
+ to: targetType,
174
+ inputMethod: (analyticsAttrs === null || analyticsAttrs === void 0 ? void 0 : analyticsAttrs.inputMethod) || INPUT_METHOD.MOUSE,
175
+ triggeredFrom: (analyticsAttrs === null || analyticsAttrs === void 0 ? void 0 : analyticsAttrs.triggeredFrom) || INPUT_METHOD.BLOCK_MENU,
176
+ conversionSource: conversionSource
177
+ }
178
+ })(_newTr);
179
+ }
180
+ return _newTr;
124
181
  }
125
182
  }
126
183
 
@@ -156,28 +213,29 @@ export var formatNode = function formatNode(api) {
156
213
  }
157
214
  try {
158
215
  var _nodeToFormat$attrs;
159
- var newTr = transformNodeToTargetType(tr, nodeToFormat, nodePos, targetType);
160
- var sourceTypeName = nodeToFormat.type.name;
161
- if (sourceTypeName === 'heading' && (_nodeToFormat$attrs = nodeToFormat.attrs) !== null && _nodeToFormat$attrs !== void 0 && _nodeToFormat$attrs.level) {
162
- sourceTypeName = "heading".concat(nodeToFormat.attrs.level);
216
+ var _newTr2 = transformNodeToTargetType(tr, nodeToFormat, nodePos, targetType);
217
+ var _sourceTypeName2 = nodeToFormat.type.name;
218
+ if (_sourceTypeName2 === 'heading' && (_nodeToFormat$attrs = nodeToFormat.attrs) !== null && _nodeToFormat$attrs !== void 0 && _nodeToFormat$attrs.level) {
219
+ _sourceTypeName2 = "heading".concat(nodeToFormat.attrs.level);
163
220
  }
164
- if (newTr && sourceTypeName !== targetType) {
165
- var _api$analytics;
166
- 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({
221
+ if (_newTr2 && _sourceTypeName2 !== targetType) {
222
+ var _api$analytics3;
223
+ api === null || api === void 0 || (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 || (_api$analytics3 = _api$analytics3.actions) === null || _api$analytics3 === void 0 || _api$analytics3.attachAnalyticsEvent({
167
224
  action: ACTION.CONVERTED,
168
225
  actionSubject: ACTION_SUBJECT.ELEMENT,
169
226
  eventType: EVENT_TYPE.TRACK,
170
227
  attributes: {
171
- from: sourceTypeName,
228
+ from: _sourceTypeName2,
172
229
  to: targetType,
173
- inputMethod: INPUT_METHOD.BLOCK_MENU
230
+ inputMethod: (analyticsAttrs === null || analyticsAttrs === void 0 ? void 0 : analyticsAttrs.inputMethod) || INPUT_METHOD.MOUSE,
231
+ triggeredFrom: (analyticsAttrs === null || analyticsAttrs === void 0 ? void 0 : analyticsAttrs.triggeredFrom) || INPUT_METHOD.BLOCK_MENU
174
232
  }
175
- })(newTr);
233
+ })(_newTr2);
176
234
  }
177
- if (newTr && fg('platform_editor_block_menu_selection_fix')) {
178
- return setSelectionAfterTransform(newTr, nodePos, targetType);
235
+ if (_newTr2 && fg('platform_editor_block_menu_selection_fix')) {
236
+ return setSelectionAfterTransform(_newTr2, nodePos, targetType);
179
237
  }
180
- return newTr;
238
+ return _newTr2;
181
239
  } catch (_unused) {
182
240
  return null;
183
241
  }
@@ -1,6 +1,7 @@
1
1
  import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
2
  import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
3
3
  import { findChildrenByType } from '@atlaskit/editor-prosemirror/utils';
4
+ import { fg } from '@atlaskit/platform-feature-flags';
4
5
  import { getInlineNodeTextContent } from './inline-node-transforms';
5
6
  import { isBlockNodeType, isListNodeType, isContainerNodeType, isBlockNodeForExtraction, convertNodeToInlineContent, getContentSupportChecker, convertCodeBlockContentToParagraphs, filterMarksForTargetNodeType, getMarksWithBreakout } from './utils';
6
7
  var convertInvalidNodeToValidNodeType = function convertInvalidNodeToValidNodeType(sourceContent, sourceNodeType, validNodeType, withMarks) {
@@ -196,7 +197,11 @@ export var unwrapAndConvertToList = function unwrapAndConvertToList(_ref3) {
196
197
  heading = _schema$nodes2.heading;
197
198
  var isTargetTaskList = targetNodeType === taskList;
198
199
  var createListItemFromInline = function createListItemFromInline(content) {
199
- return isTargetTaskList ? taskItem.create(null, content) : listItem.create(null, paragraph.create(null, content));
200
+ if (!content && fg('platform_editor_block_menu_patch_2')) {
201
+ return isTargetTaskList ? taskItem.create() : listItem.create(null, paragraph.create());
202
+ } else {
203
+ return isTargetTaskList ? taskItem.create(null, content) : listItem.create(null, paragraph.create(null, content));
204
+ }
200
205
  };
201
206
  var getInlineContent = function getInlineContent(textblock) {
202
207
  var inlineContent = [];
@@ -226,6 +231,15 @@ export var unwrapAndConvertToList = function unwrapAndConvertToList(_ref3) {
226
231
  };
227
232
  if (sourceNode.type.name === 'codeBlock') {
228
233
  var codeText = sourceNode.textContent;
234
+ // check if code block only contains newline characters
235
+ // eslint-disable-next-line require-unicode-regexp
236
+ var isOnlyNewLines = function isOnlyNewLines(codeText) {
237
+ return codeText.replace(/\n/g, '').trim() === '';
238
+ };
239
+ if ((!codeText || isOnlyNewLines(codeText)) && fg('platform_editor_block_menu_patch_2')) {
240
+ // Empty code block - create an empty list item
241
+ currentListItems.push(createListItemFromInline());
242
+ }
229
243
  if (codeText) {
230
244
  var lines = codeText.split('\n');
231
245
  // Remove empty lines
@@ -1,6 +1,7 @@
1
1
  import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
2
  import React, { Fragment } from 'react';
3
3
  import { ArrowKeyNavigationProvider, ArrowKeyNavigationType } from '@atlaskit/editor-common/ui-menu';
4
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
4
5
  var NoOp = function NoOp(props) {
5
6
  return null;
6
7
  };
@@ -41,48 +42,51 @@ export var BlockMenuRenderer = function BlockMenuRenderer(_ref) {
41
42
  var menuSections = getSortedNonNestedSections(components);
42
43
  var menuItems = components.filter(isMenuItem);
43
44
  var nestedMenus = components.filter(isNestedMenu);
44
- return /*#__PURE__*/React.createElement(ArrowKeyNavigationProvider, {
45
- type: ArrowKeyNavigationType.MENU
46
- }, /*#__PURE__*/React.createElement(Fragment, null, menuSections.map(function (section) {
47
- // Get all items for the current section, including nested menus, and sort them by rank
48
- var currentSectionItemsSorted = getSortedChildren([].concat(_toConsumableArray(menuItems), _toConsumableArray(nestedMenus)), section.key);
49
- if (currentSectionItemsSorted.length === 0) {
50
- return null;
51
- }
45
+ var renderMenu = function renderMenu() {
46
+ return /*#__PURE__*/React.createElement(Fragment, null, menuSections.map(function (section) {
47
+ // Get all items for the current section, including nested menus, and sort them by rank
48
+ var currentSectionItemsSorted = getSortedChildren([].concat(_toConsumableArray(menuItems), _toConsumableArray(nestedMenus)), section.key);
49
+ if (currentSectionItemsSorted.length === 0) {
50
+ return null;
51
+ }
52
52
 
53
- // iterate over the current section items, if it is nested menu, get their children, sort them
54
- // if they are menu items, just render as they are sorted above
55
- var getChildrenWithNestedItems = function getChildrenWithNestedItems(items) {
56
- return items.map(function (item) {
57
- if (isNestedMenu(item)) {
58
- var sortedNestedSections = getSortedNestedSections(components, item.key);
59
- return sortedNestedSections.map(function (section) {
60
- var sortedNestedMenuItems = getSortedChildren(menuItems, section.key);
61
- var NestedMenuComponent = item.component || fallbacks.nestedMenu || NoOp;
62
- var NestedSection = section.component || fallbacks.section || NoOp;
63
- return /*#__PURE__*/React.createElement(NestedMenuComponent, {
53
+ // iterate over the current section items, if it is nested menu, get their children, sort them
54
+ // if they are menu items, just render as they are sorted above
55
+ var getChildrenWithNestedItems = function getChildrenWithNestedItems(items) {
56
+ return items.map(function (item) {
57
+ if (isNestedMenu(item)) {
58
+ var sortedNestedSections = getSortedNestedSections(components, item.key);
59
+ return sortedNestedSections.map(function (section) {
60
+ var sortedNestedMenuItems = getSortedChildren(menuItems, section.key);
61
+ var NestedMenuComponent = item.component || fallbacks.nestedMenu || NoOp;
62
+ var NestedSection = section.component || fallbacks.section || NoOp;
63
+ return /*#__PURE__*/React.createElement(NestedMenuComponent, {
64
+ key: item.key
65
+ }, /*#__PURE__*/React.createElement(NestedSection, {
66
+ key: section.key
67
+ }, sortedNestedMenuItems.map(function (nestedItem) {
68
+ var NestedMenuItemComponent = nestedItem.component || fallbacks.item || NoOp;
69
+ return /*#__PURE__*/React.createElement(NestedMenuItemComponent, {
70
+ key: nestedItem.key
71
+ });
72
+ })));
73
+ });
74
+ } else {
75
+ var ItemComponent = item.component || fallbacks.item || NoOp;
76
+ return /*#__PURE__*/React.createElement(ItemComponent, {
64
77
  key: item.key
65
- }, /*#__PURE__*/React.createElement(NestedSection, {
66
- key: section.key
67
- }, sortedNestedMenuItems.map(function (nestedItem) {
68
- var NestedMenuItemComponent = nestedItem.component || fallbacks.item || NoOp;
69
- return /*#__PURE__*/React.createElement(NestedMenuItemComponent, {
70
- key: nestedItem.key
71
- });
72
- })));
73
- });
74
- } else {
75
- var ItemComponent = item.component || fallbacks.item || NoOp;
76
- return /*#__PURE__*/React.createElement(ItemComponent, {
77
- key: item.key
78
- });
79
- }
80
- });
81
- };
82
- var children = getChildrenWithNestedItems(currentSectionItemsSorted);
83
- var SectionComponent = section.component || fallbacks.section || NoOp;
84
- return /*#__PURE__*/React.createElement(SectionComponent, {
85
- key: section.key
86
- }, children);
87
- })));
78
+ });
79
+ }
80
+ });
81
+ };
82
+ var children = getChildrenWithNestedItems(currentSectionItemsSorted);
83
+ var SectionComponent = section.component || fallbacks.section || NoOp;
84
+ return /*#__PURE__*/React.createElement(SectionComponent, {
85
+ key: section.key
86
+ }, children);
87
+ }));
88
+ };
89
+ return expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true) ? /*#__PURE__*/React.createElement(ArrowKeyNavigationProvider, {
90
+ type: ArrowKeyNavigationType.MENU
91
+ }, renderMenu()) : renderMenu();
88
92
  };
@@ -56,18 +56,20 @@ var BlockMenu = function BlockMenu(_ref2) {
56
56
  boundariesElement = _ref2.boundariesElement,
57
57
  scrollableElement = _ref2.scrollableElement;
58
58
  var _useSharedPluginState = useSharedPluginStateWithSelector(api, ['blockControls', 'userIntent'], function (states) {
59
- var _states$blockControls, _states$blockControls2, _states$blockControls3, _states$userIntentSta;
59
+ var _states$blockControls, _states$blockControls2, _states$blockControls3, _states$userIntentSta, _states$blockControls4;
60
60
  return {
61
61
  menuTriggerBy: (_states$blockControls = states.blockControlsState) === null || _states$blockControls === void 0 ? void 0 : _states$blockControls.menuTriggerBy,
62
62
  isSelectedViaDragHandle: (_states$blockControls2 = states.blockControlsState) === null || _states$blockControls2 === void 0 ? void 0 : _states$blockControls2.isSelectedViaDragHandle,
63
63
  isMenuOpen: (_states$blockControls3 = states.blockControlsState) === null || _states$blockControls3 === void 0 ? void 0 : _states$blockControls3.isMenuOpen,
64
- currentUserIntent: (_states$userIntentSta = states.userIntentState) === null || _states$userIntentSta === void 0 ? void 0 : _states$userIntentSta.currentUserIntent
64
+ currentUserIntent: (_states$userIntentSta = states.userIntentState) === null || _states$userIntentSta === void 0 ? void 0 : _states$userIntentSta.currentUserIntent,
65
+ openedViaKeyboard: (_states$blockControls4 = states.blockControlsState) === null || _states$blockControls4 === void 0 || (_states$blockControls4 = _states$blockControls4.blockMenuOptions) === null || _states$blockControls4 === void 0 ? void 0 : _states$blockControls4.openedViaKeyboard
65
66
  };
66
67
  }),
67
68
  menuTriggerBy = _useSharedPluginState.menuTriggerBy,
68
69
  isSelectedViaDragHandle = _useSharedPluginState.isSelectedViaDragHandle,
69
70
  isMenuOpen = _useSharedPluginState.isMenuOpen,
70
- currentUserIntent = _useSharedPluginState.currentUserIntent;
71
+ currentUserIntent = _useSharedPluginState.currentUserIntent,
72
+ openedViaKeyboard = _useSharedPluginState.openedViaKeyboard;
71
73
  var _useBlockMenu = useBlockMenu(),
72
74
  onDropdownOpenChanged = _useBlockMenu.onDropdownOpenChanged,
73
75
  fireAnalyticsEvent = _useBlockMenu.fireAnalyticsEvent;
@@ -78,7 +80,7 @@ var BlockMenu = function BlockMenu(_ref2) {
78
80
  // hasSelection true, always show block menu
79
81
  // hasSelection false, only show block menu when empty line experiment is enabled
80
82
  var shouldShowBlockMenuForEmptyLine = hasSelection || !hasSelection && expValEqualsNoExposure('platform_editor_block_menu_empty_line', 'isEnabled', true);
81
- var selectedByShortcutORDragHandle = isSelectedViaDragHandle;
83
+ var selectedByShortcutORDragHandle = isSelectedViaDragHandle || openedViaKeyboard && expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true);
82
84
  useEffect(function () {
83
85
  var _api$userIntent;
84
86
  if (!isMenuOpen || !menuTriggerBy || !selectedByShortcutORDragHandle || !hasFocus || !shouldShowBlockMenuForEmptyLine || ['resizing', 'dragging'].includes(currentUserIntent || '')) {
@@ -141,7 +143,9 @@ var BlockMenu = function BlockMenu(_ref2) {
141
143
  preventOverflow: true // disables forced horizontal placement when forcePlacement is on, so fitWidth controls flipping
142
144
  ,
143
145
  stick: true,
144
- focusTrap: true,
146
+ focusTrap: expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true) ? openedViaKeyboard ? {
147
+ initialFocus: undefined
148
+ } : true : undefined,
145
149
  offset: [DRAG_HANDLE_WIDTH + DRAG_HANDLE_OFFSET_PADDING, 0]
146
150
  }, /*#__PURE__*/React.createElement(BlockMenuContent, {
147
151
  api: api
@@ -1,4 +1,4 @@
1
- import React, { useCallback } from 'react';
1
+ import React, { useCallback, useEffect } from 'react';
2
2
  import { useIntl, injectIntl } from 'react-intl-next';
3
3
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
4
4
  import { messages } from '@atlaskit/editor-common/block-menu';
@@ -69,10 +69,22 @@ var DeleteDropdownItemContent = function DeleteDropdownItemContent(_ref) {
69
69
  return tr;
70
70
  });
71
71
  }, [api, nodeTypes]);
72
- var onRemoveHoverDecoration = function onRemoveHoverDecoration() {
72
+ var onRemoveHoverDecoration = useCallback(function () {
73
73
  var _api$decorations2, _api$decorations2$rem;
74
74
  api === null || api === void 0 || api.core.actions.execute(api === null || api === void 0 || (_api$decorations2 = api.decorations) === null || _api$decorations2 === void 0 || (_api$decorations2 = _api$decorations2.commands) === null || _api$decorations2 === void 0 || (_api$decorations2$rem = _api$decorations2.removeDecoration) === null || _api$decorations2$rem === void 0 ? void 0 : _api$decorations2$rem.call(_api$decorations2));
75
- };
75
+ }, [api]);
76
+ useEffect(function () {
77
+ if (!expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true)) {
78
+ return;
79
+ }
80
+ return function () {
81
+ if (!expValEqualsNoExposure('platform_editor_block_menu_keyboard_navigation', 'isEnabled', true)) {
82
+ return;
83
+ }
84
+ // clean up hover decoration when unmounting
85
+ onRemoveHoverDecoration();
86
+ };
87
+ }, [onRemoveHoverDecoration]);
76
88
  var text = fg('platform_editor_block_menu_patch_1') ? formatMessage(blockMenuMessages.deleteBlock) : formatMessage(messages.deleteBlock);
77
89
  return /*#__PURE__*/React.createElement(Box, {
78
90
  onMouseEnter: onShowHoverDecoration,
@@ -4,14 +4,14 @@ import type { BlockControlsPlugin } from '@atlaskit/editor-plugin-block-controls
4
4
  import type { DecorationsPlugin } from '@atlaskit/editor-plugin-decorations';
5
5
  import type { SelectionPlugin } from '@atlaskit/editor-plugin-selection';
6
6
  import type { UserIntentPlugin } from '@atlaskit/editor-plugin-user-intent';
7
- import type { FormatNodeTargetType } from './editor-commands/transforms/types';
7
+ import type { FormatNodeTargetType, FormatNodeAnalyticsAttrs } from './editor-commands/transforms/types';
8
8
  export type BlockMenuPlugin = NextEditorPlugin<'blockMenu', {
9
9
  actions: {
10
10
  getBlockMenuComponents: () => Array<RegisterBlockMenuComponent>;
11
11
  registerBlockMenuComponents: (blockMenuComponents: Array<RegisterBlockMenuComponent>) => void;
12
12
  };
13
13
  commands: {
14
- formatNode: (targetType: FormatNodeTargetType) => EditorCommand;
14
+ formatNode: (targetType: FormatNodeTargetType, analyticsAttrs?: FormatNodeAnalyticsAttrs) => EditorCommand;
15
15
  };
16
16
  dependencies: [
17
17
  OptionalPlugin<BlockControlsPlugin>,
@@ -2,7 +2,7 @@ import type { EditorCommand, ExtractInjectionAPI } from '@atlaskit/editor-common
2
2
  import { type Schema, type Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
3
  import { type Transaction } from '@atlaskit/editor-prosemirror/state';
4
4
  import type { BlockMenuPlugin } from '../blockMenuPluginType';
5
- import type { FormatNodeTargetType } from './transforms/types';
5
+ import type { FormatNodeAnalyticsAttrs, FormatNodeTargetType } from './transforms/types';
6
6
  /**
7
7
  * Handles formatting when an empty list is selected
8
8
  * Converting an empty list to a target node, will remove the list and replace with an empty target node
@@ -15,5 +15,6 @@ export declare const formatNodeSelectEmptyList: (tr: Transaction, targetType: Fo
15
15
  * Formats the current node or selection to the specified target type
16
16
  * @param api - The editor API injection that provides access to analytics and other plugin actions
17
17
  * @param targetType - The target node type to convert to
18
+ * @param analyticsAttrs - Attributes required for formatNode analytics like: inputMethod and triggeredFrom
18
19
  */
19
- export declare const formatNode: (api?: ExtractInjectionAPI<BlockMenuPlugin>) => (targetType: FormatNodeTargetType) => EditorCommand;
20
+ export declare const formatNode: (api?: ExtractInjectionAPI<BlockMenuPlugin>) => (targetType: FormatNodeTargetType, analyticsAttrs?: FormatNodeAnalyticsAttrs) => EditorCommand;
@@ -1,4 +1,9 @@
1
+ import type { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
1
2
  import type { TransformContext } from '@atlaskit/editor-common/transforms';
2
3
  import type { Transaction } from '@atlaskit/editor-prosemirror/state';
3
4
  export type FormatNodeTargetType = 'heading1' | 'heading2' | 'heading3' | 'heading4' | 'heading5' | 'heading6' | 'paragraph' | 'blockquote' | 'expand' | 'layoutSection' | 'panel' | 'codeBlock' | 'bulletList' | 'orderedList' | 'taskList';
5
+ export type FormatNodeAnalyticsAttrs = {
6
+ inputMethod: INPUT_METHOD.MOUSE | INPUT_METHOD.KEYBOARD;
7
+ triggeredFrom: INPUT_METHOD.BLOCK_MENU;
8
+ };
4
9
  export type TransformFunction = (context: TransformContext) => Transaction | null;
@@ -4,14 +4,14 @@ import type { BlockControlsPlugin } from '@atlaskit/editor-plugin-block-controls
4
4
  import type { DecorationsPlugin } from '@atlaskit/editor-plugin-decorations';
5
5
  import type { SelectionPlugin } from '@atlaskit/editor-plugin-selection';
6
6
  import type { UserIntentPlugin } from '@atlaskit/editor-plugin-user-intent';
7
- import type { FormatNodeTargetType } from './editor-commands/transforms/types';
7
+ import type { FormatNodeTargetType, FormatNodeAnalyticsAttrs } from './editor-commands/transforms/types';
8
8
  export type BlockMenuPlugin = NextEditorPlugin<'blockMenu', {
9
9
  actions: {
10
10
  getBlockMenuComponents: () => Array<RegisterBlockMenuComponent>;
11
11
  registerBlockMenuComponents: (blockMenuComponents: Array<RegisterBlockMenuComponent>) => void;
12
12
  };
13
13
  commands: {
14
- formatNode: (targetType: FormatNodeTargetType) => EditorCommand;
14
+ formatNode: (targetType: FormatNodeTargetType, analyticsAttrs?: FormatNodeAnalyticsAttrs) => EditorCommand;
15
15
  };
16
16
  dependencies: [
17
17
  OptionalPlugin<BlockControlsPlugin>,
@@ -2,7 +2,7 @@ import type { EditorCommand, ExtractInjectionAPI } from '@atlaskit/editor-common
2
2
  import { type Schema, type Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
3
  import { type Transaction } from '@atlaskit/editor-prosemirror/state';
4
4
  import type { BlockMenuPlugin } from '../blockMenuPluginType';
5
- import type { FormatNodeTargetType } from './transforms/types';
5
+ import type { FormatNodeAnalyticsAttrs, FormatNodeTargetType } from './transforms/types';
6
6
  /**
7
7
  * Handles formatting when an empty list is selected
8
8
  * Converting an empty list to a target node, will remove the list and replace with an empty target node
@@ -15,5 +15,6 @@ export declare const formatNodeSelectEmptyList: (tr: Transaction, targetType: Fo
15
15
  * Formats the current node or selection to the specified target type
16
16
  * @param api - The editor API injection that provides access to analytics and other plugin actions
17
17
  * @param targetType - The target node type to convert to
18
+ * @param analyticsAttrs - Attributes required for formatNode analytics like: inputMethod and triggeredFrom
18
19
  */
19
- export declare const formatNode: (api?: ExtractInjectionAPI<BlockMenuPlugin>) => (targetType: FormatNodeTargetType) => EditorCommand;
20
+ export declare const formatNode: (api?: ExtractInjectionAPI<BlockMenuPlugin>) => (targetType: FormatNodeTargetType, analyticsAttrs?: FormatNodeAnalyticsAttrs) => EditorCommand;
@@ -1,4 +1,9 @@
1
+ import type { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
1
2
  import type { TransformContext } from '@atlaskit/editor-common/transforms';
2
3
  import type { Transaction } from '@atlaskit/editor-prosemirror/state';
3
4
  export type FormatNodeTargetType = 'heading1' | 'heading2' | 'heading3' | 'heading4' | 'heading5' | 'heading6' | 'paragraph' | 'blockquote' | 'expand' | 'layoutSection' | 'panel' | 'codeBlock' | 'bulletList' | 'orderedList' | 'taskList';
5
+ export type FormatNodeAnalyticsAttrs = {
6
+ inputMethod: INPUT_METHOD.MOUSE | INPUT_METHOD.KEYBOARD;
7
+ triggeredFrom: INPUT_METHOD.BLOCK_MENU;
8
+ };
4
9
  export type TransformFunction = (context: TransformContext) => Transaction | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-block-menu",
3
- "version": "4.0.3",
3
+ "version": "4.0.5",
4
4
  "description": "BlockMenu plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -31,7 +31,7 @@
31
31
  "@atlaskit/css": "^0.14.0",
32
32
  "@atlaskit/dropdown-menu": "^16.3.0",
33
33
  "@atlaskit/editor-plugin-analytics": "^6.1.0",
34
- "@atlaskit/editor-plugin-block-controls": "^7.1.0",
34
+ "@atlaskit/editor-plugin-block-controls": "^7.2.0",
35
35
  "@atlaskit/editor-plugin-decorations": "^6.1.0",
36
36
  "@atlaskit/editor-plugin-selection": "^6.0.0",
37
37
  "@atlaskit/editor-plugin-user-intent": "^4.0.0",
@@ -48,7 +48,7 @@
48
48
  "@babel/runtime": "^7.0.0"
49
49
  },
50
50
  "peerDependencies": {
51
- "@atlaskit/editor-common": "^110.3.0",
51
+ "@atlaskit/editor-common": "^110.4.0",
52
52
  "react": "^18.2.0",
53
53
  "react-intl-next": "npm:react-intl@^5.18.1"
54
54
  },