@atlaskit/editor-plugin-block-menu 3.2.0 → 3.2.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 (42) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cjs/blockMenuPlugin.js +5 -2
  3. package/dist/cjs/editor-commands/formatNode.js +48 -1
  4. package/dist/cjs/editor-commands/transforms/layout-transforms.js +26 -19
  5. package/dist/cjs/ui/block-menu-components.js +3 -2
  6. package/dist/cjs/ui/block-menu-provider.js +40 -0
  7. package/dist/cjs/ui/block-menu.js +13 -3
  8. package/dist/cjs/ui/copy-block.js +11 -3
  9. package/dist/cjs/ui/copy-section.js +7 -0
  10. package/dist/cjs/ui/delete-section.js +23 -0
  11. package/dist/cjs/ui/utils/checkIsFormatMenuHidden.js +35 -1
  12. package/dist/es2019/blockMenuPlugin.js +5 -2
  13. package/dist/es2019/editor-commands/formatNode.js +53 -2
  14. package/dist/es2019/editor-commands/transforms/layout-transforms.js +19 -12
  15. package/dist/es2019/ui/block-menu-components.js +3 -2
  16. package/dist/es2019/ui/block-menu-provider.js +31 -0
  17. package/dist/es2019/ui/block-menu.js +14 -3
  18. package/dist/es2019/ui/copy-block.js +9 -3
  19. package/dist/es2019/ui/copy-section.js +7 -0
  20. package/dist/es2019/ui/delete-section.js +17 -0
  21. package/dist/es2019/ui/utils/checkIsFormatMenuHidden.js +35 -1
  22. package/dist/esm/blockMenuPlugin.js +5 -2
  23. package/dist/esm/editor-commands/formatNode.js +49 -2
  24. package/dist/esm/editor-commands/transforms/layout-transforms.js +25 -18
  25. package/dist/esm/ui/block-menu-components.js +3 -2
  26. package/dist/esm/ui/block-menu-provider.js +32 -0
  27. package/dist/esm/ui/block-menu.js +13 -3
  28. package/dist/esm/ui/copy-block.js +11 -3
  29. package/dist/esm/ui/copy-section.js +7 -0
  30. package/dist/esm/ui/delete-section.js +16 -0
  31. package/dist/esm/ui/utils/checkIsFormatMenuHidden.js +35 -1
  32. package/dist/types/editor-commands/transforms/layout-transforms.d.ts +3 -0
  33. package/dist/types/ui/block-menu-provider.d.ts +18 -0
  34. package/dist/types/ui/block-menu.d.ts +1 -1
  35. package/dist/types/ui/copy-section.d.ts +1 -1
  36. package/dist/types/ui/delete-section.d.ts +7 -0
  37. package/dist/types-ts4.5/editor-commands/transforms/layout-transforms.d.ts +3 -0
  38. package/dist/types-ts4.5/ui/block-menu-provider.d.ts +18 -0
  39. package/dist/types-ts4.5/ui/block-menu.d.ts +1 -1
  40. package/dist/types-ts4.5/ui/copy-section.d.ts +1 -1
  41. package/dist/types-ts4.5/ui/delete-section.d.ts +7 -0
  42. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @atlaskit/editor-plugin-block-menu
2
2
 
3
+ ## 3.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`7958282e36bdf`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/7958282e36bdf) -
8
+ [ux] ED-29222: Show block menu on empty line
9
+ - Updated dependencies
10
+
3
11
  ## 3.2.0
4
12
 
5
13
  ### Minor Changes
@@ -11,6 +11,7 @@ var _formatNode2 = require("./editor-commands/formatNode");
11
11
  var _main = require("./pm-plugins/main");
12
12
  var _blockMenu = _interopRequireDefault(require("./ui/block-menu"));
13
13
  var _blockMenuComponents = require("./ui/block-menu-components");
14
+ var _blockMenuProvider = require("./ui/block-menu-provider");
14
15
  var blockMenuPlugin = exports.blockMenuPlugin = function blockMenuPlugin(_ref) {
15
16
  var api = _ref.api,
16
17
  config = _ref.config;
@@ -59,13 +60,15 @@ var blockMenuPlugin = exports.blockMenuPlugin = function blockMenuPlugin(_ref) {
59
60
  popupsMountPoint = _ref2.popupsMountPoint,
60
61
  popupsBoundariesElement = _ref2.popupsBoundariesElement,
61
62
  popupsScrollableElement = _ref2.popupsScrollableElement;
62
- return /*#__PURE__*/_react.default.createElement(_blockMenu.default, {
63
+ return /*#__PURE__*/_react.default.createElement(_blockMenuProvider.BlockMenuProvider, {
64
+ api: api
65
+ }, /*#__PURE__*/_react.default.createElement(_blockMenu.default, {
63
66
  editorView: editorView,
64
67
  api: api,
65
68
  mountTo: popupsMountPoint,
66
69
  boundariesElement: popupsBoundariesElement,
67
70
  scrollableElement: popupsScrollableElement
68
- });
71
+ }));
69
72
  }
70
73
  };
