@atlaskit/editor-plugin-block-menu 5.2.28 → 6.0.1

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/cjs/blockMenuPlugin.js +3 -1
  3. package/dist/cjs/editor-commands/transform-node-utils/flattenStep.js +3 -2
  4. package/dist/cjs/editor-commands/transform-node-utils/marks.js +12 -12
  5. package/dist/cjs/editor-commands/transform-node-utils/steps/applyTargetTextTypeStep.js +1 -1
  6. package/dist/cjs/editor-commands/transform-node-utils/steps/wrapMixedContentStep.js +6 -3
  7. package/dist/cjs/editor-commands/transform-node-utils/wrapIntoLayoutStep.js +1 -1
  8. package/dist/cjs/editor-commands/transform-node-utils/wrapStep.js +1 -1
  9. package/dist/cjs/pm-plugins/main.js +21 -1
  10. package/dist/cjs/ui/block-menu.js +19 -25
  11. package/dist/es2019/blockMenuPlugin.js +1 -1
  12. package/dist/es2019/editor-commands/transform-node-utils/flattenStep.js +5 -2
  13. package/dist/es2019/editor-commands/transform-node-utils/marks.js +9 -7
  14. package/dist/es2019/editor-commands/transform-node-utils/steps/applyTargetTextTypeStep.js +1 -1
  15. package/dist/es2019/editor-commands/transform-node-utils/steps/wrapMixedContentStep.js +5 -3
  16. package/dist/es2019/editor-commands/transform-node-utils/wrapIntoLayoutStep.js +2 -2
  17. package/dist/es2019/editor-commands/transform-node-utils/wrapStep.js +2 -2
  18. package/dist/es2019/pm-plugins/main.js +21 -1
  19. package/dist/es2019/ui/block-menu.js +17 -24
  20. package/dist/esm/blockMenuPlugin.js +3 -1
  21. package/dist/esm/editor-commands/transform-node-utils/flattenStep.js +3 -2
  22. package/dist/esm/editor-commands/transform-node-utils/marks.js +11 -11
  23. package/dist/esm/editor-commands/transform-node-utils/steps/applyTargetTextTypeStep.js +1 -1
  24. package/dist/esm/editor-commands/transform-node-utils/steps/wrapMixedContentStep.js +6 -3
  25. package/dist/esm/editor-commands/transform-node-utils/wrapIntoLayoutStep.js +2 -2
  26. package/dist/esm/editor-commands/transform-node-utils/wrapStep.js +2 -2
  27. package/dist/esm/pm-plugins/main.js +21 -1
  28. package/dist/esm/ui/block-menu.js +19 -25
  29. package/dist/types/editor-commands/transform-node-utils/marks.d.ts +9 -2
  30. package/dist/types/pm-plugins/main.d.ts +3 -2
  31. package/dist/types-ts4.5/editor-commands/transform-node-utils/marks.d.ts +9 -2
  32. package/dist/types-ts4.5/pm-plugins/main.d.ts +3 -2
  33. package/package.json +7 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @atlaskit/editor-plugin-block-menu
2
2
 
3
+ ## 6.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`3ae29083b2189`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/3ae29083b2189) -
8
+ Remove block marks when wrapping nodes (fixes multi-select codeblock)
9
+
10
+ ## 6.0.0
11
+
12
+ ### Patch Changes
13
+
14
+ - [`4da819b186eaf`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/4da819b186eaf) -
15
+ EDITOR-3911 selection preservation key handling
16
+ - [`2f000c01bd8ac`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/2f000c01bd8ac) -
17
+ Retain block marks for text -> text transforms
18
+ - Updated dependencies
19
+
3
20
  ## 5.2.28
4
21
 
5
22
  ### Patch Changes
@@ -28,7 +28,9 @@ var blockMenuPlugin = exports.blockMenuPlugin = function blockMenuPlugin(_ref) {
28
28
  pmPlugins: function pmPlugins() {
29
29
  return [{
30
30
  name: 'blockMenuPlugin',
31
- plugin: _main.createPlugin
31
+ plugin: function plugin() {
32
+ return (0, _main.createPlugin)(api);
33
+ }
32
34
  }];
33
35
  },
34
36
  actions: {
@@ -7,8 +7,9 @@ exports.flattenStep = void 0;
7
7
  var flattenStep = exports.flattenStep = function flattenStep(nodes, context) {
8
8
  var schema = context.schema,
9
9
  targetNodeTypeName = context.targetNodeTypeName;
10
+ var paragraph = schema.nodes.paragraph;
10
11
  var targetNodeType = schema.nodes[targetNodeTypeName];
11
- if (!targetNodeType) {
12
+ if (!targetNodeType || !paragraph) {
12
13
  return nodes;
13
14
  }
14
15
 
@@ -26,6 +27,6 @@ var flattenStep = exports.flattenStep = function flattenStep(nodes, context) {
26
27
  if (!isValidWithin) {
27
28
  return node;
28
29
  }
29
- return schema.nodes.paragraph.create({}, node.content);
30
+ return paragraph.create({}, node.content, node.marks);
30
31
  });
31
32
  };
@@ -3,16 +3,16 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.removeBlockMarks = void 0;
7
- var removeMarks = function removeMarks(disallowedMarks) {
8
- return function (node) {
9
- var filteredMarks = node.marks.filter(function (mark) {
10
- return !disallowedMarks.includes(mark.type);
11
- });
12
- return node.mark(filteredMarks);
13
- };
14
- };
15
- var removeBlockMarks = exports.removeBlockMarks = function removeBlockMarks(nodes, schema) {
16
- var disallowedMarks = [schema.marks.breakout, schema.marks.alignment];
17
- return nodes.map(removeMarks(disallowedMarks));
6
+ exports.removeDisallowedMarks = void 0;
7
+ /**
8
+ * Removes all disallowed marks from a node based on the target node type.
9
+ *
10
+ * @param node - The node to remove marks from.
11
+ * @param targetNode - The target node type to check against.
12
+ * @returns The nodes with the marks removed.
13
+ */
14
+ var removeDisallowedMarks = exports.removeDisallowedMarks = function removeDisallowedMarks(nodes, targetNode) {
15
+ return nodes.map(function (node) {
16
+ return node.mark(targetNode.allowedMarks(node.marks));
17
+ });
18
18
  };
@@ -42,7 +42,7 @@ var applyTargetTextTypeStep = exports.applyTargetTextTypeStep = function applyTa
42
42
  // Convert textblock nodes (paragraphs, headings) to heading with specified level
43
43
  return headingType.create({
44
44
  level: headingLevel
45
- }, node.content);
45
+ }, node.content, node.marks);
46
46
  }
