@atlaskit/editor-plugin-block-menu 5.1.6 → 5.1.8

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 (41) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/cjs/editor-commands/transform-node-utils/flattenListStep.js +9 -32
  3. package/dist/cjs/editor-commands/transform-node-utils/transform.js +5 -4
  4. package/dist/cjs/editor-commands/transform-node-utils/unwrapListStep.js +16 -1
  5. package/dist/cjs/editor-commands/transform-node-utils/utils.js +30 -1
  6. package/dist/cjs/editor-commands/transform-node-utils/wrapMixedContentStep.js +141 -0
  7. package/dist/cjs/editor-commands/transformNode.js +6 -6
  8. package/dist/cjs/ui/block-menu-components.js +29 -24
  9. package/dist/cjs/ui/suggested-items-renderer.js +62 -0
  10. package/dist/cjs/ui/utils/suggested-items-rank.js +66 -0
  11. package/dist/es2019/editor-commands/transform-node-utils/flattenListStep.js +9 -32
  12. package/dist/es2019/editor-commands/transform-node-utils/transform.js +5 -4
  13. package/dist/es2019/editor-commands/transform-node-utils/unwrapListStep.js +16 -1
  14. package/dist/es2019/editor-commands/transform-node-utils/utils.js +29 -1
  15. package/dist/es2019/editor-commands/transform-node-utils/wrapMixedContentStep.js +135 -0
  16. package/dist/es2019/editor-commands/transformNode.js +2 -2
  17. package/dist/es2019/ui/block-menu-components.js +12 -9
  18. package/dist/es2019/ui/suggested-items-renderer.js +48 -0
  19. package/dist/es2019/ui/utils/suggested-items-rank.js +130 -0
  20. package/dist/esm/editor-commands/transform-node-utils/flattenListStep.js +9 -32
  21. package/dist/esm/editor-commands/transform-node-utils/transform.js +5 -4
  22. package/dist/esm/editor-commands/transform-node-utils/unwrapListStep.js +16 -1
  23. package/dist/esm/editor-commands/transform-node-utils/utils.js +30 -1
  24. package/dist/esm/editor-commands/transform-node-utils/wrapMixedContentStep.js +135 -0
  25. package/dist/esm/editor-commands/transformNode.js +4 -4
  26. package/dist/esm/ui/block-menu-components.js +30 -25
  27. package/dist/esm/ui/suggested-items-renderer.js +54 -0
  28. package/dist/esm/ui/utils/suggested-items-rank.js +60 -0
  29. package/dist/types/editor-commands/transform-node-utils/flattenListStep.d.ts +0 -18
  30. package/dist/types/editor-commands/transform-node-utils/unwrapListStep.d.ts +16 -1
  31. package/dist/types/editor-commands/transform-node-utils/utils.d.ts +12 -0
  32. package/dist/types/editor-commands/transform-node-utils/wrapMixedContentStep.d.ts +20 -0
  33. package/dist/types/ui/suggested-items-renderer.d.ts +8 -0
  34. package/dist/types/ui/utils/suggested-items-rank.d.ts +45 -0
  35. package/dist/types-ts4.5/editor-commands/transform-node-utils/flattenListStep.d.ts +0 -18
  36. package/dist/types-ts4.5/editor-commands/transform-node-utils/unwrapListStep.d.ts +16 -1
  37. package/dist/types-ts4.5/editor-commands/transform-node-utils/utils.d.ts +12 -0
  38. package/dist/types-ts4.5/editor-commands/transform-node-utils/wrapMixedContentStep.d.ts +20 -0
  39. package/dist/types-ts4.5/ui/suggested-items-renderer.d.ts +8 -0
  40. package/dist/types-ts4.5/ui/utils/suggested-items-rank.d.ts +45 -0
  41. package/package.json +4 -4
