@blocknote/core 0.7.1-alpha.0 → 0.8.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 (77) hide show
  1. package/dist/blocknote.js +1711 -1469
  2. package/dist/blocknote.js.map +1 -1
  3. package/dist/blocknote.umd.cjs +6 -2
  4. package/dist/blocknote.umd.cjs.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +3 -3
  7. package/src/BlockNoteEditor.ts +104 -53
  8. package/src/BlockNoteExtensions.ts +24 -14
  9. package/src/api/blockManipulation/blockManipulation.test.ts +6 -3
  10. package/src/api/blockManipulation/blockManipulation.ts +7 -6
  11. package/src/api/formatConversions/formatConversions.test.ts +13 -8
  12. package/src/api/formatConversions/formatConversions.ts +15 -12
  13. package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +292 -0
  14. package/src/api/nodeConversions/nodeConversions.test.ts +265 -10
  15. package/src/api/nodeConversions/nodeConversions.ts +199 -47
  16. package/src/api/nodeConversions/testUtil.ts +8 -4
  17. package/src/editor.module.css +5 -6
  18. package/src/extensions/Blocks/api/block.ts +229 -0
  19. package/src/extensions/Blocks/api/blockTypes.ts +158 -71
  20. package/src/extensions/Blocks/api/cursorPositionTypes.ts +5 -5
  21. package/src/extensions/Blocks/api/defaultBlocks.ts +44 -0
  22. package/src/extensions/Blocks/api/selectionTypes.ts +3 -3
  23. package/src/extensions/Blocks/api/serialization.ts +29 -0
  24. package/src/extensions/Blocks/index.ts +0 -8
  25. package/src/extensions/Blocks/nodes/Block.module.css +28 -16
  26. package/src/extensions/Blocks/nodes/BlockContainer.ts +8 -4
  27. package/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.ts +4 -4
  28. package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +5 -5
  29. package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +100 -97
  30. package/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.ts +4 -4
  31. package/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.ts +11 -9
  32. package/src/extensions/DraggableBlocks/DraggableBlocksExtension.ts +6 -5
  33. package/src/extensions/DraggableBlocks/DraggableBlocksPlugin.ts +57 -14
  34. package/src/extensions/FormattingToolbar/FormattingToolbarExtension.ts +21 -16
  35. package/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.ts +9 -5
  36. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +38 -58
  37. package/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.ts +19 -0
  38. package/src/extensions/Placeholder/PlaceholderExtension.ts +1 -0
  39. package/src/extensions/SlashMenu/BaseSlashMenuItem.ts +5 -2
  40. package/src/extensions/SlashMenu/SlashMenuExtension.ts +37 -33
  41. package/src/extensions/SlashMenu/defaultSlashMenuItems.tsx +14 -10
  42. package/src/extensions/SlashMenu/index.ts +2 -2
  43. package/src/index.ts +4 -0
  44. package/src/shared/plugins/suggestion/SuggestionPlugin.ts +29 -13
  45. package/types/src/BlockNoteEditor.d.ts +38 -23
  46. package/types/src/BlockNoteExtensions.d.ts +15 -8
  47. package/types/src/api/blockManipulation/blockManipulation.d.ts +4 -4
  48. package/types/src/api/formatConversions/formatConversions.d.ts +5 -5
  49. package/types/src/api/nodeConversions/nodeConversions.d.ts +3 -3
  50. package/types/src/api/nodeConversions/testUtil.d.ts +2 -2
  51. package/types/src/extensions/Blocks/api/block.d.ts +2 -4
  52. package/types/src/extensions/Blocks/api/blockTypes.d.ts +77 -33
  53. package/types/src/extensions/Blocks/api/cursorPositionTypes.d.ts +5 -5
  54. package/types/src/extensions/Blocks/api/defaultBlocks.d.ts +4 -4
  55. package/types/src/extensions/Blocks/api/selectionTypes.d.ts +3 -3
  56. package/types/src/extensions/Blocks/api/serialization.d.ts +2 -0
  57. package/types/src/extensions/Blocks/nodes/BlockContainer.d.ts +3 -3
  58. package/types/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.d.ts +1 -2
  59. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.d.ts +1 -2
  60. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.d.ts +1 -2
  61. package/types/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.d.ts +1 -2
  62. package/types/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.d.ts +7 -7
  63. package/types/src/extensions/DraggableBlocks/DraggableBlocksExtension.d.ts +5 -4
  64. package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +12 -11
  65. package/types/src/extensions/FormattingToolbar/FormattingToolbarExtension.d.ts +6 -5
  66. package/types/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.d.ts +4 -3
  67. package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +16 -19
  68. package/types/src/extensions/Placeholder/localisation/index.d.ts +2 -0
  69. package/types/src/extensions/Placeholder/localisation/translation.d.ts +51 -0
  70. package/types/src/extensions/SlashMenu/BaseSlashMenuItem.d.ts +4 -3
  71. package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +5 -4
  72. package/types/src/extensions/SlashMenu/defaultSlashMenuItems.d.ts +66 -1
  73. package/types/src/extensions/SlashMenu/index.d.ts +2 -2
  74. package/types/src/index.d.ts +4 -0
  75. package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +5 -4
  76. package/types/src/extensions/Blocks/api/alertBlock.d.ts +0 -13
  77. package/types/src/extensions/Blocks/api/alertBlock2.d.ts +0 -13
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "homepage": "https://github.com/TypeCellOS/BlockNote",
4
4
  "private": false,
