@blocknote/core 0.8.2 → 0.8.4-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 (72) hide show
  1. package/README.md +4 -0
  2. package/dist/blocknote.js +1777 -1849
  3. package/dist/blocknote.js.map +1 -1
  4. package/dist/blocknote.umd.cjs +4 -4
  5. package/dist/blocknote.umd.cjs.map +1 -1
  6. package/dist/style.css +1 -1
  7. package/package.json +4 -4
  8. package/src/BlockNoteEditor.ts +89 -39
  9. package/src/BlockNoteExtensions.ts +1 -58
  10. package/src/api/formatConversions/__snapshots__/formatConversions.test.ts.snap +10 -10
  11. package/src/api/formatConversions/formatConversions.test.ts +587 -605
  12. package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +15 -15
  13. package/src/api/nodeConversions/nodeConversions.test.ts +90 -94
  14. package/src/extensions/Blocks/api/blockTypes.ts +3 -2
  15. package/src/extensions/Blocks/helpers/getBlockInfoFromPos.ts +6 -0
  16. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +101 -114
  17. package/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.ts +184 -149
  18. package/src/extensions/Placeholder/PlaceholderExtension.ts +2 -2
  19. package/src/extensions/{DraggableBlocks/DraggableBlocksPlugin.ts → SideMenu/SideMenuPlugin.ts} +181 -164
  20. package/src/extensions/SlashMenu/BaseSlashMenuItem.ts +7 -30
  21. package/src/extensions/SlashMenu/SlashMenuPlugin.ts +51 -0
  22. package/src/extensions/SlashMenu/defaultSlashMenuItems.ts +109 -0
  23. package/src/extensions/UniqueID/UniqueID.ts +29 -30
  24. package/src/index.ts +9 -8
  25. package/src/shared/BaseUiElementTypes.ts +8 -0
  26. package/src/shared/EditorElement.ts +0 -16
  27. package/src/shared/EventEmitter.ts +58 -0
  28. package/src/shared/plugins/suggestion/SuggestionItem.ts +3 -6
  29. package/src/shared/plugins/suggestion/SuggestionPlugin.ts +341 -403
  30. package/types/src/BlockNoteEditor.d.ts +18 -11
  31. package/types/src/BlockNoteExtensions.d.ts +0 -19
  32. package/types/src/extensions/Blocks/api/blockTypes.d.ts +3 -2
  33. package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +18 -24
  34. package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.d.ts +37 -10
  35. package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +79 -0
  36. package/types/src/extensions/SlashMenu/BaseSlashMenuItem.d.ts +5 -18
  37. package/types/src/extensions/SlashMenu/SlashMenuPlugin.d.ts +13 -0
  38. package/types/src/extensions/SlashMenu/defaultSlashMenuItems.d.ts +1 -69
  39. package/types/src/index.d.ts +9 -8
  40. package/types/src/shared/BaseUiElementTypes.d.ts +7 -0
  41. package/types/src/shared/EditorElement.d.ts +0 -10
  42. package/types/src/shared/EventEmitter.d.ts +11 -0
  43. package/types/src/shared/plugins/suggestion/SuggestionItem.d.ts +2 -7
  44. package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +12 -43
  45. package/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.ts +0 -29
  46. package/src/extensions/DraggableBlocks/DraggableBlocksExtension.ts +0 -37
  47. package/src/extensions/FormattingToolbar/FormattingToolbarExtension.ts +0 -37
  48. package/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.ts +0 -18
  49. package/src/extensions/HyperlinkToolbar/HyperlinkMark.ts +0 -28
  50. package/src/extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes.ts +0 -19
  51. package/src/extensions/SlashMenu/SlashMenuExtension.ts +0 -53
  52. package/src/extensions/SlashMenu/defaultSlashMenuItems.tsx +0 -195
  53. package/src/extensions/SlashMenu/index.ts +0 -5
  54. package/src/shared/plugins/suggestion/SuggestionsMenuFactoryTypes.ts +0 -21
  55. package/types/src/CustomBlock.d.ts +0 -15
  56. package/types/src/extensions/Blocks/nodes/BlockContent/TableContent/TableCol.d.ts +0 -2
  57. package/types/src/extensions/Blocks/nodes/BlockContent/TableContent/TableContent.d.ts +0 -2
  58. package/types/src/extensions/Blocks/nodes/BlockContent/TableContent/TableRow.d.ts +0 -2
  59. package/types/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.d.ts +0 -17
  60. package/types/src/extensions/DraggableBlocks/DraggableBlocksExtension.d.ts +0 -16
  61. package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +0 -49
  62. package/types/src/extensions/FormattingToolbar/FormattingToolbarExtension.d.ts +0 -11
  63. package/types/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.d.ts +0 -10
  64. package/types/src/extensions/HyperlinkToolbar/HyperlinkMark.d.ts +0 -8
  65. package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes.d.ts +0 -12
  66. package/types/src/extensions/Placeholder/localisation/index.d.ts +0 -2
  67. package/types/src/extensions/Placeholder/localisation/translation.d.ts +0 -51
  68. package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +0 -13
  69. package/types/src/extensions/SlashMenu/index.d.ts +0 -4
  70. package/types/src/shared/plugins/suggestion/SuggestionsMenuFactoryTypes.d.ts +0 -12
  71. /package/src/extensions/{DraggableBlocks → SideMenu}/MultipleNodeSelection.ts +0 -0
  72. /package/types/src/extensions/{DraggableBlocks → SideMenu}/MultipleNodeSelection.d.ts +0 -0
