@blocknote/core 0.1.2 → 0.2.1-alpha.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.
Files changed (171) hide show
  1. package/README.md +12 -6
  2. package/dist/blocknote.js +1425 -5109
  3. package/dist/blocknote.js.map +1 -1
  4. package/dist/blocknote.umd.cjs +1 -53
  5. package/dist/blocknote.umd.cjs.map +1 -1
  6. package/dist/style.css +1 -1
  7. package/package.json +19 -32
  8. package/src/BlockNoteEditor.ts +54 -0
  9. package/src/BlockNoteExtensions.ts +52 -7
  10. package/src/assets/fonts-inter.css +92 -0
  11. package/src/editor.module.css +37 -0
  12. package/src/extensions/Blocks/BlockAttributes.ts +1 -3
  13. package/src/extensions/Blocks/PreviousBlockTypePlugin.ts +71 -18
  14. package/src/extensions/Blocks/helpers/getBlockInfoFromPos.ts +66 -0
  15. package/src/extensions/Blocks/index.ts +7 -3
  16. package/src/extensions/Blocks/nodes/Block.module.css +116 -74
  17. package/src/extensions/Blocks/nodes/Block.ts +413 -292
  18. package/src/extensions/Blocks/nodes/BlockGroup.ts +6 -6
  19. package/src/extensions/Blocks/nodes/BlockTypes/HeadingBlock/HeadingContent.ts +84 -0
  20. package/src/extensions/Blocks/nodes/BlockTypes/ListItemBlock/ListItemContent.ts +177 -0
  21. package/src/extensions/Blocks/nodes/BlockTypes/ListItemBlock/OrderedListItemIndexPlugin.ts +77 -0
  22. package/src/extensions/Blocks/nodes/BlockTypes/TextBlock/TextContent.ts +34 -0
  23. package/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.ts +20 -0
  24. package/src/extensions/DraggableBlocks/DraggableBlocksExtension.ts +27 -9
  25. package/src/extensions/DraggableBlocks/{DraggableBlocksPlugin.tsx → DraggableBlocksPlugin.ts} +227 -147
  26. package/src/extensions/FormattingToolbar/FormattingToolbarExtension.ts +29 -0
  27. package/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.ts +35 -0
  28. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +308 -0
  29. package/src/extensions/HyperlinkToolbar/HyperlinkMark.ts +28 -0
  30. package/src/extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes.ts +19 -0
  31. package/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.ts +251 -0
  32. package/src/extensions/Placeholder/PlaceholderExtension.ts +5 -2
  33. package/src/extensions/SlashMenu/SlashMenuExtension.ts +9 -1
  34. package/src/extensions/SlashMenu/SlashMenuItem.ts +1 -3
  35. package/src/extensions/SlashMenu/defaultCommands.tsx +33 -22
  36. package/src/extensions/TrailingNode/TrailingNodeExtension.ts +4 -4
  37. package/src/extensions/UniqueID/UniqueID.ts +14 -1
  38. package/src/index.ts +8 -4
  39. package/src/shared/EditorElement.ts +10 -0
  40. package/src/shared/plugins/suggestion/SuggestionItem.ts +1 -8
  41. package/src/shared/plugins/suggestion/SuggestionPlugin.ts +222 -101
  42. package/src/shared/plugins/suggestion/SuggestionsMenuFactoryTypes.ts +21 -0
  43. package/src/{utils.ts → shared/utils.ts} +0 -0
  44. package/types/src/BlockNoteEditor.d.ts +13 -0
  45. package/types/src/BlockNoteExtensions.d.ts +12 -1
  46. package/types/src/EditorElement.d.ts +7 -0
  47. package/types/src/extensions/Blocks/PreviousBlockTypePlugin.d.ts +1 -1
  48. package/types/src/extensions/Blocks/helpers/getBlockInfoFromPos.d.ts +19 -0
  49. package/types/src/extensions/Blocks/nodes/Block.d.ts +11 -19
  50. package/types/src/extensions/Blocks/nodes/BlockTypes/HeadingBlock/HeadingContent.d.ts +8 -0
  51. package/types/src/extensions/Blocks/nodes/BlockTypes/ListItemBlock/ListItemContent.d.ts +8 -0
  52. package/types/src/extensions/Blocks/nodes/BlockTypes/ListItemBlock/OrderedListItemIndexPlugin.d.ts +2 -0
  53. package/types/src/extensions/Blocks/nodes/BlockTypes/TextBlock/TextContent.d.ts +6 -0
  54. package/types/src/extensions/BubbleMenu/BubbleMenuExtension.d.ts +4 -1
  55. package/types/src/extensions/BubbleMenu/BubbleMenuFactoryTypes.d.ts +27 -0
  56. package/types/src/extensions/BubbleMenu/BubbleMenuPlugin.d.ts +10 -12
  57. package/types/src/extensions/DraggableBlocks/BlockMenuFactoryTypes.d.ts +12 -0
  58. package/types/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.d.ts +14 -0
  59. package/types/src/extensions/DraggableBlocks/DragMenuFactoryTypes.d.ts +18 -0
  60. package/types/src/extensions/DraggableBlocks/DraggableBlocksExtension.d.ts +9 -3
  61. package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +23 -1
  62. package/types/src/extensions/FormattingToolbar/FormattingToolbarExtension.d.ts +8 -0
  63. package/types/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.d.ts +23 -0
  64. package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +43 -0
  65. package/types/src/extensions/HyperlinkToolbar/HyperlinkMark.d.ts +8 -0
  66. package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes.d.ts +12 -0
  67. package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.d.ts +11 -0
  68. package/types/src/extensions/Hyperlinks/HyperlinkMark.d.ts +2 -1
  69. package/types/src/extensions/Hyperlinks/HyperlinkMenuFactoryTypes.d.ts +11 -0
  70. package/types/src/extensions/Hyperlinks/HyperlinkMenuPlugin.d.ts +10 -1
  71. package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +3 -1
  72. package/types/src/extensions/SlashMenu/SlashMenuItem.d.ts +2 -4
  73. package/types/src/index.d.ts +8 -3
  74. package/types/src/shared/EditorElement.d.ts +6 -0
  75. package/types/src/shared/plugins/suggestion/SuggestionItem.d.ts +1 -6
  76. package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +15 -10
  77. package/types/src/shared/plugins/suggestion/SuggestionsMenuFactoryTypes.d.ts +12 -0
  78. package/types/src/shared/utils.d.ts +2 -0
  79. package/types/src/utils.d.ts +2 -2
  80. package/src/BlockNoteTheme.ts +0 -150
  81. package/src/EditorContent.tsx +0 -2
  82. package/src/extensions/Blocks/OrderedListPlugin.ts +0 -46
  83. package/src/extensions/Blocks/commands/joinBackward.ts +0 -274
  84. package/src/extensions/Blocks/helpers/setBlockHeading.ts +0 -30
  85. package/src/extensions/Blocks/nodes/Content.ts +0 -63
  86. package/src/extensions/Blocks/rule.ts +0 -48
  87. package/src/extensions/BubbleMenu/BubbleMenuExtension.tsx +0 -36
  88. package/src/extensions/BubbleMenu/BubbleMenuPlugin.ts +0 -245
  89. package/src/extensions/BubbleMenu/component/BubbleMenu.tsx +0 -240
  90. package/src/extensions/BubbleMenu/component/LinkToolbarButton.tsx +0 -67
  91. package/src/extensions/DraggableBlocks/components/DragHandle.tsx +0 -102
  92. package/src/extensions/DraggableBlocks/components/DragHandleMenu.tsx +0 -19
  93. package/src/extensions/Hyperlinks/HyperlinkMark.tsx +0 -16
  94. package/src/extensions/Hyperlinks/HyperlinkMenuPlugin.tsx +0 -165
  95. package/src/extensions/Hyperlinks/menus/EditHyperlinkMenu.tsx +0 -44
  96. package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItem.tsx +0 -34
  97. package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemIcon.tsx +0 -31
  98. package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemInput.tsx +0 -40
  99. package/src/extensions/Hyperlinks/menus/HoverHyperlinkMenu.tsx +0 -37
  100. package/src/extensions/Hyperlinks/menus/HyperlinkMenu.tsx +0 -63
  101. package/src/fonts-inter.css +0 -94
  102. package/src/globals.css +0 -28
  103. package/src/root.module.css +0 -19
  104. package/src/shared/components/toolbar/Toolbar.tsx +0 -10
  105. package/src/shared/components/toolbar/ToolbarButton.tsx +0 -57
  106. package/src/shared/components/toolbar/ToolbarDropdown.tsx +0 -35
  107. package/src/shared/components/toolbar/ToolbarDropdownItem.tsx +0 -35
  108. package/src/shared/components/toolbar/ToolbarDropdownTarget.tsx +0 -31
  109. package/src/shared/components/tooltip/TooltipContent.module.css +0 -15
  110. package/src/shared/components/tooltip/TooltipContent.tsx +0 -23
  111. package/src/shared/hooks/useEditorForceUpdate.tsx +0 -30
  112. package/src/shared/plugins/suggestion/SuggestionListReactRenderer.tsx +0 -236
  113. package/src/shared/plugins/suggestion/components/SuggestionGroup.tsx +0 -47
  114. package/src/shared/plugins/suggestion/components/SuggestionGroupItem.tsx +0 -82
  115. package/src/shared/plugins/suggestion/components/SuggestionList.tsx +0 -92
  116. package/src/useEditor.ts +0 -51
  117. package/types/src/BlockNoteTheme.d.ts +0 -2
  118. package/types/src/EditorContent.d.ts +0 -1
  119. package/types/src/commands/indentation.d.ts +0 -2
  120. package/types/src/extensions/Blocks/OrderedListPlugin.d.ts +0 -2
  121. package/types/src/extensions/Blocks/commands/joinBackward.d.ts +0 -14
  122. package/types/src/extensions/Blocks/helpers/setBlockHeading.d.ts +0 -5
  123. package/types/src/extensions/Blocks/nodes/Content.d.ts +0 -5
  124. package/types/src/extensions/Blocks/rule.d.ts +0 -16
  125. package/types/src/extensions/BubbleMenu/component/BubbleMenu.d.ts +0 -5
  126. package/types/src/extensions/BubbleMenu/component/DropdownBlockItem.d.ts +0 -10
  127. package/types/src/extensions/BubbleMenu/component/LinkToolbarButton.d.ts +0 -11
  128. package/types/src/extensions/DraggableBlocks/components/DragHandle.d.ts +0 -12
  129. package/types/src/extensions/DraggableBlocks/components/DragHandleMenu.d.ts +0 -6
  130. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenu.d.ts +0 -11
  131. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItem.d.ts +0 -13
  132. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemIcon.d.ts +0 -8
  133. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemInput.d.ts +0 -9
  134. package/types/src/extensions/Hyperlinks/menus/HoverHyperlinkMenu.d.ts +0 -12
  135. package/types/src/extensions/Hyperlinks/menus/HyperlinkBasicMenu.d.ts +0 -12
  136. package/types/src/extensions/Hyperlinks/menus/HyperlinkEditMenu.d.ts +0 -10
  137. package/types/src/extensions/Hyperlinks/menus/HyperlinkMenu.d.ts +0 -21
  138. package/types/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInput.d.ts +0 -39
  139. package/types/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInputStyles.d.ts +0 -1
  140. package/types/src/extensions/Hyperlinks/menus/atlaskit/ToolbarComponent.d.ts +0 -11
  141. package/types/src/extensions/Hyperlinks/menus/helpers/PanelTextInput.d.ts +0 -39
  142. package/types/src/extensions/Hyperlinks/menus/helpers/PanelTextInputStyles.d.ts +0 -3
  143. package/types/src/extensions/Hyperlinks/menus/helpers/ToolbarComponent.d.ts +0 -13
  144. package/types/src/extensions/helpers/formatKeyboardShortcut.d.ts +0 -1
  145. package/types/src/lib/atlaskit/browser.d.ts +0 -12
  146. package/types/src/nodes/ChildgroupNode.d.ts +0 -28
  147. package/types/src/nodes/patchNodes.d.ts +0 -1
  148. package/types/src/plugins/TreeViewPlugin/index.d.ts +0 -2
  149. package/types/src/plugins/animation.d.ts +0 -2
  150. package/types/src/react/BlockNoteComposer.d.ts +0 -17
  151. package/types/src/react/BlockNotePlugin.d.ts +0 -1
  152. package/types/src/react/index.d.ts +0 -3
  153. package/types/src/react/useBlockNoteSetup.d.ts +0 -2
  154. package/types/src/registerBlockNote.d.ts +0 -2
  155. package/types/src/shared/components/toolbar/SimpleToolbarButton.d.ts +0 -15
  156. package/types/src/shared/components/toolbar/SimpleToolbarDropdown.d.ts +0 -11
  157. package/types/src/shared/components/toolbar/SimpleToolbarDropdownItem.d.ts +0 -11
  158. package/types/src/shared/components/toolbar/Toolbar.d.ts +0 -4
  159. package/types/src/shared/components/toolbar/ToolbarButton.d.ts +0 -15
  160. package/types/src/shared/components/toolbar/ToolbarDropdown.d.ts +0 -17
  161. package/types/src/shared/components/toolbar/ToolbarDropdownItem.d.ts +0 -11
  162. package/types/src/shared/components/toolbar/ToolbarDropdownTarget.d.ts +0 -8
  163. package/types/src/shared/components/toolbar/ToolbarSeparator.d.ts +0 -2
  164. package/types/src/shared/components/tooltip/TooltipContent.d.ts +0 -15
  165. package/types/src/shared/hooks/useEditorForceUpdate.d.ts +0 -2
  166. package/types/src/shared/plugins/suggestion/SuggestionListReactRenderer.d.ts +0 -71
  167. package/types/src/shared/plugins/suggestion/components/SuggestionGroup.d.ts +0 -23
  168. package/types/src/shared/plugins/suggestion/components/SuggestionGroupItem.d.ts +0 -9
  169. package/types/src/shared/plugins/suggestion/components/SuggestionList.d.ts +0 -11
  170. package/types/src/themes/BlockNoteEditorTheme.d.ts +0 -11
  171. 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