5
5
  "license": "MPL-2.0",
6
- "version": "0.7.1-alpha.0",
6
+ "version": "0.8.1",
7
7
  "files": [
8
8
  "dist",
9
9
  "types",
@@ -92,7 +92,7 @@
92
92
  "eslint-config-react-app": "^7.0.0",
93
93
  "jsdom": "^21.1.0",
94
94
  "prettier": "^2.7.1",
95
- "typescript": "^4.5.4",
95
+ "typescript": "^5.0.4",
96
96
  "vite": "^4.1.2",
97
97
  "vite-plugin-eslint": "^1.8.1",
98
98
  "vitest": "^0.28.5"
@@ -110,5 +110,5 @@
110
110
  "access": "public",
111
111
  "registry": "https://registry.npmjs.org/"
112
112
  },
113
- "gitHead": "221da514c70e561fd8256d6bec6b825390060edf"
113
+ "gitHead": "2265d9068126df0881dd813ba6a265b82f48b466"
114
114
  }
@@ -22,9 +22,14 @@ import styles from "./editor.module.css";
22
22
  import {
23
23
  Block,
24
24
  BlockIdentifier,
25
+ BlockSchema,
25
26
  PartialBlock,
26
27
  } from "./extensions/Blocks/api/blockTypes";
27
28
  import { TextCursorPosition } from "./extensions/Blocks/api/cursorPositionTypes";