71
74
  };
@@ -5,7 +5,48 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.formatNode = void 0;
7
7
  var _utils = require("@atlaskit/editor-prosemirror/utils");
8
+ var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
9
+ var _layoutTransforms = require("./transforms/layout-transforms");
8
10
  var _transformNodeToTargetType = require("./transforms/transformNodeToTargetType");
11
+ /**
12
+ * Handles formatting when selection is empty by inserting a new target node
13
+ */
14
+ var formatNodeWhenSelectionEmpty = function formatNodeWhenSelectionEmpty(tr, targetType, nodePos, schema) {
15
+ var _pmSafeInsert;
16
+ var nodes = schema.nodes;
17
+ var paragraph = nodes.paragraph;
18
+ // if not using the ' ' here, the safeInsert from editor-common will fail to insert the heading
19
+ // and the pmSafeInsert introduce an issue that after inserting heading, and click on the handle, selection will go to top of the doc
20
+ // as an workaround, use the spaceTextNode here
21
+ var spaceTextNode = schema.text(' ');
22
+ var targetNode;
23
+ if (targetType.startsWith('heading')) {
24
+ var levelString = targetType.slice(-1);
25
+ var level = parseInt(levelString, 10);
26
+ if (isNaN(level) || level < 1 || level > 6) {
27
+ return null;
28
+ }
29
+ targetNode = nodes.heading.createAndFill({
30
+ level: level
31
+ }, spaceTextNode);
32
+ } else if (targetType === 'paragraph') {
33
+ targetNode = nodes.paragraph.createAndFill({}, spaceTextNode);
34
+ } else if (targetType === 'layoutSection') {
35
+ var contentAsParagraph = paragraph.createAndFill({}, spaceTextNode);
36
+ if (contentAsParagraph) {
37
+ targetNode = (0, _layoutTransforms.createDefaultLayoutSection)(schema, contentAsParagraph);
38
+ }
39
+ } else {
40
+ var targetNodeType = nodes[targetType];
41
+ targetNode = targetNodeType.createAndFill();
42
+ }
43
+ if (!targetNode) {
44
+ return tr;
45
+ }
46
+ tr = (_pmSafeInsert = (0, _utils.safeInsert)(targetNode, nodePos)(tr)) !== null && _pmSafeInsert !== void 0 ? _pmSafeInsert : tr;
47
+ return tr;
48
+ };
49
+
9
50
  /**
10
51
  * Formats the current node or selection to the specified target type
11
52
  * @param targetType - The target node type to convert to
@@ -14,12 +55,18 @@ var formatNode = exports.formatNode = function formatNode(targetType) {
14
55
  return function (_ref) {
15
56
  var tr = _ref.tr;
16
57
  var selection = tr.selection;
17
- var nodes = tr.doc.type.schema.nodes;
58
+ var schema = tr.doc.type.schema;
59
+ var nodes = schema.nodes;
18
60
 
19
61
  // Find the node to format from the current selection
20
62
  var nodeToFormat;
21
63
  var nodePos = selection.from;
22
64
 
65
+ // when selection is empty, we insert a empty target node
66
+ if (selection.empty && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_empty_line', 'isEnabled', true)) {
67
+ return formatNodeWhenSelectionEmpty(tr, targetType, nodePos, schema);
68
+ }
69
+
23
70
  // Try to find the current node from selection
24
71
  var selectedNode = (0, _utils.findSelectedNodeOfType)([nodes.paragraph, nodes.heading, nodes.blockquote, nodes.panel, nodes.expand, nodes.codeBlock, nodes.bulletList, nodes.orderedList, nodes.taskList, nodes.layoutSection])(selection);
25
72
  if (selectedNode) {
@@ -4,31 +4,38 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.transformLayoutNode = exports.convertToLayout = void 0;
7
+ exports.transformLayoutNode = exports.createDefaultLayoutSection = exports.convertToLayout = void 0;
8
8
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
9
  var _styles = require("@atlaskit/editor-common/styles");
10
10
  var _model = require("@atlaskit/editor-prosemirror/model");
11
11
  var _utils = require("./layout/utils");
12
+ var _utils2 = require("./utils");
13
+ var createDefaultLayoutSection = exports.createDefaultLayoutSection = function createDefaultLayoutSection(schema, content) {
14
+ var _schema$nodes = schema.nodes,
15
+ layoutSection = _schema$nodes.layoutSection,
16
+ layoutColumn = _schema$nodes.layoutColumn,
17
+ paragraph = _schema$nodes.paragraph;
18
+ var layoutContent = _model.Fragment.fromArray([layoutColumn.createChecked({
19
+ width: _styles.DEFAULT_TWO_COLUMN_LAYOUT_COLUMN_WIDTH
20
+ }, content), layoutColumn.create({
21
+ width: _styles.DEFAULT_TWO_COLUMN_LAYOUT_COLUMN_WIDTH
22
+ }, paragraph.createAndFill())]);
23
+ return layoutSection.createChecked(undefined, layoutContent);
24
+ };
12
25
  var convertToLayout = exports.convertToLayout = function convertToLayout(context) {
13
26
  var tr = context.tr,
14
27
  sourceNode = context.sourceNode,
15
28
  sourcePos = context.sourcePos;
16
- var _ref = tr.doc.type.schema.nodes || {},
17
- layoutSection = _ref.layoutSection,
18
- layoutColumn = _ref.layoutColumn,
19
- paragraph = _ref.paragraph;
20
29
  var content = sourceNode.mark(sourceNode.marks.filter(function (mark) {
21
30
  return mark.type.name !== 'breakout';
22
31
  }));
23
- var layoutContent = _model.Fragment.fromArray([layoutColumn.createChecked({
24
- width: _styles.DEFAULT_TWO_COLUMN_LAYOUT_COLUMN_WIDTH
25
- }, content), layoutColumn.create({
26
- width: _styles.DEFAULT_TWO_COLUMN_LAYOUT_COLUMN_WIDTH
27
- }, paragraph.createAndFill())]);
28
- var layoutSectionNode = layoutSection.createChecked(undefined, layoutContent);
29
-
30
- // Replace the original node with the new layout node
31
- tr.replaceRangeWith(sourcePos, sourcePos + sourceNode.nodeSize, layoutSectionNode);
32
+ var layoutSectionNode = createDefaultLayoutSection(tr.doc.type.schema, content);
33
+ if ((0, _utils2.isHeadingOrParagraphNode)(sourceNode)) {
34
+ // -1 to fix when sourceNode is the last node in the document, unable to convert to layout
35
+ tr.replaceRangeWith(sourcePos > 0 ? sourcePos - 1 : sourcePos, sourcePos + sourceNode.nodeSize - 1, layoutSectionNode);
36
+ } else {
37
+ tr.replaceRangeWith(sourcePos, sourcePos + sourceNode.nodeSize, layoutSectionNode);
38
+ }
32
39
  return tr;
33
40
  };
34
41
  var transformLayoutNode = exports.transformLayoutNode = function transformLayoutNode(context) {
@@ -38,11 +45,11 @@ var transformLayoutNode = exports.transformLayoutNode = function transformLayout
38
45
  sourcePos = context.sourcePos,
39
46
  targetAttrs = context.targetAttrs;
40
47
  var schema = tr.doc.type.schema || {};
41
- var _ref2 = schema.nodes || {},
42
- layoutSection = _ref2.layoutSection,
43
- layoutColumn = _ref2.layoutColumn,
44
- paragraph = _ref2.paragraph,
45
- heading = _ref2.heading;
48
+ var _ref = schema.nodes || {},
49
+ layoutSection = _ref.layoutSection,
50
+ layoutColumn = _ref.layoutColumn,
51
+ paragraph = _ref.paragraph,
52
+ heading = _ref.heading;
46
53
  var layoutColumnNodes = [];
47
54
  var targetTextNodeType = targetNodeType === heading ? heading : paragraph;
48
55
  sourceNode.children.forEach(function (child) {
@@ -15,6 +15,7 @@ var _copyBlock = _interopRequireDefault(require("./copy-block"));
15
15
  var _copyLink = require("./copy-link");
16
16
  var _copySection = require("./copy-section");
17
17
  var _deleteButton = require("./delete-button");
18
+ var _deleteSection = require("./delete-section");
18
19
  var _formatMenuSection = require("./format-menu-section");
19
20
  var _moveDown = require("./move-down");
20
21
  var _moveUp = require("./move-up");
@@ -155,8 +156,8 @@ var getBlockMenuComponents = exports.getBlockMenuComponents = function getBlockM
155
156
  rank: _blockMenu.BLOCK_MENU_SECTION_RANK[_blockMenu.DELETE_MENU_SECTION.key],
156
157
  component: function component(_ref7) {
157
158
  var children = _ref7.children;
158
- return /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarDropdownItemSection, {
159
- hasSeparator: true
159
+ return /*#__PURE__*/_react.default.createElement(_deleteSection.DeleteSection, {
160
+ api: api
160
161
  }, children);