47
47
  // Non-textblock nodes are left unchanged
48
48
  return node;
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", {
7
7
  exports.wrapMixedContentStep = void 0;
8
8
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
9
  var _model = require("@atlaskit/editor-prosemirror/model");
10
+ var _marks = require("../marks");
10
11
  var _types = require("../types");
11
12
  var _utils = require("../utils");
12
13
  /**
@@ -19,7 +20,7 @@ var isTextNode = function isTextNode(node) {
19
20
  };
20
21
 
21
22
  /**
22
- * Determines if a node can be wrapped in the target container type.
23
+ * Determines if a node can be wrapped in the target container type, removes block marks from the node during check.
23
24
  * Uses the schema's validContent to check if the target container can hold this node.
24
25
  *
25
26
  * Note: What can be wrapped depends on the target container type - for example:
@@ -33,7 +34,7 @@ var canWrapInTarget = function canWrapInTarget(node, targetNodeType, targetNodeT
33
34
  }
34
35
 
35
36
  // Use the schema to determine if this node can be contained in the target
36
- return targetNodeType.validContent(_model.Fragment.from(node));
37
+ return targetNodeType.validContent(_model.Fragment.from((0, _marks.removeDisallowedMarks)([node], targetNodeType)));
37
38
  };
38
39
 
39
40
  /**
@@ -110,8 +111,10 @@ var wrapMixedContentStep = exports.wrapMixedContentStep = function wrapMixedCont
110
111
  };
111
112
  nodes.forEach(function (node) {
112
113
  if (canWrapInTarget(node, targetNodeType, targetNodeTypeName)) {
114
+ var _currentContainerCont;
113
115
  // Node can be wrapped - add to current container content
114
- currentContainerContent.push(node);
116
+ // remove marks from node as nested nodes don't usually support block marks
117
+ (_currentContainerCont = currentContainerContent).push.apply(_currentContainerCont, (0, _toConsumableArray2.default)((0, _marks.removeDisallowedMarks)([node], targetNodeType)));
115
118
  } else if (node.type.name === targetNodeTypeName) {
116
119
  // Same-type container - breaks out as a separate container (preserved as-is)
117
120
  // This handles: "If there's a panel in the expand, it breaks out into a separate panel"
@@ -10,7 +10,7 @@ var wrapIntoLayoutStep = exports.wrapIntoLayoutStep = function wrapIntoLayoutSte
10
10
  var _ref = schema.nodes || {},
11
11
  layoutSection = _ref.layoutSection,
12
12
  layoutColumn = _ref.layoutColumn;
13
- var columnOne = layoutColumn.createAndFill({}, (0, _marks.removeBlockMarks)(nodes, schema));
13
+ var columnOne = layoutColumn.createAndFill({}, (0, _marks.removeDisallowedMarks)(nodes, layoutColumn));
14
14
  var columnTwo = layoutColumn.createAndFill();
15
15
  if (!columnOne || !columnTwo) {
16
16
  return nodes;
@@ -27,6 +27,6 @@ var wrapStep = exports.wrapStep = function wrapStep(nodes, context) {
27
27
  return node;
28
28
  });
29
29
  }
30
- var outputNode = schema.nodes[targetNodeTypeName].createAndFill({}, (0, _marks.removeBlockMarks)(processedNodes, schema));
30
+ var outputNode = schema.nodes[targetNodeTypeName].createAndFill({}, (0, _marks.removeDisallowedMarks)(processedNodes, schema.nodes[targetNodeTypeName]));
31
31
  return outputNode ? [outputNode] : nodes;
32
32
  };
@@ -7,7 +7,7 @@ exports.createPlugin = exports.blockMenuPluginKey = void 0;
7
7
  var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
8
8
  var _state = require("@atlaskit/editor-prosemirror/state");
9
9
  var blockMenuPluginKey = exports.blockMenuPluginKey = new _state.PluginKey('blockMenuPlugin');
10
- var createPlugin = exports.createPlugin = function createPlugin() {
10
+ var createPlugin = exports.createPlugin = function createPlugin(api) {
11
11
  return new _safePlugin.SafePlugin({
12
12
  key: blockMenuPluginKey,
13
13
  state: {
@@ -23,6 +23,26 @@ var createPlugin = exports.createPlugin = function createPlugin() {
23
23
  showFlag: (_meta$showFlag = meta === null || meta === void 0 ? void 0 : meta.showFlag) !== null && _meta$showFlag !== void 0 ? _meta$showFlag : currentPluginState.showFlag
24
24
  };
25
25
  }
26
+ },
27
+ props: {
28
+ handleKeyDown: function handleKeyDown(_editorView, event) {
29
+ var _api$userIntent;
30
+ var blockMenuOpen = (api === null || api === void 0 || (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 || (_api$userIntent = _api$userIntent.sharedState.currentState()) === null || _api$userIntent === void 0 ? void 0 : _api$userIntent.currentUserIntent) === 'blockMenuOpen';
31
+
32
+ // Exit early and do nothing when block menu is closed
33
+ if (!blockMenuOpen) {
34
+ return false;
35
+ }
36
+
37
+ // Block further handling of key events when block menu is open
38
+ // Except for backspace/delete/copy/cut/paste which should be handled by the selection preservation plugin
39
+ var key = event.key.toLowerCase();
40
+ var isMetaCtrl = event.metaKey || event.ctrlKey;
41
+ var isBackspaceDelete = ['backspace', 'delete'].includes(key);
42
+ var isCopyCutPaste = isMetaCtrl && ['c', 'x', 'v'].includes(key);
43
+ var suppressNativeHandling = !isCopyCutPaste && !isBackspaceDelete;
44
+ return suppressNativeHandling;
45
+ }
26
46
  }
27
47
  });
28
48
  };
@@ -16,7 +16,6 @@ var _css = require("@atlaskit/css");
16
16
  var _analytics = require("@atlaskit/editor-common/analytics");
17
17
  var _errorBoundary = require("@atlaskit/editor-common/error-boundary");
18
18
  var _hooks = require("@atlaskit/editor-common/hooks");
19
- var _selection = require("@atlaskit/editor-common/selection");
20
19
  var _styles = require("@atlaskit/editor-common/styles");
21
20
  var _ui = require("@atlaskit/editor-common/ui");
22
21
  var _uiMenu = require("@atlaskit/editor-common/ui-menu");
@@ -185,25 +184,20 @@ var BlockMenu = function BlockMenu(_ref4) {
185
184
  if (!isMenuOpen) {
186
185
  return null;
187
186
  }
188
- var handleBackspaceDeleteKeydown = function handleBackspaceDeleteKeydown(event) {
189
- // Pevents delete/backspace keypress being handled by editor, avoids double deletions
190
- event === null || event === void 0 || event.preventDefault();
191
- event === null || event === void 0 || event.stopPropagation();
192
- api === null || api === void 0 || api.core.actions.execute(function (_ref6) {
193
- var _api$blockControls, _api$blockControls2;
194
- var tr = _ref6.tr;
195
- (0, _selection.deleteSelectedRange)(tr, api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.sharedState.currentState()) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.preservedSelection);
196
- api === null || api === void 0 || (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || _api$blockControls2.commands.toggleBlockMenu({
197
- closeMenu: true
198
- })({
199
- tr: tr
200
- });
201
- if (editorView && !editorView.hasFocus()) {
202
- // if focus is outside editor, e.g. in block menu popup, then refocus editor after delete
203
- editorView.focus();
204
- }
205
- return tr;
206
- });
187
+ var handleKeyDown = function handleKeyDown(event) {
188
+ var _api$core, _api$blockControls;
189
+ // When the editor view has focus, the keydown will be handled by the
190
+ // selection preservation plugin exit early to avoid double handling
191
+ if (!editorView || editorView !== null && editorView !== void 0 && editorView.hasFocus()) {
192
+ return;
193
+ }
194
+
195
+ // Necessary to prevent the editor from handling the delete natively
196
+ if (['backspace', 'delete'].includes(event.key.toLowerCase())) {
197
+ event.preventDefault();
198
+ event.stopPropagation();
199
+ }
200
+ api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.commands) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.handleKeyDownWithPreservedSelection(event));
207
201
  };
208
202
  var handleClickOutside = function handleClickOutside(e) {
209
203
  // check if the clicked element was another drag handle, if so don't close the menu
@@ -213,10 +207,10 @@ var BlockMenu = function BlockMenu(_ref4) {
213
207
  closeMenu();
214
208
  };
215
209
  var closeMenu = function closeMenu() {
216
- api === null || api === void 0 || api.core.actions.execute(function (_ref7) {
217
- var _api$blockControls3, _api$userIntent3;
218
- var tr = _ref7.tr;
219
- api === null || api === void 0 || (_api$blockControls3 = api.blockControls) === null || _api$blockControls3 === void 0 || _api$blockControls3.commands.toggleBlockMenu({
210
+ api === null || api === void 0 || api.core.actions.execute(function (_ref6) {
211
+ var _api$blockControls2, _api$userIntent3;
212
+ var tr = _ref6.tr;
213
+ api === null || api === void 0 || (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || _api$blockControls2.commands.toggleBlockMenu({
220
214
  closeMenu: true
221
215
  })({
222
216
  tr: tr
@@ -249,7 +243,7 @@ var BlockMenu = function BlockMenu(_ref4) {
249
243
  alignY: 'start',
250
244
  handleClickOutside: handleClickOutside,
251
245
  handleEscapeKeydown: closeMenu,
252
- handleBackspaceDeleteKeydown: handleBackspaceDeleteKeydown,
246
+ handleKeyDown: handleKeyDown,
253
247
  mountTo: mountTo,
254
248
  boundariesElement: boundariesElement,
255
249
  scrollableElement: scrollableElement,
@@ -22,7 +22,7 @@ export const blockMenuPlugin = ({
22
22
  pmPlugins() {
23
23
  return [{
24
24
  name: 'blockMenuPlugin',
25
- plugin: createPlugin
25
+ plugin: () => createPlugin(api)
26
26
  }];
27
27
  },
28
28
  actions: {
@@ -3,8 +3,11 @@ export const flattenStep = (nodes, context) => {
3
3
  schema,
4
4
  targetNodeTypeName
5
5
  } = context;
6
+ const {
7
+ paragraph
8
+ } = schema.nodes;
6
9
  const targetNodeType = schema.nodes[targetNodeTypeName];
7
- if (!targetNodeType) {
10
+ if (!targetNodeType || !paragraph) {
8
11
  return nodes;
9
12
  }
10
13
 
@@ -20,6 +23,6 @@ export const flattenStep = (nodes, context) => {
20
23
  if (!isValidWithin) {
21
24
  return node;
22
25
  }
23
- return schema.nodes.paragraph.create({}, node.content);
26
+ return paragraph.create({}, node.content, node.marks);
24
27
  });
25
28
  };
@@ -1,8 +1,10 @@
1
- const removeMarks = disallowedMarks => node => {
2
- const filteredMarks = node.marks.filter(mark => !disallowedMarks.includes(mark.type));
3
- return node.mark(filteredMarks);
4
- };
5
- export const removeBlockMarks = (nodes, schema) => {
6
- const disallowedMarks = [schema.marks.breakout, schema.marks.alignment];
7
- return nodes.map(removeMarks(disallowedMarks));
1
+ /**
2
+ * Removes all disallowed marks from a node based on the target node type.
3
+ *
4
+ * @param node - The node to remove marks from.
5
+ * @param targetNode - The target node type to check against.
6
+ * @returns The nodes with the marks removed.
7
+ */
8
+ export const removeDisallowedMarks = (nodes, targetNode) => {
9
+ return nodes.map(node => node.mark(targetNode.allowedMarks(node.marks)));
8
10
  };