@@ -1,33 +1,28 @@
1
1
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
2
- const extractNestedLists = (node, listTypes, itemTypes) => {
2
+ const extractNestedLists = (node, listTypes, itemTypes, schema) => {
3
3
  const items = [];
4
+ const paragraph = schema.nodes.paragraph;
4
5
  const extract = currentNode => {
5
6
  currentNode.forEach(child => {
6
- // list item -> take content without nested lists, then recurse into nested lists
7
7
  if (itemTypes.some(type => child.type === type)) {
8
- // Filter out nested list nodes from the list item's content
9
8
  const contentWithoutNestedLists = [];
10
9
  const nestedLists = [];
11
10
  child.forEach(grandChild => {
12
11
  if (listTypes.some(type => grandChild.type === type)) {
13
- // This is a nested list - collect it for later processing
14
12
  nestedLists.push(grandChild);
15
13
  } else {
16
- // This is regular content (paragraph, etc.) - keep it
17
- contentWithoutNestedLists.push(grandChild);
14
+ if (grandChild.isText) {
15
+ contentWithoutNestedLists.push(paragraph.createAndFill({}, grandChild));
16
+ } else {
17
+ contentWithoutNestedLists.push(grandChild);
18
+ }
18
19
  }
19
20
  });
20
-
21
- // Add the list item with only its non-list content
22
21
  items.push(child.copy(Fragment.from(contentWithoutNestedLists)));
23
-
24
- // Now process nested lists to maintain document order
25
22
  nestedLists.forEach(nestedList => {
26
23
  extract(nestedList);
27
24
  });
28
- }
29
- // lists -> keep operating
30
- else if (listTypes.some(type => child.type === type)) {
25
+ } else if (listTypes.some(type => child.type === type)) {
31
26
  extract(child);
32
27
  }
33
28
  });
@@ -41,24 +36,6 @@ const extractNestedLists = (node, listTypes, itemTypes) => {
41
36
  * to it's first ancestor list, maintaining document order.
42
37
  *
43
38
  * @example
44
- * Input:
45
- * - bulletList
46
- * - listItem "A"
47
- * - listItem "B"
48
- * - bulletList
49
- * - listItem "C"
50
- * - listItem "D"
51
- * - listItem "E"
52
- *
53
- * Output:
54
- * - bulletList
55
- * - listItem "A"
56
- * - listItem "B"
57
- * - listItem "C"
58
- * - listItem "D"
59
- * - listItem "E"
60
- *
61
- * @example
62
39
  * Input (deeply nested):
63
40
  * - bulletList
64
41
  * - listItem "1"
@@ -87,7 +64,7 @@ export const flattenListStep = (nodes, context) => {
87
64
  const listTypes = [context.schema.nodes.bulletList, context.schema.nodes.orderedList, context.schema.nodes.taskList];
88
65
  return nodes.map(node => {
89
66
  if (listTypes.some(type => node.type === type)) {
90
- return node.copy(Fragment.from(extractNestedLists(node, listTypes, [context.schema.nodes.listItem, context.schema.nodes.taskItem])));
67
+ return node.copy(Fragment.from(extractNestedLists(node, listTypes, [context.schema.nodes.listItem, context.schema.nodes.taskItem], context.schema)));
91
68
  }
92
69
  return node;
93
70
  });
@@ -8,6 +8,7 @@ import { unwrapExpandStep } from './unwrapExpandStep';
8
8
  import { unwrapListStep } from './unwrapListStep';
9
9
  import { unwrapStep } from './unwrapStep';
10
10
  import { wrapIntoLayoutStep } from './wrapIntoLayoutStep';
11
+ import { wrapMixedContentStep } from './wrapMixedContentStep';
11
12
  import { wrapStep } from './wrapStep';
12
13
 
13
14
  // Exampled step for overrides:
@@ -57,15 +58,15 @@ const TRANSFORM_STEPS_OVERRIDE = {
57
58
  codeBlock: [unwrapStep, flattenStep, wrapStep]
58
59
  },
59
60
  expand: {
60
- panel: [unwrapExpandStep, wrapStep],
61
- blockquote: [unwrapExpandStep, wrapStep],
61
+ panel: [unwrapExpandStep, wrapMixedContentStep],
62
+ blockquote: [unwrapExpandStep, wrapMixedContentStep],
62
63
  layoutSection: [unwrapExpandStep, wrapIntoLayoutStep],
63
64
  paragraph: [unwrapExpandStep],
64
65
  codeBlock: [unwrapExpandStep, flattenStep, wrapStep]
65
66
  },
66
67
  nestedExpand: {
67
- panel: [unwrapExpandStep, wrapStep],
68
- blockquote: [unwrapExpandStep, wrapStep],
68
+ panel: [unwrapExpandStep, wrapMixedContentStep],
69
+ blockquote: [unwrapExpandStep, wrapMixedContentStep],
69
70
  paragraph: [unwrapExpandStep],
70
71
  codeBlock: [unwrapExpandStep, flattenStep, wrapStep]
71
72
  },
@@ -1,6 +1,21 @@
1
1
  /**
2
- * Given an array of nodes, returns an array with the flattened children of any list nodes.
2
+ * Given an array of nodes, processes each list removing all parent list nodes and
3
+ * just returning their child contents.
4
+ *
5
+ * @example
6
+ * Input:
7
+ * - bulletList
8
+ * - listItem "1"
9
+ * - paragraph "1"
10
+ * - listItem "2"
11
+ * - paragraph "2"
12
+ *
13
+ * Output:
14
+ * - paragraph "1"
15
+ * - paragraph "2"
16
+ *
3
17
  * @param nodes
18
+ * @param context
4
19
  * @returns
5
20
  */
6
21
  export const unwrapListStep = (nodes, context) => {
@@ -1,5 +1,6 @@
1
+ import { expandToBlockRange } from '@atlaskit/editor-common/selection';
1
2
  import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
2
- import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
3
+ import { findParentNodeOfType, hasParentNode } from '@atlaskit/editor-prosemirror/utils';
3
4
  import { CellSelection } from '@atlaskit/editor-tables';
4
5
  export const getSelectedNode = selection => {
5
6
  if (selection instanceof NodeSelection) {
@@ -49,4 +50,31 @@ export const getTargetNodeTypeNameInContext = (nodeTypeName, isNested) => {
49
50
  return 'nestedExpand';
50
51
  }
51
52
  return nodeTypeName;
53
+ };
54
+
55
+ /**
56
+ * Use common expandToBlockRange function, but account for edge cases with lists.
57
+ *
58
+ * @param selection
59
+ * @param schema
60
+ * @returns
61
+ */
62
+ export const expandSelectionToBlockRange = (selection, schema) => {
63
+ const isListInSelection = hasParentNode(node => node.type === schema.nodes.bulletList || node.type === schema.nodes.orderedList)(selection);
64
+ const {
65
+ $from,
66
+ $to
67
+ } = expandToBlockRange(selection.$from, selection.$to, node => {
68
+ if (!isListInSelection) {
69
+ return true;
70
+ }
71
+ if (node.type === schema.nodes.bulletList || node.type === schema.nodes.orderedList) {
72
+ return true;
73
+ }
74
+ return false;
75
+ });
76
+ return {
77
+ $from,
78
+ $to
79
+ };
52
80
  };
@@ -0,0 +1,135 @@
1
+ import { Fragment } from '@atlaskit/editor-prosemirror/model';
2
+ import { NODE_CATEGORY_BY_TYPE } from './types';
3
+ import { unwrapStep } from './unwrapStep';
4
+
5
+ /**
6
+ * Determines if a node can be flattened (unwrapped and its contents merged).
7
+ *
8
+ * According to the text transformations list, flattenable nodes are:
9
+ * - Bulleted list, Numbered list, Task list
10
+ * - Text nodes (heading, paragraph)
11
+ *
12
+ * Containers (panels, expands, layouts, blockquotes) and atomic nodes (tables, media, macros) break out.
13
+ */
14
+ const canFlatten = node => {
15
+ const category = NODE_CATEGORY_BY_TYPE[node.type.name];
16
+ // Text and list nodes can be flattened (converted to simpler forms)
17
+ return category === 'text' || category === 'list';
18
+ };
19
+
20
+ /**
21
+ * Flattens a node by extracting its contents using the appropriate unwrap step.
22
+ * This is only called for text and list nodes that can be converted to simpler forms.
23
+ * Uses unwrapStep to extract children from list containers.
24
+ */
25
+ const flattenNode = (node, context) => {
26
+ return unwrapStep([node], context);
27
+ };
28
+
29
+ /**
30
+ * Determines if a node can be wrapped in the target container type.
31
+ * Uses the schema's validContent to check if the target container can hold this node.
32
+ *
33
+ * Note: What can be wrapped depends on the target container type - for example:
34
+ * - Tables and media CAN go inside expand nodes
35
+ * - Tables CANNOT go inside panels or blockquotes
36
+ */
37
+ const canWrapInTarget = (node, targetNodeType, targetNodeTypeName) => {
38
+ // Same-type containers should break out as separate containers
39
+ if (node.type.name === targetNodeTypeName) {
40
+ return false;
41
+ }
42
+
43
+ // Use the schema to determine if this node can be contained in the target
44
+ try {
45
+ return targetNodeType.validContent(Fragment.from(node));
46
+ } catch {
47
+ return false;
48
+ }
49
+ };
50
+
51
+ /**
52
+ * Converts a nestedExpand to a regular expand node.
53
+ * NestedExpands can only exist inside expands, so when breaking out they must be converted.
54
+ */
55
+ const convertNestedExpandToExpand = (node, schema) => {
56
+ var _node$attrs;
57
+ const expandType = schema.nodes.expand;
58
+ if (!expandType) {
59
+ return null;
60
+ }
61
+ return expandType.createAndFill({
62
+ title: ((_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.title) || ''
63
+ }, node.content);
64
+ };
65
+
66
+ /**
67
+ * A wrap step that handles mixed content according to the Compatibility Matrix:
68
+ * - Wraps consecutive compatible nodes into the target container
69
+ * - Same-type containers break out as separate containers (preserved as-is)
70
+ * - NestedExpands break out as regular expands (converted since nestedExpand can't exist outside expand)
71
+ * - Container structures that can't be nested in target break out (not flattened)
72
+ * - Text/list nodes that can't be wrapped are flattened and merged into the container
73
+ * - Atomic nodes (tables, media, macros) break out
74
+ *
75
+ * What can be wrapped depends on the target container's schema:
76
+ * - expand → panel: tables break out, nestedExpands convert to expands and break out
77
+ * - expand → blockquote: tables/media break out, nestedExpands convert to expands and break out
78
+ * - expand → expand: tables/media stay inside (expands can contain them)
79
+ *
80
+ * Example: expand(p('a'), table(), p('b')) → panel: [panel(p('a')), table(), panel(p('b'))]
81
+ * Example: expand(p('a'), panel(p('x')), p('b')) → panel: [panel(p('a')), panel(p('x')), panel(p('b'))]
82
+ * Example: expand(p('a'), nestedExpand({title: 'inner'})(p('x')), p('b')) → panel: [panel(p('a')), expand({title: 'inner'})(p('x')), panel(p('b'))]
83
+ */
84
+ export const wrapMixedContentStep = (nodes, context) => {
85
+ const {
86
+ schema,
87
+ targetNodeTypeName
88
+ } = context;
89
+ const targetNodeType = schema.nodes[targetNodeTypeName];
90
+ if (!targetNodeType) {
91
+ return nodes;
92
+ }
93
+ const result = [];
94
+ let currentContainerContent = [];
95
+ const flushCurrentContainer = () => {
96
+ if (currentContainerContent.length > 0) {
97
+ const containerNode = targetNodeType.createAndFill({}, Fragment.fromArray(currentContainerContent));
98
+ if (containerNode) {
99
+ result.push(containerNode);
100
+ }
101
+ currentContainerContent = [];
102
+ }
103
+ };
104
+ nodes.forEach(node => {
105
+ if (canWrapInTarget(node, targetNodeType, targetNodeTypeName)) {
106
+ // Node can be wrapped - add to current container content
107
+ currentContainerContent.push(node);
108
+ } else if (node.type.name === targetNodeTypeName) {
109
+ // Same-type container - breaks out as a separate container (preserved as-is)
110
+ // This handles: "If there's a panel in the expand, it breaks out into a separate panel"
111
+ flushCurrentContainer();
112
+ result.push(node);
113
+ } else if (node.type.name === 'nestedExpand') {
114
+ // NestedExpand can't be wrapped and can't exist outside an expand
115
+ // Convert to regular expand and break out
116
+ flushCurrentContainer();
117
+ const expandNode = convertNestedExpandToExpand(node, schema);
118
+ if (expandNode) {
119
+ result.push(expandNode);
120
+ }
121
+ } else if (canFlatten(node)) {
122
+ // Node cannot be wrapped but CAN be flattened - flatten and add to container
123
+ const flattenedNodes = flattenNode(node, context);
124
+ currentContainerContent.push(...flattenedNodes);
125
+ } else {
126
+ // Node cannot be wrapped AND cannot be flattened (containers, tables, media, macros) - break out
127
+ flushCurrentContainer();
128
+ result.push(node);
129
+ }
130
+ });
131
+
132
+ // Flush any remaining content into a container
133
+ flushCurrentContainer();
134
+ return result.length > 0 ? result : nodes;
135
+ };
@@ -1,7 +1,7 @@
1
- import { expandToBlockRange } from '@atlaskit/editor-common/selection';
2
1
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
3
2
  import { isNestedNode } from '../ui/utils/isNestedNode';
4
3
  import { getOutputNodes } from './transform-node-utils/transform';
4
+ import { expandSelectionToBlockRange } from './transform-node-utils/utils';
5
5
  import { isListNode } from './transforms/utils';
6
6
  export const transformNode = api =>
7
7
  // eslint-disable-next-line no-unused-vars
@@ -17,7 +17,7 @@ export const transformNode = api =>
17
17
  const {
18
18
  $from,
19
19
  $to
20
- } = expandToBlockRange(preservedSelection.$from, preservedSelection.$to);
20
+ } = expandSelectionToBlockRange(preservedSelection, tr.doc.type.schema);
21
21
  const isNested = isNestedNode(preservedSelection, '');
22
22
  const selectedParent = $from.parent;
23
23
  let fragment = Fragment.empty;
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { BLOCK_ACTIONS_COPY_LINK_TO_BLOCK_MENU_ITEM, BLOCK_ACTIONS_MENU_SECTION, BLOCK_ACTIONS_MENU_SECTION_RANK, DELETE_MENU_SECTION, DELETE_MENU_SECTION_RANK, DELETE_MENU_ITEM, POSITION_MENU_SECTION, POSITION_MENU_SECTION_RANK, POSITION_MOVE_DOWN_MENU_ITEM, POSITION_MOVE_UP_MENU_ITEM, TRANSFORM_MENU_ITEM, TRANSFORM_MENU_ITEM_RANK, TRANSFORM_MENU_SECTION, TRANSFORM_MENU_SECTION_RANK, TRANSFORM_CREATE_MENU_SECTION, TRANSFORM_SUGGESTED_MENU_SECTION, TRANSFORM_STRUCTURE_MENU_SECTION, TRANSFORM_HEADINGS_MENU_SECTION, MAIN_BLOCK_MENU_SECTION_RANK } from '@atlaskit/editor-common/block-menu';
2
+ import { BLOCK_ACTIONS_COPY_LINK_TO_BLOCK_MENU_ITEM, BLOCK_ACTIONS_MENU_SECTION, BLOCK_ACTIONS_MENU_SECTION_RANK, DELETE_MENU_SECTION, DELETE_MENU_SECTION_RANK, DELETE_MENU_ITEM, POSITION_MENU_SECTION, POSITION_MENU_SECTION_RANK, POSITION_MOVE_DOWN_MENU_ITEM, POSITION_MOVE_UP_MENU_ITEM, TRANSFORM_MENU_ITEM, TRANSFORM_MENU_ITEM_RANK, TRANSFORM_MENU_SECTION, TRANSFORM_MENU_SECTION_RANK, TRANSFORM_CREATE_MENU_SECTION, TRANSFORM_SUGGESTED_MENU_SECTION, TRANSFORM_STRUCTURE_MENU_SECTION, TRANSFORM_HEADINGS_MENU_SECTION, MAIN_BLOCK_MENU_SECTION_RANK, TRANSFORM_SUGGESTED_MENU_SECTION_RANK, TRANSFORM_SUGGESTED_MENU_ITEM } from '@atlaskit/editor-common/block-menu';
3
3
  import { ToolbarDropdownItemSection } from '@atlaskit/editor-toolbar';
4
4
  import { CopyLinkDropdownItem } from './copy-link';
5
5
  import { CopySection } from './copy-section';
@@ -9,6 +9,7 @@ import { FormatMenuComponent } from './format-menu-nested';
9
9
  import { FormatMenuSection } from './format-menu-section';
10
10
  import { MoveDownDropdownItem } from './move-down';
11
11
  import { MoveUpDropdownItem } from './move-up';
12
+ import { SuggestedItemsRenderer } from './suggested-items-renderer';
12
13
  const getMoveUpMoveDownMenuComponents = api => {
13
14
  return [{
14
15
  type: 'block-menu-item',
@@ -60,14 +61,16 @@ const getTurnIntoMenuComponents = api => {
60
61
  key: TRANSFORM_MENU_ITEM.key,
61
62
  rank: TRANSFORM_MENU_ITEM_RANK[TRANSFORM_SUGGESTED_MENU_SECTION.key]
62
63
  },
63
- component: ({
64
- children
65
- } = {
66
- children: null
67
- }) => {
68
- return /*#__PURE__*/React.createElement(ToolbarDropdownItemSection, {
69
- title: "Suggested"
70
- }, children);
64
+ component: () => /*#__PURE__*/React.createElement(SuggestedItemsRenderer, {
65
+ api: api
66
+ })
67
+ }, {
68
+ type: 'block-menu-item',
69
+ key: TRANSFORM_SUGGESTED_MENU_ITEM.key,
70
+ parent: {
71
+ type: 'block-menu-section',
72
+ key: TRANSFORM_SUGGESTED_MENU_SECTION.key,
73
+ rank: TRANSFORM_SUGGESTED_MENU_SECTION_RANK[TRANSFORM_SUGGESTED_MENU_ITEM.key]
71
74
  }
72
75
  }, {
73
76
  type: 'block-menu-section',
@@ -0,0 +1,48 @@
1
+ import React, { useMemo } from 'react';
2
+ import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
3
+ import { ToolbarDropdownItemSection } from '@atlaskit/editor-toolbar';
4
+ import { getSelectedNode } from '../editor-commands/transform-node-utils/utils';
5
+ import { getSortedSuggestedItems } from './utils/suggested-items-rank';
6
+ export const SuggestedItemsRenderer = /*#__PURE__*/React.memo(({
7
+ api
8
+ }) => {
9
+ var _api$blockMenu;
10
+ const {
11
+ preservedSelection
12
+ } = useSharedPluginStateWithSelector(api, ['blockControls'], states => {
13
+ var _states$blockControls;
14
+ return {
15
+ preservedSelection: (_states$blockControls = states.blockControlsState) === null || _states$blockControls === void 0 ? void 0 : _states$blockControls.preservedSelection
16
+ };
17
+ });
18
+ 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
+ const menuItemsMap = useMemo(() => {
20
+ if (!blockMenuComponents) {
21
+ return new Map();
22
+ }
23
+ return new Map(blockMenuComponents.filter(c => c.type === 'block-menu-item').map(item => [item.key, item]));
24
+ }, [blockMenuComponents]);
25
+ const suggestedItems = useMemo(() => {
26
+ if (!preservedSelection || menuItemsMap.size === 0) {
27
+ return [];
28
+ }
29
+ const selectedNode = getSelectedNode(preservedSelection);
30
+ if (!selectedNode) {
31
+ return [];
32
+ }
33
+ const nodeTypeName = selectedNode.node.type.name;
34
+ const sortedKeys = getSortedSuggestedItems(nodeTypeName);
35
+ return sortedKeys.map(key => menuItemsMap.get(key)).filter(item => item !== undefined);
36
+ }, [menuItemsMap, preservedSelection]);
37
+ if (suggestedItems.length === 0) {
38
+ return null;
39
+ }
40
+ return /*#__PURE__*/React.createElement(ToolbarDropdownItemSection, {
41
+ title: "Suggested"
42
+ }, suggestedItems.map(item => {
43
+ const ItemComponent = item.component;
44
+ return ItemComponent ? /*#__PURE__*/React.createElement(ItemComponent, {
45
+ key: item.key
46
+ }) : null;
47
+ }));
48
+ });
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Suggested transformations mapping for each block type.
3
+ * Based on the Block Menu Compatibility Matrix:
4
+ * https://hello.atlassian.net/wiki/spaces/egcuc/pages/5868774224/Block+Menu+Compatibility+Matrix#Suggested-for-each-block-type
5
+ *
6
+ * This mapping defines which transform items should appear in the TRANSFORM_SUGGESTED_MENU_SECTION
7
+ * for each block type, ranked by priority (lower rank = higher priority).
8
+ *
9
+ * Structure:
10
+ * {
11
+ * [sourceNodeType]: {
12
+ * [targetMenuItemKey]: rank
13
+ * }
14
+ * }
15
+ */
16
+
17
+ import { TRANSFORM_STRUCTURE_PANEL_MENU_ITEM, TRANSFORM_STRUCTURE_EXPAND_MENU_ITEM, TRANSFORM_STRUCTURE_LAYOUT_MENU_ITEM, TRANSFORM_STRUCTURE_QUOTE_MENU_ITEM, TRANSFORM_STRUCTURE_CODE_BLOCK_MENU_ITEM, TRANSFORM_STRUCTURE_BULLETED_LIST_MENU_ITEM, TRANSFORM_STRUCTURE_NUMBERED_LIST_MENU_ITEM, TRANSFORM_STRUCTURE_TASK_LIST_MENU_ITEM, TRANSFORM_HEADINGS_H2_MENU_ITEM, TRANSFORM_HEADINGS_H3_MENU_ITEM, TRANSFORM_STRUCTURE_PARAGRAPH_MENU_ITEM } from '@atlaskit/editor-common/block-menu';
18
+ export const BLOCK_MENU_NODE_TYPES = {
19
+ PARAGRAPH: 'paragraph',
20
+ EXPAND: 'expand',
21
+ BLOCKQUOTE: 'blockquote',
22
+ LAYOUT_SECTION: 'layoutSection',
23
+ PANEL: 'panel',
24
+ CODE_BLOCK: 'codeBlock',
25
+ DECISION: 'decisionList',
26
+ BULLET_LIST: 'bulletList',
27
+ ORDERED_LIST: 'orderedList',
28
+ HEADING: 'heading',
29
+ TASK_LIST: 'taskList',
30
+ MEDIA_SINGLE: 'mediaSingle',
31
+ EXTENSION: 'extension',
32
+ BODIED_EXTENSION: 'bodiedExtension',
33
+ BLOCK_CARD: 'blockCard',
34
+ EMBED_CARD: 'embedCard',
35
+ TABLE: 'table'
36
+ };
37
+ export const TRANSFORM_SUGGESTED_ITEMS_RANK = {
38
+ [BLOCK_MENU_NODE_TYPES.PARAGRAPH]: {
39
+ [TRANSFORM_STRUCTURE_PANEL_MENU_ITEM.key]: 100,
40
+ [TRANSFORM_HEADINGS_H2_MENU_ITEM.key]: 200,
41
+ [TRANSFORM_HEADINGS_H3_MENU_ITEM.key]: 300
42
+ },
43
+ [BLOCK_MENU_NODE_TYPES.EXPAND]: {
44
+ [TRANSFORM_STRUCTURE_PANEL_MENU_ITEM.key]: 100,
45
+ [TRANSFORM_STRUCTURE_LAYOUT_MENU_ITEM.key]: 200,
46
+ [TRANSFORM_STRUCTURE_PARAGRAPH_MENU_ITEM.key]: 300
47
+ },
48
+ [BLOCK_MENU_NODE_TYPES.BLOCKQUOTE]: {
49
+ [TRANSFORM_STRUCTURE_PANEL_MENU_ITEM.key]: 100,
50
+ [TRANSFORM_STRUCTURE_PARAGRAPH_MENU_ITEM.key]: 200,
51
+ [TRANSFORM_STRUCTURE_LAYOUT_MENU_ITEM.key]: 300
52
+ },
53
+ [BLOCK_MENU_NODE_TYPES.LAYOUT_SECTION]: {
54
+ [TRANSFORM_STRUCTURE_PANEL_MENU_ITEM.key]: 100,
55
+ [TRANSFORM_STRUCTURE_EXPAND_MENU_ITEM.key]: 200,
56
+ [TRANSFORM_HEADINGS_H2_MENU_ITEM.key]: 300
57
+ },
58
+ [BLOCK_MENU_NODE_TYPES.PANEL]: {
59
+ [TRANSFORM_STRUCTURE_PARAGRAPH_MENU_ITEM.key]: 100,
60
+ [TRANSFORM_STRUCTURE_QUOTE_MENU_ITEM.key]: 200,
61
+ [TRANSFORM_STRUCTURE_CODE_BLOCK_MENU_ITEM.key]: 300
62
+ },
63
+ [BLOCK_MENU_NODE_TYPES.CODE_BLOCK]: {
64
+ [TRANSFORM_STRUCTURE_EXPAND_MENU_ITEM.key]: 100,
65
+ [TRANSFORM_STRUCTURE_LAYOUT_MENU_ITEM.key]: 200,
66
+ [TRANSFORM_STRUCTURE_PANEL_MENU_ITEM.key]: 300
67
+ },
68
+ [BLOCK_MENU_NODE_TYPES.DECISION]: {
69
+ [TRANSFORM_STRUCTURE_PANEL_MENU_ITEM.key]: 100,
70
+ [TRANSFORM_STRUCTURE_PARAGRAPH_MENU_ITEM.key]: 200,
71
+ [TRANSFORM_STRUCTURE_TASK_LIST_MENU_ITEM.key]: 300
72
+ },
73
+ [BLOCK_MENU_NODE_TYPES.BULLET_LIST]: {
74
+ [TRANSFORM_STRUCTURE_NUMBERED_LIST_MENU_ITEM.key]: 100,
75
+ [TRANSFORM_STRUCTURE_QUOTE_MENU_ITEM.key]: 200,
76
+ [TRANSFORM_STRUCTURE_PARAGRAPH_MENU_ITEM.key]: 300
77
+ },
78
+ [BLOCK_MENU_NODE_TYPES.ORDERED_LIST]: {
79
+ [TRANSFORM_STRUCTURE_BULLETED_LIST_MENU_ITEM.key]: 100,
80
+ [TRANSFORM_STRUCTURE_TASK_LIST_MENU_ITEM.key]: 200,
81
+ [TRANSFORM_STRUCTURE_PARAGRAPH_MENU_ITEM.key]: 300
82
+ },
83
+ [BLOCK_MENU_NODE_TYPES.HEADING]: {
84
+ [TRANSFORM_STRUCTURE_PARAGRAPH_MENU_ITEM.key]: 100,
85
+ [TRANSFORM_HEADINGS_H2_MENU_ITEM.key]: 200,
86
+ [TRANSFORM_STRUCTURE_PANEL_MENU_ITEM.key]: 300
87
+ },
88
+ [BLOCK_MENU_NODE_TYPES.TASK_LIST]: {
89
+ [TRANSFORM_STRUCTURE_PARAGRAPH_MENU_ITEM.key]: 100,
90
+ [TRANSFORM_STRUCTURE_NUMBERED_LIST_MENU_ITEM.key]: 200,
91
+ [TRANSFORM_STRUCTURE_CODE_BLOCK_MENU_ITEM.key]: 300
92
+ },
93
+ [BLOCK_MENU_NODE_TYPES.MEDIA_SINGLE]: {
94
+ [TRANSFORM_STRUCTURE_PANEL_MENU_ITEM.key]: 100,
95
+ [TRANSFORM_STRUCTURE_LAYOUT_MENU_ITEM.key]: 200,
96
+ [TRANSFORM_STRUCTURE_PARAGRAPH_MENU_ITEM.key]: 300
97
+ },
98
+ [BLOCK_MENU_NODE_TYPES.EXTENSION]: {
99
+ [TRANSFORM_STRUCTURE_PANEL_MENU_ITEM.key]: 100,
100
+ [TRANSFORM_STRUCTURE_PARAGRAPH_MENU_ITEM.key]: 200,
101
+ [TRANSFORM_STRUCTURE_EXPAND_MENU_ITEM.key]: 300
102
+ },
103
+ [BLOCK_MENU_NODE_TYPES.BODIED_EXTENSION]: {
104
+ [TRANSFORM_STRUCTURE_PANEL_MENU_ITEM.key]: 100,
105
+ [TRANSFORM_STRUCTURE_PARAGRAPH_MENU_ITEM.key]: 200,
106
+ [TRANSFORM_STRUCTURE_EXPAND_MENU_ITEM.key]: 300
107
+ },
108
+ [BLOCK_MENU_NODE_TYPES.BLOCK_CARD]: {
109
+ [TRANSFORM_STRUCTURE_PANEL_MENU_ITEM.key]: 100,
110
+ [TRANSFORM_STRUCTURE_PARAGRAPH_MENU_ITEM.key]: 200
111
+ },
112
+ [BLOCK_MENU_NODE_TYPES.EMBED_CARD]: {
113
+ [TRANSFORM_STRUCTURE_PANEL_MENU_ITEM.key]: 100,
114
+ [TRANSFORM_STRUCTURE_PARAGRAPH_MENU_ITEM.key]: 200
115
+ },
116
+ [BLOCK_MENU_NODE_TYPES.TABLE]: {
117
+ [TRANSFORM_STRUCTURE_EXPAND_MENU_ITEM.key]: 100,
118
+ [TRANSFORM_STRUCTURE_LAYOUT_MENU_ITEM.key]: 200
119
+ }
120
+ };
121
+ export const getSuggestedItemsForNodeType = nodeType => {
122
+ return TRANSFORM_SUGGESTED_ITEMS_RANK[nodeType];
123
+ };
124
+ export const getSortedSuggestedItems = nodeType => {
125
+ const suggestions = getSuggestedItemsForNodeType(nodeType);
126
+ if (!suggestions) {
127
+ return [];
128
+ }
129
+ return Object.entries(suggestions).sort(([, rankA], [, rankB]) => rankA - rankB).map(([key]) => key);
130
+ };
@@ -1,37 +1,32 @@
1
1
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
2
- var extractNestedLists = function extractNestedLists(node, listTypes, itemTypes) {
2
+ var extractNestedLists = function extractNestedLists(node, listTypes, itemTypes, schema) {
3
3
  var items = [];
4
+ var paragraph = schema.nodes.paragraph;
4
5
  var _extract = function extract(currentNode) {
5
6
  currentNode.forEach(function (child) {
6
- // list item -> take content without nested lists, then recurse into nested lists
7
7
  if (itemTypes.some(function (type) {
8
8
  return child.type === type;
9
9
  })) {
10
- // Filter out nested list nodes from the list item's content
11
10
  var contentWithoutNestedLists = [];
12
11
  var nestedLists = [];
13
12
  child.forEach(function (grandChild) {
14
13
  if (listTypes.some(function (type) {
15
14
  return grandChild.type === type;
16
15
  })) {
17
- // This is a nested list - collect it for later processing
18
16
  nestedLists.push(grandChild);
19
17
  } else {
20
- // This is regular content (paragraph, etc.) - keep it
21
- contentWithoutNestedLists.push(grandChild);
18
+ if (grandChild.isText) {
19
+ contentWithoutNestedLists.push(paragraph.createAndFill({}, grandChild));
20
+ } else {
21
+ contentWithoutNestedLists.push(grandChild);
22
+ }
22
23
  }
23
24
  });
24
-
25
- // Add the list item with only its non-list content
26
25
  items.push(child.copy(Fragment.from(contentWithoutNestedLists)));
27
-
28
- // Now process nested lists to maintain document order
29
26
  nestedLists.forEach(function (nestedList) {
30
27
  _extract(nestedList);
31
28
  });
32
- }
33
- // lists -> keep operating
34
- else if (listTypes.some(function (type) {
29
+ } else if (listTypes.some(function (type) {
35
30
  return child.type === type;
36
31
  })) {
37
32
  _extract(child);
@@ -47,24 +42,6 @@ var extractNestedLists = function extractNestedLists(node, listTypes, itemTypes)
47
42
  * to it's first ancestor list, maintaining document order.
48
43
  *
49
44
  * @example
50
- * Input:
51
- * - bulletList
52
- * - listItem "A"
53
- * - listItem "B"
54
- * - bulletList
55
- * - listItem "C"
56
- * - listItem "D"
57
- * - listItem "E"
58
- *
59
- * Output:
60
- * - bulletList
61
- * - listItem "A"
62
- * - listItem "B"
63
- * - listItem "C"
64
- * - listItem "D"
65
- * - listItem "E"
66
- *
67
- * @example
68
45
  * Input (deeply nested):
69
46
  * - bulletList
70
47
  * - listItem "1"
@@ -95,7 +72,7 @@ export var flattenListStep = function flattenListStep(nodes, context) {
95
72
  if (listTypes.some(function (type) {
96
73
  return node.type === type;
97
74
  })) {
98
- return node.copy(Fragment.from(extractNestedLists(node, listTypes, [context.schema.nodes.listItem, context.schema.nodes.taskItem])));
75
+ return node.copy(Fragment.from(extractNestedLists(node, listTypes, [context.schema.nodes.listItem, context.schema.nodes.taskItem], context.schema)));
99
76
  }
100
77
  return node;
101
78
  });
@@ -8,6 +8,7 @@ import { unwrapExpandStep } from './unwrapExpandStep';
8
8
  import { unwrapListStep } from './unwrapListStep';
9
9
  import { unwrapStep } from './unwrapStep';
10
10
  import { wrapIntoLayoutStep } from './wrapIntoLayoutStep';
11
+ import { wrapMixedContentStep } from './wrapMixedContentStep';
11
12
  import { wrapStep } from './wrapStep';
12
13
 
13
14
  // Exampled step for overrides:
@@ -57,15 +58,15 @@ var TRANSFORM_STEPS_OVERRIDE = {
57
58
  codeBlock: [unwrapStep, flattenStep, wrapStep]
58
59
  },
59
60
  expand: {
60
- panel: [unwrapExpandStep, wrapStep],
61
- blockquote: [unwrapExpandStep, wrapStep],
61
+ panel: [unwrapExpandStep, wrapMixedContentStep],
62
+ blockquote: [unwrapExpandStep, wrapMixedContentStep],
62
63
  layoutSection: [unwrapExpandStep, wrapIntoLayoutStep],
63
64
  paragraph: [unwrapExpandStep],
64
65
  codeBlock: [unwrapExpandStep, flattenStep, wrapStep]
65
66
  },
66
67
  nestedExpand: {
67
- panel: [unwrapExpandStep, wrapStep],
68
- blockquote: [unwrapExpandStep, wrapStep],
68
+ panel: [unwrapExpandStep, wrapMixedContentStep],
69
+ blockquote: [unwrapExpandStep, wrapMixedContentStep],
69
70
  paragraph: [unwrapExpandStep],
70
71
  codeBlock: [unwrapExpandStep, flattenStep, wrapStep]
71
72
  },
@@ -1,7 +1,22 @@
1
1
  import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
2
  /**
3
- * Given an array of nodes, returns an array with the flattened children of any list nodes.
3
+ * Given an array of nodes, processes each list removing all parent list nodes and
4
+ * just returning their child contents.
5
+ *
6
+ * @example
7
+ * Input:
8
+ * - bulletList
9
+ * - listItem "1"
10
+ * - paragraph "1"
11
+ * - listItem "2"
12
+ * - paragraph "2"
13
+ *
14
+ * Output:
15
+ * - paragraph "1"
16
+ * - paragraph "2"
17
+ *
4
18
  * @param nodes
19
+ * @param context
5
20
  * @returns
6
21
  */
7
22
  export var unwrapListStep = function unwrapListStep(nodes, context) {