161
162
  }
162
163
  }], (0, _toConsumableArray2.default)(getMoveUpMoveDownMenuComponents(api)), [{
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+
3
+ var _typeof = require("@babel/runtime/helpers/typeof");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.useBlockMenu = exports.BlockMenuProvider = void 0;
8
+ var _react = _interopRequireWildcard(require("react"));
9
+ 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); }
10
+ var BlockMenuContext = /*#__PURE__*/(0, _react.createContext)({
11
+ onDropdownOpenChanged: function onDropdownOpenChanged() {}
12
+ });
13
+ var useBlockMenu = exports.useBlockMenu = function useBlockMenu() {
14
+ var context = (0, _react.useContext)(BlockMenuContext);
15
+ if (!context) {
16
+ throw new Error('useBlockMenu must be used within BlockMenuProvider');
17
+ }
18
+ return context;
19
+ };
20
+ var BlockMenuProvider = exports.BlockMenuProvider = function BlockMenuProvider(_ref) {
21
+ var children = _ref.children,
22
+ api = _ref.api;
23
+ var onDropdownOpenChanged = (0, _react.useCallback)(function (isOpen) {
24
+ if (!isOpen) {
25
+ // On Dropdown closed, return focus to editor
26
+ setTimeout(function () {
27
+ return requestAnimationFrame(function () {
28
+ api === null || api === void 0 || api.core.actions.focus({
29
+ scrollIntoView: false
30
+ });
31
+ });
32
+ }, 1);
33
+ }
34
+ }, [api]);
35
+ return /*#__PURE__*/_react.default.createElement(BlockMenuContext.Provider, {
36
+ value: {
37
+ onDropdownOpenChanged: onDropdownOpenChanged
38
+ }
39
+ }, children);
40
+ };
@@ -18,6 +18,8 @@ var _uiReact = require("@atlaskit/editor-common/ui-react");
18
18
  var _editorSharedStyles = require("@atlaskit/editor-shared-styles");