@@ -38,7 +38,7 @@ export const applyTargetTextTypeStep = (nodes, context) => {
38
38
  // Convert textblock nodes (paragraphs, headings) to heading with specified level
39
39
  return headingType.create({
40
40
  level: headingLevel
41
- }, node.content);
41
+ }, node.content, node.marks);
42
42
  }
43
43
  // Non-textblock nodes are left unchanged
44
44
  return node;
@@ -1,4 +1,5 @@
1
1
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
2
+ import { removeDisallowedMarks } from '../marks';
2
3
  import { NODE_CATEGORY_BY_TYPE } from '../types';
3
4
  import { convertTextNodeToParagraph } from '../utils';
4
5
 
@@ -12,7 +13,7 @@ const isTextNode = node => {
12
13
  };
13
14
 
14
15
  /**
15
- * Determines if a node can be wrapped in the target container type.
16
+ * Determines if a node can be wrapped in the target container type, removes block marks from the node during check.
16
17
  * Uses the schema's validContent to check if the target container can hold this node.
17
18
  *
18
19
  * Note: What can be wrapped depends on the target container type - for example:
@@ -26,7 +27,7 @@ const canWrapInTarget = (node, targetNodeType, targetNodeTypeName) => {
26
27
  }
27
28
 
28
29
  // Use the schema to determine if this node can be contained in the target
29
- return targetNodeType.validContent(Fragment.from(node));
30
+ return targetNodeType.validContent(Fragment.from(removeDisallowedMarks([node], targetNodeType)));
30
31
  };
31
32
 
32
33
  /**
@@ -106,7 +107,8 @@ export const wrapMixedContentStep = (nodes, context) => {
106
107
  nodes.forEach(node => {
107
108
  if (canWrapInTarget(node, targetNodeType, targetNodeTypeName)) {
108
109
  // Node can be wrapped - add to current container content
109
- currentContainerContent.push(node);
110
+ // remove marks from node as nested nodes don't usually support block marks
111
+ currentContainerContent.push(...removeDisallowedMarks([node], targetNodeType));
110
112
  } else if (node.type.name === targetNodeTypeName) {
111
113
  // Same-type container - breaks out as a separate container (preserved as-is)
112
114
  // This handles: "If there's a panel in the expand, it breaks out into a separate panel"
@@ -1,4 +1,4 @@
1
- import { removeBlockMarks } from './marks';
1
+ import { removeDisallowedMarks } from './marks';
2
2
  export const wrapIntoLayoutStep = (nodes, context) => {
3
3
  const {
4
4
  schema
@@ -7,7 +7,7 @@ export const wrapIntoLayoutStep = (nodes, context) => {
7
7
  layoutSection,
8
8
  layoutColumn
9
9
  } = schema.nodes || {};
10
- const columnOne = layoutColumn.createAndFill({}, removeBlockMarks(nodes, schema));
10
+ const columnOne = layoutColumn.createAndFill({}, removeDisallowedMarks(nodes, layoutColumn));
11
11
  const columnTwo = layoutColumn.createAndFill();
12
12
  if (!columnOne || !columnTwo) {
13
13
  return nodes;
@@ -1,4 +1,4 @@
1
- import { removeBlockMarks } from './marks';
1
+ import { removeDisallowedMarks } from './marks';
2
2
  import { convertExpandToNestedExpand } from './utils';
3
3
 
4
4
  /**
@@ -24,6 +24,6 @@ export const wrapStep = (nodes, context) => {
24
24
  return node;
25
25
  });
26
26
  }
27
- const outputNode = schema.nodes[targetNodeTypeName].createAndFill({}, removeBlockMarks(processedNodes, schema));
27
+ const outputNode = schema.nodes[targetNodeTypeName].createAndFill({}, removeDisallowedMarks(processedNodes, schema.nodes[targetNodeTypeName]));
28
28
  return outputNode ? [outputNode] : nodes;
29
29
  };
@@ -1,7 +1,7 @@
1
1
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
2
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
3
3
  export const blockMenuPluginKey = new PluginKey('blockMenuPlugin');
4
- export const createPlugin = () => {
4
+ export const createPlugin = api => {
5
5
  return new SafePlugin({
6
6
  key: blockMenuPluginKey,
7
7
  state: {
@@ -17,6 +17,26 @@ export const createPlugin = () => {
17
17
  showFlag: (_meta$showFlag = meta === null || meta === void 0 ? void 0 : meta.showFlag) !== null && _meta$showFlag !== void 0 ? _meta$showFlag : currentPluginState.showFlag
18
18
  };
19
19
  }
20
+ },
21
+ props: {
22
+ handleKeyDown: (_editorView, event) => {
23
+ var _api$userIntent, _api$userIntent$share;
24
+ const blockMenuOpen = (api === null || api === void 0 ? void 0 : (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 ? void 0 : (_api$userIntent$share = _api$userIntent.sharedState.currentState()) === null || _api$userIntent$share === void 0 ? void 0 : _api$userIntent$share.currentUserIntent) === 'blockMenuOpen';
25
+
26
+ // Exit early and do nothing when block menu is closed
27
+ if (!blockMenuOpen) {
28
+ return false;
29
+ }
30
+
31
+ // Block further handling of key events when block menu is open
32
+ // Except for backspace/delete/copy/cut/paste which should be handled by the selection preservation plugin
33
+ const key = event.key.toLowerCase();
34
+ const isMetaCtrl = event.metaKey || event.ctrlKey;
35
+ const isBackspaceDelete = ['backspace', 'delete'].includes(key);
36
+ const isCopyCutPaste = isMetaCtrl && ['c', 'x', 'v'].includes(key);
37
+ const suppressNativeHandling = !isCopyCutPaste && !isBackspaceDelete;
38
+ return suppressNativeHandling;
39
+ }
20
40
  }
21
41
  });
22
42
  };
@@ -7,7 +7,6 @@ import { cx } from '@atlaskit/css';
7
7
  import { ACTION, ACTION_SUBJECT, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
8
8
  import { ErrorBoundary } from '@atlaskit/editor-common/error-boundary';
9
9
  import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
10
- import { deleteSelectedRange } from '@atlaskit/editor-common/selection';
11
10
  import { DRAG_HANDLE_SELECTOR, DRAG_HANDLE_WIDTH } from '@atlaskit/editor-common/styles';
12
11
  import { Popup } from '@atlaskit/editor-common/ui';
13
12
  import { ArrowKeyNavigationProvider, ArrowKeyNavigationType } from '@atlaskit/editor-common/ui-menu';
@@ -174,26 +173,20 @@ const BlockMenu = ({
174
173
  if (!isMenuOpen) {
175
174
  return null;
176
175
  }
177
- const handleBackspaceDeleteKeydown = event => {
178
- // Pevents delete/backspace keypress being handled by editor, avoids double deletions
179
- event === null || event === void 0 ? void 0 : event.preventDefault();
180
- event === null || event === void 0 ? void 0 : event.stopPropagation();
181
- api === null || api === void 0 ? void 0 : api.core.actions.execute(({
182
- tr
183
- }) => {
184
- var _api$blockControls, _api$blockControls$sh, _api$blockControls2;
185
- deleteSelectedRange(tr, api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : (_api$blockControls$sh = _api$blockControls.sharedState.currentState()) === null || _api$blockControls$sh === void 0 ? void 0 : _api$blockControls$sh.preservedSelection);
186
- api === null || api === void 0 ? void 0 : (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 ? void 0 : _api$blockControls2.commands.toggleBlockMenu({
187
- closeMenu: true
188
- })({
189
- tr
190
- });
191
- if (editorView && !editorView.hasFocus()) {
192
- // if focus is outside editor, e.g. in block menu popup, then refocus editor after delete
193
- editorView.focus();
194
- }
195
- return tr;
196
- });
176
+ const handleKeyDown = event => {
177
+ var _api$core, _api$blockControls, _api$blockControls$co;
178
+ // When the editor view has focus, the keydown will be handled by the
179
+ // selection preservation plugin exit early to avoid double handling
180
+ if (!editorView || editorView !== null && editorView !== void 0 && editorView.hasFocus()) {
181
+ return;
182
+ }
183
+
184
+ // Necessary to prevent the editor from handling the delete natively
185
+ if (['backspace', 'delete'].includes(event.key.toLowerCase())) {
186
+ event.preventDefault();
187
+ event.stopPropagation();
188
+ }
189
+ api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : (_api$blockControls$co = _api$blockControls.commands) === null || _api$blockControls$co === void 0 ? void 0 : _api$blockControls$co.handleKeyDownWithPreservedSelection(event));
197
190
  };
198
191
  const handleClickOutside = e => {
199
192
  // check if the clicked element was another drag handle, if so don't close the menu
@@ -206,8 +199,8 @@ const BlockMenu = ({
206
199
  api === null || api === void 0 ? void 0 : api.core.actions.execute(({
207
200
  tr
208
201
  }) => {
209
- var _api$blockControls3, _api$userIntent3;
210
- api === null || api === void 0 ? void 0 : (_api$blockControls3 = api.blockControls) === null || _api$blockControls3 === void 0 ? void 0 : _api$blockControls3.commands.toggleBlockMenu({
202
+ var _api$blockControls2, _api$userIntent3;
203
+ api === null || api === void 0 ? void 0 : (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 ? void 0 : _api$blockControls2.commands.toggleBlockMenu({
211
204
  closeMenu: true
212
205
  })({
213
206
  tr
@@ -240,7 +233,7 @@ const BlockMenu = ({
240
233
  alignY: 'start',
241
234
  handleClickOutside: handleClickOutside,
242
235
  handleEscapeKeydown: closeMenu,
243
- handleBackspaceDeleteKeydown: handleBackspaceDeleteKeydown,
236
+ handleKeyDown: handleKeyDown,
244
237
  mountTo: mountTo,
245
238
  boundariesElement: boundariesElement,
246
239
  scrollableElement: scrollableElement,
@@ -21,7 +21,9 @@ export var blockMenuPlugin = function blockMenuPlugin(_ref) {
21
21
  pmPlugins: function pmPlugins() {
22
22
  return [{
23
23
  name: 'blockMenuPlugin',
24
- plugin: createPlugin
24
+ plugin: function plugin() {
25
+ return createPlugin(api);
26
+ }
25
27
  }];
26
28
  },
27
29
  actions: {
@@ -1,8 +1,9 @@
1
1
  export var flattenStep = function flattenStep(nodes, context) {
2
2
  var schema = context.schema,
3
3
  targetNodeTypeName = context.targetNodeTypeName;
4
+ var paragraph = schema.nodes.paragraph;
4
5
  var targetNodeType = schema.nodes[targetNodeTypeName];
5
- if (!targetNodeType) {
6
+ if (!targetNodeType || !paragraph) {
6
7
  return nodes;
7
8
  }
8
9
 
@@ -20,6 +21,6 @@ export var flattenStep = function flattenStep(nodes, context) {
20
21
  if (!isValidWithin) {
21
22
  return node;
22
23
  }
23
- return schema.nodes.paragraph.create({}, node.content);
24
+ return paragraph.create({}, node.content, node.marks);
24
25
  });
25
26
  };
@@ -1,12 +1,12 @@
1
- var removeMarks = function removeMarks(disallowedMarks) {
2
- return function (node) {
3
- var filteredMarks = node.marks.filter(function (mark) {
4
- return !disallowedMarks.includes(mark.type);
5
- });
6
- return node.mark(filteredMarks);
7
- };
8
- };
9
- export var removeBlockMarks = function removeBlockMarks(nodes, schema) {
10
- var disallowedMarks = [schema.marks.breakout, schema.marks.alignment];
11
- return nodes.map(removeMarks(disallowedMarks));
1
+ /**
2
+ * Removes all disallowed marks from a node based on the target node type.
3
+ *
4
+ * @param node - The node to remove marks from.
5
+ * @param targetNode - The target node type to check against.
6
+ * @returns The nodes with the marks removed.
7
+ */
8
+ export var removeDisallowedMarks = function removeDisallowedMarks(nodes, targetNode) {
9
+ return nodes.map(function (node) {
10
+ return node.mark(targetNode.allowedMarks(node.marks));
11
+ });
12
12
  };
