@blocknote/core 0.1.0-alpha.3 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +4 -2
  3. package/dist/blocknote.js +3461 -2429
  4. package/dist/blocknote.js.map +1 -1
  5. package/dist/blocknote.umd.cjs +35 -71
  6. package/dist/blocknote.umd.cjs.map +1 -1
  7. package/dist/style.css +1 -1
  8. package/package.json +9 -7
  9. package/src/BlockNoteExtensions.ts +10 -17
  10. package/src/BlockNoteTheme.ts +150 -0
  11. package/src/EditorContent.tsx +2 -1
  12. package/src/extensions/Blocks/BlockAttributes.ts +12 -0
  13. package/src/extensions/Blocks/MultipleNodeSelection.ts +87 -0
  14. package/src/extensions/Blocks/OrderedListPlugin.ts +2 -2
  15. package/src/extensions/Blocks/PreviousBlockTypePlugin.ts +8 -2
  16. package/src/extensions/Blocks/helpers/findBlock.ts +1 -1
  17. package/src/extensions/Blocks/nodes/Block.module.css +37 -37
  18. package/src/extensions/Blocks/nodes/Block.ts +89 -45
  19. package/src/extensions/Blocks/nodes/BlockGroup.ts +19 -2
  20. package/src/extensions/Blocks/nodes/Content.ts +15 -2
  21. package/src/extensions/BubbleMenu/BubbleMenuExtension.tsx +10 -2
  22. package/src/extensions/BubbleMenu/component/BubbleMenu.tsx +122 -98
  23. package/src/extensions/BubbleMenu/component/LinkToolbarButton.tsx +8 -8
  24. package/src/extensions/DraggableBlocks/DraggableBlocksPlugin.tsx +143 -33
  25. package/src/extensions/DraggableBlocks/components/DragHandle.tsx +15 -21
  26. package/src/extensions/DraggableBlocks/components/DragHandleMenu.tsx +8 -7
  27. package/src/extensions/Hyperlinks/HyperlinkMenuPlugin.tsx +31 -66
  28. package/src/extensions/Hyperlinks/menus/EditHyperlinkMenu.tsx +44 -0
  29. package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItem.tsx +34 -0
  30. package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemIcon.tsx +31 -0
  31. package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemInput.tsx +40 -0
  32. package/src/extensions/Hyperlinks/menus/HoverHyperlinkMenu.tsx +37 -0
  33. package/src/extensions/Hyperlinks/menus/HyperlinkMenu.tsx +63 -0
  34. package/src/extensions/SlashMenu/SlashMenuItem.ts +3 -1
  35. package/src/extensions/SlashMenu/defaultCommands.tsx +4 -4
  36. package/src/extensions/TrailingNode/TrailingNodeExtension.ts +8 -5
  37. package/src/shared/components/toolbar/Toolbar.tsx +8 -3
  38. package/src/shared/components/toolbar/ToolbarButton.tsx +57 -0
  39. package/src/shared/components/toolbar/ToolbarDropdown.tsx +35 -0
  40. package/src/shared/components/toolbar/ToolbarDropdownItem.tsx +35 -0
  41. package/src/shared/components/toolbar/ToolbarDropdownTarget.tsx +31 -0
  42. package/src/shared/plugins/suggestion/SuggestionItem.ts +3 -1
  43. package/src/shared/plugins/suggestion/{SuggestionListReactRenderer.ts → SuggestionListReactRenderer.tsx} +13 -4
  44. package/src/shared/plugins/suggestion/components/SuggestionGroup.tsx +6 -93
  45. package/src/shared/plugins/suggestion/components/SuggestionGroupItem.tsx +82 -0
  46. package/src/shared/plugins/suggestion/components/SuggestionList.tsx +24 -23
  47. package/src/useEditor.ts +4 -0
  48. package/src/utils.ts +12 -0
  49. package/types/src/BlockNoteExtensions.d.ts +3 -0
  50. package/types/src/BlockNoteTheme.d.ts +2 -0
  51. package/types/src/commands/indentation.d.ts +2 -0
  52. package/types/src/extensions/Blocks/BlockAttributes.d.ts +2 -0
  53. package/types/src/extensions/Blocks/MultipleNodeSelection.d.ts +24 -0
  54. package/types/src/extensions/Blocks/nodes/Block.d.ts +1 -1
  55. package/types/src/extensions/BubbleMenu/component/LinkToolbarButton.d.ts +2 -2
  56. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenu.d.ts +11 -0
  57. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItem.d.ts +13 -0
  58. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemIcon.d.ts +8 -0
  59. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemInput.d.ts +9 -0
  60. package/types/src/extensions/Hyperlinks/menus/HoverHyperlinkMenu.d.ts +12 -0
  61. package/types/src/extensions/Hyperlinks/menus/HyperlinkMenu.d.ts +21 -0
  62. package/types/src/extensions/Hyperlinks/menus/helpers/PanelTextInput.d.ts +39 -0
  63. package/types/src/extensions/Hyperlinks/menus/helpers/PanelTextInputStyles.d.ts +3 -0
  64. package/types/src/extensions/Hyperlinks/menus/helpers/ToolbarComponent.d.ts +13 -0
  65. package/types/src/extensions/SlashMenu/SlashMenuItem.d.ts +4 -7
  66. package/types/src/extensions/TrailingNode/TrailingNodeExtension.d.ts +3 -0
  67. package/types/src/nodes/ChildgroupNode.d.ts +28 -0
  68. package/types/src/nodes/patchNodes.d.ts +1 -0
  69. package/types/src/plugins/TreeViewPlugin/index.d.ts +2 -0
  70. package/types/src/plugins/animation.d.ts +2 -0
  71. package/types/src/react/BlockNoteComposer.d.ts +17 -0
  72. package/types/src/react/BlockNotePlugin.d.ts +1 -0
  73. package/types/src/react/index.d.ts +3 -0
  74. package/types/src/react/useBlockNoteSetup.d.ts +2 -0
  75. package/types/src/registerBlockNote.d.ts +2 -0
  76. package/types/src/shared/components/toolbar/SimpleToolbarButton.d.ts +2 -3
  77. package/types/src/shared/components/toolbar/SimpleToolbarDropdown.d.ts +11 -0
  78. package/types/src/shared/components/toolbar/SimpleToolbarDropdownItem.d.ts +11 -0
  79. package/types/src/shared/components/toolbar/Toolbar.d.ts +2 -2
  80. package/types/src/shared/components/toolbar/ToolbarButton.d.ts +15 -0
  81. package/types/src/shared/components/toolbar/ToolbarDropdown.d.ts +17 -0
  82. package/types/src/shared/components/toolbar/ToolbarDropdownItem.d.ts +11 -0
  83. package/types/src/shared/components/toolbar/ToolbarDropdownTarget.d.ts +8 -0
  84. package/types/src/shared/plugins/suggestion/SuggestionItem.d.ts +2 -4
  85. package/types/src/shared/plugins/suggestion/components/SuggestionGroupItem.d.ts +9 -0
  86. package/types/src/shared/plugins/suggestion/components/SuggestionList.d.ts +0 -15
  87. package/types/src/themes/BlockNoteEditorTheme.d.ts +11 -0
  88. package/types/src/useEditor.d.ts +3 -0
  89. package/types/src/utils.d.ts +2 -0
  90. package/src/extensions/Blocks/nodes/README.md +0 -26
  91. package/src/extensions/BubbleMenu/component/DropdownBlockItem.module.css +0 -13
  92. package/src/extensions/BubbleMenu/component/DropdownBlockItem.tsx +0 -25
  93. package/src/extensions/DraggableBlocks/components/DragHandle.module.css +0 -33
  94. package/src/extensions/DraggableBlocks/components/DragHandleMenu.module.css +0 -10
  95. package/src/extensions/Hyperlinks/menus/HyperlinkBasicMenu.tsx +0 -59
  96. package/src/extensions/Hyperlinks/menus/HyperlinkEditMenu.tsx +0 -72
  97. package/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInput.tsx +0 -173
  98. package/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInputStyles.ts +0 -36
  99. package/src/extensions/Hyperlinks/menus/atlaskit/README.md +0 -1
  100. package/src/extensions/Hyperlinks/menus/atlaskit/ToolbarComponent.tsx +0 -61
  101. package/src/extensions/helpers/formatKeyboardShortcut.ts +0 -9
  102. package/src/lib/atlaskit/browser.ts +0 -47
  103. package/src/shared/components/toolbar/SimpleToolbarButton.module.css +0 -13
  104. package/src/shared/components/toolbar/SimpleToolbarButton.tsx +0 -56
  105. package/src/shared/components/toolbar/Toolbar.module.css +0 -10
  106. package/src/shared/components/toolbar/ToolbarSeparator.module.css +0 -13
  107. package/src/shared/components/toolbar/ToolbarSeparator.tsx +0 -7
  108. package/src/shared/plugins/suggestion/components/SuggestionGroup.module.css +0 -45
  109. package/src/shared/plugins/suggestion/components/SuggestionList.module.css +0 -10