19
19
  var _editorToolbar = require("@atlaskit/editor-toolbar");
20
20
  var _compiled = require("@atlaskit/primitives/compiled");
21
+ var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
22
+ var _blockMenuProvider = require("./block-menu-provider");
21
23
  var _blockMenuRenderer = require("./block-menu-renderer");
22
24
  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); }
23
25
  var styles = {
@@ -73,15 +75,22 @@ var BlockMenu = function BlockMenu(_ref2) {
73
75
  isSelectedViaDragHandle = _useSharedPluginState.isSelectedViaDragHandle,
74
76
  isMenuOpen = _useSharedPluginState.isMenuOpen,
75
77
  currentUserIntent = _useSharedPluginState.currentUserIntent;
78
+ var _useBlockMenu = (0, _blockMenuProvider.useBlockMenu)(),
79
+ onDropdownOpenChanged = _useBlockMenu.onDropdownOpenChanged;
76
80
  var hasFocus = (_editorView$hasFocus = editorView === null || editorView === void 0 ? void 0 : editorView.hasFocus()) !== null && _editorView$hasFocus !== void 0 ? _editorView$hasFocus : false;
77
81
  var hasSelection = !!editorView && !editorView.state.selection.empty;
82
+ var emptyLineEnabled = (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_empty_line', 'isEnabled', true);
83
+
84
+ // hasSelection true, always show block menu
85
+ // hasSelection false, only show block menu when empty line experiment is enabled
86
+ var shouldShowBlockMenuForEmptyLine = hasSelection || emptyLineEnabled && !hasSelection;
78
87
  (0, _react.useEffect)(function () {
79
88
  var _api$userIntent;
80
- if (!isMenuOpen || !menuTriggerBy || !isSelectedViaDragHandle || !hasFocus || !hasSelection || ['resizing', 'dragging'].includes(currentUserIntent || '')) {
89
+ if (!isMenuOpen || !menuTriggerBy || !isSelectedViaDragHandle || !hasFocus || !shouldShowBlockMenuForEmptyLine || ['resizing', 'dragging'].includes(currentUserIntent || '')) {
81
90
  return;
82
91
  }
83
92
  api === null || api === void 0 || api.core.actions.execute(api === null || api === void 0 || (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 ? void 0 : _api$userIntent.commands.setCurrentUserIntent('blockMenuOpen'));
84
- }, [api, isMenuOpen, menuTriggerBy, isSelectedViaDragHandle, hasFocus, hasSelection, currentUserIntent]);
93
+ }, [api, isMenuOpen, menuTriggerBy, isSelectedViaDragHandle, hasFocus, shouldShowBlockMenuForEmptyLine, currentUserIntent]);
85
94
  if (!isMenuOpen) {
86
95
  return null;
87
96
  }
@@ -94,13 +103,14 @@ var BlockMenu = function BlockMenu(_ref2) {
94
103
  })({
95
104
  tr: tr
96
105
  });
106
+ onDropdownOpenChanged(false);
97
107
  api === null || api === void 0 || (_api$userIntent2 = api.userIntent) === null || _api$userIntent2 === void 0 || _api$userIntent2.commands.setCurrentUserIntent(currentUserIntent === 'blockMenuOpen' ? 'default' : currentUserIntent || 'default')({
98
108
  tr: tr
99
109
  });
100
110
  return tr;
101
111
  });