@@ -36,7 +36,7 @@ export var applyTargetTextTypeStep = function applyTargetTextTypeStep(nodes, con
36
36
  // Convert textblock nodes (paragraphs, headings) to heading with specified level
37
37
  return headingType.create({
38
38
  level: headingLevel
39
- }, node.content);
39
+ }, node.content, node.marks);
40
40
  }
41
41
  // Non-textblock nodes are left unchanged
42
42
  return node;
@@ -1,5 +1,6 @@
1
1
  import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
2
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
3
+ import { removeDisallowedMarks } from '../marks';
3
4
  import { NODE_CATEGORY_BY_TYPE } from '../types';
4
5
  import { convertTextNodeToParagraph } from '../utils';
5
6
 
@@ -13,7 +14,7 @@ var isTextNode = function isTextNode(node) {
13
14
  };
14
15
 
15
16
  /**
16
- * Determines if a node can be wrapped in the target container type.
17
+ * Determines if a node can be wrapped in the target container type, removes block marks from the node during check.
17
18
  * Uses the schema's validContent to check if the target container can hold this node.
18
19
  *
19
20
  * Note: What can be wrapped depends on the target container type - for example:
@@ -27,7 +28,7 @@ var canWrapInTarget = function canWrapInTarget(node, targetNodeType, targetNodeT
27
28
  }
28
29
 
29
30
  // Use the schema to determine if this node can be contained in the target
30
- return targetNodeType.validContent(Fragment.from(node));
31
+ return targetNodeType.validContent(Fragment.from(removeDisallowedMarks([node], targetNodeType)));
31
32
  };
32
33
 
33
34
  /**
@@ -104,8 +105,10 @@ export var wrapMixedContentStep = function wrapMixedContentStep(nodes, context)
104
105
  };
105
106
  nodes.forEach(function (node) {
106
107
  if (canWrapInTarget(node, targetNodeType, targetNodeTypeName)) {
108
+ var _currentContainerCont;
107
109
  // Node can be wrapped - add to current container content
108
- currentContainerContent.push(node);
110
+ // remove marks from node as nested nodes don't usually support block marks
111
+ (_currentContainerCont = currentContainerContent).push.apply(_currentContainerCont, _toConsumableArray(removeDisallowedMarks([node], targetNodeType)));
109
112
  } else if (node.type.name === targetNodeTypeName) {
110
113
  // Same-type container - breaks out as a separate container (preserved as-is)
111
114
  // This handles: "If there's a panel in the expand, it breaks out into a separate panel"
@@ -1,10 +1,10 @@
1
- import { removeBlockMarks } from './marks';
1
+ import { removeDisallowedMarks } from './marks';
2
2
  export var wrapIntoLayoutStep = function wrapIntoLayoutStep(nodes, context) {
3
3
  var schema = context.schema;
4
4
  var _ref = schema.nodes || {},
5
5
  layoutSection = _ref.layoutSection,
6
6
  layoutColumn = _ref.layoutColumn;
7
- var columnOne = layoutColumn.createAndFill({}, removeBlockMarks(nodes, schema));
7
+ var columnOne = layoutColumn.createAndFill({}, removeDisallowedMarks(nodes, layoutColumn));
8
8
  var columnTwo = layoutColumn.createAndFill();
9
9
  if (!columnOne || !columnTwo) {
10
10
  return nodes;
@@ -1,4 +1,4 @@
1
- import { removeBlockMarks } from './marks';
1
+ import { removeDisallowedMarks } from './marks';
2
2
  import { convertExpandToNestedExpand } from './utils';
3
3
 
4
4
  /**
@@ -22,6 +22,6 @@ export var wrapStep = function wrapStep(nodes, context) {
22
22
  return node;
23
23
  });
24
24
  }
25
- var outputNode = schema.nodes[targetNodeTypeName].createAndFill({}, removeBlockMarks(processedNodes, schema));
25
+ var outputNode = schema.nodes[targetNodeTypeName].createAndFill({}, removeDisallowedMarks(processedNodes, schema.nodes[targetNodeTypeName]));
26
26
  return outputNode ? [outputNode] : nodes;
27
27
  };
@@ -1,7 +1,7 @@
1
1
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
2
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
3
3
  export var blockMenuPluginKey = new PluginKey('blockMenuPlugin');
4
- export var createPlugin = function createPlugin() {
4
+ export var createPlugin = function createPlugin(api) {
5
5
  return new SafePlugin({
6
6
  key: blockMenuPluginKey,
7
7
  state: {
@@ -17,6 +17,26 @@ export var createPlugin = function createPlugin() {
17
17
  showFlag: (_meta$showFlag = meta === null || meta === void 0 ? void 0 : meta.showFlag) !== null && _meta$showFlag !== void 0 ? _meta$showFlag : currentPluginState.showFlag
18
18
  };
19
19
  }
20
+ },
21
+ props: {
22
+ handleKeyDown: function handleKeyDown(_editorView, event) {
23
+ var _api$userIntent;
24
+ var blockMenuOpen = (api === null || api === void 0 || (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 || (_api$userIntent = _api$userIntent.sharedState.currentState()) === null || _api$userIntent === void 0 ? void 0 : _api$userIntent.currentUserIntent) === 'blockMenuOpen';
25
+
26
+ // Exit early and do nothing when block menu is closed
27
+ if (!blockMenuOpen) {
28
+ return false;
29
+ }
30
+
31
+ // Block further handling of key events when block menu is open
32
+ // Except for backspace/delete/copy/cut/paste which should be handled by the selection preservation plugin
33
+ var key = event.key.toLowerCase();
34
+ var isMetaCtrl = event.metaKey || event.ctrlKey;
35
+ var isBackspaceDelete = ['backspace', 'delete'].includes(key);
36
+ var isCopyCutPaste = isMetaCtrl && ['c', 'x', 'v'].includes(key);
37
+ var suppressNativeHandling = !isCopyCutPaste && !isBackspaceDelete;
38
+ return suppressNativeHandling;
39
+ }
20
40
  }
21
41
  });
22
42
  };
@@ -8,7 +8,6 @@ import { cx } from '@atlaskit/css';
8
8
  import { ACTION, ACTION_SUBJECT, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
9
9
  import { ErrorBoundary } from '@atlaskit/editor-common/error-boundary';
10
10
  import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
11
- import { deleteSelectedRange } from '@atlaskit/editor-common/selection';
12
11
  import { DRAG_HANDLE_SELECTOR, DRAG_HANDLE_WIDTH } from '@atlaskit/editor-common/styles';
13
12
  import { Popup } from '@atlaskit/editor-common/ui';
14
13
  import { ArrowKeyNavigationProvider, ArrowKeyNavigationType } from '@atlaskit/editor-common/ui-menu';
@@ -176,25 +175,20 @@ var BlockMenu = function BlockMenu(_ref4) {
176
175
  if (!isMenuOpen) {
177
176
  return null;
178
177
  }
179
- var handleBackspaceDeleteKeydown = function handleBackspaceDeleteKeydown(event) {
180
- // Pevents delete/backspace keypress being handled by editor, avoids double deletions
181
- event === null || event === void 0 || event.preventDefault();
182
- event === null || event === void 0 || event.stopPropagation();
183
- api === null || api === void 0 || api.core.actions.execute(function (_ref6) {
184
- var _api$blockControls, _api$blockControls2;
185
- var tr = _ref6.tr;
186
- deleteSelectedRange(tr, api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.sharedState.currentState()) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.preservedSelection);
187
- api === null || api === void 0 || (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || _api$blockControls2.commands.toggleBlockMenu({
188
- closeMenu: true
189
- })({
190
- tr: tr
191
- });
192
- if (editorView && !editorView.hasFocus()) {
193
- // if focus is outside editor, e.g. in block menu popup, then refocus editor after delete
194
- editorView.focus();
195
- }
196
- return tr;
197
- });
178
+ var handleKeyDown = function handleKeyDown(event) {
179
+ var _api$core, _api$blockControls;
180
+ // When the editor view has focus, the keydown will be handled by the
181
+ // selection preservation plugin exit early to avoid double handling
182
+ if (!editorView || editorView !== null && editorView !== void 0 && editorView.hasFocus()) {
183
+ return;
184
+ }
185
+
186
+ // Necessary to prevent the editor from handling the delete natively
187
+ if (['backspace', 'delete'].includes(event.key.toLowerCase())) {
188
+ event.preventDefault();
189
+ event.stopPropagation();
190
+ }
191
+ api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.commands) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.handleKeyDownWithPreservedSelection(event));
198
192
  };
199
193
  var handleClickOutside = function handleClickOutside(e) {
200
194
  // check if the clicked element was another drag handle, if so don't close the menu
@@ -204,10 +198,10 @@ var BlockMenu = function BlockMenu(_ref4) {
204
198
  closeMenu();
205
199
  };
206
200
  var closeMenu = function closeMenu() {
207
- api === null || api === void 0 || api.core.actions.execute(function (_ref7) {
208
- var _api$blockControls3, _api$userIntent3;
209
- var tr = _ref7.tr;
210
- api === null || api === void 0 || (_api$blockControls3 = api.blockControls) === null || _api$blockControls3 === void 0 || _api$blockControls3.commands.toggleBlockMenu({
201
+ api === null || api === void 0 || api.core.actions.execute(function (_ref6) {
202
+ var _api$blockControls2, _api$userIntent3;
203
+ var tr = _ref6.tr;
204
+ api === null || api === void 0 || (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || _api$blockControls2.commands.toggleBlockMenu({
211
205
  closeMenu: true
212
206
  })({
213
207
  tr: tr
@@ -240,7 +234,7 @@ var BlockMenu = function BlockMenu(_ref4) {
240
234
  alignY: 'start',
241
235
  handleClickOutside: handleClickOutside,
242
236
  handleEscapeKeydown: closeMenu,
243
- handleBackspaceDeleteKeydown: handleBackspaceDeleteKeydown,
237
+ handleKeyDown: handleKeyDown,
244
238
  mountTo: mountTo,
245
239
  boundariesElement: boundariesElement,
246
240
  scrollableElement: scrollableElement,
@@ -1,2 +1,9 @@
1
- import type { Node as PMNode, Schema } from '@atlaskit/editor-prosemirror/model';
2
- export declare const removeBlockMarks: (nodes: PMNode[], schema: Schema) => PMNode[];
1
+ import type { NodeType, Node as PMNode } from '@atlaskit/editor-prosemirror/model';
2
+ /**
3
+ * Removes all disallowed marks from a node based on the target node type.
4
+ *
5
+ * @param node - The node to remove marks from.
6
+ * @param targetNode - The target node type to check against.
7
+ * @returns The nodes with the marks removed.
8
+ */
9
+ export declare const removeDisallowedMarks: (nodes: PMNode[], targetNode: NodeType) => PMNode[];
@@ -1,9 +1,10 @@
1
1
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
+ import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
2
3
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
3
- import type { FLAG_ID } from '../blockMenuPluginType';
4
+ import type { BlockMenuPlugin, FLAG_ID } from '../blockMenuPluginType';
4
5
  export declare const blockMenuPluginKey: PluginKey<any>;