- .addNewBlockAsSibling({ headingType: "1" })
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
- .addNewBlockAsSibling({ headingType: "2" })
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
- .addNewBlockAsSibling({ headingType: "3" })
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
- .addNewBlockAsSibling({ listType: "oli" })
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
- bulletlist: new SlashMenuItem(
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
- .addNewBlockAsSibling({ listType: "li" })
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
- .addNewBlockAsSibling()
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 contenttype = schema.nodes["content"];
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, contenttype.create())
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 !== "blockgroup") {
59
- throw new Error("Expected blockgroup");
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: () => v4(),
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
  },
package/src/index.ts CHANGED
@@ -1,5 +1,9 @@
1
- import "./globals.css";
2
-
1
+ export * from "./BlockNoteEditor";
3
2
  export * from "./BlockNoteExtensions";
4
- export * from "./EditorContent";
5
- export * from "./useEditor";
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 default interface SuggestionItem {
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, groupBy } from "lodash";
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 SuggestionItem from "./SuggestionItem";
7
-
8
- import createRenderer, {
9
- SuggestionRendererProps,
10
- } from "./SuggestionListReactRenderer";
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 renderer = createRenderer<T>(editor);
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
- filterTransaction(transaction) {
120
- // prevent blurring when clicking with the mouse inside the popup menu
121
- const blurMeta = transaction.getMeta("blur");
122
- if (blurMeta?.event.relatedTarget) {
123
- const c = renderer.getComponent();
124
- if (c?.contains(blurMeta.event.relatedTarget)) {
125
- return false;
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: {} as any, // TODO
206
- query: null as string | 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 as string | 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, _newState) {
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.from) {
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.to > prev.range.to) {
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
- return false;
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
- // pass the key event onto the renderer (to handle arrow keys, enter and escape)
321
- // return true if the event got handled by the renderer or false otherwise
322
- return renderer.onKeyDown?.(event) || false;
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
@@ -0,0 +1,13 @@
1
+ import { Editor, EditorOptions } from "@tiptap/core";
2
+ import { UiFactories } from "./BlockNoteExtensions";
3
+ export declare type BlockNoteEditorOptions = EditorOptions & {
4
+ enableBlockNoteExtensions: boolean;
5
+ disableHistoryExtension: boolean;
6
+ uiFactories: UiFactories;
7
+ };
8
+ export declare class BlockNoteEditor {
9
+ readonly tiptapEditor: Editor & {
10
+ contentComponent: any;
11
+ };
12
+ constructor(options?: Partial<BlockNoteEditorOptions>);
13
+ }