29
+ import {
30
+ DefaultBlockSchema,
31
+ defaultBlockSchema,
32
+ } from "./extensions/Blocks/api/defaultBlocks";
28
33
  import {
29
34
  ColorStyle,
30
35
  Styles,
@@ -37,21 +42,23 @@ import {
37
42
  defaultSlashMenuItems,
38
43
  } from "./extensions/SlashMenu";
39
44
 
40
- export type BlockNoteEditorOptions = {
41
- // TODO: Figure out if enableBlockNoteExtensions is needed and document them.
45
+ export type BlockNoteEditorOptions<BSchema extends BlockSchema> = {
46
+ // TODO: Figure out if enableBlockNoteExtensions/disableHistoryExtension are needed and document them.
42
47
  enableBlockNoteExtensions: boolean;
43
48
 
44
49
  /**
45
50
  * UI element factories for creating a custom UI, including custom positioning
46
51
  * & rendering.
47
52
  */
48
- uiFactories: UiFactories;
53
+ uiFactories: UiFactories<BSchema>;
49
54
  /**
50
55
  * TODO: why is this called slashCommands and not slashMenuItems?
51
56
  *
57
+ * (couldn't fix any type, see https://github.com/TypeCellOS/BlockNote/pull/191#discussion_r1210708771)
58
+ *
52
59
  * @default defaultSlashMenuItems from `./extensions/SlashMenu`
53
60
  */
54
- slashCommands: BaseSlashMenuItem[];
61
+ slashCommands: BaseSlashMenuItem<any>[];
55
62
 
56
63
  /**
57
64
  * The HTML element that should be used as the parent element for the editor.
@@ -68,15 +75,15 @@ export type BlockNoteEditorOptions = {
68
75
  /**
69
76
  * A callback function that runs when the editor is ready to be used.
70
77
  */
71
- onEditorReady: (editor: BlockNoteEditor) => void;
78
+ onEditorReady: (editor: BlockNoteEditor<BSchema>) => void;
72
79
  /**
73
80
  * A callback function that runs whenever the editor's contents change.
74
81
  */
75
- onEditorContentChange: (editor: BlockNoteEditor) => void;
82
+ onEditorContentChange: (editor: BlockNoteEditor<BSchema>) => void;
76
83
  /**
77
84
  * A callback function that runs whenever the text cursor position changes.
78
85
  */
79
- onTextCursorPositionChange: (editor: BlockNoteEditor) => void;
86
+ onTextCursorPositionChange: (editor: BlockNoteEditor<BSchema>) => void;
80
87
  /**
81
88
  * Locks the editor from being editable by the user if set to `false`.
82
89
  */
@@ -84,7 +91,7 @@ export type BlockNoteEditorOptions = {
84
91
  /**
85
92
  * The content that should be in the editor when it's created, represented as an array of partial block objects.
86
93
  */
87
- initialContent: PartialBlock[];
94
+ initialContent: PartialBlock<BSchema>[];
88
95
  /**
89
96
  * Use default BlockNote font and reset the styles of <p> <li> <h1> elements etc., that are used in BlockNote.
90
97
  *
@@ -98,6 +105,11 @@ export type BlockNoteEditorOptions = {
98
105
  */
99
106
  theme: "light" | "dark";
100
107
 
108
+ /**
109
+ * A list of block types that should be available in the editor.
110
+ */
111
+ blockSchema: BSchema;
112
+
101
113
  /**
102
114
  * When enabled, allows for collaboration between multiple users.
103
115
  */
@@ -133,32 +145,51 @@ const blockNoteTipTapOptions = {
133
145
  enableCoreExtensions: false,
134
146
  };
135
147
 
136
- export class BlockNoteEditor {
148
+ export class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema> {
137
149
  public readonly _tiptapEditor: TiptapEditor & { contentComponent: any };
138
- private blockCache = new WeakMap<Node, Block>();
150
+ public blockCache = new WeakMap<Node, Block<BSchema>>();
151
+ public readonly schema: BSchema;
139
152
 
140
153
  public get domElement() {
141
154
  return this._tiptapEditor.view.dom as HTMLDivElement;
142
155
  }
143
156
 
157
+ public isFocused() {
158
+ return this._tiptapEditor.view.hasFocus();
159
+ }
160
+
144
161
  public focus() {
145
162
  this._tiptapEditor.view.focus();
146
163
  }
147
164
 
148
- constructor(private readonly options: Partial<BlockNoteEditorOptions> = {}) {
165
+ constructor(
166
+ private readonly options: Partial<BlockNoteEditorOptions<BSchema>> = {}
167
+ ) {
149
168
  // apply defaults
150
- options = {
169
+ const newOptions: Omit<typeof options, "defaultStyles" | "blockSchema"> & {
170
+ defaultStyles: boolean;
171
+ blockSchema: BSchema;
172
+ } = {
151
173
  defaultStyles: true,
174
+ // TODO: There's a lot of annoying typing stuff to deal with here. If
175
+ // BSchema is specified, then options.blockSchema should also be required.
176
+ // If BSchema is not specified, then options.blockSchema should also not
177
+ // be defined. Unfortunately, trying to implement these constraints seems
178
+ // to be a huge pain, hence the `as any` casts.
179
+ blockSchema: options.blockSchema || (defaultBlockSchema as any),
152
180
  ...options,
153
181
  };
154
182
 
155
- const extensions = getBlockNoteExtensions({
183
+ const extensions = getBlockNoteExtensions<BSchema>({
156
184
  editor: this,
157
- uiFactories: options.uiFactories || {},
158
- slashCommands: options.slashCommands || defaultSlashMenuItems,
159
- collaboration: options.collaboration,
185
+ uiFactories: newOptions.uiFactories || {},
186
+ slashCommands: newOptions.slashCommands || defaultSlashMenuItems,
187
+ blockSchema: newOptions.blockSchema,
188
+ collaboration: newOptions.collaboration,
160
189
  });
161
190
 
191
+ this.schema = newOptions.blockSchema;
192
+
162
193
  const tiptapOptions: EditorOptions = {
163
194
  // TODO: This approach to setting initial content is "cleaner" but requires the PM editor schema, which is only
164
195
  // created after initializing the TipTap editor. Not sure it's feasible.
@@ -168,39 +199,39 @@ export class BlockNoteEditor {
168
199
  // blockToNode(block, this._tiptapEditor.schema).toJSON()
169
200
  // ),
170
201
  ...blockNoteTipTapOptions,
171
- ...options._tiptapOptions,
202
+ ...newOptions._tiptapOptions,
172
203
  onCreate: () => {
173
- options.onEditorReady?.(this);
174
- options.initialContent &&
175
- this.replaceBlocks(this.topLevelBlocks, options.initialContent);
204
+ newOptions.onEditorReady?.(this);
205
+ newOptions.initialContent &&
206
+ this.replaceBlocks(this.topLevelBlocks, newOptions.initialContent);
176
207
  },
177
208
  onUpdate: () => {
178
- options.onEditorContentChange?.(this);
209
+ newOptions.onEditorContentChange?.(this);
179
210
  },
180
211
  onSelectionUpdate: () => {
181
- options.onTextCursorPositionChange?.(this);
212
+ newOptions.onTextCursorPositionChange?.(this);
182
213
  },
183
214
  editable: options.editable === undefined ? true : options.editable,
184
215
  extensions:
185
- options.enableBlockNoteExtensions === false
186
- ? options._tiptapOptions?.extensions
187
- : [...(options._tiptapOptions?.extensions || []), ...extensions],
216
+ newOptions.enableBlockNoteExtensions === false
217
+ ? newOptions._tiptapOptions?.extensions
218
+ : [...(newOptions._tiptapOptions?.extensions || []), ...extensions],
188
219
  editorProps: {
189
220
  attributes: {
190
221
  "data-theme": options.theme || "light",
191
- ...(options.editorDOMAttributes || {}),
222
+ ...(newOptions.editorDOMAttributes || {}),
192
223
  class: [
193
224
  styles.bnEditor,
194
225
  styles.bnRoot,
195
- options.defaultStyles ? styles.defaultStyles : "",
196
- options.editorDOMAttributes?.class || "",
226
+ newOptions.defaultStyles ? styles.defaultStyles : "",
227
+ newOptions.editorDOMAttributes?.class || "",
197
228
  ].join(" "),
198
229
  },
199
230
  },
200
231
  };
201
232
 
202
- if (options.parentElement) {
203
- tiptapOptions.element = options.parentElement;
233
+ if (newOptions.parentElement) {
234
+ tiptapOptions.element = newOptions.parentElement;
204
235
  }
205
236
 
206
237
  this._tiptapEditor = new Editor(tiptapOptions) as Editor & {
@@ -212,11 +243,11 @@ export class BlockNoteEditor {
212
243
  * Gets a snapshot of all top-level (non-nested) blocks in the editor.
213
244
  * @returns A snapshot of all top-level (non-nested) blocks in the editor.
214
245
  */
215
- public get topLevelBlocks(): Block[] {
216
- const blocks: Block[] = [];
246
+ public get topLevelBlocks(): Block<BSchema>[] {
247
+ const blocks: Block<BSchema>[] = [];
217
248
 
218
249
  this._tiptapEditor.state.doc.firstChild!.descendants((node) => {
219
- blocks.push(nodeToBlock(node, this.blockCache));
250
+ blocks.push(nodeToBlock(node, this.schema, this.blockCache));
220
251
 
221
252
  return false;
222
253
  });
@@ -229,12 +260,14 @@ export class BlockNoteEditor {
229
260
  * @param blockIdentifier The identifier of an existing block that should be retrieved.
230
261
  * @returns The block that matches the identifier, or `undefined` if no matching block was found.
231
262
  */
232
- public getBlock(blockIdentifier: BlockIdentifier): Block | undefined {
263
+ public getBlock(
264
+ blockIdentifier: BlockIdentifier
265
+ ): Block<BSchema> | undefined {
233
266
  const id =
234
267
  typeof blockIdentifier === "string"
235
268
  ? blockIdentifier
236
269
  : blockIdentifier.id;
237
- let newBlock: Block | undefined = undefined;
270
+ let newBlock: Block<BSchema> | undefined = undefined;
238
271
 
239
272
  this._tiptapEditor.state.doc.firstChild!.descendants((node) => {
240
273
  if (typeof newBlock !== "undefined") {
@@ -245,7 +278,7 @@ export class BlockNoteEditor {
245
278
  return true;
246
279
  }
247
280
 
248
- newBlock = nodeToBlock(node, this.blockCache);
281
+ newBlock = nodeToBlock(node, this.schema, this.blockCache);
249
282
 
250
283
  return false;
251
284
  });
@@ -259,7 +292,7 @@ export class BlockNoteEditor {
259
292
  * @param reverse Whether the blocks should be traversed in reverse order.
260
293
  */
261
294
  public forEachBlock(
262
- callback: (block: Block) => boolean,
295
+ callback: (block: Block<BSchema>) => boolean,
263
296
  reverse: boolean = false
264
297
  ): void {
265
298
  const blocks = this.topLevelBlocks.slice();
@@ -268,7 +301,7 @@ export class BlockNoteEditor {
268
301
  blocks.reverse();
269
302
  }
270
303
 
271
- function traverseBlockArray(blockArray: Block[]): boolean {
304
+ function traverseBlockArray(blockArray: Block<BSchema>[]): boolean {
272
305
  for (const block of blockArray) {
273
306
  if (!callback(block)) {
274
307
  return false;
@@ -289,11 +322,19 @@ export class BlockNoteEditor {
289
322
  traverseBlockArray(blocks);
290
323
  }
291
324
 
325
+ /**
326
+ * Executes a callback whenever the editor's contents change.
327
+ * @param callback The callback to execute.
328
+ */
329
+ public onEditorContentChange(callback: () => void) {
330
+ this._tiptapEditor.on("update", callback);
331
+ }
332
+
292
333
  /**
293
334
  * Gets a snapshot of the current text cursor position.
294
335
  * @returns A snapshot of the current text cursor position.
295
336
  */
296
- public getTextCursorPosition(): TextCursorPosition {
337
+ public getTextCursorPosition(): TextCursorPosition<BSchema> {
297
338
  const { node, depth, startPos, endPos } = getBlockInfoFromPos(
298
339
  this._tiptapEditor.state.doc,
299
340
  this._tiptapEditor.state.selection.from
@@ -321,15 +362,15 @@ export class BlockNoteEditor {
321
362
  }
322
363
 
323
364
  return {
324
- block: nodeToBlock(node, this.blockCache),
365
+ block: nodeToBlock(node, this.schema, this.blockCache),
325
366
  prevBlock:
326
367
  prevNode === undefined
327
368
  ? undefined
328
- : nodeToBlock(prevNode, this.blockCache),
369
+ : nodeToBlock(prevNode, this.schema, this.blockCache),
329
370
  nextBlock:
330
371
  nextNode === undefined
331
372
  ? undefined
332
- : nodeToBlock(nextNode, this.blockCache),
373
+ : nodeToBlock(nextNode, this.schema, this.blockCache),
333
374
  };
334
375
  }
335
376
 
@@ -363,7 +404,7 @@ export class BlockNoteEditor {
363
404
  /**
364
405
  * Gets a snapshot of the current selection.
365
406
  */
366
- public getSelection(): Selection | undefined {
407
+ public getSelection(): Selection<BSchema> | undefined {
367
408
  if (
368
409
  this._tiptapEditor.state.selection.from ===
369
410
  this._tiptapEditor.state.selection.to
@@ -371,7 +412,7 @@ export class BlockNoteEditor {
371
412
  return undefined;
372
413
  }
373
414
 
374
- const blocks: Block[] = [];
415
+ const blocks: Block<BSchema>[] = [];
375
416
 
376
417
  this._tiptapEditor.state.doc.descendants((node, pos) => {
377
418
  if (node.type.spec.group !== "blockContent") {
@@ -388,6 +429,7 @@ export class BlockNoteEditor {
388
429
  blocks.push(
389
430
  nodeToBlock(
390
431
  this._tiptapEditor.state.doc.resolve(pos).node(),
432
+ this.schema,
391
433
  this.blockCache
392
434
  )
393
435
  );
@@ -423,7 +465,7 @@ export class BlockNoteEditor {
423
465
  * `referenceBlock`. Inserts the blocks at the start of the existing block's children if "nested" is used.
424
466
  */
425
467
  public insertBlocks(
426
- blocksToInsert: PartialBlock[],
468
+ blocksToInsert: PartialBlock<BSchema>[],
427
469
  referenceBlock: BlockIdentifier,
428
470
  placement: "before" | "after" | "nested" = "before"
429
471
  ): void {
@@ -437,7 +479,10 @@ export class BlockNoteEditor {
437
479
  * @param blockToUpdate The block that should be updated.
438
480
  * @param update A partial block which defines how the existing block should be changed.
439
481
  */
440
- public updateBlock(blockToUpdate: BlockIdentifier, update: PartialBlock) {
482
+ public updateBlock(
483
+ blockToUpdate: BlockIdentifier,
484
+ update: PartialBlock<BSchema>
485
+ ) {
441
486
  updateBlock(blockToUpdate, update, this._tiptapEditor);
442
487
  }
443
488
 
@@ -458,7 +503,7 @@ export class BlockNoteEditor {
458
503
  */
459
504
  public replaceBlocks(
460
505
  blocksToRemove: BlockIdentifier[],
461
- blocksToInsert: PartialBlock[]
506
+ blocksToInsert: PartialBlock<BSchema>[]
462
507
  ) {
463
508
  replaceBlocks(blocksToRemove, blocksToInsert, this._tiptapEditor);
464
509
  }
@@ -504,6 +549,8 @@ export class BlockNoteEditor {
504
549
  ]);
505
550
  const colorStyles = new Set<ColorStyle>(["textColor", "backgroundColor"]);
506
551
 
552
+ this._tiptapEditor.view.focus();
553
+
507
554
  for (const [style, value] of Object.entries(styles)) {
508
555
  if (toggleStyles.has(style as ToggledStyle)) {
509
556
  this._tiptapEditor.commands.setMark(style);
@@ -518,6 +565,8 @@ export class BlockNoteEditor {
518
565
  * @param styles The styles to remove.
519
566
  */
520
567
  public removeStyles(styles: Styles) {
568
+ this._tiptapEditor.view.focus();
569
+
521
570
  for (const style of Object.keys(styles)) {
522
571
  this._tiptapEditor.commands.unsetMark(style);
523
572
  }
@@ -537,6 +586,8 @@ export class BlockNoteEditor {
537
586
  ]);
538
587
  const colorStyles = new Set<ColorStyle>(["textColor", "backgroundColor"]);
539
588
 
589
+ this._tiptapEditor.view.focus();
590
+
540
591
  for (const [style, value] of Object.entries(styles)) {
541
592
  if (toggleStyles.has(style as ToggledStyle)) {
542
593
  this._tiptapEditor.commands.toggleMark(style);
@@ -632,7 +683,7 @@ export class BlockNoteEditor {
632
683
  * @param blocks An array of blocks that should be serialized into HTML.
633
684
  * @returns The blocks, serialized as an HTML string.
634
685
  */
635
- public async blocksToHTML(blocks: Block[]): Promise<string> {
686
+ public async blocksToHTML(blocks: Block<BSchema>[]): Promise<string> {
636
687
  return blocksToHTML(blocks, this._tiptapEditor.schema);
637
688
  }
638
689
 
@@ -643,8 +694,8 @@ export class BlockNoteEditor {
643
694
  * @param html The HTML string to parse blocks from.
644
695
  * @returns The blocks parsed from the HTML string.
645
696
  */
646
- public async HTMLToBlocks(html: string): Promise<Block[]> {
647
- return HTMLToBlocks(html, this._tiptapEditor.schema);
697
+ public async HTMLToBlocks(html: string): Promise<Block<BSchema>[]> {
698
+ return HTMLToBlocks(html, this.schema, this._tiptapEditor.schema);
648
699
  }
649
700
 
650
701
  /**
@@ -653,7 +704,7 @@ export class BlockNoteEditor {
653
704
  * @param blocks An array of blocks that should be serialized into Markdown.
654
705
  * @returns The blocks, serialized as a Markdown string.
655
706
  */
656
- public async blocksToMarkdown(blocks: Block[]): Promise<string> {
707
+ public async blocksToMarkdown(blocks: Block<BSchema>[]): Promise<string> {
657
708
  return blocksToMarkdown(blocks, this._tiptapEditor.schema);
658
709
  }
659
710
 
@@ -664,8 +715,8 @@ export class BlockNoteEditor {
664
715
  * @param markdown The Markdown string to parse blocks from.
665
716
  * @returns The blocks parsed from the Markdown string.
666
717
  */
667
- public async markdownToBlocks(markdown: string): Promise<Block[]> {
668
- return markdownToBlocks(markdown, this._tiptapEditor.schema);
718
+ public async markdownToBlocks(markdown: string): Promise<Block<BSchema>[]> {
719
+ return markdownToBlocks(markdown, this.schema, this._tiptapEditor.schema);
669
720
  }
670
721
 
671
722
  /**
@@ -20,15 +20,20 @@ import styles from "./editor.module.css";
20
20
  import { BackgroundColorExtension } from "./extensions/BackgroundColor/BackgroundColorExtension";
21
21
  import { BackgroundColorMark } from "./extensions/BackgroundColor/BackgroundColorMark";
22
22
  import { blocks } from "./extensions/Blocks";
23
+ import { BlockSchema } from "./extensions/Blocks/api/blockTypes";
24
+ import { CustomBlockSerializerExtension } from "./extensions/Blocks/api/serialization";
23
25
  import blockStyles from "./extensions/Blocks/nodes/Block.module.css";
24
26
  import { BlockSideMenuFactory } from "./extensions/DraggableBlocks/BlockSideMenuFactoryTypes";
25
- import { DraggableBlocksExtension } from "./extensions/DraggableBlocks/DraggableBlocksExtension";
26
- import { FormattingToolbarExtension } from "./extensions/FormattingToolbar/FormattingToolbarExtension";
27
+ import { createDraggableBlocksExtension } from "./extensions/DraggableBlocks/DraggableBlocksExtension";
28
+ import { createFormattingToolbarExtension } from "./extensions/FormattingToolbar/FormattingToolbarExtension";
27
29
  import { FormattingToolbarFactory } from "./extensions/FormattingToolbar/FormattingToolbarFactoryTypes";
28
30
  import HyperlinkMark from "./extensions/HyperlinkToolbar/HyperlinkMark";
29
31
  import { HyperlinkToolbarFactory } from "./extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes";
30
32
  import { Placeholder } from "./extensions/Placeholder/PlaceholderExtension";
31
- import { BaseSlashMenuItem, SlashMenuExtension } from "./extensions/SlashMenu";
33
+ import {
34
+ BaseSlashMenuItem,
35
+ createSlashMenuExtension,
36
+ } from "./extensions/SlashMenu";
32
37
  import { TextAlignmentExtension } from "./extensions/TextAlignment/TextAlignmentExtension";
33
38
  import { TextColorExtension } from "./extensions/TextColor/TextColorExtension";
34
39
  import { TextColorMark } from "./extensions/TextColor/TextColorMark";
@@ -36,20 +41,21 @@ import { TrailingNode } from "./extensions/TrailingNode/TrailingNodeExtension";
36
41
  import UniqueID from "./extensions/UniqueID/UniqueID";
37
42
  import { SuggestionsMenuFactory } from "./shared/plugins/suggestion/SuggestionsMenuFactoryTypes";
38
43
 
39
- export type UiFactories = Partial<{
40
- formattingToolbarFactory: FormattingToolbarFactory;
44
+ export type UiFactories<BSchema extends BlockSchema> = Partial<{
45
+ formattingToolbarFactory: FormattingToolbarFactory<BSchema>;
41
46
  hyperlinkToolbarFactory: HyperlinkToolbarFactory;
42
- slashMenuFactory: SuggestionsMenuFactory<BaseSlashMenuItem>;
43
- blockSideMenuFactory: BlockSideMenuFactory;
47
+ slashMenuFactory: SuggestionsMenuFactory<BaseSlashMenuItem<BSchema>>;
48
+ blockSideMenuFactory: BlockSideMenuFactory<BSchema>;
44
49
  }>;
45
50
 
46
51
  /**
47
52
  * Get all the Tiptap extensions BlockNote is configured with by default
48
53
  */
49
- export const getBlockNoteExtensions = (opts: {
50
- editor: BlockNoteEditor;
51
- uiFactories: UiFactories;
52
- slashCommands: BaseSlashMenuItem[];
54
+ export const getBlockNoteExtensions = <BSchema extends BlockSchema>(opts: {
55
+ editor: BlockNoteEditor<BSchema>;
56
+ uiFactories: UiFactories<BSchema>;
57
+ slashCommands: BaseSlashMenuItem<any>[]; // couldn't fix type, see https://github.com/TypeCellOS/BlockNote/pull/191#discussion_r1210708771
58
+ blockSchema: BSchema;
53
59
  collaboration?: {
54
60
  fragment: Y.XmlFragment;
55
61
  user: {
@@ -101,6 +107,10 @@ export const getBlockNoteExtensions = (opts: {
101
107
 
102
108
  // custom blocks:
103
109
  ...blocks,
110
+ ...Object.values(opts.blockSchema).map((blockSpec) =>
111
+ blockSpec.node.configure({ editor: opts.editor })
112
+ ),
113
+ CustomBlockSerializerExtension,
104
114
 
105
115
  Dropcursor.configure({ width: 5, color: "#ddeeff" }),
106
116
  // This needs to be at the bottom of this list, because Key events (such as enter, when selecting a /command),
@@ -147,7 +157,7 @@ export const getBlockNoteExtensions = (opts: {
147
157
 
148
158
  if (opts.uiFactories.blockSideMenuFactory) {
149
159
  ret.push(
150
- DraggableBlocksExtension.configure({
160
+ createDraggableBlocksExtension<BSchema>().configure({
151
161
  editor: opts.editor,
152
162
  blockSideMenuFactory: opts.uiFactories.blockSideMenuFactory,
153
163
  })
@@ -156,7 +166,7 @@ export const getBlockNoteExtensions = (opts: {
156
166
 
157
167
  if (opts.uiFactories.formattingToolbarFactory) {
158
168
  ret.push(
159
- FormattingToolbarExtension.configure({
169
+ createFormattingToolbarExtension<BSchema>().configure({
160
170
  editor: opts.editor,
161
171
  formattingToolbarFactory: opts.uiFactories.formattingToolbarFactory,
162
172
  })
@@ -175,7 +185,7 @@ export const getBlockNoteExtensions = (opts: {
175
185
 
176
186
  if (opts.uiFactories.slashMenuFactory) {
177
187
  ret.push(
178
- SlashMenuExtension.configure({
188
+ createSlashMenuExtension<BSchema>().configure({
179
189
  editor: opts.editor,
180
190
  commands: opts.slashCommands,
181
191
  slashMenuFactory: opts.uiFactories.slashMenuFactory,
@@ -1,5 +1,6 @@
1
1
  import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
2
  import { Block, BlockNoteEditor, PartialBlock } from "../..";
3
+ import { DefaultBlockSchema } from "../../extensions/Blocks/api/defaultBlocks";
3
4
 
4
5
  let editor: BlockNoteEditor;
5
6
 
@@ -14,11 +15,13 @@ function waitForEditor() {
14
15
  });
15
16
  }
16
17
 
17
- let singleBlock: PartialBlock;
18
+ let singleBlock: PartialBlock<DefaultBlockSchema>;
18
19
 
19
- let multipleBlocks: PartialBlock[];
20
+ let multipleBlocks: PartialBlock<DefaultBlockSchema>[];
20
21
 
21
- let insert: (placement: "before" | "nested" | "after") => Block[];
22
+ let insert: (
23
+ placement: "before" | "nested" | "after"
24
+ ) => Block<DefaultBlockSchema>[];
22
25
 
23
26
  beforeEach(() => {
24
27
  (window as Window & { __TEST_OPTIONS?: {} }).__TEST_OPTIONS = {};
@@ -2,13 +2,14 @@ import { Editor } from "@tiptap/core";
2
2
  import { Node } from "prosemirror-model";
3
3
  import {
4
4
  BlockIdentifier,
5
+ BlockSchema,
5
6
  PartialBlock,
6
7
  } from "../../extensions/Blocks/api/blockTypes";
7
8
  import { blockToNode } from "../nodeConversions/nodeConversions";
8
9
  import { getNodeById } from "../util/nodeUtil";
9
10
 
10
- export function insertBlocks(
11
- blocksToInsert: PartialBlock[],
11
+ export function insertBlocks<BSchema extends BlockSchema>(
12
+ blocksToInsert: PartialBlock<BSchema>[],
12
13
  referenceBlock: BlockIdentifier,
13
14
  placement: "before" | "after" | "nested" = "before",
14
15
  editor: Editor
@@ -56,9 +57,9 @@ export function insertBlocks(
56
57
  editor.view.dispatch(editor.state.tr.insert(insertionPos, nodesToInsert));
57
58
  }
58
59
 
59
- export function updateBlock(
60
+ export function updateBlock<BSchema extends BlockSchema>(
60
61
  blockToUpdate: BlockIdentifier,
61
- update: PartialBlock,
62
+ update: PartialBlock<BSchema>,
62
63
  editor: Editor
63
64
  ) {
64
65
  const id =
@@ -115,9 +116,9 @@ export function removeBlocks(
115
116
  }
116
117
  }
117
118
 
118
- export function replaceBlocks(
119
+ export function replaceBlocks<BSchema extends BlockSchema>(
119
120
  blocksToRemove: BlockIdentifier[],
120
- blocksToInsert: PartialBlock[],
121
+ blocksToInsert: PartialBlock<BSchema>[],
121
122
  editor: Editor
122
123
  ) {
123
124
  insertBlocks(blocksToInsert, blocksToRemove[0], "before", editor);
@@ -1,25 +1,30 @@
1
1
  import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
2
  import { Block, BlockNoteEditor } from "../..";
3
3
  import UniqueID from "../../extensions/UniqueID/UniqueID";
4
+ import { DefaultBlockSchema } from "../../extensions/Blocks/api/defaultBlocks";
4
5
 
5
6
  let editor: BlockNoteEditor;
6
7
 
7
- let nonNestedBlocks: Block[];
8
+ let nonNestedBlocks: Block<DefaultBlockSchema>[];
8
9
  let nonNestedHTML: string;
9
10
  let nonNestedMarkdown: string;
10
11
 
11
- let nestedBlocks: Block[];
12
+ let nestedBlocks: Block<DefaultBlockSchema>[];
12
13
  // let nestedHTML: string;
13
14
  // let nestedMarkdown: string;
14
15
 
15
- let styledBlocks: Block[];
16
+ let styledBlocks: Block<DefaultBlockSchema>[];
16
17
  let styledHTML: string;
17
18
  let styledMarkdown: string;
18
19
 
19
- let complexBlocks: Block[];
20
+ let complexBlocks: Block<DefaultBlockSchema>[];
20
21
  // let complexHTML: string;
21
22
  // let complexMarkdown: string;
22
23
 
24
+ function removeInlineContentClass(html: string) {
25
+ return html.replace(/ class="_inlineContent_1c48ad"/g, "");
26
+ }
27
+
23
28
  beforeEach(() => {
24
29
  (window as Window & { __TEST_OPTIONS?: {} }).__TEST_OPTIONS = {};
25
30
 
@@ -665,7 +670,7 @@ describe("Non-Nested Block/HTML/Markdown Conversions", () => {
665
670
  it("Convert non-nested blocks to HTML", async () => {
666
671
  const output = await editor.blocksToHTML(nonNestedBlocks);
667
672
 
668
- expect(output).toMatchSnapshot();
673
+ expect(removeInlineContentClass(output)).toMatchSnapshot();
669
674
  });
670
675
 
671
676
  it("Convert non-nested blocks to Markdown", async () => {
@@ -691,7 +696,7 @@ describe("Nested Block/HTML/Markdown Conversions", () => {
691
696
  it("Convert nested blocks to HTML", async () => {
692
697
  const output = await editor.blocksToHTML(nestedBlocks);
693
698
 
694
- expect(output).toMatchSnapshot();
699
+ expect(removeInlineContentClass(output)).toMatchSnapshot();
695
700
  });
696
701
 
697
702
  it("Convert nested blocks to Markdown", async () => {
@@ -717,7 +722,7 @@ describe("Styled Block/HTML/Markdown Conversions", () => {
717
722
  it("Convert styled blocks to HTML", async () => {
718
723
  const output = await editor.blocksToHTML(styledBlocks);
719
724
 
720
- expect(output).toMatchSnapshot();
725
+ expect(removeInlineContentClass(output)).toMatchSnapshot();
721
726
  });
722
727
 
723
728
  it("Convert styled blocks to Markdown", async () => {
@@ -743,7 +748,7 @@ describe("Complex Block/HTML/Markdown Conversions", () => {
743
748
  it("Convert complex blocks to HTML", async () => {
744
749
  const output = await editor.blocksToHTML(complexBlocks);
745
750
 
746
- expect(output).toMatchSnapshot();
751
+ expect(removeInlineContentClass(output)).toMatchSnapshot();
747
752
  });
748
753
 
749
754
  it("Convert complex blocks to Markdown", async () => {