5
6
  type BlockMenuPluginState = {
6
7
  showFlag: FLAG_ID | false;
7
8
  };
8
- export declare const createPlugin: () => SafePlugin<BlockMenuPluginState>;
9
+ export declare const createPlugin: (api: ExtractInjectionAPI<BlockMenuPlugin> | undefined) => SafePlugin<BlockMenuPluginState>;
9
10
  export {};
@@ -1,2 +1,9 @@
1
- import type { Node as PMNode, Schema } from '@atlaskit/editor-prosemirror/model';
2
- export declare const removeBlockMarks: (nodes: PMNode[], schema: Schema) => PMNode[];
1
+ import type { NodeType, Node as PMNode } from '@atlaskit/editor-prosemirror/model';
2
+ /**
3
+ * Removes all disallowed marks from a node based on the target node type.
4
+ *
5
+ * @param node - The node to remove marks from.
6
+ * @param targetNode - The target node type to check against.
7
+ * @returns The nodes with the marks removed.
8
+ */
9
+ export declare const removeDisallowedMarks: (nodes: PMNode[], targetNode: NodeType) => PMNode[];
@@ -1,9 +1,10 @@
1
1
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
+ import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
2
3
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
3
- import type { FLAG_ID } from '../blockMenuPluginType';
4
+ import type { BlockMenuPlugin, FLAG_ID } from '../blockMenuPluginType';
4
5
  export declare const blockMenuPluginKey: PluginKey<any>;
