@blocknote/core 0.1.1 → 0.2.0
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/README.md +12 -6
- package/dist/blocknote.js +1425 -5114
- package/dist/blocknote.js.map +1 -1
- package/dist/blocknote.umd.cjs +1 -53
- package/dist/blocknote.umd.cjs.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +3 -16
- package/src/BlockNoteEditor.ts +54 -0
- package/src/BlockNoteExtensions.ts +52 -7
- package/src/assets/fonts-inter.css +92 -0
- package/src/editor.module.css +37 -0
- package/src/extensions/Blocks/BlockAttributes.ts +1 -3
- package/src/extensions/Blocks/PreviousBlockTypePlugin.ts +71 -18
- package/src/extensions/Blocks/helpers/getBlockInfoFromPos.ts +66 -0
- package/src/extensions/Blocks/index.ts +7 -3
- package/src/extensions/Blocks/nodes/Block.module.css +116 -74
- package/src/extensions/Blocks/nodes/Block.ts +415 -292
- package/src/extensions/Blocks/nodes/BlockGroup.ts +6 -6
- package/src/extensions/Blocks/nodes/BlockTypes/HeadingBlock/HeadingContent.ts +84 -0
- package/src/extensions/Blocks/nodes/BlockTypes/ListItemBlock/ListItemContent.ts +177 -0
- package/src/extensions/Blocks/nodes/BlockTypes/ListItemBlock/OrderedListItemIndexPlugin.ts +77 -0
- package/src/extensions/Blocks/nodes/BlockTypes/TextBlock/TextContent.ts +34 -0
- package/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.ts +20 -0
- package/src/extensions/DraggableBlocks/DraggableBlocksExtension.ts +27 -9
- package/src/extensions/DraggableBlocks/{DraggableBlocksPlugin.tsx → DraggableBlocksPlugin.ts} +227 -147
- package/src/extensions/FormattingToolbar/FormattingToolbarExtension.ts +29 -0
- package/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.ts +35 -0
- package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +308 -0
- package/src/extensions/HyperlinkToolbar/HyperlinkMark.ts +28 -0
- package/src/extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes.ts +19 -0
- package/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.ts +251 -0
- package/src/extensions/Placeholder/PlaceholderExtension.ts +2 -2
- package/src/extensions/SlashMenu/SlashMenuExtension.ts +9 -1
- package/src/extensions/SlashMenu/SlashMenuItem.ts +1 -3
- package/src/extensions/SlashMenu/defaultCommands.tsx +33 -22
- package/src/extensions/TrailingNode/TrailingNodeExtension.ts +4 -4
- package/src/extensions/UniqueID/UniqueID.ts +14 -12
- package/src/index.ts +8 -4
- package/src/shared/EditorElement.ts +10 -0
- package/src/shared/plugins/suggestion/SuggestionItem.ts +1 -8
- package/src/shared/plugins/suggestion/SuggestionPlugin.ts +222 -101
- package/src/shared/plugins/suggestion/SuggestionsMenuFactoryTypes.ts +21 -0
- package/src/{utils.ts → shared/utils.ts} +0 -0
- package/types/src/BlockNoteEditor.d.ts +13 -0
- package/types/src/BlockNoteExtensions.d.ts +12 -1
- package/types/src/EditorElement.d.ts +7 -0
- package/types/src/extensions/Blocks/PreviousBlockTypePlugin.d.ts +1 -1
- package/types/src/extensions/Blocks/helpers/getBlockInfoFromPos.d.ts +19 -0
- package/types/src/extensions/Blocks/nodes/Block.d.ts +11 -19
- package/types/src/extensions/Blocks/nodes/BlockTypes/HeadingBlock/HeadingContent.d.ts +8 -0
- package/types/src/extensions/Blocks/nodes/BlockTypes/ListItemBlock/ListItemContent.d.ts +8 -0
- package/types/src/extensions/Blocks/nodes/BlockTypes/ListItemBlock/OrderedListItemIndexPlugin.d.ts +2 -0
- package/types/src/extensions/Blocks/nodes/BlockTypes/TextBlock/TextContent.d.ts +6 -0
- package/types/src/extensions/BubbleMenu/BubbleMenuExtension.d.ts +4 -1
- package/types/src/extensions/BubbleMenu/BubbleMenuFactoryTypes.d.ts +27 -0
- package/types/src/extensions/BubbleMenu/BubbleMenuPlugin.d.ts +10 -12
- package/types/src/extensions/DraggableBlocks/BlockMenuFactoryTypes.d.ts +12 -0
- package/types/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.d.ts +14 -0
- package/types/src/extensions/DraggableBlocks/DragMenuFactoryTypes.d.ts +18 -0
- package/types/src/extensions/DraggableBlocks/DraggableBlocksExtension.d.ts +9 -3
- package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +23 -1
- package/types/src/extensions/FormattingToolbar/FormattingToolbarExtension.d.ts +8 -0
- package/types/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.d.ts +23 -0
- package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +43 -0
- package/types/src/extensions/HyperlinkToolbar/HyperlinkMark.d.ts +8 -0
- package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes.d.ts +12 -0
- package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.d.ts +11 -0
- package/types/src/extensions/Hyperlinks/HyperlinkMark.d.ts +2 -1
- package/types/src/extensions/Hyperlinks/HyperlinkMenuFactoryTypes.d.ts +11 -0
- package/types/src/extensions/Hyperlinks/HyperlinkMenuPlugin.d.ts +10 -1
- package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +3 -1
- package/types/src/extensions/SlashMenu/SlashMenuItem.d.ts +2 -4
- package/types/src/index.d.ts +8 -3
- package/types/src/shared/EditorElement.d.ts +6 -0
- package/types/src/shared/plugins/suggestion/SuggestionItem.d.ts +1 -6
- package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +15 -10
- package/types/src/shared/plugins/suggestion/SuggestionsMenuFactoryTypes.d.ts +12 -0
- package/types/src/shared/utils.d.ts +2 -0
- package/types/src/utils.d.ts +2 -2
- package/src/BlockNoteTheme.ts +0 -150
- package/src/EditorContent.tsx +0 -2
- package/src/extensions/Blocks/OrderedListPlugin.ts +0 -46
- package/src/extensions/Blocks/commands/joinBackward.ts +0 -274
- package/src/extensions/Blocks/helpers/setBlockHeading.ts +0 -30
- package/src/extensions/Blocks/nodes/Content.ts +0 -63
- package/src/extensions/Blocks/rule.ts +0 -48
- package/src/extensions/BubbleMenu/BubbleMenuExtension.tsx +0 -36
- package/src/extensions/BubbleMenu/BubbleMenuPlugin.ts +0 -245
- package/src/extensions/BubbleMenu/component/BubbleMenu.tsx +0 -240
- package/src/extensions/BubbleMenu/component/LinkToolbarButton.tsx +0 -67
- package/src/extensions/DraggableBlocks/components/DragHandle.tsx +0 -102
- package/src/extensions/DraggableBlocks/components/DragHandleMenu.tsx +0 -19
- package/src/extensions/Hyperlinks/HyperlinkMark.tsx +0 -16
- package/src/extensions/Hyperlinks/HyperlinkMenuPlugin.tsx +0 -165
- package/src/extensions/Hyperlinks/menus/EditHyperlinkMenu.tsx +0 -44
- package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItem.tsx +0 -34
- package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemIcon.tsx +0 -31
- package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemInput.tsx +0 -40
- package/src/extensions/Hyperlinks/menus/HoverHyperlinkMenu.tsx +0 -37
- package/src/extensions/Hyperlinks/menus/HyperlinkMenu.tsx +0 -63
- package/src/fonts-inter.css +0 -94
- package/src/globals.css +0 -28
- package/src/root.module.css +0 -19
- package/src/shared/components/toolbar/Toolbar.tsx +0 -10
- package/src/shared/components/toolbar/ToolbarButton.tsx +0 -57
- package/src/shared/components/toolbar/ToolbarDropdown.tsx +0 -35
- package/src/shared/components/toolbar/ToolbarDropdownItem.tsx +0 -35
- package/src/shared/components/toolbar/ToolbarDropdownTarget.tsx +0 -31
- package/src/shared/components/tooltip/TooltipContent.module.css +0 -15
- package/src/shared/components/tooltip/TooltipContent.tsx +0 -23
- package/src/shared/hooks/useEditorForceUpdate.tsx +0 -30
- package/src/shared/plugins/suggestion/SuggestionListReactRenderer.tsx +0 -236
- package/src/shared/plugins/suggestion/components/SuggestionGroup.tsx +0 -47
- package/src/shared/plugins/suggestion/components/SuggestionGroupItem.tsx +0 -82
- package/src/shared/plugins/suggestion/components/SuggestionList.tsx +0 -92
- package/src/useEditor.ts +0 -51
- package/types/src/BlockNoteTheme.d.ts +0 -2
- package/types/src/EditorContent.d.ts +0 -1
- package/types/src/commands/indentation.d.ts +0 -2
- package/types/src/extensions/Blocks/OrderedListPlugin.d.ts +0 -2
- package/types/src/extensions/Blocks/commands/joinBackward.d.ts +0 -14
- package/types/src/extensions/Blocks/helpers/setBlockHeading.d.ts +0 -5
- package/types/src/extensions/Blocks/nodes/Content.d.ts +0 -5
- package/types/src/extensions/Blocks/rule.d.ts +0 -16
- package/types/src/extensions/BubbleMenu/component/BubbleMenu.d.ts +0 -5
- package/types/src/extensions/BubbleMenu/component/DropdownBlockItem.d.ts +0 -10
- package/types/src/extensions/BubbleMenu/component/LinkToolbarButton.d.ts +0 -11
- package/types/src/extensions/DraggableBlocks/components/DragHandle.d.ts +0 -12
- package/types/src/extensions/DraggableBlocks/components/DragHandleMenu.d.ts +0 -6
- package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenu.d.ts +0 -11
- package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItem.d.ts +0 -13
- package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemIcon.d.ts +0 -8
- package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemInput.d.ts +0 -9
- package/types/src/extensions/Hyperlinks/menus/HoverHyperlinkMenu.d.ts +0 -12
- package/types/src/extensions/Hyperlinks/menus/HyperlinkBasicMenu.d.ts +0 -12
- package/types/src/extensions/Hyperlinks/menus/HyperlinkEditMenu.d.ts +0 -10
- package/types/src/extensions/Hyperlinks/menus/HyperlinkMenu.d.ts +0 -21
- package/types/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInput.d.ts +0 -39
- package/types/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInputStyles.d.ts +0 -1
- package/types/src/extensions/Hyperlinks/menus/atlaskit/ToolbarComponent.d.ts +0 -11
- package/types/src/extensions/Hyperlinks/menus/helpers/PanelTextInput.d.ts +0 -39
- package/types/src/extensions/Hyperlinks/menus/helpers/PanelTextInputStyles.d.ts +0 -3
- package/types/src/extensions/Hyperlinks/menus/helpers/ToolbarComponent.d.ts +0 -13
- package/types/src/extensions/helpers/formatKeyboardShortcut.d.ts +0 -1
- package/types/src/lib/atlaskit/browser.d.ts +0 -12
- package/types/src/nodes/ChildgroupNode.d.ts +0 -28
- package/types/src/nodes/patchNodes.d.ts +0 -1
- package/types/src/plugins/TreeViewPlugin/index.d.ts +0 -2
- package/types/src/plugins/animation.d.ts +0 -2
- package/types/src/react/BlockNoteComposer.d.ts +0 -17
- package/types/src/react/BlockNotePlugin.d.ts +0 -1
- package/types/src/react/index.d.ts +0 -3
- package/types/src/react/useBlockNoteSetup.d.ts +0 -2
- package/types/src/registerBlockNote.d.ts +0 -2
- package/types/src/shared/components/toolbar/SimpleToolbarButton.d.ts +0 -15
- package/types/src/shared/components/toolbar/SimpleToolbarDropdown.d.ts +0 -11
- package/types/src/shared/components/toolbar/SimpleToolbarDropdownItem.d.ts +0 -11
- package/types/src/shared/components/toolbar/Toolbar.d.ts +0 -4
- package/types/src/shared/components/toolbar/ToolbarButton.d.ts +0 -15
- package/types/src/shared/components/toolbar/ToolbarDropdown.d.ts +0 -17
- package/types/src/shared/components/toolbar/ToolbarDropdownItem.d.ts +0 -11
- package/types/src/shared/components/toolbar/ToolbarDropdownTarget.d.ts +0 -8
- package/types/src/shared/components/toolbar/ToolbarSeparator.d.ts +0 -2
- package/types/src/shared/components/tooltip/TooltipContent.d.ts +0 -15
- package/types/src/shared/hooks/useEditorForceUpdate.d.ts +0 -2
- package/types/src/shared/plugins/suggestion/SuggestionListReactRenderer.d.ts +0 -71
- package/types/src/shared/plugins/suggestion/components/SuggestionGroup.d.ts +0 -23
- package/types/src/shared/plugins/suggestion/components/SuggestionGroupItem.d.ts +0 -9
- package/types/src/shared/plugins/suggestion/components/SuggestionList.d.ts +0 -11
- package/types/src/themes/BlockNoteEditorTheme.d.ts +0 -11
- package/types/src/useEditor.d.ts +0 -11
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
RiH1,
|
|
3
|
-
RiH2,
|
|
4
|
-
RiH3,
|
|
5
|
-
RiListOrdered,
|
|
6
|
-
RiListUnordered,
|
|
7
|
-
RiText,
|
|
8
|
-
} from "react-icons/ri";
|
|
9
|
-
import { formatKeyboardShortcut } from "../../utils";
|
|
1
|
+
import { formatKeyboardShortcut } from "../../shared/utils";
|
|
10
2
|
import { SlashMenuGroups, SlashMenuItem } from "./SlashMenuItem";
|
|
11
3
|
|
|
12
4
|
/**
|
|
@@ -22,11 +14,15 @@ const defaultCommands: { [key: string]: SlashMenuItem } = {
|
|
|
22
14
|
.chain()
|
|
23
15
|
.focus()
|
|
24
16
|
.deleteRange(range)
|
|
25
|
-
.
|
|
17
|
+
.BNCreateBlockOrSetContentType(range.from, {
|
|
18
|
+
name: "headingContent",
|
|
19
|
+
attrs: {
|
|
20
|
+
headingLevel: "1",
|
|
21
|
+
},
|
|
22
|
+
})
|
|
26
23
|
.run();
|
|
27
24
|
},
|
|
28
25
|
["h", "heading1", "h1"],
|
|
29
|
-
RiH1,
|
|
30
26
|
"Used for a top-level heading",
|
|
31
27
|
formatKeyboardShortcut("Mod-Alt-1")
|
|
32
28
|
),
|
|
@@ -40,11 +36,15 @@ const defaultCommands: { [key: string]: SlashMenuItem } = {
|
|
|
40
36
|
.chain()
|
|
41
37
|
.focus()
|
|
42
38
|
.deleteRange(range)
|
|
43
|
-
.
|
|
39
|
+
.BNCreateBlockOrSetContentType(range.from, {
|
|
40
|
+
name: "headingContent",
|
|
41
|
+
attrs: {
|
|
42
|
+
headingLevel: "2",
|
|
43
|
+
},
|
|
44
|
+
})
|
|
44
45
|
.run();
|
|
45
46
|
},
|
|
46
47
|
["h2", "heading2", "subheading"],
|
|
47
|
-
RiH2,
|
|
48
48
|
"Used for key sections",
|
|
49
49
|
formatKeyboardShortcut("Mod-Alt-2")
|
|
50
50
|
),
|
|
@@ -58,11 +58,15 @@ const defaultCommands: { [key: string]: SlashMenuItem } = {
|
|
|
58
58
|
.chain()
|
|
59
59
|
.focus()
|
|
60
60
|
.deleteRange(range)
|
|
61
|
-
.
|
|
61
|
+
.BNCreateBlockOrSetContentType(range.from, {
|
|
62
|
+
name: "headingContent",
|
|
63
|
+
attrs: {
|
|
64
|
+
headingLevel: "3",
|
|
65
|
+
},
|
|
66
|
+
})
|
|
62
67
|
.run();
|
|
63
68
|
},
|
|
64
69
|
["h3", "heading3", "subheading"],
|
|
65
|
-
RiH3,
|
|
66
70
|
"Used for subsections and group headings",
|
|
67
71
|
formatKeyboardShortcut("Mod-Alt-3")
|
|
68
72
|
),
|
|
@@ -76,17 +80,21 @@ const defaultCommands: { [key: string]: SlashMenuItem } = {
|
|
|
76
80
|
.chain()
|
|
77
81
|
.focus()
|
|
78
82
|
.deleteRange(range)
|
|
79
|
-
.
|
|
83
|
+
.BNCreateBlockOrSetContentType(range.from, {
|
|
84
|
+
name: "listItemContent",
|
|
85
|
+
attrs: {
|
|
86
|
+
listItemType: "ordered",
|
|
87
|
+
},
|
|
88
|
+
})
|
|
80
89
|
.run();
|
|
81
90
|
},
|
|
82
91
|
["li", "list", "numberedlist", "numbered list"],
|
|
83
|
-
RiListOrdered,
|
|
84
92
|
"Used to display a numbered list",
|
|
85
93
|
formatKeyboardShortcut("Mod-Shift-7")
|
|
86
94
|
),
|
|
87
95
|
|
|
88
96
|
// Command for creating a bullet list
|
|
89
|
-
|
|
97
|
+
bulletList: new SlashMenuItem(
|
|
90
98
|
"Bullet List",
|
|
91
99
|
SlashMenuGroups.BASIC_BLOCKS,
|
|
92
100
|
(editor, range) => {
|
|
@@ -94,11 +102,15 @@ const defaultCommands: { [key: string]: SlashMenuItem } = {
|
|
|
94
102
|
.chain()
|
|
95
103
|
.focus()
|
|
96
104
|
.deleteRange(range)
|
|
97
|
-
.
|
|
105
|
+
.BNCreateBlockOrSetContentType(range.from, {
|
|
106
|
+
name: "listItemContent",
|
|
107
|
+
attrs: {
|
|
108
|
+
listItemType: "unordered",
|
|
109
|
+
},
|
|
110
|
+
})
|
|
98
111
|
.run();
|
|
99
112
|
},
|
|
100
113
|
["ul", "list", "bulletlist", "bullet list"],
|
|
101
|
-
RiListUnordered,
|
|
102
114
|
"Used to display an unordered list",
|
|
103
115
|
formatKeyboardShortcut("Mod-Shift-8")
|
|
104
116
|
),
|
|
@@ -112,11 +124,10 @@ const defaultCommands: { [key: string]: SlashMenuItem } = {
|
|
|
112
124
|
.chain()
|
|
113
125
|
.focus()
|
|
114
126
|
.deleteRange(range)
|
|
115
|
-
.
|
|
127
|
+
.BNCreateBlockOrSetContentType(range.from, { name: "textContent" })
|
|
116
128
|
.run();
|
|
117
129
|
},
|
|
118
130
|
["p"],
|
|
119
|
-
RiText,
|
|
120
131
|
"Used for the body of your document",
|
|
121
132
|
formatKeyboardShortcut("Mod-Alt-0")
|
|
122
133
|
),
|
|
@@ -33,14 +33,14 @@ export const TrailingNode = Extension.create<TrailingNodeOptions>({
|
|
|
33
33
|
const shouldInsertNodeAtEnd = plugin.getState(state);
|
|
34
34
|
const endPosition = doc.content.size - 2;
|
|
35
35
|
const type = schema.nodes["block"];
|
|
36
|
-
const
|
|
36
|
+
const contentType = schema.nodes["textContent"];
|
|
37
37
|
if (!shouldInsertNodeAtEnd) {
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
return tr.insert(
|
|
42
42
|
endPosition,
|
|
43
|
-
type.create(undefined,
|
|
43
|
+
type.create(undefined, contentType.create())
|
|
44
44
|
);
|
|
45
45
|
},
|
|
46
46
|
state: {
|
|
@@ -55,8 +55,8 @@ export const TrailingNode = Extension.create<TrailingNodeOptions>({
|
|
|
55
55
|
|
|
56
56
|
let lastNode = tr.doc.lastChild;
|
|
57
57
|
|
|
58
|
-
if (!lastNode || lastNode.type.name !== "
|
|
59
|
-
throw new Error("Expected
|
|
58
|
+
if (!lastNode || lastNode.type.name !== "blockGroup") {
|
|
59
|
+
throw new Error("Expected blockGroup");
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
lastNode = lastNode.lastChild;
|
|
@@ -51,7 +51,20 @@ const UniqueID = Extension.create({
|
|
|
51
51
|
return {
|
|
52
52
|
attributeName: "id",
|
|
53
53
|
types: [],
|
|
54
|
-
generateID: () =>
|
|
54
|
+
generateID: () => {
|
|
55
|
+
// Use mock ID if tests are running.
|
|
56
|
+
if ((window as any).__TEST_OPTIONS) {
|
|
57
|
+
if ((window as any).__TEST_OPTIONS.mockID === undefined) {
|
|
58
|
+
(window as any).__TEST_OPTIONS.mockID = 0;
|
|
59
|
+
} else {
|
|
60
|
+
(window as any).__TEST_OPTIONS.mockID++;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return parseInt((window as any).__TEST_OPTIONS.mockID);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return v4();
|
|
67
|
+
},
|
|
55
68
|
filterTransaction: null,
|
|
56
69
|
};
|
|
57
70
|
},
|
|
@@ -62,17 +75,6 @@ const UniqueID = Extension.create({
|
|
|
62
75
|
attributes: {
|
|
63
76
|
[this.options.attributeName]: {
|
|
64
77
|
default: null,
|
|
65
|
-
parseHTML: (element) =>
|
|
66
|
-
element.getAttribute(`data-${this.options.attributeName}`),
|
|
67
|
-
renderHTML: (attributes) => {
|
|
68
|
-
if (!attributes[this.options.attributeName]) {
|
|
69
|
-
return {};
|
|
70
|
-
}
|
|
71
|
-
return {
|
|
72
|
-
[`data-${this.options.attributeName}`]:
|
|
73
|
-
attributes[this.options.attributeName],
|
|
74
|
-
};
|
|
75
|
-
},
|
|
76
78
|
},
|
|
77
79
|
},
|
|
78
80
|
},
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export * from "./BlockNoteEditor";
|
|
3
2
|
export * from "./BlockNoteExtensions";
|
|
4
|
-
export
|
|
5
|
-
export * from "./
|
|
3
|
+
export type { BlockContentType } from "./extensions/Blocks/nodes/Block";
|
|
4
|
+
export * from "./extensions/FormattingToolbar/FormattingToolbarFactoryTypes";
|
|
5
|
+
export * from "./extensions/DraggableBlocks/BlockSideMenuFactoryTypes";
|
|
6
|
+
export * from "./extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes";
|
|
7
|
+
export * from "./extensions/SlashMenu/SlashMenuItem";
|
|
8
|
+
export type { SuggestionItem } from "./shared/plugins/suggestion/SuggestionItem";
|
|
9
|
+
export * from "./shared/plugins/suggestion/SuggestionsMenuFactoryTypes";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type EditorElement<ElementDynamicParams extends Record<string, any>> = {
|
|
2
|
+
element: HTMLElement | undefined;
|
|
3
|
+
render: (params: ElementDynamicParams, isHidden: boolean) => void;
|
|
4
|
+
hide: () => void;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type ElementFactory<
|
|
8
|
+
ElementStaticParams extends Record<string, any>,
|
|
9
|
+
ElementDynamicParams extends Record<string, any>
|
|
10
|
+
> = (staticParams: ElementStaticParams) => EditorElement<ElementDynamicParams>;
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { IconType } from "react-icons";
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* A generic interface used in all suggestion menus (slash menu, mentions, etc)
|
|
5
3
|
*/
|
|
6
|
-
export
|
|
4
|
+
export interface SuggestionItem {
|
|
7
5
|
/**
|
|
8
6
|
* The name of the item
|
|
9
7
|
*/
|
|
@@ -14,11 +12,6 @@ export default interface SuggestionItem {
|
|
|
14
12
|
*/
|
|
15
13
|
groupName: string;
|
|
16
14
|
|
|
17
|
-
/**
|
|
18
|
-
* The react icon
|
|
19
|
-
*/
|
|
20
|
-
icon?: IconType;
|
|
21
|
-
|
|
22
15
|
hint?: string;
|
|
23
16
|
|
|
24
17
|
shortcut?: string;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { Editor, Range } from "@tiptap/core";
|
|
2
|
-
import { escapeRegExp
|
|
3
|
-
import { Plugin, PluginKey, Selection } from "prosemirror-state";
|
|
4
|
-
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
2
|
+
import { escapeRegExp } from "lodash";
|
|
3
|
+
import { EditorState, Plugin, PluginKey, Selection } from "prosemirror-state";
|
|
4
|
+
import { Decoration, DecorationSet, EditorView } from "prosemirror-view";
|
|
5
5
|
import { findBlock } from "../../../extensions/Blocks/helpers/findBlock";
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
import {
|
|
7
|
+
SuggestionsMenu,
|
|
8
|
+
SuggestionsMenuDynamicParams,
|
|
9
|
+
SuggestionsMenuFactory,
|
|
10
|
+
SuggestionsMenuStaticParams,
|
|
11
|
+
} from "./SuggestionsMenuFactoryTypes";
|
|
12
|
+
import { SuggestionItem } from "./SuggestionItem";
|
|
11
13
|
|
|
12
14
|
export type SuggestionPluginOptions<T extends SuggestionItem> = {
|
|
13
15
|
/**
|
|
@@ -27,6 +29,8 @@ export type SuggestionPluginOptions<T extends SuggestionItem> = {
|
|
|
27
29
|
*/
|
|
28
30
|
char: string;
|
|
29
31
|
|
|
32
|
+
suggestionsMenuFactory: SuggestionsMenuFactory<T>;
|
|
33
|
+
|
|
30
34
|
/**
|
|
31
35
|
* The callback that gets executed when an item is selected by the user.
|
|
32
36
|
*
|
|
@@ -44,6 +48,24 @@ export type SuggestionPluginOptions<T extends SuggestionItem> = {
|
|
|
44
48
|
allow?: (props: { editor: Editor; range: Range }) => boolean;
|
|
45
49
|
};
|
|
46
50
|
|
|
51
|
+
type SuggestionPluginState<T extends SuggestionItem> = {
|
|
52
|
+
active: boolean;
|
|
53
|
+
range: Range | null;
|
|
54
|
+
query: string | null;
|
|
55
|
+
notFoundCount: number;
|
|
56
|
+
items: T[];
|
|
57
|
+
selectedItemIndex: number;
|
|
58
|
+
type: string;
|
|
59
|
+
decorationId: string | null;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type SuggestionPluginViewOptions<T extends SuggestionItem> = {
|
|
63
|
+
editor: Editor;
|
|
64
|
+
pluginKey: PluginKey;
|
|
65
|
+
onSelectItem: (props: { item: T; editor: Editor; range: Range }) => void;
|
|
66
|
+
suggestionsMenuFactory: SuggestionsMenuFactory<T>;
|
|
67
|
+
};
|
|
68
|
+
|
|
47
69
|
export type MenuType = "slash" | "drag";
|
|
48
70
|
|
|
49
71
|
/**
|
|
@@ -85,6 +107,105 @@ export function findCommandBeforeCursor(
|
|
|
85
107
|
};
|
|
86
108
|
}
|
|
87
109
|
|
|
110
|
+
class SuggestionPluginView<T extends SuggestionItem> {
|
|
111
|
+
editor: Editor;
|
|
112
|
+
pluginKey: PluginKey;
|
|
113
|
+
|
|
114
|
+
suggestionsMenu: SuggestionsMenu<T>;
|
|
115
|
+
|
|
116
|
+
pluginState: SuggestionPluginState<T>;
|
|
117
|
+
itemCallback: (item: T) => void;
|
|
118
|
+
|
|
119
|
+
constructor({
|
|
120
|
+
editor,
|
|
121
|
+
pluginKey,
|
|
122
|
+
onSelectItem: selectItemCallback = () => {},
|
|
123
|
+
suggestionsMenuFactory,
|
|
124
|
+
}: SuggestionPluginViewOptions<T>) {
|
|
125
|
+
this.editor = editor;
|
|
126
|
+
this.pluginKey = pluginKey;
|
|
127
|
+
|
|
128
|
+
this.pluginState = {
|
|
129
|
+
active: false,
|
|
130
|
+
range: null,
|
|
131
|
+
query: null,
|
|
132
|
+
notFoundCount: 0,
|
|
133
|
+
items: [],
|
|
134
|
+
selectedItemIndex: 0,
|
|
135
|
+
type: "slash",
|
|
136
|
+
decorationId: null,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
this.itemCallback = (item: T) =>
|
|
140
|
+
selectItemCallback({
|
|
141
|
+
item: item,
|
|
142
|
+
editor: editor,
|
|
143
|
+
range: this.pluginState.range as Range,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
this.suggestionsMenu = suggestionsMenuFactory(this.getStaticParams());
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
update(view: EditorView, prevState: EditorState) {
|
|
150
|
+
const prev = this.pluginKey.getState(prevState);
|
|
151
|
+
const next = this.pluginKey.getState(view.state);
|
|
152
|
+
|
|
153
|
+
// See how the state changed
|
|
154
|
+
const started = !prev.active && next.active;
|
|
155
|
+
const stopped = prev.active && !next.active;
|
|
156
|
+
// TODO: Currently also true for cases in which an update isn't needed so selected list item index updates still
|
|
157
|
+
// cause the view to update. May need to be more strict.
|
|
158
|
+
const changed = prev.active && next.active;
|
|
159
|
+
|
|
160
|
+
// Cancel when suggestion isn't active
|
|
161
|
+
if (!started && !changed && !stopped) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
this.pluginState = stopped ? prev : next;
|
|
166
|
+
|
|
167
|
+
if (stopped) {
|
|
168
|
+
this.suggestionsMenu.hide();
|
|
169
|
+
|
|
170
|
+
// Listener stops focus moving to the menu on click.
|
|
171
|
+
this.suggestionsMenu.element!.removeEventListener("mousedown", (event) =>
|
|
172
|
+
event.preventDefault()
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (changed) {
|
|
177
|
+
this.suggestionsMenu.render(this.getDynamicParams(), false);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (started) {
|
|
181
|
+
this.suggestionsMenu.render(this.getDynamicParams(), true);
|
|
182
|
+
|
|
183
|
+
// Listener stops focus moving to the menu on click.
|
|
184
|
+
this.suggestionsMenu.element!.addEventListener("mousedown", (event) =>
|
|
185
|
+
event.preventDefault()
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
getStaticParams(): SuggestionsMenuStaticParams<T> {
|
|
191
|
+
return {
|
|
192
|
+
itemCallback: (item: T) => this.itemCallback(item),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
getDynamicParams(): SuggestionsMenuDynamicParams<T> {
|
|
197
|
+
const decorationNode = document.querySelector(
|
|
198
|
+
`[data-decoration-id="${this.pluginState.decorationId}"]`
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
items: this.pluginState.items,
|
|
203
|
+
selectedItemIndex: this.pluginState.selectedItemIndex,
|
|
204
|
+
queryStartBoundingBox: decorationNode!.getBoundingClientRect(),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
88
209
|
/**
|
|
89
210
|
* A ProseMirror plugin for suggestions, designed to make '/'-commands possible as well as mentions.
|
|
90
211
|
*
|
|
@@ -102,6 +223,7 @@ export function createSuggestionPlugin<T extends SuggestionItem>({
|
|
|
102
223
|
pluginKey,
|
|
103
224
|
editor,
|
|
104
225
|
char,
|
|
226
|
+
suggestionsMenuFactory,
|
|
105
227
|
onSelectItem: selectItemCallback = () => {},
|
|
106
228
|
items = () => [],
|
|
107
229
|
}: SuggestionPluginOptions<T>) {
|
|
@@ -110,112 +232,70 @@ export function createSuggestionPlugin<T extends SuggestionItem>({
|
|
|
110
232
|
throw new Error("'char' should be a single character");
|
|
111
233
|
}
|
|
112
234
|
|
|
113
|
-
const
|
|
235
|
+
const deactivate = (view: EditorView) => {
|
|
236
|
+
view.dispatch(view.state.tr.setMeta(pluginKey, { deactivate: true }));
|
|
237
|
+
};
|
|
114
238
|
|
|
115
239
|
// Plugin key is passed in parameter so it can be exported and used in draghandle
|
|
116
240
|
return new Plugin({
|
|
117
241
|
key: pluginKey,
|
|
118
242
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return true;
|
|
129
|
-
},
|
|
130
|
-
|
|
131
|
-
view() {
|
|
132
|
-
return {
|
|
133
|
-
update: async (view, prevState) => {
|
|
134
|
-
const prev = this.key?.getState(prevState);
|
|
135
|
-
const next = this.key?.getState(view.state);
|
|
136
|
-
|
|
137
|
-
// See how the state changed
|
|
138
|
-
const started = !prev.active && next.active;
|
|
139
|
-
const stopped = prev.active && !next.active;
|
|
140
|
-
const changed = !started && !stopped && prev.query !== next.query;
|
|
141
|
-
|
|
142
|
-
// Cancel when suggestion isn't active
|
|
143
|
-
if (!started && !changed && !stopped) {
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const state = stopped ? prev : next;
|
|
148
|
-
const decorationNode = document.querySelector(
|
|
149
|
-
`[data-decoration-id="${state.decorationId}"]`
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
const groups: { [groupName: string]: T[] } = groupBy(
|
|
153
|
-
state.items,
|
|
154
|
-
"groupName"
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
const deactivate = () => {
|
|
158
|
-
view.dispatch(
|
|
159
|
-
view.state.tr.setMeta(pluginKey, { deactivate: true })
|
|
160
|
-
);
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
const rendererProps: SuggestionRendererProps<T> = {
|
|
164
|
-
groups: changed || started ? groups : {},
|
|
165
|
-
count: state.items.length,
|
|
166
|
-
onSelectItem: (item: T) => {
|
|
167
|
-
deactivate();
|
|
168
|
-
selectItemCallback({
|
|
169
|
-
item,
|
|
170
|
-
editor,
|
|
171
|
-
range: state.range,
|
|
172
|
-
});
|
|
173
|
-
},
|
|
174
|
-
// virtual node for popper.js or tippy.js
|
|
175
|
-
// this can be used for building popups without a DOM node
|
|
176
|
-
clientRect: decorationNode
|
|
177
|
-
? () => decorationNode.getBoundingClientRect()
|
|
178
|
-
: null,
|
|
179
|
-
onClose: () => {
|
|
180
|
-
deactivate();
|
|
181
|
-
renderer.onExit?.(rendererProps);
|
|
182
|
-
},
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
if (stopped) {
|
|
186
|
-
renderer.onExit?.(rendererProps);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (changed) {
|
|
190
|
-
renderer.onUpdate?.(rendererProps);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (started) {
|
|
194
|
-
renderer.onStart?.(rendererProps);
|
|
195
|
-
}
|
|
243
|
+
view: (view: EditorView) =>
|
|
244
|
+
new SuggestionPluginView({
|
|
245
|
+
editor: editor,
|
|
246
|
+
pluginKey: pluginKey,
|
|
247
|
+
onSelectItem: (props: { item: T; editor: Editor; range: Range }) => {
|
|
248
|
+
deactivate(view);
|
|
249
|
+
selectItemCallback(props);
|
|
196
250
|
},
|
|
197
|
-
|
|
198
|
-
|
|
251
|
+
suggestionsMenuFactory: suggestionsMenuFactory,
|
|
252
|
+
}),
|
|
199
253
|
|
|
200
254
|
state: {
|
|
201
255
|
// Initialize the plugin's internal state.
|
|
202
|
-
init() {
|
|
256
|
+
init(): SuggestionPluginState<T> {
|
|
203
257
|
return {
|
|
204
258
|
active: false,
|
|
205
|
-
range:
|
|
206
|
-
query: null
|
|
259
|
+
range: null, // TODO
|
|
260
|
+
query: null,
|
|
207
261
|
notFoundCount: 0,
|
|
208
262
|
items: [] as T[],
|
|
263
|
+
selectedItemIndex: 0,
|
|
209
264
|
type: "slash",
|
|
210
|
-
decorationId: null
|
|
265
|
+
decorationId: null,
|
|
211
266
|
};
|
|
212
267
|
},
|
|
213
268
|
|
|
214
269
|
// Apply changes to the plugin state from a view transaction.
|
|
215
|
-
apply(transaction, prev, _oldState,
|
|
270
|
+
apply(transaction, prev, _oldState, newState) {
|
|
216
271
|
const { selection } = transaction;
|
|
217
272
|
const next = { ...prev };
|
|
218
273
|
|
|
274
|
+
// TODO: More clearly define which transactions should be ignored and which should deactivate the menu.
|
|
275
|
+
if (transaction.getMeta("orderedListIndexing") !== undefined) {
|
|
276
|
+
return next;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Handles transactions created by navigating the menu using the up/down arrow keys.
|
|
280
|
+
if (
|
|
281
|
+
transaction.getMeta(pluginKey)?.selectedItemIndexChanged !== undefined
|
|
282
|
+
) {
|
|
283
|
+
let newIndex =
|
|
284
|
+
transaction.getMeta(pluginKey).selectedItemIndexChanged;
|
|
285
|
+
|
|
286
|
+
if (newIndex < 0) {
|
|
287
|
+
newIndex = prev.items.length - 1;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (newIndex >= prev.items.length) {
|
|
291
|
+
newIndex = 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
next.selectedItemIndex = newIndex;
|
|
295
|
+
|
|
296
|
+
return next;
|
|
297
|
+
}
|
|
298
|
+
|
|
219
299
|
if (
|
|
220
300
|
// only show popup if selection is a blinking cursor
|
|
221
301
|
selection.from === selection.to &&
|
|
@@ -227,7 +307,7 @@ export function createSuggestionPlugin<T extends SuggestionItem>({
|
|
|
227
307
|
!transaction.getMeta("pointer")
|
|
228
308
|
) {
|
|
229
309
|
// Reset active state if we just left the previous suggestion range (e.g.: key arrows moving before /)
|
|
230
|
-
if (prev.active && selection.from <= prev.range
|
|
310
|
+
if (prev.active && selection.from <= prev.range!.from) {
|
|
231
311
|
next.active = false;
|
|
232
312
|
} else if (transaction.getMeta(pluginKey)?.activate) {
|
|
233
313
|
// Start showing suggestions. activate has been set after typing a "/" (or whatever the specified character is), so let's create the decoration and initialize
|
|
@@ -242,13 +322,14 @@ export function createSuggestionPlugin<T extends SuggestionItem>({
|
|
|
242
322
|
next.query = "";
|
|
243
323
|
next.active = true;
|
|
244
324
|
next.type = transaction.getMeta(pluginKey)?.type;
|
|
325
|
+
next.selectedItemIndex = 0;
|
|
245
326
|
} else if (prev.active) {
|
|
246
327
|
// Try to match against where our cursor currently is
|
|
247
328
|
// if the type is slash we get the command after the character
|
|
248
329
|
// otherwise we get the whole query
|
|
249
330
|
const match = findCommandBeforeCursor(
|
|
250
331
|
prev.type === "slash" ? char : "",
|
|
251
|
-
selection
|
|
332
|
+
newState.selection
|
|
252
333
|
);
|
|
253
334
|
if (!match) {
|
|
254
335
|
throw new Error("active but no match (suggestions)");
|
|
@@ -258,6 +339,7 @@ export function createSuggestionPlugin<T extends SuggestionItem>({
|
|
|
258
339
|
next.active = true;
|
|
259
340
|
next.decorationId = prev.decorationId;
|
|
260
341
|
next.query = match.query;
|
|
342
|
+
next.selectedItemIndex = 0;
|
|
261
343
|
}
|
|
262
344
|
} else {
|
|
263
345
|
next.active = false;
|
|
@@ -270,7 +352,7 @@ export function createSuggestionPlugin<T extends SuggestionItem>({
|
|
|
270
352
|
} else {
|
|
271
353
|
// Update the "notFoundCount",
|
|
272
354
|
// which indicates how many characters have been typed after showing no results
|
|
273
|
-
if (next.range
|
|
355
|
+
if (next.range!.to > prev.range!.to) {
|
|
274
356
|
// Text has been entered (selection moved to right), but still no items found, update Count
|
|
275
357
|
next.notFoundCount = prev.notFoundCount + 1;
|
|
276
358
|
} else {
|
|
@@ -288,7 +370,7 @@ export function createSuggestionPlugin<T extends SuggestionItem>({
|
|
|
288
370
|
// Make sure to empty the range if suggestion is inactive
|
|
289
371
|
if (!next.active) {
|
|
290
372
|
next.decorationId = null;
|
|
291
|
-
next.range =
|
|
373
|
+
next.range = null;
|
|
292
374
|
next.query = null;
|
|
293
375
|
next.notFoundCount = 0;
|
|
294
376
|
next.items = [];
|
|
@@ -314,12 +396,51 @@ export function createSuggestionPlugin<T extends SuggestionItem>({
|
|
|
314
396
|
// return true to cancel the original event, as we insert / ourselves
|
|
315
397
|
return true;
|
|
316
398
|
}
|
|
317
|
-
|
|
399
|
+
} else {
|
|
400
|
+
const { items, range, selectedItemIndex } = pluginKey.getState(
|
|
401
|
+
view.state
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
if (event.key === "ArrowUp") {
|
|
405
|
+
view.dispatch(
|
|
406
|
+
view.state.tr.setMeta(pluginKey, {
|
|
407
|
+
selectedItemIndexChanged: selectedItemIndex - 1,
|
|
408
|
+
})
|
|
409
|
+
);
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (event.key === "ArrowDown") {
|
|
414
|
+
view.dispatch(
|
|
415
|
+
view.state.tr.setMeta(pluginKey, {
|
|
416
|
+
selectedItemIndexChanged: selectedItemIndex + 1,
|
|
417
|
+
})
|
|
418
|
+
);
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (event.key === "Enter") {
|
|
423
|
+
deactivate(view);
|
|
424
|
+
selectItemCallback({
|
|
425
|
+
item: items[selectedItemIndex],
|
|
426
|
+
editor: editor,
|
|
427
|
+
range: range,
|
|
428
|
+
});
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (event.key === "Escape") {
|
|
433
|
+
deactivate(view);
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
318
436
|
}
|
|
319
437
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
438
|
+
return false;
|
|
439
|
+
},
|
|
440
|
+
|
|
441
|
+
// Hides menu in cases where mouse click does not cause an editor state change.
|
|
442
|
+
handleClick(view) {
|
|
443
|
+
deactivate(view);
|
|
323
444
|
},
|
|
324
445
|
|
|
325
446
|
// Setup decorator on the currently active suggestion.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { EditorElement, ElementFactory } from "../../EditorElement";
|
|
2
|
+
import { SuggestionItem } from "./SuggestionItem";
|
|
3
|
+
|
|
4
|
+
export type SuggestionsMenuStaticParams<T extends SuggestionItem> = {
|
|
5
|
+
itemCallback: (item: T) => void;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type SuggestionsMenuDynamicParams<T extends SuggestionItem> = {
|
|
9
|
+
items: T[];
|
|
10
|
+
selectedItemIndex: number;
|
|
11
|
+
|
|
12
|
+
queryStartBoundingBox: DOMRect;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type SuggestionsMenu<T extends SuggestionItem> = EditorElement<
|
|
16
|
+
SuggestionsMenuDynamicParams<T>
|
|
17
|
+
>;
|
|
18
|
+
export type SuggestionsMenuFactory<T extends SuggestionItem> = ElementFactory<
|
|
19
|
+
SuggestionsMenuStaticParams<T>,
|
|
20
|
+
SuggestionsMenuDynamicParams<T>
|
|
21
|
+
>;
|
|
File without changes
|