@@ -1,27 +1,22 @@
1
- import { Editor, getMarkRange, posToDOMRect, Range } from "@tiptap/core";
1
+ import { getMarkRange, posToDOMRect, Range } from "@tiptap/core";
2
+ import { EditorView } from "@tiptap/pm/view";
2
3
  import { Mark } from "prosemirror-model";
3
4
  import { Plugin, PluginKey } from "prosemirror-state";
4
- import {
5
- HyperlinkToolbar,
6
- HyperlinkToolbarDynamicParams,
7
- HyperlinkToolbarFactory,
8
- HyperlinkToolbarStaticParams,
9
- } from "./HyperlinkToolbarFactoryTypes";
10
- const PLUGIN_KEY = new PluginKey("HyperlinkToolbarPlugin");
11
-
12
- export type HyperlinkToolbarPluginProps = {
13
- hyperlinkToolbarFactory: HyperlinkToolbarFactory;
5
+ import { BlockNoteEditor } from "../../BlockNoteEditor";
6
+ import { BaseUiElementState } from "../../shared/BaseUiElementTypes";
7
+ import { EventEmitter } from "../../shared/EventEmitter";
8
+ import { BlockSchema } from "../Blocks/api/blockTypes";
9
+
10
+ export type HyperlinkToolbarState = BaseUiElementState & {
11
+ // The hovered hyperlink's URL, and the text it's displayed with in the
12
+ // editor.
13
+ url: string;
14
+ text: string;
14
15
  };
15
16
 
16
- export type HyperlinkToolbarViewProps = {
17
- editor: Editor;
18
- hyperlinkToolbarFactory: HyperlinkToolbarFactory;
19
- };
20
-
21
- class HyperlinkToolbarView {
22
- editor: Editor;
23
-
24
- hyperlinkToolbar: HyperlinkToolbar;
17
+ class HyperlinkToolbarView<BSchema extends BlockSchema> {
18
+ private hyperlinkToolbarState?: HyperlinkToolbarState;
19
+ public updateHyperlinkToolbar: () => void;
25
20
 
26
21
  menuUpdateTimer: NodeJS.Timeout | undefined;
27
22
  startMenuUpdateTimer: () => void;
@@ -36,12 +31,20 @@ class HyperlinkToolbarView {
36
31
  hyperlinkMark: Mark | undefined;
37
32
  hyperlinkMarkRange: Range | undefined;
38
33
 
39
- private lastPosition: DOMRect | undefined;
40
-
41
- constructor({ editor, hyperlinkToolbarFactory }: HyperlinkToolbarViewProps) {
42
- this.editor = editor;
34
+ constructor(
35
+ private readonly editor: BlockNoteEditor<BSchema>,
36
+ private readonly pmView: EditorView,
37
+ updateHyperlinkToolbar: (
38
+ hyperlinkToolbarState: HyperlinkToolbarState
39
+ ) => void
40
+ ) {
41
+ this.updateHyperlinkToolbar = () => {
42
+ if (!this.hyperlinkToolbarState) {
43
+ throw new Error("Attempting to update uninitialized hyperlink toolbar");
44
+ }
43
45
 
44
- this.hyperlinkToolbar = hyperlinkToolbarFactory(this.getStaticParams());
46
+ updateHyperlinkToolbar(this.hyperlinkToolbarState);
47
+ };
45
48
 
46
49
  this.startMenuUpdateTimer = () => {
47
50
  this.menuUpdateTimer = setTimeout(() => {
@@ -58,8 +61,9 @@ class HyperlinkToolbarView {
58
61
  return false;
59
62
  };
60
63
 
61
- this.editor.view.dom.addEventListener("mouseover", this.mouseOverHandler);
64
+ this.pmView.dom.addEventListener("mouseover", this.mouseOverHandler);
62
65
  document.addEventListener("click", this.clickHandler, true);
66
+ document.addEventListener("scroll", this.scrollHandler);
63
67
  }
64
68
 
65
69
  mouseOverHandler = (event: MouseEvent) => {
@@ -77,14 +81,16 @@ class HyperlinkToolbarView {
77
81
  // mouseHoveredHyperlinkMarkRange.
78
82
  const hoveredHyperlinkElement = event.target;
79
83
  const posInHoveredHyperlinkMark =
80
- this.editor.view.posAtDOM(hoveredHyperlinkElement, 0) + 1;
81
- const resolvedPosInHoveredHyperlinkMark = this.editor.state.doc.resolve(
84
+ this.pmView.posAtDOM(hoveredHyperlinkElement, 0) + 1;
85
+ const resolvedPosInHoveredHyperlinkMark = this.pmView.state.doc.resolve(
82
86
  posInHoveredHyperlinkMark
83
87
  );
84
88
  const marksAtPos = resolvedPosInHoveredHyperlinkMark.marks();
85
89
 
86
90
  for (const mark of marksAtPos) {
87
- if (mark.type.name === this.editor.schema.mark("link").type.name) {
91
+ if (
92
+ mark.type.name === this.pmView.state.schema.mark("link").type.name
93
+ ) {
88
94
  this.mouseHoveredHyperlinkMark = mark;
89
95
  this.mouseHoveredHyperlinkMarkRange =
90
96
  getMarkRange(
@@ -104,25 +110,80 @@ class HyperlinkToolbarView {
104
110
  };
105
111
 
106
112
  clickHandler = (event: MouseEvent) => {
113
+ const editorWrapper = this.pmView.dom.parentElement!;
114
+
107
115
  if (
108
116
  // Toolbar is open.
109
117
  this.hyperlinkMark &&
110
118
  // An element is clicked.
111
119
  event &&
112
120
  event.target &&
113
- // Element is outside the editor.
114
- this.editor.view.dom !== (event.target as Node) &&
115
- !this.editor.view.dom.contains(event.target as Node) &&
116
- // Element is outside the toolbar.
117
- this.hyperlinkToolbar.element !== (event.target as Node) &&
118
- !this.hyperlinkToolbar.element?.contains(event.target as Node)
121
+ // The clicked element is not the editor.
122
+ !(
123
+ editorWrapper === (event.target as Node) ||
124
+ editorWrapper.contains(event.target as Node)
125
+ )
119
126
  ) {
120
- this.hyperlinkToolbar.hide();
127
+ if (this.hyperlinkToolbarState?.show) {
128
+ this.hyperlinkToolbarState.show = false;
129
+ this.updateHyperlinkToolbar();
130
+ }
131
+ }
132
+ };
133
+
134
+ scrollHandler = () => {
135
+ if (this.hyperlinkMark !== undefined) {
136
+ if (this.hyperlinkToolbarState?.show) {
137
+ this.hyperlinkToolbarState.referencePos = posToDOMRect(
138
+ this.pmView,
139
+ this.hyperlinkMarkRange!.from,
140
+ this.hyperlinkMarkRange!.to
141
+ );
142
+ this.updateHyperlinkToolbar();
143
+ }
121
144
  }
122
145
  };
123
146
 
147
+ editHyperlink(url: string, text: string) {
148
+ const tr = this.pmView.state.tr.insertText(
149
+ text,
150
+ this.hyperlinkMarkRange!.from,
151
+ this.hyperlinkMarkRange!.to
152
+ );
153
+ tr.addMark(
154
+ this.hyperlinkMarkRange!.from,
155
+ this.hyperlinkMarkRange!.from + text.length,
156
+ this.pmView.state.schema.mark("link", { href: url })
157
+ );
158
+ this.pmView.dispatch(tr);
159
+ this.pmView.focus();
160
+
161
+ if (this.hyperlinkToolbarState?.show) {
162
+ this.hyperlinkToolbarState.show = false;
163
+ this.updateHyperlinkToolbar();
164
+ }
165
+ }
166
+
167
+ deleteHyperlink() {
168
+ this.pmView.dispatch(
169
+ this.pmView.state.tr
170
+ .removeMark(
171
+ this.hyperlinkMarkRange!.from,
172
+ this.hyperlinkMarkRange!.to,
173
+ this.hyperlinkMark!.type
174
+ )
175
+ .setMeta("preventAutolink", true)
176
+ );
177
+ this.pmView.focus();
178
+
179
+ if (this.hyperlinkToolbarState?.show) {
180
+ this.hyperlinkToolbarState.show = false;
181
+ this.updateHyperlinkToolbar();
182
+ }
183
+ }
184
+
124
185
  update() {
125
- if (!this.editor.view.hasFocus()) {
186
+ if (!this.pmView.hasFocus()) {
126
187
  return;
127
188
  }
128
189
 
@@ -139,15 +200,17 @@ class HyperlinkToolbarView {
139
200
 
140
201
  // Finds link mark at the editor selection's position to update keyboardHoveredHyperlinkMark and
141
202
  // keyboardHoveredHyperlinkMarkRange.
142
- if (this.editor.state.selection.empty) {
143
- const marksAtPos = this.editor.state.selection.$from.marks();
203
+ if (this.pmView.state.selection.empty) {
204
+ const marksAtPos = this.pmView.state.selection.$from.marks();
144
205
 
145
206
  for (const mark of marksAtPos) {
146
- if (mark.type.name === this.editor.schema.mark("link").type.name) {
207
+ if (
208
+ mark.type.name === this.pmView.state.schema.mark("link").type.name
209
+ ) {
147
210
  this.keyboardHoveredHyperlinkMark = mark;
148
211
  this.keyboardHoveredHyperlinkMarkRange =
149
212
  getMarkRange(
150
- this.editor.state.selection.$from,
213
+ this.pmView.state.selection.$from,
151
214
  mark.type,
152
215
  mark.attrs
153
216
  ) || undefined;
@@ -169,130 +232,102 @@ class HyperlinkToolbarView {
169
232
  }
170
233
 
171
234
  if (this.hyperlinkMark && this.editor.isEditable) {
172
- this.getDynamicParams();
173
-
174
- // Shows menu.
175
- if (!prevHyperlinkMark) {
176
- this.hyperlinkToolbar.render(this.getDynamicParams(), true);
177
-
178
- this.hyperlinkToolbar.element?.addEventListener(
179
- "mouseleave",
180
- this.startMenuUpdateTimer
181
- );
182
- this.hyperlinkToolbar.element?.addEventListener(
183
- "mouseenter",
184
- this.stopMenuUpdateTimer
185
- );
186
-
187
- return;
188
- }
189
-
190
- // Updates menu.
191
- this.hyperlinkToolbar.render(this.getDynamicParams(), false);
235
+ this.hyperlinkToolbarState = {
236
+ show: true,
237
+ referencePos: posToDOMRect(
238
+ this.pmView,
239
+ this.hyperlinkMarkRange!.from,
240
+ this.hyperlinkMarkRange!.to
241
+ ),
242
+ url: this.hyperlinkMark!.attrs.href,
243
+ text: this.pmView.state.doc.textBetween(
244
+ this.hyperlinkMarkRange!.from,
245
+ this.hyperlinkMarkRange!.to
246
+ ),
247
+ };
248
+ this.updateHyperlinkToolbar();
192
249
 
193
250
  return;
194
251
  }
195
252
 
196
253
  // Hides menu.
197
- if (prevHyperlinkMark && (!this.hyperlinkMark || !this.editor.isEditable)) {
198
- this.hyperlinkToolbar.element?.removeEventListener(
199
- "mouseleave",
200
- this.startMenuUpdateTimer
201
- );
202
- this.hyperlinkToolbar.element?.removeEventListener(
203
- "mouseenter",
204
- this.stopMenuUpdateTimer
205
- );
206
-
207
- this.hyperlinkToolbar.hide();
254
+ if (
255
+ this.hyperlinkToolbarState?.show &&
256
+ prevHyperlinkMark &&
257
+ (!this.hyperlinkMark || !this.editor.isEditable)
258
+ ) {
259
+ this.hyperlinkToolbarState.show = false;
260
+ this.updateHyperlinkToolbar();
208
261
 
209
262
  return;
210
263
  }
211
264
  }
212
265
 
213
266
  destroy() {
214
- this.editor.view.dom.removeEventListener(
215
- "mouseover",
216
- this.mouseOverHandler
217
- );
267
+ this.pmView.dom.removeEventListener("mouseover", this.mouseOverHandler);
268
+ document.removeEventListener("scroll", this.scrollHandler);
269
+ document.removeEventListener("click", this.clickHandler, true);
218
270
  }
271
+ }
219
272
 
220
- getStaticParams(): HyperlinkToolbarStaticParams {
221
- return {
222
- editHyperlink: (url: string, text: string) => {
223
- const tr = this.editor.view.state.tr.insertText(
224
- text,
225
- this.hyperlinkMarkRange!.from,
226
- this.hyperlinkMarkRange!.to
227
- );
228
- tr.addMark(
229
- this.hyperlinkMarkRange!.from,
230
- this.hyperlinkMarkRange!.from + text.length,
231
- this.editor.schema.mark("link", { href: url })
232
- );
233
- this.editor.view.dispatch(tr);
234
- this.editor.view.focus();
235
-
236
- this.hyperlinkToolbar.hide();
273
+ export const hyperlinkToolbarPluginKey = new PluginKey(
274
+ "HyperlinkToolbarPlugin"
275
+ );
276
+
277
+ export class HyperlinkToolbarProsemirrorPlugin<
278
+ BSchema extends BlockSchema
279
+ > extends EventEmitter<any> {
280
+ private view: HyperlinkToolbarView<BSchema> | undefined;
281
+ public readonly plugin: Plugin;
282
+
283
+ constructor(editor: BlockNoteEditor<BSchema>) {
284
+ super();
285
+ this.plugin = new Plugin({
286
+ key: hyperlinkToolbarPluginKey,
287
+ view: (editorView) => {
288
+ this.view = new HyperlinkToolbarView(editor, editorView, (state) => {
289
+ this.emit("update", state);
290
+ });
291
+ return this.view;
237
292
  },
238
- deleteHyperlink: () => {
239
- this.editor.view.dispatch(
240
- this.editor.view.state.tr
241
- .removeMark(
242
- this.hyperlinkMarkRange!.from,
243
- this.hyperlinkMarkRange!.to,
244
- this.hyperlinkMark!.type
245
- )
246
- .setMeta("preventAutolink", true)
247
- );
248
- this.editor.view.focus();
293
+ });
294
+ }
249
295
 
250
- this.hyperlinkToolbar.hide();
251
- },
252
- getReferenceRect: () => {
253
- if (!this.hyperlinkMark) {
254
- if (this.lastPosition === undefined) {
255
- throw new Error(
256
- "Attempted to access hyperlink reference rect before rendering hyperlink toolbar."
257
- );
258
- }
259
-
260
- return this.lastPosition;
261
- }
296
+ public onUpdate(callback: (state: HyperlinkToolbarState) => void) {
297
+ return this.on("update", callback);
298
+ }
262
299
 
263
- const hyperlinkBoundingBox = posToDOMRect(
264
- this.editor.view,
265
- this.hyperlinkMarkRange!.from,
266
- this.hyperlinkMarkRange!.to
267
- );
268
- this.lastPosition = hyperlinkBoundingBox;
300
+ /**
301
+ * Edit the currently hovered hyperlink.
302
+ */
303
+ public editHyperlink = (url: string, text: string) => {
304
+ this.view!.editHyperlink(url, text);
305
+ };
269
306
 
270
- return hyperlinkBoundingBox;
271
- },
272
- };
273
- }
307
+ /**
308
+ * Delete the currently hovered hyperlink.
309
+ */
310
+ public deleteHyperlink = () => {
311
+ this.view!.deleteHyperlink();
312
+ };
274
313
 
275
- getDynamicParams(): HyperlinkToolbarDynamicParams {
276
- return {
277
- url: this.hyperlinkMark!.attrs.href,
278
- text: this.editor.view.state.doc.textBetween(
279
- this.hyperlinkMarkRange!.from,
280
- this.hyperlinkMarkRange!.to
281
- ),
282
- };
283
- }
284
- }
314
+ /**
315
+ * When hovering on/off hyperlinks using the mouse cursor, the hyperlink
316
+ * toolbar will open & close with a delay.
317
+ *
318
+ * This function starts the delay timer, and should be used for when the mouse cursor enters the hyperlink toolbar.
319
+ */
320
+ public startHideTimer = () => {
321
+ this.view!.startMenuUpdateTimer();
322
+ };
285
323
 
286
- export const createHyperlinkToolbarPlugin = (
287
- editor: Editor,
288
- options: HyperlinkToolbarPluginProps
289
- ) => {
290
- return new Plugin({
291
- key: PLUGIN_KEY,
292
- view: () =>
293
- new HyperlinkToolbarView({
294
- editor: editor,
295
- hyperlinkToolbarFactory: options.hyperlinkToolbarFactory,
296
- }),
297
- });
298
- };
324
+ /**
325
+ * When hovering on/off hyperlinks using the mouse cursor, the hyperlink
326
+ * toolbar will open & close with a delay.
327
+ *
328
+ * This function stops the delay timer, and should be used for when the mouse cursor exits the hyperlink toolbar.
329
+ */
330
+ public stopHideTimer = () => {
331
+ this.view!.stopMenuUpdateTimer();
332
+ };
333
+ }
@@ -2,7 +2,7 @@ import { Editor, Extension } from "@tiptap/core";
2
2
  import { Node as ProsemirrorNode } from "prosemirror-model";
3
3
  import { Plugin, PluginKey } from "prosemirror-state";
4
4
  import { Decoration, DecorationSet } from "prosemirror-view";
5
- import { SlashMenuPluginKey } from "../SlashMenu/SlashMenuExtension";
5
+ import { slashMenuPluginKey } from "../SlashMenu/SlashMenuPlugin";
6
6
 
7
7
  const PLUGIN_KEY = new PluginKey(`blocknote-placeholder`);
8
8
 
@@ -55,7 +55,7 @@ export const Placeholder = Extension.create<PlaceholderOptions>({
55
55
  decorations: (state) => {
56
56
  const { doc, selection } = state;
57
57
  // Get state of slash menu
58
- const menuState = SlashMenuPluginKey.getState(state);
58
+ const menuState = slashMenuPluginKey.getState(state);
59
59
  const active =
60
60
  this.editor.isEditable || !this.options.showOnlyWhenEditable;
61
61
  const { anchor } = selection;