102
112
  };
103
- if (!menuTriggerBy || !isSelectedViaDragHandle || !hasFocus || !hasSelection || ['resizing', 'dragging'].includes(currentUserIntent || '')) {
113
+ if (!menuTriggerBy || !isSelectedViaDragHandle || !hasFocus || !shouldShowBlockMenuForEmptyLine || ['resizing', 'dragging'].includes(currentUserIntent || '')) {
104
114
  closeMenu();
105
115
  return null;
106
116
  }
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.default = void 0;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
8
9
  var _react = _interopRequireDefault(require("react"));
9
10
  var _reactIntlNext = require("react-intl-next");
10
11
  var _blockMenu = require("@atlaskit/editor-common/block-menu");
@@ -15,6 +16,8 @@ var _state = require("@atlaskit/editor-prosemirror/state");
15
16
  var _utils = require("@atlaskit/editor-tables/utils");
16
17
  var _editorToolbar = require("@atlaskit/editor-toolbar");
17
18
  var _copy = _interopRequireDefault(require("@atlaskit/icon/core/copy"));
19
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
20
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
18
21
  var toDOMFromFragment = function toDOMFromFragment(fragment, schema) {
19
22
  return _model.DOMSerializer.fromSchema(schema).serializeFragment(fragment);
20
23
  };
@@ -80,10 +83,15 @@ var CopyBlockMenuItem = function CopyBlockMenuItem(_ref) {
80
83
  // When nodeType.inlineContent is true, it will be treated as an inline node in the copyDomNode function,
81
84
  // but we want to treat it as a block node when copying, hence setting it to false here
82
85
  if (selection.node.type.name === 'codeBlock') {
83
- _nodeType.inlineContent = false;
86
+ var codeBlockNodeType = _objectSpread(_objectSpread({}, _nodeType), {}, {
87
+ inlineContent: false
88
+ });
89
+ var _domNode2 = (0, _copyButton.toDOM)(selection.node, schema);
90
+ (0, _copyButton.copyDomNode)(_domNode2, codeBlockNodeType, selection);
91
+ } else {
92
+ var _domNode3 = (0, _copyButton.toDOM)(selection.node, schema);
93
+ (0, _copyButton.copyDomNode)(_domNode3, _nodeType, selection);
84
94
  }
85
- var _domNode2 = (0, _copyButton.toDOM)(selection.node, schema);
86
- (0, _copyButton.copyDomNode)(_domNode2, _nodeType, selection);
87
95
  }
88
96
 
89
97
  // close the block menu after copying
@@ -7,14 +7,21 @@ Object.defineProperty(exports, "__esModule", {
7
7
  exports.CopySection = void 0;
8
8
  var _react = _interopRequireWildcard(require("react"));
9
9
  var _editorToolbar = require("@atlaskit/editor-toolbar");
10
+ var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
10
11
  var _checkIsFormatMenuHidden = require("./utils/checkIsFormatMenuHidden");
11
12
  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); }
12
13
  var CopySection = exports.CopySection = function CopySection(_ref) {
14
+ var _api$selection;
13
15
  var api = _ref.api,
14
16
  children = _ref.children;
15
17
  var isFormatMenuHidden = (0, _react.useCallback)(function () {
16
18
  return (0, _checkIsFormatMenuHidden.checkIsFormatMenuHidden)(api);
17
19
  }, [api]);
20
+ var selection = api === null || api === void 0 || (_api$selection = api.selection) === null || _api$selection === void 0 || (_api$selection = _api$selection.sharedState) === null || _api$selection === void 0 || (_api$selection = _api$selection.currentState()) === null || _api$selection === void 0 ? void 0 : _api$selection.selection;
21
+ var isEmptyLineSelected = !!(selection !== null && selection !== void 0 && selection.empty) && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_empty_line', 'isEnabled', true);
22
+ if (isEmptyLineSelected) {
23
+ return null;
24
+ }
18
25
  return /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarDropdownItemSection, {
19
26
  hasSeparator: !isFormatMenuHidden()
20
27
  }, children);
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.DeleteSection = void 0;
8
+ var _react = _interopRequireDefault(require("react"));
9
+ var _editorToolbar = require("@atlaskit/editor-toolbar");
10
+ var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
11
+ var DeleteSection = exports.DeleteSection = function DeleteSection(_ref) {
12
+ var _api$selection;
13
+ var api = _ref.api,
14
+ children = _ref.children;
15
+ var selection = api === null || api === void 0 || (_api$selection = api.selection) === null || _api$selection === void 0 || (_api$selection = _api$selection.sharedState) === null || _api$selection === void 0 || (_api$selection = _api$selection.currentState()) === null || _api$selection === void 0 ? void 0 : _api$selection.selection;
16
+ var isEmptyLineSelected = !!(selection !== null && selection !== void 0 && selection.empty) && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_empty_line', 'isEnabled', true);
17
+ if (isEmptyLineSelected) {
18
+ return null;
19
+ }
20
+ return /*#__PURE__*/_react.default.createElement(_editorToolbar.ToolbarDropdownItemSection, {
21
+ hasSeparator: true
22
+ }, children);
23
+ };
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.checkIsFormatMenuHidden = void 0;
7
7
  var _utils = require("@atlaskit/editor-prosemirror/utils");
8
8
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
9
+ var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
9
10
  var _isNestedNode = require("./isNestedNode");
10
11
  var getIsFormatMenuHidden = function getIsFormatMenuHidden(selection, schema, menuTriggerBy) {
11
12
  var nodes = schema.nodes;
@@ -32,6 +33,39 @@ var getIsFormatMenuHidden = function getIsFormatMenuHidden(selection, schema, me
32
33
  var isNested = (0, _isNestedNode.isNestedNode)(selection, menuTriggerBy);
33
34
  return !content || isNested;
34
35
  };
36
+ var getIsFormatMenuHiddenEmptyLine = function getIsFormatMenuHiddenEmptyLine(selection, schema, menuTriggerBy) {
37
+ var nodes = schema.nodes;
38
+ if (!nodes) {
39
+ return false;
40
+ }
41
+ var isNested = (0, _isNestedNode.isNestedNode)(selection, menuTriggerBy);
42
+ if (selection.empty || selection.content().size === 0) {
43
+ // if empty selection, show format menu
44
+ return false;
45
+ } else if (isNested) {
46
+ // if nested, always hide format menu
47
+ return true;
48
+ } else {
49
+ var content;
50
+ var allowedNodes = [nodes.paragraph, nodes.heading, nodes.blockquote, nodes.panel, nodes.codeBlock, nodes.bulletList, nodes.orderedList, nodes.taskList];
51
+ if ((0, _expValEquals.expValEquals)('platform_editor_block_menu_layout_format', 'isEnabled', true)) {
52
+ allowedNodes.push(nodes.layoutSection);
53
+ }
54
+ if ((0, _expValEquals.expValEquals)('platform_editor_block_menu_expand_format', 'isEnabled', true)) {
55
+ allowedNodes.push(nodes.expand);
56
+ }
57
+ var selectedNode = (0, _utils.findSelectedNodeOfType)(allowedNodes)(selection);
58
+ if (selectedNode) {
59
+ content = selectedNode.node;
60
+ } else {
61
+ var listTypeOrBlockQuoteNode = (0, _utils.findParentNodeOfType)([nodes.paragraph, nodes.heading, nodes.blockquote, nodes.listItem, nodes.taskItem])(selection);
62
+ if (listTypeOrBlockQuoteNode) {
63
+ content = listTypeOrBlockQuoteNode.node;
64
+ }
65
+ }
66
+ return !content;
67
+ }
68
+ };
35
69
  var checkIsFormatMenuHidden = exports.checkIsFormatMenuHidden = function checkIsFormatMenuHidden(api) {
36
70
  var _api$selection, _api$core$sharedState, _api$blockControls;
37
71
  var selection = api === null || api === void 0 || (_api$selection = api.selection) === null || _api$selection === void 0 || (_api$selection = _api$selection.sharedState) === null || _api$selection === void 0 || (_api$selection = _api$selection.currentState()) === null || _api$selection === void 0 ? void 0 : _api$selection.selection;
@@ -40,5 +74,5 @@ var checkIsFormatMenuHidden = exports.checkIsFormatMenuHidden = function checkIs
40
74
  if (!selection || !schema || !menuTriggerBy) {
41
75
  return false;
42
76
  }
43
- return getIsFormatMenuHidden(selection, schema, menuTriggerBy);
77
+ return (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu_empty_line', 'isEnabled', true) ? getIsFormatMenuHiddenEmptyLine(selection, schema, menuTriggerBy) : getIsFormatMenuHidden(selection, schema, menuTriggerBy);
44
78
  };
@@ -4,6 +4,7 @@ import { formatNode } from './editor-commands/formatNode';
4
4
  import { createPlugin } from './pm-plugins/main';
5
5
  import BlockMenu from './ui/block-menu';
6
6
  import { getBlockMenuComponents } from './ui/block-menu-components';
7
+ import { BlockMenuProvider } from './ui/block-menu-provider';
7
8
  export const blockMenuPlugin = ({
8
9
  api,
9
10
  config
@@ -54,13 +55,15 @@ export const blockMenuPlugin = ({
54
55
  popupsBoundariesElement,
55
56
  popupsScrollableElement
56
57
  }) {
57
- return /*#__PURE__*/React.createElement(BlockMenu, {
58
+ return /*#__PURE__*/React.createElement(BlockMenuProvider, {
59
+ api: api
60
+ }, /*#__PURE__*/React.createElement(BlockMenu, {
58
61
  editorView: editorView,
59
62
  api: api,
60
63
  mountTo: popupsMountPoint,
61
64
  boundariesElement: popupsBoundariesElement,
62
65
  scrollableElement: popupsScrollableElement
63
- });
66
+ }));
64
67
  }
65
68
  };
66
69
  };
@@ -1,5 +1,50 @@
1
- import { findParentNodeOfType, findSelectedNodeOfType } from '@atlaskit/editor-prosemirror/utils';
1
+ import { findParentNodeOfType, findSelectedNodeOfType, safeInsert as pmSafeInsert } from '@atlaskit/editor-prosemirror/utils';
2
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
3
+ import { createDefaultLayoutSection } from './transforms/layout-transforms';
2
4
  import { transformNodeToTargetType } from './transforms/transformNodeToTargetType';
5
+ /**
6
+ * Handles formatting when selection is empty by inserting a new target node
7
+ */
8
+ const formatNodeWhenSelectionEmpty = (tr, targetType, nodePos, schema) => {
9
+ var _pmSafeInsert;
10
+ const {
11
+ nodes
12
+ } = schema;
13
+ const {
14
+ paragraph
15
+ } = nodes;
16
+ // if not using the ' ' here, the safeInsert from editor-common will fail to insert the heading
17
+ // and the pmSafeInsert introduce an issue that after inserting heading, and click on the handle, selection will go to top of the doc
18
+ // as an workaround, use the spaceTextNode here
19
+ const spaceTextNode = schema.text(' ');
20
+ let targetNode;
21
+ if (targetType.startsWith('heading')) {
22
+ const levelString = targetType.slice(-1);
23
+ const level = parseInt(levelString, 10);
24
+ if (isNaN(level) || level < 1 || level > 6) {
25
+ return null;
26
+ }
27
+ targetNode = nodes.heading.createAndFill({
28
+ level
29
+ }, spaceTextNode);
30
+ } else if (targetType === 'paragraph') {
31
+ targetNode = nodes.paragraph.createAndFill({}, spaceTextNode);
32
+ } else if (targetType === 'layoutSection') {
33
+ const contentAsParagraph = paragraph.createAndFill({}, spaceTextNode);
34
+ if (contentAsParagraph) {
35
+ targetNode = createDefaultLayoutSection(schema, contentAsParagraph);
36
+ }
37
+ } else {
38
+ const targetNodeType = nodes[targetType];
39
+ targetNode = targetNodeType.createAndFill();
40
+ }
41
+ if (!targetNode) {
42
+ return tr;
43
+ }
44
+ tr = (_pmSafeInsert = pmSafeInsert(targetNode, nodePos)(tr)) !== null && _pmSafeInsert !== void 0 ? _pmSafeInsert : tr;
45
+ return tr;
46
+ };
47
+
3
48
  /**
4
49
  * Formats the current node or selection to the specified target type
5
50
  * @param targetType - The target node type to convert to
@@ -11,14 +56,20 @@ export const formatNode = targetType => {
11
56
  const {
12
57
  selection
13
58
  } = tr;
59
+ const schema = tr.doc.type.schema;
14
60
  const {
15
61
  nodes
16
- } = tr.doc.type.schema;
62
+ } = schema;
17
63
 
18
64
  // Find the node to format from the current selection
19
65
  let nodeToFormat;
20
66
  let nodePos = selection.from;
21
67
 
68
+ // when selection is empty, we insert a empty target node
69
+ if (selection.empty && expValEqualsNoExposure('platform_editor_block_menu_empty_line', 'isEnabled', true)) {
70
+ return formatNodeWhenSelectionEmpty(tr, targetType, nodePos, schema);
71
+ }
72
+
22
73
  // Try to find the current node from selection
23
74
  const selectedNode = findSelectedNodeOfType([nodes.paragraph, nodes.heading, nodes.blockquote, nodes.panel, nodes.expand, nodes.codeBlock, nodes.bulletList, nodes.orderedList, nodes.taskList, nodes.layoutSection])(selection);
24
75
  if (selectedNode) {
@@ -1,27 +1,34 @@
1
1
  import { DEFAULT_TWO_COLUMN_LAYOUT_COLUMN_WIDTH } from '@atlaskit/editor-common/styles';
2
2
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
3
3
  import { convertUnwrappedLayoutContent, unwrapLayoutNodesToTextNodes } from './layout/utils';
4
- export const convertToLayout = context => {
5
- const {
6
- tr,
7
- sourceNode,
8
- sourcePos
9
- } = context;
4
+ import { isHeadingOrParagraphNode } from './utils';
5
+ export const createDefaultLayoutSection = (schema, content) => {
10
6
  const {
11
7
  layoutSection,
12
8
  layoutColumn,
13
9
  paragraph
14
- } = tr.doc.type.schema.nodes || {};
15
- const content = sourceNode.mark(sourceNode.marks.filter(mark => mark.type.name !== 'breakout'));
10
+ } = schema.nodes;
16
11
  const layoutContent = Fragment.fromArray([layoutColumn.createChecked({
17
12
  width: DEFAULT_TWO_COLUMN_LAYOUT_COLUMN_WIDTH
18
13
  }, content), layoutColumn.create({
19
14
  width: DEFAULT_TWO_COLUMN_LAYOUT_COLUMN_WIDTH
20
15
  }, paragraph.createAndFill())]);
21
- const layoutSectionNode = layoutSection.createChecked(undefined, layoutContent);
22
-
23
- // Replace the original node with the new layout node
24
- tr.replaceRangeWith(sourcePos, sourcePos + sourceNode.nodeSize, layoutSectionNode);
16
+ return layoutSection.createChecked(undefined, layoutContent);
17
+ };
18
+ export const convertToLayout = context => {
19
+ const {
20
+ tr,
21
+ sourceNode,
22
+ sourcePos
23
+ } = context;
24
+ const content = sourceNode.mark(sourceNode.marks.filter(mark => mark.type.name !== 'breakout'));
25
+ const layoutSectionNode = createDefaultLayoutSection(tr.doc.type.schema, content);
26
+ if (isHeadingOrParagraphNode(sourceNode)) {
27
+ // -1 to fix when sourceNode is the last node in the document, unable to convert to layout
28
+ tr.replaceRangeWith(sourcePos > 0 ? sourcePos - 1 : sourcePos, sourcePos + sourceNode.nodeSize - 1, layoutSectionNode);
29
+ } else {
30
+ tr.replaceRangeWith(sourcePos, sourcePos + sourceNode.nodeSize, layoutSectionNode);
31
+ }
25
32
  return tr;
26
33
  };
27
34
  export const transformLayoutNode = context => {
@@ -7,6 +7,7 @@ import CopyBlockMenuItem from './copy-block';
7
7
  import { CopyLinkDropdownItem } from './copy-link';
8
8
  import { CopySection } from './copy-section';
9
9
  import { DeleteDropdownItem } from './delete-button';
10
+ import { DeleteSection } from './delete-section';
10
11
  import { FormatMenuSection } from './format-menu-section';
11
12
  import { MoveDownDropdownItem } from './move-down';
12
13
  import { MoveUpDropdownItem } from './move-up';
@@ -144,8 +145,8 @@ export const getBlockMenuComponents = ({
144
145
  component: ({
145
146
  children
146
147
  }) => {
147
- return /*#__PURE__*/React.createElement(ToolbarDropdownItemSection, {
148
- hasSeparator: true
148
+ return /*#__PURE__*/React.createElement(DeleteSection, {
149
+ api: api
149
150
  }, children);
150
151
  }
151
152
  }, ...getMoveUpMoveDownMenuComponents(api), {
@@ -0,0 +1,31 @@
1
+ import React, { useCallback, createContext, useContext } from 'react';
2
+ const BlockMenuContext = /*#__PURE__*/createContext({
3
+ onDropdownOpenChanged: () => {}
4
+ });
5
+ export const useBlockMenu = () => {
6
+ const context = useContext(BlockMenuContext);
7
+ if (!context) {
8
+ throw new Error('useBlockMenu must be used within BlockMenuProvider');
9
+ }
10
+ return context;
11
+ };
12
+ export const BlockMenuProvider = ({
13
+ children,
14
+ api
15
+ }) => {
16
+ const onDropdownOpenChanged = useCallback(isOpen => {
17
+ if (!isOpen) {
18
+ // On Dropdown closed, return focus to editor
19
+ setTimeout(() => requestAnimationFrame(() => {
20
+ api === null || api === void 0 ? void 0 : api.core.actions.focus({
21
+ scrollIntoView: false
22
+ });
23
+ }), 1);
24
+ }
25
+ }, [api]);
26
+ return /*#__PURE__*/React.createElement(BlockMenuContext.Provider, {
27
+ value: {
28
+ onDropdownOpenChanged
29
+ }
30
+ }, children);
31
+ };