5
6
  type BlockMenuPluginState = {
6
7
  showFlag: FLAG_ID | false;
7
8
  };
8
- export declare const createPlugin: () => SafePlugin<BlockMenuPluginState>;
9
+ export declare const createPlugin: (api: ExtractInjectionAPI<BlockMenuPlugin> | undefined) => SafePlugin<BlockMenuPluginState>;
9
10
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-block-menu",
3
- "version": "5.2.28",
3
+ "version": "6.0.1",
4
4
  "description": "BlockMenu plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -30,11 +30,11 @@
30
30
  "dependencies": {
31
31
  "@atlaskit/css": "^0.19.0",
32
32
  "@atlaskit/dropdown-menu": "^16.3.0",
33
- "@atlaskit/editor-plugin-analytics": "^6.2.0",
34
- "@atlaskit/editor-plugin-block-controls": "^7.19.0",
35
- "@atlaskit/editor-plugin-decorations": "^6.1.0",
36
- "@atlaskit/editor-plugin-selection": "^6.1.0",
37
- "@atlaskit/editor-plugin-user-intent": "^4.0.0",
33
+ "@atlaskit/editor-plugin-analytics": "^7.0.0",
34
+ "@atlaskit/editor-plugin-block-controls": "^8.0.0",
35
+ "@atlaskit/editor-plugin-decorations": "^7.0.0",
36
+ "@atlaskit/editor-plugin-selection": "^7.0.0",
37
+ "@atlaskit/editor-plugin-user-intent": "^5.0.0",
38
38
  "@atlaskit/editor-prosemirror": "^7.2.0",
39
39
  "@atlaskit/editor-shared-styles": "^3.10.0",
40
40
  "@atlaskit/editor-tables": "^2.9.0",
@@ -49,7 +49,7 @@
49
49
  "@babel/runtime": "^7.0.0"
50
50
  },
51
51
  "peerDependencies": {
52
- "@atlaskit/editor-common": "^110.50.0",
52
+ "@atlaskit/editor-common": "^111.0.0",
53
53
  "react": "^18.2.0",
54
54
  "react-intl-next": "npm:react-intl@^5.18.1"
55
55
  },