@atlaskit/editor-plugin-block-menu 6.0.10 → 6.0.13
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 +21 -0
- package/dist/cjs/editor-commands/transform-node-utils/steps/wrapMixedContentStep.js +1 -12
- package/dist/cjs/editor-commands/transform-node-utils/utils.js +11 -33
- package/dist/cjs/ui/block-menu-components.js +12 -0
- package/dist/cjs/ui/block-menu-renderer/BlockMenuComponent.js +3 -3
- package/dist/cjs/ui/block-menu-renderer/utils.js +4 -7
- package/dist/cjs/ui/block-menu.js +8 -1
- package/dist/cjs/ui/hooks/useSuggestedItems.js +4 -37
- package/dist/cjs/ui/utils/createMenuItemsMap.js +19 -0
- package/dist/cjs/ui/utils/getSuggestedItemsFromSelection.js +40 -0
- package/dist/es2019/editor-commands/transform-node-utils/steps/wrapMixedContentStep.js +2 -13
- package/dist/es2019/editor-commands/transform-node-utils/utils.js +10 -32
- package/dist/es2019/ui/block-menu-components.js +13 -1
- package/dist/es2019/ui/block-menu-renderer/BlockMenuComponent.js +3 -3
- package/dist/es2019/ui/block-menu-renderer/utils.js +4 -7
- package/dist/es2019/ui/block-menu.js +9 -1
- package/dist/es2019/ui/hooks/useSuggestedItems.js +4 -28
- package/dist/es2019/ui/utils/createMenuItemsMap.js +9 -0
- package/dist/es2019/ui/utils/getSuggestedItemsFromSelection.js +30 -0
- package/dist/esm/editor-commands/transform-node-utils/steps/wrapMixedContentStep.js +2 -13
- package/dist/esm/editor-commands/transform-node-utils/utils.js +10 -32
- package/dist/esm/ui/block-menu-components.js +12 -0
- package/dist/esm/ui/block-menu-renderer/BlockMenuComponent.js +3 -3
- package/dist/esm/ui/block-menu-renderer/utils.js +4 -7
- package/dist/esm/ui/block-menu.js +8 -1
- package/dist/esm/ui/hooks/useSuggestedItems.js +4 -37
- package/dist/esm/ui/utils/createMenuItemsMap.js +13 -0
- package/dist/esm/ui/utils/getSuggestedItemsFromSelection.js +35 -0
- package/dist/types/blockMenuPluginType.d.ts +1 -0
- package/dist/types/editor-commands/transform-node-utils/utils.d.ts +5 -10
- package/dist/types/ui/block-menu-renderer/utils.d.ts +2 -3
- package/dist/types/ui/block-menu.d.ts +1 -1
- package/dist/types/ui/utils/createMenuItemsMap.d.ts +6 -0
- package/dist/types/ui/utils/getSuggestedItemsFromSelection.d.ts +6 -0
- package/dist/types-ts4.5/blockMenuPluginType.d.ts +1 -0
- package/dist/types-ts4.5/editor-commands/transform-node-utils/utils.d.ts +5 -10
- package/dist/types-ts4.5/ui/block-menu-renderer/utils.d.ts +2 -3
- package/dist/types-ts4.5/ui/block-menu.d.ts +1 -1
- package/dist/types-ts4.5/ui/utils/createMenuItemsMap.d.ts +6 -0
- package/dist/types-ts4.5/ui/utils/getSuggestedItemsFromSelection.d.ts +6 -0
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-block-menu
|
|
2
2
|
|
|
3
|
+
## 6.0.13
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`799170edab4f8`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/799170edab4f8) -
|
|
8
|
+
Fix the delete wrong content via keyboard when block menu open
|
|
9
|
+
|
|
10
|
+
## 6.0.12
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- [`b909e5f47ea91`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/b909e5f47ea91) -
|
|
15
|
+
EDITOR-4159 Make sure block-menu-item type component return null when tranform disabled
|
|
16
|
+
- Updated dependencies
|
|
17
|
+
|
|
18
|
+
## 6.0.11
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- Updated dependencies
|
|
23
|
+
|
|
3
24
|
## 6.0.10
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
|
@@ -155,18 +155,7 @@ var wrapMixedContentStep = exports.wrapMixedContentStep = function wrapMixedCont
|
|
|
155
155
|
(_currentContainerCont = currentContainerContent).push.apply(_currentContainerCont, (0, _toConsumableArray2.default)((0, _marks.removeDisallowedMarks)([node], validationType)));
|
|
156
156
|
};
|
|
157
157
|
var handleCodeblockTextNode = function handleCodeblockTextNode(node) {
|
|
158
|
-
|
|
159
|
-
var parts = (0, _utils.splitTextNodeForCodeBlock)(node, schema);
|
|
160
|
-
parts.forEach(function (part) {
|
|
161
|
-
if (typeof part === 'string') {
|
|
162
|
-
// Text content - add to current codeBlock
|
|
163
|
-
currentContainerContent.push(part);
|
|
164
|
-
} else {
|
|
165
|
-
// Incompatible inline node wrapped in paragraph - break it out
|
|
166
|
-
flushCurrentContainer();
|
|
167
|
-
result.push(part);
|
|
168
|
-
}
|
|
169
|
-
});
|
|
158
|
+
currentContainerContent.push((0, _utils.createTextContent)(node));
|
|
170
159
|
};
|
|
171
160
|
var handleConvertibleTextNode = function handleConvertibleTextNode(node) {
|
|
172
161
|
var paragraph = (0, _utils.convertTextNodeToParagraph)(node, schema);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.
|
|
6
|
+
exports.isTextNode = exports.getTargetNodeTypeNameInContext = exports.getSelectedNode = exports.getBlockNodesInRange = exports.createTextContent = exports.convertTextNodeToParagraph = exports.convertNestedExpandToExpand = exports.convertExpandToNestedExpand = void 0;
|
|
7
7
|
var _state = require("@atlaskit/editor-prosemirror/state");
|
|
8
8
|
var _utils = require("@atlaskit/editor-prosemirror/utils");
|
|
9
9
|
var _editorTables = require("@atlaskit/editor-tables");
|
|
@@ -131,42 +131,20 @@ var getBlockNodesInRange = exports.getBlockNodesInRange = function getBlockNodes
|
|
|
131
131
|
};
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* - strings (text content that can go into codeBlock)
|
|
137
|
-
* - PMNodes (incompatible inline nodes wrapped in paragraphs that need to break out)
|
|
134
|
+
* Iterates over a nodes children and extracting text content, removing all other inline content and converting
|
|
135
|
+
* hardbreaks to newlines.
|
|
138
136
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* @param node - The text node (paragraph or heading) to split
|
|
142
|
-
* @param schema - The schema to use for creating paragraph nodes
|
|
143
|
-
* @returns Array of strings (for codeBlock) and PMNodes (to break out)
|
|
137
|
+
* @param node - The node to create text content from (should be paragraph)
|
|
138
|
+
* @returns The text content string.
|
|
144
139
|
*/
|
|
145
|
-
var
|
|
146
|
-
var
|
|
147
|
-
var currentText = '';
|
|
148
|
-
node.content.forEach(function (child) {
|
|
140
|
+
var createTextContent = exports.createTextContent = function createTextContent(node) {
|
|
141
|
+
var textContent = node.children.map(function (child) {
|
|
149
142
|
if (child.isText) {
|
|
150
|
-
|
|
143
|
+
return child.text;
|
|
151
144
|
} else if (child.type.name === 'hardBreak') {
|
|
152
|
-
|
|
153
|
-
} else {
|
|
154
|
-
// Incompatible inline node (status, inlineExtension, etc.)
|
|
155
|
-
// Flush accumulated text if any
|
|
156
|
-
if (currentText) {
|
|
157
|
-
result.push(currentText);
|
|
158
|
-
currentText = '';
|
|
159
|
-
}
|
|
160
|
-
// Wrap the inline node in a paragraph and add it to break out
|
|
161
|
-
var paragraph = schema.nodes.paragraph.create({}, child);
|
|
162
|
-
result.push(paragraph);
|
|
145
|
+
return '\n';
|
|
163
146
|
}
|
|
147
|
+
return '';
|
|
164
148
|
});
|
|
165
|
-
|
|
166
|
-
// Don't forget remaining text (or empty string for empty nodes)
|
|
167
|
-
// Always push at least an empty string so empty paragraphs create empty codeBlocks
|
|
168
|
-
if (currentText || result.length === 0) {
|
|
169
|
-
result.push(currentText);
|
|
170
|
-
}
|
|
171
|
-
return result;
|
|
149
|
+
return textContent.join('');
|
|
172
150
|
};
|
|
@@ -19,6 +19,8 @@ var _moveDown = require("./move-down");
|
|
|
19
19
|
var _moveUp = require("./move-up");
|
|
20
20
|
var _suggestedItemsMenuSection = require("./suggested-items-menu-section");
|
|
21
21
|
var _suggestedMenuItems = require("./suggested-menu-items");
|
|
22
|
+
var _createMenuItemsMap = require("./utils/createMenuItemsMap");
|
|
23
|
+
var _getSuggestedItemsFromSelection = require("./utils/getSuggestedItemsFromSelection");
|
|
22
24
|
var getMoveUpMoveDownMenuComponents = function getMoveUpMoveDownMenuComponents(api) {
|
|
23
25
|
return [{
|
|
24
26
|
type: 'block-menu-item',
|
|
@@ -95,6 +97,16 @@ var getTurnIntoMenuComponents = function getTurnIntoMenuComponents(api) {
|
|
|
95
97
|
return /*#__PURE__*/_react.default.createElement(_suggestedMenuItems.SuggestedMenuItems, {
|
|
96
98
|
api: api
|
|
97
99
|
});
|
|
100
|
+
},
|
|
101
|
+
isHidden: function isHidden() {
|
|
102
|
+
var _api$blockMenu, _api$selection, _api$blockControls;
|
|
103
|
+
var blockMenuComponents = api === null || api === void 0 || (_api$blockMenu = api.blockMenu) === null || _api$blockMenu === void 0 ? void 0 : _api$blockMenu.actions.getBlockMenuComponents();
|
|
104
|
+
var menuItemsMap = (0, _createMenuItemsMap.createMenuItemsMap)(blockMenuComponents);
|
|
105
|
+
var selection = api === null || api === void 0 || (_api$selection = api.selection) === null || _api$selection === void 0 || (_api$selection = _api$selection.sharedState.currentState()) === null || _api$selection === void 0 ? void 0 : _api$selection.selection;
|
|
106
|
+
var preservedSelection = api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.sharedState.currentState()) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.preservedSelection;
|
|
107
|
+
var currentSelection = preservedSelection || selection;
|
|
108
|
+
var suggestedItems = (0, _getSuggestedItemsFromSelection.getSuggestedItemsFromSelection)(menuItemsMap, currentSelection);
|
|
109
|
+
return suggestedItems.length === 0;
|
|
98
110
|
}
|
|
99
111
|
}, {
|
|
100
112
|
type: 'block-menu-section',
|
|
@@ -15,15 +15,15 @@ var BlockMenuComponent = exports.BlockMenuComponent = function BlockMenuComponen
|
|
|
15
15
|
var registeredComponent = _ref.registeredComponent,
|
|
16
16
|
childrenMap = _ref.childrenMap,
|
|
17
17
|
fallbacks = _ref.fallbacks;
|
|
18
|
+
if (!(0, _utils.willComponentRender)(registeredComponent, childrenMap)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
18
21
|
if (registeredComponent.type === 'block-menu-item') {
|
|
19
22
|
var ItemComponent = registeredComponent.component || fallbacks['block-menu-item'];
|
|
20
23
|
return /*#__PURE__*/_react.default.createElement(ItemComponent, {
|
|
21
24
|
key: registeredComponent.key
|
|
22
25
|
});
|
|
23
26
|
}
|
|
24
|
-
if (!(0, _utils.willComponentRender)(registeredComponent, childrenMap)) {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
27
|
var ParentComponent = registeredComponent.component || fallbacks[registeredComponent.type];
|
|
28
28
|
var childrenMapKey = (0, _utils.getChildrenMapKey)(registeredComponent.key, registeredComponent.type);
|
|
29
29
|
var registeredComponents = childrenMap.get(childrenMapKey);
|
|
@@ -106,21 +106,18 @@ var buildChildrenMap = exports.buildChildrenMap = function buildChildrenMap(comp
|
|
|
106
106
|
* Determines whether a component will render based on its type and children
|
|
107
107
|
*
|
|
108
108
|
* Rules:
|
|
109
|
-
* - An item will not render if has
|
|
110
|
-
* - A nested menu will render if
|
|
109
|
+
* - An item will not render if it has isHidden that returns true OR if its component returns null (fallback)
|
|
110
|
+
* - A nested menu will render if at least one section, that has at least one registered child
|
|
111
111
|
* - A section will render if it has at least one registered child component that will render
|
|
112
112
|
*
|
|
113
|
-
* NOTE: This requires invoking each item's component function to check for null return
|
|
114
113
|
*/
|
|
115
114
|
var _willComponentRender = exports.willComponentRender = function willComponentRender(registeredComponent, childrenMap) {
|
|
116
115
|
if (registeredComponent.type === 'block-menu-item') {
|
|
117
|
-
|
|
116
|
+
var _registeredComponent$;
|
|
117
|
+
return !(registeredComponent !== null && registeredComponent !== void 0 && (_registeredComponent$ = registeredComponent.isHidden) !== null && _registeredComponent$ !== void 0 && _registeredComponent$.call(registeredComponent));
|
|
118
118
|
}
|
|
119
119
|
var childrenMapKey = getChildrenMapKey(registeredComponent.key, registeredComponent.type);
|
|
120
120
|
var registeredComponents = childrenMap.get(childrenMapKey) || [];
|
|
121
|
-
if (registeredComponent.type === 'block-menu-nested') {
|
|
122
|
-
return registeredComponents.length > 0;
|
|
123
|
-
}
|
|
124
121
|
return registeredComponents.some(function (childComponent) {
|
|
125
122
|
return _willComponentRender(childComponent, childrenMap);
|
|
126
123
|
});
|
|
@@ -105,6 +105,12 @@ var useConditionalBlockMenuEffect = (0, _platformFeatureFlagsReact.conditionalHo
|
|
|
105
105
|
api === null || api === void 0 || api.core.actions.execute(api === null || api === void 0 || (_api$userIntent2 = api.userIntent) === null || _api$userIntent2 === void 0 ? void 0 : _api$userIntent2.commands.setCurrentUserIntent('blockMenuOpen'));
|
|
106
106
|
}, [api, isMenuOpen, menuTriggerBy, selectedByShortcutOrDragHandle, hasFocus, currentUserIntent, openedViaKeyboard, prevIsMenuOpenRef]);
|
|
107
107
|
});
|
|
108
|
+
var isSelectionWithinCodeBlock = function isSelectionWithinCodeBlock(state) {
|
|
109
|
+
var _state$selection = state.selection,
|
|
110
|
+
$from = _state$selection.$from,
|
|
111
|
+
$to = _state$selection.$to;
|
|
112
|
+
return $from.sameParent($to) && $from.parent.type === state.schema.nodes.codeBlock;
|
|
113
|
+
};
|
|
108
114
|
var BlockMenuContent = function BlockMenuContent(_ref3) {
|
|
109
115
|
var _api$blockMenu;
|
|
110
116
|
var api = _ref3.api,
|
|
@@ -188,7 +194,8 @@ var BlockMenu = function BlockMenu(_ref4) {
|
|
|
188
194
|
var _api$core, _api$blockControls;
|
|
189
195
|
// When the editor view has focus, the keydown will be handled by the
|
|
190
196
|
// selection preservation plugin – exit early to avoid double handling
|
|
191
|
-
if
|
|
197
|
+
// Also exit if selection is within a code block to avoid double handling when code block got focus when the node after it is deleted
|
|
198
|
+
if (!editorView || editorView !== null && editorView !== void 0 && editorView.hasFocus() || isSelectionWithinCodeBlock(editorView.state)) {
|
|
192
199
|
return;
|
|
193
200
|
}
|
|
194
201
|
|
|
@@ -6,9 +6,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.useSuggestedItems = void 0;
|
|
7
7
|
var _react = require("react");
|
|
8
8
|
var _hooks = require("@atlaskit/editor-common/hooks");
|
|
9
|
-
var
|
|
10
|
-
var
|
|
11
|
-
var _suggestedItemsRank = require("../utils/suggested-items-rank");
|
|
9
|
+
var _createMenuItemsMap = require("../utils/createMenuItemsMap");
|
|
10
|
+
var _getSuggestedItemsFromSelection = require("../utils/getSuggestedItemsFromSelection");
|
|
12
11
|
var useSuggestedItems = exports.useSuggestedItems = function useSuggestedItems(api) {
|
|
13
12
|
var _api$blockMenu;
|
|
14
13
|
var _useSharedPluginState = (0, _hooks.useSharedPluginStateWithSelector)(api, ['blockControls', 'selection'], function (states) {
|
|
@@ -22,43 +21,11 @@ var useSuggestedItems = exports.useSuggestedItems = function useSuggestedItems(a
|
|
|
22
21
|
selection = _useSharedPluginState.selection;
|
|
23
22
|
var blockMenuComponents = api === null || api === void 0 || (_api$blockMenu = api.blockMenu) === null || _api$blockMenu === void 0 ? void 0 : _api$blockMenu.actions.getBlockMenuComponents();
|
|
24
23
|
var menuItemsMap = (0, _react.useMemo)(function () {
|
|
25
|
-
|
|
26
|
-
return new Map();
|
|
27
|
-
}
|
|
28
|
-
return new Map(blockMenuComponents.filter(function (c) {
|
|
29
|
-
return c.type === 'block-menu-item';
|
|
30
|
-
}).map(function (item) {
|
|
31
|
-
return [item.key, item];
|
|
32
|
-
}));
|
|
24
|
+
return (0, _createMenuItemsMap.createMenuItemsMap)(blockMenuComponents);
|
|
33
25
|
}, [blockMenuComponents]);
|
|
34
26
|
var suggestedItems = (0, _react.useMemo)(function () {
|
|
35
27
|
var currentSelection = preservedSelection || selection;
|
|
36
|
-
|
|
37
|
-
return [];
|
|
38
|
-
}
|
|
39
|
-
var _expandSelectionToBlo = (0, _selection.expandSelectionToBlockRange)(currentSelection),
|
|
40
|
-
range = _expandSelectionToBlo.range;
|
|
41
|
-
if (!range) {
|
|
42
|
-
return [];
|
|
43
|
-
}
|
|
44
|
-
var blockNodes = (0, _utils.getBlockNodesInRange)(range);
|
|
45
|
-
if (blockNodes.length === 0) {
|
|
46
|
-
return [];
|
|
47
|
-
}
|
|
48
|
-
var firstNodeType = blockNodes[0].type.name;
|
|
49
|
-
var allSameType = blockNodes.every(function (node) {
|
|
50
|
-
return node.type.name === firstNodeType;
|
|
51
|
-
});
|
|
52
|
-
if (!allSameType) {
|
|
53
|
-
return [];
|
|
54
|
-
}
|
|
55
|
-
var nodeTypeName = firstNodeType;
|
|
56
|
-
var sortedKeys = (0, _suggestedItemsRank.getSortedSuggestedItems)(nodeTypeName);
|
|
57
|
-
return sortedKeys.map(function (key) {
|
|
58
|
-
return menuItemsMap.get(key);
|
|
59
|
-
}).filter(function (item) {
|
|
60
|
-
return item !== undefined;
|
|
61
|
-
});
|
|
28
|
+
return (0, _getSuggestedItemsFromSelection.getSuggestedItemsFromSelection)(menuItemsMap, currentSelection);
|
|
62
29
|
}, [menuItemsMap, preservedSelection, selection]);
|
|
63
30
|
return suggestedItems;
|
|
64
31
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.createMenuItemsMap = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Helper function to create menu items map from block menu components.
|
|
9
|
+
*/
|
|
10
|
+
var createMenuItemsMap = exports.createMenuItemsMap = function createMenuItemsMap(blockMenuComponents) {
|
|
11
|
+
if (!blockMenuComponents) {
|
|
12
|
+
return new Map();
|
|
13
|
+
}
|
|
14
|
+
return new Map(blockMenuComponents.filter(function (c) {
|
|
15
|
+
return c.type === 'block-menu-item';
|
|
16
|
+
}).map(function (item) {
|
|
17
|
+
return [item.key, item];
|
|
18
|
+
}));
|
|
19
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getSuggestedItemsFromSelection = void 0;
|
|
7
|
+
var _selection = require("@atlaskit/editor-common/selection");
|
|
8
|
+
var _utils = require("../../editor-commands/transform-node-utils/utils");
|
|
9
|
+
var _suggestedItemsRank = require("../utils/suggested-items-rank");
|
|
10
|
+
/**
|
|
11
|
+
* Pure function to calculate suggested items based on selection and menu components.
|
|
12
|
+
*/
|
|
13
|
+
var getSuggestedItemsFromSelection = exports.getSuggestedItemsFromSelection = function getSuggestedItemsFromSelection(menuItemsMap, currentSelection) {
|
|
14
|
+
if (menuItemsMap.size === 0 || !currentSelection) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
var _expandSelectionToBlo = (0, _selection.expandSelectionToBlockRange)(currentSelection),
|
|
18
|
+
range = _expandSelectionToBlo.range;
|
|
19
|
+
if (!range) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
var blockNodes = (0, _utils.getBlockNodesInRange)(range);
|
|
23
|
+
if (blockNodes.length === 0) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
var firstNodeType = blockNodes[0].type.name;
|
|
27
|
+
var allSameType = blockNodes.every(function (node) {
|
|
28
|
+
return node.type.name === firstNodeType;
|
|
29
|
+
});
|
|
30
|
+
if (!allSameType) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
var nodeTypeName = firstNodeType;
|
|
34
|
+
var sortedKeys = (0, _suggestedItemsRank.getSortedSuggestedItems)(nodeTypeName);
|
|
35
|
+
return sortedKeys.map(function (key) {
|
|
36
|
+
return menuItemsMap.get(key);
|
|
37
|
+
}).filter(function (item) {
|
|
38
|
+
return item !== undefined;
|
|
39
|
+
});
|
|
40
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Fragment } from '@atlaskit/editor-prosemirror/model';
|
|
2
2
|
import { removeDisallowedMarks } from '../marks';
|
|
3
3
|
import { NODE_CATEGORY_BY_TYPE } from '../types';
|
|
4
|
-
import { convertTextNodeToParagraph,
|
|
4
|
+
import { convertTextNodeToParagraph, createTextContent, isTextNode } from '../utils';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Creates a layout section with two columns, where the first column contains the provided content.
|
|
@@ -150,18 +150,7 @@ export const wrapMixedContentStep = (nodes, context) => {
|
|
|
150
150
|
currentContainerContent.push(...removeDisallowedMarks([node], validationType));
|
|
151
151
|
};
|
|
152
152
|
const handleCodeblockTextNode = node => {
|
|
153
|
-
|
|
154
|
-
const parts = splitTextNodeForCodeBlock(node, schema);
|
|
155
|
-
parts.forEach(part => {
|
|
156
|
-
if (typeof part === 'string') {
|
|
157
|
-
// Text content - add to current codeBlock
|
|
158
|
-
currentContainerContent.push(part);
|
|
159
|
-
} else {
|
|
160
|
-
// Incompatible inline node wrapped in paragraph - break it out
|
|
161
|
-
flushCurrentContainer();
|
|
162
|
-
result.push(part);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
153
|
+
currentContainerContent.push(createTextContent(node));
|
|
165
154
|
};
|
|
166
155
|
const handleConvertibleTextNode = node => {
|
|
167
156
|
const paragraph = convertTextNodeToParagraph(node, schema);
|
|
@@ -127,42 +127,20 @@ export const getBlockNodesInRange = range => {
|
|
|
127
127
|
};
|
|
128
128
|
|
|
129
129
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* - strings (text content that can go into codeBlock)
|
|
133
|
-
* - PMNodes (incompatible inline nodes wrapped in paragraphs that need to break out)
|
|
130
|
+
* Iterates over a nodes children and extracting text content, removing all other inline content and converting
|
|
131
|
+
* hardbreaks to newlines.
|
|
134
132
|
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* @param node - The text node (paragraph or heading) to split
|
|
138
|
-
* @param schema - The schema to use for creating paragraph nodes
|
|
139
|
-
* @returns Array of strings (for codeBlock) and PMNodes (to break out)
|
|
133
|
+
* @param node - The node to create text content from (should be paragraph)
|
|
134
|
+
* @returns The text content string.
|
|
140
135
|
*/
|
|
141
|
-
export const
|
|
142
|
-
const
|
|
143
|
-
let currentText = '';
|
|
144
|
-
node.content.forEach(child => {
|
|
136
|
+
export const createTextContent = node => {
|
|
137
|
+
const textContent = node.children.map(child => {
|
|
145
138
|
if (child.isText) {
|
|
146
|
-
|
|
139
|
+
return child.text;
|
|
147
140
|
} else if (child.type.name === 'hardBreak') {
|
|
148
|
-
|
|
149
|
-
} else {
|
|
150
|
-
// Incompatible inline node (status, inlineExtension, etc.)
|
|
151
|
-
// Flush accumulated text if any
|
|
152
|
-
if (currentText) {
|
|
153
|
-
result.push(currentText);
|
|
154
|
-
currentText = '';
|
|
155
|
-
}
|
|
156
|
-
// Wrap the inline node in a paragraph and add it to break out
|
|
157
|
-
const paragraph = schema.nodes.paragraph.create({}, child);
|
|
158
|
-
result.push(paragraph);
|
|
141
|
+
return '\n';
|
|
159
142
|
}
|
|
143
|
+
return '';
|
|
160
144
|
});
|
|
161
|
-
|
|
162
|
-
// Don't forget remaining text (or empty string for empty nodes)
|
|
163
|
-
// Always push at least an empty string so empty paragraphs create empty codeBlocks
|
|
164
|
-
if (currentText || result.length === 0) {
|
|
165
|
-
result.push(currentText);
|
|
166
|
-
}
|
|
167
|
-
return result;
|
|
145
|
+
return textContent.join('');
|
|
168
146
|
};
|
|
@@ -11,6 +11,8 @@ import { MoveDownDropdownItem } from './move-down';
|
|
|
11
11
|
import { MoveUpDropdownItem } from './move-up';
|
|
12
12
|
import { SuggestedItemsMenuSection } from './suggested-items-menu-section';
|
|
13
13
|
import { SuggestedMenuItems } from './suggested-menu-items';
|
|
14
|
+
import { createMenuItemsMap } from './utils/createMenuItemsMap';
|
|
15
|
+
import { getSuggestedItemsFromSelection } from './utils/getSuggestedItemsFromSelection';
|
|
14
16
|
const getMoveUpMoveDownMenuComponents = api => {
|
|
15
17
|
return [{
|
|
16
18
|
type: 'block-menu-item',
|
|
@@ -79,7 +81,17 @@ const getTurnIntoMenuComponents = api => {
|
|
|
79
81
|
},
|
|
80
82
|
component: () => /*#__PURE__*/React.createElement(SuggestedMenuItems, {
|
|
81
83
|
api: api
|
|
82
|
-
})
|
|
84
|
+
}),
|
|
85
|
+
isHidden: () => {
|
|
86
|
+
var _api$blockMenu, _api$selection, _api$selection$shared, _api$blockControls, _api$blockControls$sh;
|
|
87
|
+
const blockMenuComponents = api === null || api === void 0 ? void 0 : (_api$blockMenu = api.blockMenu) === null || _api$blockMenu === void 0 ? void 0 : _api$blockMenu.actions.getBlockMenuComponents();
|
|
88
|
+
const menuItemsMap = createMenuItemsMap(blockMenuComponents);
|
|
89
|
+
const selection = api === null || api === void 0 ? void 0 : (_api$selection = api.selection) === null || _api$selection === void 0 ? void 0 : (_api$selection$shared = _api$selection.sharedState.currentState()) === null || _api$selection$shared === void 0 ? void 0 : _api$selection$shared.selection;
|
|
90
|
+
const preservedSelection = api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : (_api$blockControls$sh = _api$blockControls.sharedState.currentState()) === null || _api$blockControls$sh === void 0 ? void 0 : _api$blockControls$sh.preservedSelection;
|
|
91
|
+
const currentSelection = preservedSelection || selection;
|
|
92
|
+
const suggestedItems = getSuggestedItemsFromSelection(menuItemsMap, currentSelection);
|
|
93
|
+
return suggestedItems.length === 0;
|
|
94
|
+
}
|
|
83
95
|
}, {
|
|
84
96
|
type: 'block-menu-section',
|
|
85
97
|
key: TRANSFORM_CREATE_MENU_SECTION.key,
|
|
@@ -9,15 +9,15 @@ export const BlockMenuComponent = ({
|
|
|
9
9
|
childrenMap,
|
|
10
10
|
fallbacks
|
|
11
11
|
}) => {
|
|
12
|
+
if (!willComponentRender(registeredComponent, childrenMap)) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
12
15
|
if (registeredComponent.type === 'block-menu-item') {
|
|
13
16
|
const ItemComponent = registeredComponent.component || fallbacks['block-menu-item'];
|
|
14
17
|
return /*#__PURE__*/React.createElement(ItemComponent, {
|
|
15
18
|
key: registeredComponent.key
|
|
16
19
|
});
|
|
17
20
|
}
|
|
18
|
-
if (!willComponentRender(registeredComponent, childrenMap)) {
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
21
|
const ParentComponent = registeredComponent.component || fallbacks[registeredComponent.type];
|
|
22
22
|
const childrenMapKey = getChildrenMapKey(registeredComponent.key, registeredComponent.type);
|
|
23
23
|
const registeredComponents = childrenMap.get(childrenMapKey);
|
|
@@ -74,20 +74,17 @@ export const buildChildrenMap = components => {
|
|
|
74
74
|
* Determines whether a component will render based on its type and children
|
|
75
75
|
*
|
|
76
76
|
* Rules:
|
|
77
|
-
* - An item will not render if has
|
|
78
|
-
* - A nested menu will render if
|
|
77
|
+
* - An item will not render if it has isHidden that returns true OR if its component returns null (fallback)
|
|
78
|
+
* - A nested menu will render if at least one section, that has at least one registered child
|
|
79
79
|
* - A section will render if it has at least one registered child component that will render
|
|
80
80
|
*
|
|
81
|
-
* NOTE: This requires invoking each item's component function to check for null return
|
|
82
81
|
*/
|
|
83
82
|
export const willComponentRender = (registeredComponent, childrenMap) => {
|
|
84
83
|
if (registeredComponent.type === 'block-menu-item') {
|
|
85
|
-
|
|
84
|
+
var _registeredComponent$;
|
|
85
|
+
return !(registeredComponent !== null && registeredComponent !== void 0 && (_registeredComponent$ = registeredComponent.isHidden) !== null && _registeredComponent$ !== void 0 && _registeredComponent$.call(registeredComponent));
|
|
86
86
|
}
|
|
87
87
|
const childrenMapKey = getChildrenMapKey(registeredComponent.key, registeredComponent.type);
|
|
88
88
|
const registeredComponents = childrenMap.get(childrenMapKey) || [];
|
|
89
|
-
if (registeredComponent.type === 'block-menu-nested') {
|
|
90
|
-
return registeredComponents.length > 0;
|
|
91
|
-
}
|
|
92
89
|
return registeredComponents.some(childComponent => willComponentRender(childComponent, childrenMap));
|
|
93
90
|
};
|
|
@@ -95,6 +95,13 @@ const useConditionalBlockMenuEffect = conditionalHooksFactory(() => fg('platform
|
|
|
95
95
|
api === null || api === void 0 ? void 0 : api.core.actions.execute(api === null || api === void 0 ? void 0 : (_api$userIntent2 = api.userIntent) === null || _api$userIntent2 === void 0 ? void 0 : _api$userIntent2.commands.setCurrentUserIntent('blockMenuOpen'));
|
|
96
96
|
}, [api, isMenuOpen, menuTriggerBy, selectedByShortcutOrDragHandle, hasFocus, currentUserIntent, openedViaKeyboard, prevIsMenuOpenRef]);
|
|
97
97
|
});
|
|
98
|
+
const isSelectionWithinCodeBlock = state => {
|
|
99
|
+
const {
|
|
100
|
+
$from,
|
|
101
|
+
$to
|
|
102
|
+
} = state.selection;
|
|
103
|
+
return $from.sameParent($to) && $from.parent.type === state.schema.nodes.codeBlock;
|
|
104
|
+
};
|
|
98
105
|
const BlockMenuContent = ({
|
|
99
106
|
api,
|
|
100
107
|
setRef
|
|
@@ -177,7 +184,8 @@ const BlockMenu = ({
|
|
|
177
184
|
var _api$core, _api$blockControls, _api$blockControls$co;
|
|
178
185
|
// When the editor view has focus, the keydown will be handled by the
|
|
179
186
|
// selection preservation plugin – exit early to avoid double handling
|
|
180
|
-
if
|
|
187
|
+
// Also exit if selection is within a code block to avoid double handling when code block got focus when the node after it is deleted
|
|
188
|
+
if (!editorView || editorView !== null && editorView !== void 0 && editorView.hasFocus() || isSelectionWithinCodeBlock(editorView.state)) {
|
|
181
189
|
return;
|
|
182
190
|
}
|
|
183
191
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { getSortedSuggestedItems } from '../utils/suggested-items-rank';
|
|
3
|
+
import { createMenuItemsMap } from '../utils/createMenuItemsMap';
|
|
4
|
+
import { getSuggestedItemsFromSelection } from '../utils/getSuggestedItemsFromSelection';
|
|
6
5
|
export const useSuggestedItems = api => {
|
|
7
6
|
var _api$blockMenu;
|
|
8
7
|
const {
|
|
@@ -17,34 +16,11 @@ export const useSuggestedItems = api => {
|
|
|
17
16
|
});
|
|
18
17
|
const blockMenuComponents = api === null || api === void 0 ? void 0 : (_api$blockMenu = api.blockMenu) === null || _api$blockMenu === void 0 ? void 0 : _api$blockMenu.actions.getBlockMenuComponents();
|
|
19
18
|
const menuItemsMap = useMemo(() => {
|
|
20
|
-
|
|
21
|
-
return new Map();
|
|
22
|
-
}
|
|
23
|
-
return new Map(blockMenuComponents.filter(c => c.type === 'block-menu-item').map(item => [item.key, item]));
|
|
19
|
+
return createMenuItemsMap(blockMenuComponents);
|
|
24
20
|
}, [blockMenuComponents]);
|
|
25
21
|
const suggestedItems = useMemo(() => {
|
|
26
22
|
const currentSelection = preservedSelection || selection;
|
|
27
|
-
|
|
28
|
-
return [];
|
|
29
|
-
}
|
|
30
|
-
const {
|
|
31
|
-
range
|
|
32
|
-
} = expandSelectionToBlockRange(currentSelection);
|
|
33
|
-
if (!range) {
|
|
34
|
-
return [];
|
|
35
|
-
}
|
|
36
|
-
const blockNodes = getBlockNodesInRange(range);
|
|
37
|
-
if (blockNodes.length === 0) {
|
|
38
|
-
return [];
|
|
39
|
-
}
|
|
40
|
-
const firstNodeType = blockNodes[0].type.name;
|
|
41
|
-
const allSameType = blockNodes.every(node => node.type.name === firstNodeType);
|
|
42
|
-
if (!allSameType) {
|
|
43
|
-
return [];
|
|
44
|
-
}
|
|
45
|
-
const nodeTypeName = firstNodeType;
|
|
46
|
-
const sortedKeys = getSortedSuggestedItems(nodeTypeName);
|
|
47
|
-
return sortedKeys.map(key => menuItemsMap.get(key)).filter(item => item !== undefined);
|
|
23
|
+
return getSuggestedItemsFromSelection(menuItemsMap, currentSelection);
|
|
48
24
|
}, [menuItemsMap, preservedSelection, selection]);
|
|
49
25
|
return suggestedItems;
|
|
50
26
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper function to create menu items map from block menu components.
|
|
3
|
+
*/
|
|
4
|
+
export const createMenuItemsMap = blockMenuComponents => {
|
|
5
|
+
if (!blockMenuComponents) {
|
|
6
|
+
return new Map();
|
|
7
|
+
}
|
|
8
|
+
return new Map(blockMenuComponents.filter(c => c.type === 'block-menu-item').map(item => [item.key, item]));
|
|
9
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { expandSelectionToBlockRange } from '@atlaskit/editor-common/selection';
|
|
2
|
+
import { getBlockNodesInRange } from '../../editor-commands/transform-node-utils/utils';
|
|
3
|
+
import { getSortedSuggestedItems } from '../utils/suggested-items-rank';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Pure function to calculate suggested items based on selection and menu components.
|
|
7
|
+
*/
|
|
8
|
+
export const getSuggestedItemsFromSelection = (menuItemsMap, currentSelection) => {
|
|
9
|
+
if (menuItemsMap.size === 0 || !currentSelection) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
const {
|
|
13
|
+
range
|
|
14
|
+
} = expandSelectionToBlockRange(currentSelection);
|
|
15
|
+
if (!range) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
const blockNodes = getBlockNodesInRange(range);
|
|
19
|
+
if (blockNodes.length === 0) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
const firstNodeType = blockNodes[0].type.name;
|
|
23
|
+
const allSameType = blockNodes.every(node => node.type.name === firstNodeType);
|
|
24
|
+
if (!allSameType) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
const nodeTypeName = firstNodeType;
|
|
28
|
+
const sortedKeys = getSortedSuggestedItems(nodeTypeName);
|
|
29
|
+
return sortedKeys.map(key => menuItemsMap.get(key)).filter(item => item !== undefined);
|
|
30
|
+
};
|
|
@@ -2,7 +2,7 @@ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
|
2
2
|
import { Fragment } from '@atlaskit/editor-prosemirror/model';
|
|
3
3
|
import { removeDisallowedMarks } from '../marks';
|
|
4
4
|
import { NODE_CATEGORY_BY_TYPE } from '../types';
|
|
5
|
-
import { convertTextNodeToParagraph,
|
|
5
|
+
import { convertTextNodeToParagraph, createTextContent, isTextNode } from '../utils';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Creates a layout section with two columns, where the first column contains the provided content.
|
|
@@ -149,18 +149,7 @@ export var wrapMixedContentStep = function wrapMixedContentStep(nodes, context)
|
|
|
149
149
|
(_currentContainerCont = currentContainerContent).push.apply(_currentContainerCont, _toConsumableArray(removeDisallowedMarks([node], validationType)));
|
|
150
150
|
};
|
|
151
151
|
var handleCodeblockTextNode = function handleCodeblockTextNode(node) {
|
|
152
|
-
|
|
153
|
-
var parts = splitTextNodeForCodeBlock(node, schema);
|
|
154
|
-
parts.forEach(function (part) {
|
|
155
|
-
if (typeof part === 'string') {
|
|
156
|
-
// Text content - add to current codeBlock
|
|
157
|
-
currentContainerContent.push(part);
|
|
158
|
-
} else {
|
|
159
|
-
// Incompatible inline node wrapped in paragraph - break it out
|
|
160
|
-
flushCurrentContainer();
|
|
161
|
-
result.push(part);
|
|
162
|
-
}
|
|
163
|
-
});
|
|
152
|
+
currentContainerContent.push(createTextContent(node));
|
|
164
153
|
};
|
|
165
154
|
var handleConvertibleTextNode = function handleConvertibleTextNode(node) {
|
|
166
155
|
var paragraph = convertTextNodeToParagraph(node, schema);
|
|
@@ -126,42 +126,20 @@ export var getBlockNodesInRange = function getBlockNodesInRange(range) {
|
|
|
126
126
|
};
|
|
127
127
|
|
|
128
128
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* - strings (text content that can go into codeBlock)
|
|
132
|
-
* - PMNodes (incompatible inline nodes wrapped in paragraphs that need to break out)
|
|
129
|
+
* Iterates over a nodes children and extracting text content, removing all other inline content and converting
|
|
130
|
+
* hardbreaks to newlines.
|
|
133
131
|
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* @param node - The text node (paragraph or heading) to split
|
|
137
|
-
* @param schema - The schema to use for creating paragraph nodes
|
|
138
|
-
* @returns Array of strings (for codeBlock) and PMNodes (to break out)
|
|
132
|
+
* @param node - The node to create text content from (should be paragraph)
|
|
133
|
+
* @returns The text content string.
|
|
139
134
|
*/
|
|
140
|
-
export var
|
|
141
|
-
var
|
|
142
|
-
var currentText = '';
|
|
143
|
-
node.content.forEach(function (child) {
|
|
135
|
+
export var createTextContent = function createTextContent(node) {
|
|
136
|
+
var textContent = node.children.map(function (child) {
|
|
144
137
|
if (child.isText) {
|
|
145
|
-
|
|
138
|
+
return child.text;
|
|
146
139
|
} else if (child.type.name === 'hardBreak') {
|
|
147
|
-
|
|
148
|
-
} else {
|
|
149
|
-
// Incompatible inline node (status, inlineExtension, etc.)
|
|
150
|
-
// Flush accumulated text if any
|
|
151
|
-
if (currentText) {
|
|
152
|
-
result.push(currentText);
|
|
153
|
-
currentText = '';
|
|
154
|
-
}
|
|
155
|
-
// Wrap the inline node in a paragraph and add it to break out
|
|
156
|
-
var paragraph = schema.nodes.paragraph.create({}, child);
|
|
157
|
-
result.push(paragraph);
|
|
140
|
+
return '\n';
|
|
158
141
|
}
|
|
142
|
+
return '';
|
|
159
143
|
});
|
|
160
|
-
|
|
161
|
-
// Don't forget remaining text (or empty string for empty nodes)
|
|
162
|
-
// Always push at least an empty string so empty paragraphs create empty codeBlocks
|
|
163
|
-
if (currentText || result.length === 0) {
|
|
164
|
-
result.push(currentText);
|
|
165
|
-
}
|
|
166
|
-
return result;
|
|
144
|
+
return textContent.join('');
|
|
167
145
|
};
|
|
@@ -12,6 +12,8 @@ import { MoveDownDropdownItem } from './move-down';
|
|
|
12
12
|
import { MoveUpDropdownItem } from './move-up';
|
|
13
13
|
import { SuggestedItemsMenuSection } from './suggested-items-menu-section';
|
|
14
14
|
import { SuggestedMenuItems } from './suggested-menu-items';
|
|
15
|
+
import { createMenuItemsMap } from './utils/createMenuItemsMap';
|
|
16
|
+
import { getSuggestedItemsFromSelection } from './utils/getSuggestedItemsFromSelection';
|
|
15
17
|
var getMoveUpMoveDownMenuComponents = function getMoveUpMoveDownMenuComponents(api) {
|
|
16
18
|
return [{
|
|
17
19
|
type: 'block-menu-item',
|
|
@@ -88,6 +90,16 @@ var getTurnIntoMenuComponents = function getTurnIntoMenuComponents(api) {
|
|
|
88
90
|
return /*#__PURE__*/React.createElement(SuggestedMenuItems, {
|
|
89
91
|
api: api
|
|
90
92
|
});
|
|
93
|
+
},
|
|
94
|
+
isHidden: function isHidden() {
|
|
95
|
+
var _api$blockMenu, _api$selection, _api$blockControls;
|
|
96
|
+
var blockMenuComponents = api === null || api === void 0 || (_api$blockMenu = api.blockMenu) === null || _api$blockMenu === void 0 ? void 0 : _api$blockMenu.actions.getBlockMenuComponents();
|
|
97
|
+
var menuItemsMap = createMenuItemsMap(blockMenuComponents);
|
|
98
|
+
var selection = api === null || api === void 0 || (_api$selection = api.selection) === null || _api$selection === void 0 || (_api$selection = _api$selection.sharedState.currentState()) === null || _api$selection === void 0 ? void 0 : _api$selection.selection;
|
|
99
|
+
var preservedSelection = api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.sharedState.currentState()) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.preservedSelection;
|
|
100
|
+
var currentSelection = preservedSelection || selection;
|
|
101
|
+
var suggestedItems = getSuggestedItemsFromSelection(menuItemsMap, currentSelection);
|
|
102
|
+
return suggestedItems.length === 0;
|
|
91
103
|
}
|
|
92
104
|
}, {
|
|
93
105
|
type: 'block-menu-section',
|
|
@@ -8,15 +8,15 @@ export var BlockMenuComponent = function BlockMenuComponent(_ref) {
|
|
|
8
8
|
var registeredComponent = _ref.registeredComponent,
|
|
9
9
|
childrenMap = _ref.childrenMap,
|
|
10
10
|
fallbacks = _ref.fallbacks;
|
|
11
|
+
if (!willComponentRender(registeredComponent, childrenMap)) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
11
14
|
if (registeredComponent.type === 'block-menu-item') {
|
|
12
15
|
var ItemComponent = registeredComponent.component || fallbacks['block-menu-item'];
|
|
13
16
|
return /*#__PURE__*/React.createElement(ItemComponent, {
|
|
14
17
|
key: registeredComponent.key
|
|
15
18
|
});
|
|
16
19
|
}
|
|
17
|
-
if (!willComponentRender(registeredComponent, childrenMap)) {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
20
|
var ParentComponent = registeredComponent.component || fallbacks[registeredComponent.type];
|
|
21
21
|
var childrenMapKey = getChildrenMapKey(registeredComponent.key, registeredComponent.type);
|
|
22
22
|
var registeredComponents = childrenMap.get(childrenMapKey);
|
|
@@ -99,21 +99,18 @@ export var buildChildrenMap = function buildChildrenMap(components) {
|
|
|
99
99
|
* Determines whether a component will render based on its type and children
|
|
100
100
|
*
|
|
101
101
|
* Rules:
|
|
102
|
-
* - An item will not render if has
|
|
103
|
-
* - A nested menu will render if
|
|
102
|
+
* - An item will not render if it has isHidden that returns true OR if its component returns null (fallback)
|
|
103
|
+
* - A nested menu will render if at least one section, that has at least one registered child
|
|
104
104
|
* - A section will render if it has at least one registered child component that will render
|
|
105
105
|
*
|
|
106
|
-
* NOTE: This requires invoking each item's component function to check for null return
|
|
107
106
|
*/
|
|
108
107
|
var _willComponentRender = function willComponentRender(registeredComponent, childrenMap) {
|
|
109
108
|
if (registeredComponent.type === 'block-menu-item') {
|
|
110
|
-
|
|
109
|
+
var _registeredComponent$;
|
|
110
|
+
return !(registeredComponent !== null && registeredComponent !== void 0 && (_registeredComponent$ = registeredComponent.isHidden) !== null && _registeredComponent$ !== void 0 && _registeredComponent$.call(registeredComponent));
|
|
111
111
|
}
|
|
112
112
|
var childrenMapKey = getChildrenMapKey(registeredComponent.key, registeredComponent.type);
|
|
113
113
|
var registeredComponents = childrenMap.get(childrenMapKey) || [];
|
|
114
|
-
if (registeredComponent.type === 'block-menu-nested') {
|
|
115
|
-
return registeredComponents.length > 0;
|
|
116
|
-
}
|
|
117
114
|
return registeredComponents.some(function (childComponent) {
|
|
118
115
|
return _willComponentRender(childComponent, childrenMap);
|
|
119
116
|
});
|
|
@@ -96,6 +96,12 @@ var useConditionalBlockMenuEffect = conditionalHooksFactory(function () {
|
|
|
96
96
|
api === null || api === void 0 || api.core.actions.execute(api === null || api === void 0 || (_api$userIntent2 = api.userIntent) === null || _api$userIntent2 === void 0 ? void 0 : _api$userIntent2.commands.setCurrentUserIntent('blockMenuOpen'));
|
|
97
97
|
}, [api, isMenuOpen, menuTriggerBy, selectedByShortcutOrDragHandle, hasFocus, currentUserIntent, openedViaKeyboard, prevIsMenuOpenRef]);
|
|
98
98
|
});
|
|
99
|
+
var isSelectionWithinCodeBlock = function isSelectionWithinCodeBlock(state) {
|
|
100
|
+
var _state$selection = state.selection,
|
|
101
|
+
$from = _state$selection.$from,
|
|
102
|
+
$to = _state$selection.$to;
|
|
103
|
+
return $from.sameParent($to) && $from.parent.type === state.schema.nodes.codeBlock;
|
|
104
|
+
};
|
|
99
105
|
var BlockMenuContent = function BlockMenuContent(_ref3) {
|
|
100
106
|
var _api$blockMenu;
|
|
101
107
|
var api = _ref3.api,
|
|
@@ -179,7 +185,8 @@ var BlockMenu = function BlockMenu(_ref4) {
|
|
|
179
185
|
var _api$core, _api$blockControls;
|
|
180
186
|
// When the editor view has focus, the keydown will be handled by the
|
|
181
187
|
// selection preservation plugin – exit early to avoid double handling
|
|
182
|
-
if
|
|
188
|
+
// Also exit if selection is within a code block to avoid double handling when code block got focus when the node after it is deleted
|
|
189
|
+
if (!editorView || editorView !== null && editorView !== void 0 && editorView.hasFocus() || isSelectionWithinCodeBlock(editorView.state)) {
|
|
183
190
|
return;
|
|
184
191
|
}
|
|
185
192
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { getSortedSuggestedItems } from '../utils/suggested-items-rank';
|
|
3
|
+
import { createMenuItemsMap } from '../utils/createMenuItemsMap';
|
|
4
|
+
import { getSuggestedItemsFromSelection } from '../utils/getSuggestedItemsFromSelection';
|
|
6
5
|
export var useSuggestedItems = function useSuggestedItems(api) {
|
|
7
6
|
var _api$blockMenu;
|
|
8
7
|
var _useSharedPluginState = useSharedPluginStateWithSelector(api, ['blockControls', 'selection'], function (states) {
|
|
@@ -16,43 +15,11 @@ export var useSuggestedItems = function useSuggestedItems(api) {
|
|
|
16
15
|
selection = _useSharedPluginState.selection;
|
|
17
16
|
var blockMenuComponents = api === null || api === void 0 || (_api$blockMenu = api.blockMenu) === null || _api$blockMenu === void 0 ? void 0 : _api$blockMenu.actions.getBlockMenuComponents();
|
|
18
17
|
var menuItemsMap = useMemo(function () {
|
|
19
|
-
|
|
20
|
-
return new Map();
|
|
21
|
-
}
|
|
22
|
-
return new Map(blockMenuComponents.filter(function (c) {
|
|
23
|
-
return c.type === 'block-menu-item';
|
|
24
|
-
}).map(function (item) {
|
|
25
|
-
return [item.key, item];
|
|
26
|
-
}));
|
|
18
|
+
return createMenuItemsMap(blockMenuComponents);
|
|
27
19
|
}, [blockMenuComponents]);
|
|
28
20
|
var suggestedItems = useMemo(function () {
|
|
29
21
|
var currentSelection = preservedSelection || selection;
|
|
30
|
-
|
|
31
|
-
return [];
|
|
32
|
-
}
|
|
33
|
-
var _expandSelectionToBlo = expandSelectionToBlockRange(currentSelection),
|
|
34
|
-
range = _expandSelectionToBlo.range;
|
|
35
|
-
if (!range) {
|
|
36
|
-
return [];
|
|
37
|
-
}
|
|
38
|
-
var blockNodes = getBlockNodesInRange(range);
|
|
39
|
-
if (blockNodes.length === 0) {
|
|
40
|
-
return [];
|
|
41
|
-
}
|
|
42
|
-
var firstNodeType = blockNodes[0].type.name;
|
|
43
|
-
var allSameType = blockNodes.every(function (node) {
|
|
44
|
-
return node.type.name === firstNodeType;
|
|
45
|
-
});
|
|
46
|
-
if (!allSameType) {
|
|
47
|
-
return [];
|
|
48
|
-
}
|
|
49
|
-
var nodeTypeName = firstNodeType;
|
|
50
|
-
var sortedKeys = getSortedSuggestedItems(nodeTypeName);
|
|
51
|
-
return sortedKeys.map(function (key) {
|
|
52
|
-
return menuItemsMap.get(key);
|
|
53
|
-
}).filter(function (item) {
|
|
54
|
-
return item !== undefined;
|
|
55
|
-
});
|
|
22
|
+
return getSuggestedItemsFromSelection(menuItemsMap, currentSelection);
|
|
56
23
|
}, [menuItemsMap, preservedSelection, selection]);
|
|
57
24
|
return suggestedItems;
|
|
58
25
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper function to create menu items map from block menu components.
|
|
3
|
+
*/
|
|
4
|
+
export var createMenuItemsMap = function createMenuItemsMap(blockMenuComponents) {
|
|
5
|
+
if (!blockMenuComponents) {
|
|
6
|
+
return new Map();
|
|
7
|
+
}
|
|
8
|
+
return new Map(blockMenuComponents.filter(function (c) {
|
|
9
|
+
return c.type === 'block-menu-item';
|
|
10
|
+
}).map(function (item) {
|
|
11
|
+
return [item.key, item];
|
|
12
|
+
}));
|
|
13
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { expandSelectionToBlockRange } from '@atlaskit/editor-common/selection';
|
|
2
|
+
import { getBlockNodesInRange } from '../../editor-commands/transform-node-utils/utils';
|
|
3
|
+
import { getSortedSuggestedItems } from '../utils/suggested-items-rank';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Pure function to calculate suggested items based on selection and menu components.
|
|
7
|
+
*/
|
|
8
|
+
export var getSuggestedItemsFromSelection = function getSuggestedItemsFromSelection(menuItemsMap, currentSelection) {
|
|
9
|
+
if (menuItemsMap.size === 0 || !currentSelection) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
var _expandSelectionToBlo = expandSelectionToBlockRange(currentSelection),
|
|
13
|
+
range = _expandSelectionToBlo.range;
|
|
14
|
+
if (!range) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
var blockNodes = getBlockNodesInRange(range);
|
|
18
|
+
if (blockNodes.length === 0) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
var firstNodeType = blockNodes[0].type.name;
|
|
22
|
+
var allSameType = blockNodes.every(function (node) {
|
|
23
|
+
return node.type.name === firstNodeType;
|
|
24
|
+
});
|
|
25
|
+
if (!allSameType) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
var nodeTypeName = firstNodeType;
|
|
29
|
+
var sortedKeys = getSortedSuggestedItems(nodeTypeName);
|
|
30
|
+
return sortedKeys.map(function (key) {
|
|
31
|
+
return menuItemsMap.get(key);
|
|
32
|
+
}).filter(function (item) {
|
|
33
|
+
return item !== undefined;
|
|
34
|
+
});
|
|
35
|
+
};
|
|
@@ -106,6 +106,7 @@ export type RegisterBlockMenuSection = BlockMenuSection & {
|
|
|
106
106
|
};
|
|
107
107
|
export type RegisterBlockMenuItem = BlockMenuItem & {
|
|
108
108
|
component?: BlockMenuItemComponent;
|
|
109
|
+
isHidden?: () => boolean;
|
|
109
110
|
parent: Parent<BlockMenuSection>;
|
|
110
111
|
};
|
|
111
112
|
export type RegisterBlockMenuComponent = RegisterBlockMenuNested | RegisterBlockMenuSection | RegisterBlockMenuItem;
|
|
@@ -29,15 +29,10 @@ export declare const convertExpandToNestedExpand: (node: PMNode, schema: Schema)
|
|
|
29
29
|
export declare const convertTextNodeToParagraph: (node: PMNode, schema: Schema) => PMNode | null;
|
|
30
30
|
export declare const getBlockNodesInRange: (range: NodeRange) => PMNode[];
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* - strings (text content that can go into codeBlock)
|
|
35
|
-
* - PMNodes (incompatible inline nodes wrapped in paragraphs that need to break out)
|
|
32
|
+
* Iterates over a nodes children and extracting text content, removing all other inline content and converting
|
|
33
|
+
* hardbreaks to newlines.
|
|
36
34
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* @param node - The text node (paragraph or heading) to split
|
|
40
|
-
* @param schema - The schema to use for creating paragraph nodes
|
|
41
|
-
* @returns Array of strings (for codeBlock) and PMNodes (to break out)
|
|
35
|
+
* @param node - The node to create text content from (should be paragraph)
|
|
36
|
+
* @returns The text content string.
|
|
42
37
|
*/
|
|
43
|
-
export declare const
|
|
38
|
+
export declare const createTextContent: (node: PMNode) => string;
|
|
@@ -28,10 +28,9 @@ export declare const buildChildrenMap: (components: RegisterBlockMenuComponent[]
|
|
|
28
28
|
* Determines whether a component will render based on its type and children
|
|
29
29
|
*
|
|
30
30
|
* Rules:
|
|
31
|
-
* - An item will not render if has
|
|
32
|
-
* - A nested menu will render if
|
|
31
|
+
* - An item will not render if it has isHidden that returns true OR if its component returns null (fallback)
|
|
32
|
+
* - A nested menu will render if at least one section, that has at least one registered child
|
|
33
33
|
* - A section will render if it has at least one registered child component that will render
|
|
34
34
|
*
|
|
35
|
-
* NOTE: This requires invoking each item's component function to check for null return
|
|
36
35
|
*/
|
|
37
36
|
export declare const willComponentRender: (registeredComponent: RegisterBlockMenuComponent, childrenMap: ChildrenMap) => boolean;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { WrappedComponentProps } from 'react-intl-next';
|
|
3
3
|
import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
|
|
4
|
-
import {
|
|
4
|
+
import type { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
5
5
|
import type { BlockMenuPlugin } from '../blockMenuPluginType';
|
|
6
6
|
export type BlockMenuProps = {
|
|
7
7
|
api: ExtractInjectionAPI<BlockMenuPlugin> | undefined;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
|
|
2
|
+
import type { BlockMenuPlugin, RegisterBlockMenuItem } from '../../blockMenuPluginType';
|
|
3
|
+
/**
|
|
4
|
+
* Helper function to create menu items map from block menu components.
|
|
5
|
+
*/
|
|
6
|
+
export declare const createMenuItemsMap: (blockMenuComponents: ReturnType<ExtractInjectionAPI<BlockMenuPlugin>["blockMenu"]["actions"]["getBlockMenuComponents"]> | undefined) => Map<string, RegisterBlockMenuItem>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Selection } from '@atlaskit/editor-prosemirror/state';
|
|
2
|
+
import type { RegisterBlockMenuItem } from '../../blockMenuPluginType';
|
|
3
|
+
/**
|
|
4
|
+
* Pure function to calculate suggested items based on selection and menu components.
|
|
5
|
+
*/
|
|
6
|
+
export declare const getSuggestedItemsFromSelection: (menuItemsMap: Map<string, RegisterBlockMenuItem>, currentSelection: Selection | null | undefined) => RegisterBlockMenuItem[];
|
|
@@ -106,6 +106,7 @@ export type RegisterBlockMenuSection = BlockMenuSection & {
|
|
|
106
106
|
};
|
|
107
107
|
export type RegisterBlockMenuItem = BlockMenuItem & {
|
|
108
108
|
component?: BlockMenuItemComponent;
|
|
109
|
+
isHidden?: () => boolean;
|
|
109
110
|
parent: Parent<BlockMenuSection>;
|
|
110
111
|
};
|
|
111
112
|
export type RegisterBlockMenuComponent = RegisterBlockMenuNested | RegisterBlockMenuSection | RegisterBlockMenuItem;
|
|
@@ -29,15 +29,10 @@ export declare const convertExpandToNestedExpand: (node: PMNode, schema: Schema)
|
|
|
29
29
|
export declare const convertTextNodeToParagraph: (node: PMNode, schema: Schema) => PMNode | null;
|
|
30
30
|
export declare const getBlockNodesInRange: (range: NodeRange) => PMNode[];
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* - strings (text content that can go into codeBlock)
|
|
35
|
-
* - PMNodes (incompatible inline nodes wrapped in paragraphs that need to break out)
|
|
32
|
+
* Iterates over a nodes children and extracting text content, removing all other inline content and converting
|
|
33
|
+
* hardbreaks to newlines.
|
|
36
34
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* @param node - The text node (paragraph or heading) to split
|
|
40
|
-
* @param schema - The schema to use for creating paragraph nodes
|
|
41
|
-
* @returns Array of strings (for codeBlock) and PMNodes (to break out)
|
|
35
|
+
* @param node - The node to create text content from (should be paragraph)
|
|
36
|
+
* @returns The text content string.
|
|
42
37
|
*/
|
|
43
|
-
export declare const
|
|
38
|
+
export declare const createTextContent: (node: PMNode) => string;
|
|
@@ -28,10 +28,9 @@ export declare const buildChildrenMap: (components: RegisterBlockMenuComponent[]
|
|
|
28
28
|
* Determines whether a component will render based on its type and children
|
|
29
29
|
*
|
|
30
30
|
* Rules:
|
|
31
|
-
* - An item will not render if has
|
|
32
|
-
* - A nested menu will render if
|
|
31
|
+
* - An item will not render if it has isHidden that returns true OR if its component returns null (fallback)
|
|
32
|
+
* - A nested menu will render if at least one section, that has at least one registered child
|
|
33
33
|
* - A section will render if it has at least one registered child component that will render
|
|
34
34
|
*
|
|
35
|
-
* NOTE: This requires invoking each item's component function to check for null return
|
|
36
35
|
*/
|
|
37
36
|
export declare const willComponentRender: (registeredComponent: RegisterBlockMenuComponent, childrenMap: ChildrenMap) => boolean;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { WrappedComponentProps } from 'react-intl-next';
|
|
3
3
|
import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
|
|
4
|
-
import {
|
|
4
|
+
import type { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
5
5
|
import type { BlockMenuPlugin } from '../blockMenuPluginType';
|
|
6
6
|
export type BlockMenuProps = {
|
|
7
7
|
api: ExtractInjectionAPI<BlockMenuPlugin> | undefined;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
|
|
2
|
+
import type { BlockMenuPlugin, RegisterBlockMenuItem } from '../../blockMenuPluginType';
|
|
3
|
+
/**
|
|
4
|
+
* Helper function to create menu items map from block menu components.
|
|
5
|
+
*/
|
|
6
|
+
export declare const createMenuItemsMap: (blockMenuComponents: ReturnType<ExtractInjectionAPI<BlockMenuPlugin>["blockMenu"]["actions"]["getBlockMenuComponents"]> | undefined) => Map<string, RegisterBlockMenuItem>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Selection } from '@atlaskit/editor-prosemirror/state';
|
|
2
|
+
import type { RegisterBlockMenuItem } from '../../blockMenuPluginType';
|
|
3
|
+
/**
|
|
4
|
+
* Pure function to calculate suggested items based on selection and menu components.
|
|
5
|
+
*/
|
|
6
|
+
export declare const getSuggestedItemsFromSelection: (menuItemsMap: Map<string, RegisterBlockMenuItem>, currentSelection: Selection | null | undefined) => RegisterBlockMenuItem[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-block-menu",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.13",
|
|
4
4
|
"description": "BlockMenu plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -38,18 +38,18 @@
|
|
|
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",
|
|
41
|
-
"@atlaskit/editor-toolbar": "^0.
|
|
42
|
-
"@atlaskit/flag": "^17.
|
|
41
|
+
"@atlaskit/editor-toolbar": "^0.19.0",
|
|
42
|
+
"@atlaskit/flag": "^17.8.0",
|
|
43
43
|
"@atlaskit/icon": "^29.4.0",
|
|
44
44
|
"@atlaskit/platform-feature-flags": "^1.1.0",
|
|
45
45
|
"@atlaskit/platform-feature-flags-react": "^0.4.0",
|
|
46
46
|
"@atlaskit/primitives": "^17.0.0",
|
|
47
|
-
"@atlaskit/tmp-editor-statsig": "^16.
|
|
47
|
+
"@atlaskit/tmp-editor-statsig": "^16.12.0",
|
|
48
48
|
"@atlaskit/tokens": "^9.1.0",
|
|
49
49
|
"@babel/runtime": "^7.0.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@atlaskit/editor-common": "^111.
|
|
52
|
+
"@atlaskit/editor-common": "^111.7.0",
|
|
53
53
|
"react": "^18.2.0",
|
|
54
54
|
"react-intl-next": "npm:react-intl@^5.18.1"
|
|
55
55
|
},
|