@@ -7,12 +7,13 @@ import { OrderedListPlugin } from "../OrderedListPlugin";
7
7
  import { PreviousBlockTypePlugin } from "../PreviousBlockTypePlugin";
8
8
  import { textblockTypeInputRuleSameNodeType } from "../rule";
9
9
  import styles from "./Block.module.css";
10
+ import BlockAttributes from "../BlockAttributes";
10
11
 
11
12
  export interface IBlock {
12
13
  HTMLAttributes: Record<string, any>;
13
14
  }
14
15
 
15
- export type Level = 1 | 2 | 3;
16
+ export type Level = "1" | "2" | "3";
16
17
  export type ListType = "li" | "oli";
17
18
 
18
19
  declare module "@tiptap/core" {
@@ -43,7 +44,7 @@ declare module "@tiptap/core" {
43
44
  * The main "Block node" documents consist of
44
45
  */
45
46
  export const Block = Node.create<IBlock>({
46
- name: "tcblock",
47
+ name: "block",
47
48
  group: "block",
48
49
  addOptions() {
49
50
  return {
@@ -52,7 +53,7 @@ export const Block = Node.create<IBlock>({
52
53
  },
53
54
 
54
55
  // A block always contains content, and optionally a blockGroup which contains nested blocks
55
- content: "tccontent blockgroup?",
56
+ content: "content blockgroup?",
56
57
 
57
58
  defining: true,
58
59
 
@@ -60,63 +61,107 @@ export const Block = Node.create<IBlock>({
60
61
  return {
61
62
  listType: {
62
63
  default: undefined,
63
- renderHTML: (attributes) => {
64
- return {
65
- "data-listType": attributes.listType,
66
- };
67
- },
68
- parseHTML: (element) => element.getAttribute("data-listType"),
69
64
  },
70
65
  blockColor: {
71
66
  default: undefined,
72
- renderHTML: (attributes) => {
73
- return {
74
- "data-blockColor": attributes.blockColor,
75
- };
76
- },
77
- parseHTML: (element) => element.getAttribute("data-blockColor"),
78
67
  },
79
68
  blockStyle: {
80
69
  default: undefined,
81
- renderHTML: (attributes) => {
82
- return {
83
- "data-blockStyle": attributes.blockStyle,
84
- };
85
- },
86
- parseHTML: (element) => element.getAttribute("data-blockStyle"),
87
70
  },
88
71
  headingType: {
89
72
  default: undefined,
90
73
  keepOnSplit: false,
91
- renderHTML: (attributes) => {
92
- return {
93
- "data-headingType": attributes.headingType,
94
- };
95
- },
96
- parseHTML: (element) => element.getAttribute("data-headingType"),
97
74
  },
98
75
  };
99
76
  },
100
77
 
101
- // TODO: should we parse <li>, <ol>, <h1>, etc?
102
78
  parseHTML() {
103
79
  return [
80
+ // For parsing blocks within the editor.
104
81
  {
105
82
  tag: "div",
83
+ getAttrs: (element) => {
84
+ if (typeof element === "string") {
85
+ return false;
86
+ }
87
+
88
+ const attrs: Record<string, string> = {};
89
+ for (let [nodeAttr, HTMLAttr] of Object.entries(BlockAttributes)) {
90
+ if (element.getAttribute(HTMLAttr)) {
91
+ attrs[nodeAttr] = element.getAttribute(HTMLAttr)!;
92
+ }
93
+ }
94
+
95
+ if (element.getAttribute("data-node-type") === "block") {
96
+ return attrs;
97
+ }
98
+
99
+ return false;
100
+ },
101
+ },
102
+ // For parsing headings & paragraphs copied from outside the editor.
103
+ {
104
+ tag: "p",
105
+ priority: 100,
106
+ },
107
+ {
108
+ tag: "h1",
109
+ attrs: { headingType: "1" },
110
+ },
111
+ {
112
+ tag: "h2",
113
+ attrs: { headingType: "2" },
114
+ },
115
+ {
116
+ tag: "h3",
117
+ attrs: { headingType: "3" },
118
+ },
119
+ // For parsing list items copied from outside the editor.
120
+ {
121
+ tag: "li",
122
+ getAttrs: (element) => {
123
+ if (typeof element === "string") {
124
+ return false;
125
+ }
126
+
127
+ const parent = element.parentElement;
128
+
129
+ if (parent === null) {
130
+ return false;
131
+ }
132
+
133
+ // Gets type of list item (ordered/unordered) based on parent element's tag ("ol"/"ul").
134
+ if (parent.tagName === "UL") {
135
+ return { listType: "li" };
136
+ }
137
+
138
+ if (parent.tagName === "OL") {
139
+ return { listType: "oli" };
140
+ }
141
+
142
+ return false;
143
+ },
106
144
  },
107
145
  ];
108
146
  },
109
147
 
110
148
  renderHTML({ HTMLAttributes }) {
149
+ const attrs: Record<string, string> = {};
150
+ for (let [nodeAttr, HTMLAttr] of Object.entries(BlockAttributes)) {
151
+ attrs[HTMLAttr] = HTMLAttributes[nodeAttr];
152
+ }
153
+
111
154
  return [
112
155
  "div",
113
- mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
156
+ mergeAttributes(attrs, {
114
157
  class: styles.blockOuter,
158
+ "data-node-type": "block-outer",
115
159
  }),
116
160
  [
117
161
  "div",
118
- mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
162
+ mergeAttributes(attrs, {
119
163
  class: styles.block,
164
+ "data-node-type": "block",
120
165
  }),
121
166
  0,
122
167
  ],
@@ -125,7 +170,7 @@ export const Block = Node.create<IBlock>({
125
170
 
126
171
  addInputRules() {
127
172
  return [
128
- ...[1, 2, 3].map((level) => {
173
+ ...["1", "2", "3"].map((level) => {
129
174
  // Create a heading when starting with "#", "##", or "###""
130
175
  return textblockTypeInputRuleSameNodeType({
131
176
  find: new RegExp(`^(#{1,${level}})\\s$`),
@@ -172,7 +217,7 @@ export const Block = Node.create<IBlock>({
172
217
  const nodePos = tr.selection.$anchor.posAtIndex(0, -1) - 1;
173
218
 
174
219
  // const node2 = tr.doc.nodeAt(nodePos);
175
- if (node.type.name === "tcblock" && node.attrs["listType"]) {
220
+ if (node.type.name === "block" && node.attrs["listType"]) {
176
221
  if (dispatch) {
177
222
  tr.setNodeMarkup(nodePos, undefined, {
178
223
  ...node.attrs,
@@ -203,8 +248,7 @@ export const Block = Node.create<IBlock>({
203
248
 
204
249
  // Create new block after current block
205
250
  const endOfBlock = currentBlock.pos + currentBlock.node.nodeSize;
206
- let newBlock =
207
- state.schema.nodes["tcblock"].createAndFill(attributes)!;
251
+ let newBlock = state.schema.nodes["block"].createAndFill(attributes)!;
208
252
  if (dispatch) {
209
253
  tr.insert(endOfBlock, newBlock);
210
254
  tr.setSelection(new TextSelection(tr.doc.resolve(endOfBlock + 1)));
@@ -218,7 +262,7 @@ export const Block = Node.create<IBlock>({
218
262
  const nodePos = tr.selection.$anchor.posAtIndex(0, -1) - 1;
219
263
 
220
264
  // const node2 = tr.doc.nodeAt(nodePos);
221
- if (node.type.name === "tcblock") {
265
+ if (node.type.name === "block") {
222
266
  if (dispatch) {
223
267
  tr.setNodeMarkup(nodePos, undefined, {
224
268
  ...node.attrs,
@@ -268,11 +312,11 @@ export const Block = Node.create<IBlock>({
268
312
  commands.command(({ tr }) => {
269
313
  const isAtStartOfNode = tr.selection.$anchor.parentOffset === 0;
270
314
  const node = tr.selection.$anchor.node(-1);
271
- if (isAtStartOfNode && node.type.name === "tcblock") {
315
+ if (isAtStartOfNode && node.type.name === "block") {
272
316
  // we're at the start of the block, so we're trying to "backspace" the bullet or indentation
273
317
  return commands.first([
274
318
  () => commands.unsetList(), // first try to remove the "list" property
275
- () => commands.liftListItem("tcblock"), // then try to remove a level of indentation
319
+ () => commands.liftListItem("block"), // then try to remove a level of indentation
276
320
  ]);
277
321
  }
278
322
  return false;
@@ -292,7 +336,7 @@ export const Block = Node.create<IBlock>({
292
336
  const isAtStartOfNode = tr.selection.$anchor.parentOffset === 0;
293
337
  const anchor = tr.selection.$anchor;
294
338
  const node = anchor.node(-1);
295
- if (isAtStartOfNode && node.type.name === "tcblock") {
339
+ if (isAtStartOfNode && node.type.name === "block") {
296
340
  if (node.childCount === 2) {
297
341
  // BlockB has children. We want to go from this:
298
342
  //
@@ -337,7 +381,7 @@ export const Block = Node.create<IBlock>({
337
381
  const handleEnter = () =>
338
382
  this.editor.commands.first(({ commands }) => [
339
383
  // Try to split the current block into 2 items:
340
- () => commands.splitListItem("tcblock"),
384
+ () => commands.splitListItem("block"),
341
385
  // Otherwise, maybe we are in an empty list item. "Enter" should remove the list bullet
342
386
  ({ tr, dispatch }) => {
343
387
  const $from = tr.selection.$from;
@@ -348,7 +392,7 @@ export const Block = Node.create<IBlock>({
348
392
  const node = tr.selection.$anchor.node(-1);
349
393
  const nodePos = tr.selection.$anchor.posAtIndex(0, -1) - 1;
350
394
 
351
- if (node.type.name === "tcblock" && node.attrs["listType"]) {
395
+ if (node.type.name === "block" && node.attrs["listType"]) {
352
396
  if (dispatch) {
353
397
  tr.setNodeMarkup(nodePos, undefined, {
354
398
  ...node.attrs,
@@ -373,15 +417,15 @@ export const Block = Node.create<IBlock>({
373
417
  return {
374
418
  Backspace: handleBackspace,
375
419
  Enter: handleEnter,
376
- Tab: () => this.editor.commands.sinkListItem("tcblock"),
420
+ Tab: () => this.editor.commands.sinkListItem("block"),
377
421
  "Shift-Tab": () => {
378
- return this.editor.commands.liftListItem("tcblock");
422
+ return this.editor.commands.liftListItem("block");
379
423
  },
380
424
  "Mod-Alt-0": () =>
381
425
  this.editor.chain().unsetList().unsetBlockHeading().run(),
382
- "Mod-Alt-1": () => this.editor.commands.setBlockHeading({ level: 1 }),
383
- "Mod-Alt-2": () => this.editor.commands.setBlockHeading({ level: 2 }),
384
- "Mod-Alt-3": () => this.editor.commands.setBlockHeading({ level: 3 }),
426
+ "Mod-Alt-1": () => this.editor.commands.setBlockHeading({ level: "1" }),
427
+ "Mod-Alt-2": () => this.editor.commands.setBlockHeading({ level: "2" }),
428
+ "Mod-Alt-3": () => this.editor.commands.setBlockHeading({ level: "3" }),
385
429
  "Mod-Shift-7": () => this.editor.commands.setBlockList("li"),
386
430
  "Mod-Shift-8": () => this.editor.commands.setBlockList("oli"),
387
431
  // TODO: Add shortcuts for numbered and bullet list
@@ -10,10 +10,26 @@ export const BlockGroup = Node.create({
10
10
  };
11
11
  },
12
12
 
13
- content: "tcblock+",
13
+ content: "block+",
14
14
 
15
15
  parseHTML() {
16
- return [{ tag: "div" }];
16
+ return [
17
+ {
18
+ tag: "div",
19
+ getAttrs: (element) => {
20
+ if(typeof element === "string") {
21
+ return false;
22
+ }
23
+
24
+ if(element.getAttribute("data-node-type") === "block-group") {
25
+ // Null means the element matches, but we don't want to add any attributes to the node.
26
+ return null;
27
+ }
28
+
29
+ return false;
30
+ }
31
+ }
32
+ ];
17
33
  },
18
34
 
19
35
  renderHTML({ HTMLAttributes }) {
@@ -21,6 +37,7 @@ export const BlockGroup = Node.create({
21
37
  "div",
22
38
  mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
23
39
  class: styles.blockGroup,
40
+ "data-node-type": "block-group"
24
41
  }),
25
42
  0,
26
43
  ];
@@ -5,7 +5,7 @@ export interface IBlock {
5
5
  }
6
6
 
7
7
  export const ContentBlock = Node.create<IBlock>({
8
- name: "tccontent",
8
+ name: "content",
9
9
 
10
10
  addOptions() {
11
11
  return {
@@ -32,7 +32,19 @@ export const ContentBlock = Node.create<IBlock>({
32
32
  return [
33
33
  {
34
34
  tag: "div",
35
- },
35
+ getAttrs: (element) => {
36
+ if(typeof element === "string") {
37
+ return false;
38
+ }
39
+
40
+ if(element.getAttribute("data-node-type") === "block-content") {
41
+ // Null means the element matches, but we don't want to add any attributes to the node.
42
+ return null;
43
+ }
44
+
45
+ return false;
46
+ }
47
+ }
36
48
  ];
37
49
  },
38
50
 
@@ -41,6 +53,7 @@ export const ContentBlock = Node.create<IBlock>({
41
53
  "div",
42
54
  mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
43
55
  class: styles.blockContent,
56
+ "data-node-type": "block-content"
44
57
  }),
45
58
  // TODO: The extra nested div is only needed for placeholders, different solution (without extra div) would be preferable
46
59
  // We can't use the other div because the ::before attribute on that one is already reserved for list-bullets
@@ -1,9 +1,12 @@
1
+ import { MantineProvider } from "@mantine/core";
1
2
  import { Extension } from "@tiptap/core";
2
3
  import { PluginKey } from "prosemirror-state";
3
4
  import ReactDOM from "react-dom";
5
+ import { BlockNoteTheme } from "../../BlockNoteTheme";
6
+ import rootStyles from "../../root.module.css";
4
7
  import { createBubbleMenuPlugin } from "./BubbleMenuPlugin";
5
8
  import { BubbleMenu } from "./component/BubbleMenu";
6
- import rootStyles from "../../root.module.css";
9
+
7
10
  /**
8
11
  * The menu that is displayed when selecting a piece of text.
9
12
  */
@@ -13,7 +16,12 @@ export const BubbleMenuExtension = Extension.create<{}>({
13
16
  addProseMirrorPlugins() {
14
17
  const element = document.createElement("div");
15
18
  element.className = rootStyles.bnRoot;
16
- ReactDOM.render(<BubbleMenu editor={this.editor} />, element);
19
+ ReactDOM.render(
20
+ <MantineProvider theme={BlockNoteTheme}>
21
+ <BubbleMenu editor={this.editor} />
22
+ </MantineProvider>,
23
+ element
24
+ );
17
25
  return [
18
26
  createBubbleMenuPlugin({
19
27
  editor: this.editor,
@@ -4,24 +4,24 @@ import {
4
4
  RiH1,
5
5
  RiH2,
6
6
  RiH3,
7
+ RiIndentDecrease,
8
+ RiIndentIncrease,
7
9
  RiItalic,
8
10
  RiLink,
9
- RiStrikethrough,
10
- RiUnderline,
11
- RiIndentIncrease,
12
- RiIndentDecrease,
13
- RiText,
14
11
  RiListOrdered,
15
12
  RiListUnordered,
13
+ RiStrikethrough,
14
+ RiText,
15
+ RiUnderline,
16
16
  } from "react-icons/ri";
17
- import { SimpleToolbarButton } from "../../../shared/components/toolbar/SimpleToolbarButton";
17
+ import { ToolbarButton } from "../../../shared/components/toolbar/ToolbarButton";
18
+ import { ToolbarDropdown } from "../../../shared/components/toolbar/ToolbarDropdown";
18
19
  import { Toolbar } from "../../../shared/components/toolbar/Toolbar";
19
20
  import { useEditorForceUpdate } from "../../../shared/hooks/useEditorForceUpdate";
20
21
  import { findBlock } from "../../Blocks/helpers/findBlock";
21
- import formatKeyboardShortcut from "../../helpers/formatKeyboardShortcut";
22
+ import { formatKeyboardShortcut } from "../../../utils";
22
23
  import LinkToolbarButton from "./LinkToolbarButton";
23
- import DropdownMenu, { DropdownItemGroup } from "@atlaskit/dropdown-menu";
24
- import DropdownBlockItem from "./DropdownBlockItem";
24
+ import { IconType } from "react-icons";
25
25
 
26
26
  type ListType = "li" | "oli";
27
27
 
@@ -59,127 +59,151 @@ export const BubbleMenu = (props: { editor: Editor }) => {
59
59
  currentBlockListType
60
60
  );
61
61
 
62
+ const blockIconMap: Record<string, IconType> = {
63
+ Text: RiText,
64
+ "Heading 1": RiH1,
65
+ "Heading 2": RiH2,
66
+ "Heading 3": RiH3,
67
+ "Bullet List": RiListUnordered,
68
+ "Numbered List": RiListOrdered,
69
+ };
70
+
62
71
  return (
63
72
  <Toolbar>
64
- <DropdownMenu trigger={currentBlockName}>
65
- <DropdownItemGroup>
66
- <DropdownBlockItem
67
- title="Text"
68
- icon={RiText}
69
- isSelected={currentBlockName === "Paragraph"}
70
- onClick={() =>
71
- props.editor.chain().focus().unsetBlockHeading().unsetList().run()
72
- }
73
- />
74
- <DropdownBlockItem
75
- title="Heading 1"
76
- icon={RiH1}
77
- isSelected={currentBlockName === "Heading 1"}
78
- onClick={() =>
73
+ <ToolbarDropdown
74
+ text={currentBlockName}
75
+ icon={blockIconMap[currentBlockName]}
76
+ items={[
77
+ {
78
+ onClick: () => {
79
+ // Setting editor focus using a chained command instead causes bubble menu to flicker on click.
80
+ props.editor.view.focus();
81
+ props.editor.chain().unsetBlockHeading().unsetList().run();
82
+ },
83
+ text: "Text",
84
+ icon: RiText,
85
+ isSelected: currentBlockName === "Text",
86
+ },
87
+ {
88
+ onClick: () => {
89
+ props.editor.view.focus();
79
90
  props.editor
80
91
  .chain()
81
- .focus()
82
92
  .unsetList()
83
- .setBlockHeading({ level: 1 })
84
- .run()
85
- }
86
- />
87
- <DropdownBlockItem
88
- title="Heading 2"
89
- icon={RiH2}
90
- isSelected={currentBlockName === "Heading 2"}
91
- onClick={() =>
93
+ .setBlockHeading({ level: "1" })
94
+ .run();
95
+ },
96
+ text: "Heading 1",
97
+ icon: RiH1,
98
+ isSelected: currentBlockName === "Heading 1",
99
+ },
100
+ {
101
+ onClick: () => {
102
+ props.editor.view.focus();
92
103
  props.editor
93
104
  .chain()
94
- .focus()
95
105
  .unsetList()
96
- .setBlockHeading({ level: 2 })
97
- .run()
98
- }
99
- />
100
- <DropdownBlockItem
101
- title="Heading 3"
102
- icon={RiH3}
103
- isSelected={currentBlockName === "Heading 3"}
104
- onClick={() =>
106
+ .setBlockHeading({ level: "2" })
107
+ .run();
108
+ },
109
+ text: "Heading 2",
110
+ icon: RiH2,
111
+ isSelected: currentBlockName === "Heading 2",
112
+ },
113
+ {
114
+ onClick: () => {
115
+ props.editor.view.focus();
105
116
  props.editor
106
117
  .chain()
107
- .focus()
108
118
  .unsetList()
109
- .setBlockHeading({ level: 3 })
110
- .run()
111
- }
112
- />
113
- <DropdownBlockItem
114
- title="Bullet List"
115
- icon={RiListUnordered}
116
- isSelected={currentBlockName === "Bullet List"}
117
- onClick={() =>
118
- props.editor
119
- .chain()
120
- .focus()
121
- .unsetBlockHeading()
122
- .setBlockList("li")
123
- .run()
124
- }
125
- />
126
- <DropdownBlockItem
127
- title="Numbered List"
128
- icon={RiListOrdered}
129
- isSelected={currentBlockName === "Numbered List"}
130
- onClick={() =>
119
+ .setBlockHeading({ level: "3" })
120
+ .run();
121
+ },
122
+ text: "Heading 3",
123
+ icon: RiH3,
124
+ isSelected: currentBlockName === "Heading 3",
125
+ },
126
+ {
127
+ onClick: () => {
128
+ props.editor.view.focus();
129
+ props.editor.chain().unsetBlockHeading().setBlockList("li").run();
130
+ },
131
+ text: "Bullet List",
132
+ icon: RiListUnordered,
133
+ isSelected: currentBlockName === "Bullet List",
134
+ },
135
+ {
136
+ onClick: () => {
137
+ props.editor.view.focus();
131
138
  props.editor
132
139
  .chain()
133
- .focus()
134
140
  .unsetBlockHeading()
135
141
  .setBlockList("oli")
136
- .run()
137
- }
138
- />
139
- </DropdownItemGroup>
140
- </DropdownMenu>
141
- <SimpleToolbarButton
142
- onClick={() => props.editor.chain().focus().toggleBold().run()}
142
+ .run();
143
+ },
144
+ text: "Numbered List",
145
+ icon: RiListOrdered,
146
+ isSelected: currentBlockName === "Numbered List",
147
+ },
148
+ ]}
149
+ />
150
+ <ToolbarButton
151
+ onClick={() => {
152
+ // Setting editor focus using a chained command instead causes bubble menu to flicker on click.
153
+ props.editor.view.focus();
154
+ props.editor.commands.toggleBold();
155
+ }}
143
156
  isSelected={props.editor.isActive("bold")}
144
157
  mainTooltip="Bold"
145
158
  secondaryTooltip={formatKeyboardShortcut("Mod+B")}
146
159
  icon={RiBold}
147
160
  />
148
- <SimpleToolbarButton
149
- onClick={() => props.editor.chain().focus().toggleItalic().run()}
161
+ <ToolbarButton
162
+ onClick={() => {
163
+ props.editor.view.focus();
164
+ props.editor.commands.toggleItalic();
165
+ }}
150
166
  isSelected={props.editor.isActive("italic")}
151
167
  mainTooltip="Italic"
152
168
  secondaryTooltip={formatKeyboardShortcut("Mod+I")}
153
169
  icon={RiItalic}
154
170
  />
155
- <SimpleToolbarButton
156
- onClick={() => props.editor.chain().focus().toggleUnderline().run()}
171
+ <ToolbarButton
172
+ onClick={() => {
173
+ props.editor.view.focus();
174
+ props.editor.commands.toggleUnderline();
175
+ }}
157
176
  isSelected={props.editor.isActive("underline")}
158
177
  mainTooltip="Underline"
159
178
  secondaryTooltip={formatKeyboardShortcut("Mod+U")}
160
179
  icon={RiUnderline}
161
180
  />
162
- <SimpleToolbarButton
163
- onClick={() => props.editor.chain().focus().toggleStrike().run()}
164
- isDisabled={props.editor.isActive("strike")}
181
+ <ToolbarButton
182
+ onClick={() => {
183
+ props.editor.view.focus();
184
+ props.editor.commands.toggleStrike();
185
+ }}
186
+ isSelected={props.editor.isActive("strike")}
165
187
  mainTooltip="Strike-through"
166
188
  secondaryTooltip={formatKeyboardShortcut("Mod+Shift+X")}
167
189
  icon={RiStrikethrough}
168
190
  />
169
- <SimpleToolbarButton
170
- onClick={() =>
171
- props.editor.chain().focus().sinkListItem("tcblock").run()
172
- }
173
- isDisabled={!props.editor.can().sinkListItem("tcblock")}
191
+ <ToolbarButton
192
+ onClick={() => {
193
+ props.editor.view.focus();
194
+ props.editor.commands.sinkListItem("block");
195
+ }}
196
+ isDisabled={!props.editor.can().sinkListItem("block")}
174
197
  mainTooltip="Indent"
175
198
  secondaryTooltip={formatKeyboardShortcut("Tab")}
176
199
  icon={RiIndentIncrease}
177
200
  />
178
201
 
179
- <SimpleToolbarButton
180
- onClick={() =>
181
- props.editor.chain().focus().liftListItem("tcblock").run()
182
- }
202
+ <ToolbarButton
203
+ onClick={() => {
204
+ props.editor.view.focus();
205
+ props.editor.commands.liftListItem("block");
206
+ }}
183
207
  isDisabled={
184
208
  !props.editor.can().command(({ state }) => {
185
209
  const block = findBlock(state.selection);
@@ -204,13 +228,13 @@ export const BubbleMenu = (props: { editor: Editor }) => {
204
228
  editor={props.editor}
205
229
  />
206
230
  {/* <SimpleBubbleMenuButton
207
- editor={props.editor}
208
- onClick={() => {
209
- const comment = this.props.commentStore.createComment();
210
- props.editor.chain().focus().setComment(comment.id).run();
211
- }}
212
- styleDetails={comment}
213
- /> */}
231
+ editor={props.editor}
232
+ onClick={() => {
233
+ const comment = this.props.commentStore.createComment();
234
+ props.editor.chain().focus().setComment(comment.id).run();
235
+ }}
236
+ styleDetails={comment}
237
+ /> */}
214
238
  </Toolbar>
215
239
  );
216
240
  };