@blocknote/core 0.7.0 → 0.8.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 (70) hide show
  1. package/dist/blocknote.js +1428 -1252
  2. package/dist/blocknote.js.map +1 -1
  3. package/dist/blocknote.umd.cjs +2 -2
  4. package/dist/blocknote.umd.cjs.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +4 -3
  7. package/src/BlockNoteEditor.ts +100 -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/nodeConversions.test.ts +29 -10
  14. package/src/api/nodeConversions/nodeConversions.ts +33 -12
  15. package/src/api/nodeConversions/testUtil.ts +8 -4
  16. package/src/editor.module.css +0 -1
  17. package/src/extensions/Blocks/api/block.ts +229 -0
  18. package/src/extensions/Blocks/api/blockTypes.ts +158 -71
  19. package/src/extensions/Blocks/api/cursorPositionTypes.ts +5 -5
  20. package/src/extensions/Blocks/api/defaultBlocks.ts +44 -0
  21. package/src/extensions/Blocks/api/selectionTypes.ts +3 -3
  22. package/src/extensions/Blocks/api/serialization.ts +29 -0
  23. package/src/extensions/Blocks/index.ts +0 -8
  24. package/src/extensions/Blocks/nodes/Block.module.css +24 -12
  25. package/src/extensions/Blocks/nodes/BlockContainer.ts +8 -4
  26. package/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.ts +4 -4
  27. package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +5 -5
  28. package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +100 -97
  29. package/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.ts +4 -4
  30. package/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.ts +11 -9
  31. package/src/extensions/DraggableBlocks/DraggableBlocksExtension.ts +6 -5
  32. package/src/extensions/DraggableBlocks/DraggableBlocksPlugin.ts +12 -11
  33. package/src/extensions/FormattingToolbar/FormattingToolbarExtension.ts +21 -16
  34. package/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.ts +9 -5
  35. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +30 -42
  36. package/src/extensions/Placeholder/PlaceholderExtension.ts +1 -0
  37. package/src/extensions/SlashMenu/BaseSlashMenuItem.ts +5 -2
  38. package/src/extensions/SlashMenu/SlashMenuExtension.ts +37 -33
  39. package/src/extensions/SlashMenu/defaultSlashMenuItems.tsx +14 -10
  40. package/src/extensions/SlashMenu/index.ts +2 -2
  41. package/src/index.ts +4 -0
  42. package/src/shared/plugins/suggestion/SuggestionPlugin.ts +29 -13
  43. package/types/src/BlockNoteEditor.d.ts +37 -23
  44. package/types/src/BlockNoteExtensions.d.ts +15 -8
  45. package/types/src/api/blockManipulation/blockManipulation.d.ts +4 -4
  46. package/types/src/api/formatConversions/formatConversions.d.ts +5 -5
  47. package/types/src/api/nodeConversions/nodeConversions.d.ts +3 -3
  48. package/types/src/api/nodeConversions/testUtil.d.ts +2 -2
  49. package/types/src/extensions/Blocks/api/block.d.ts +6 -5
  50. package/types/src/extensions/Blocks/api/blockTypes.d.ts +77 -33
  51. package/types/src/extensions/Blocks/api/cursorPositionTypes.d.ts +5 -5
  52. package/types/src/extensions/Blocks/api/selectionTypes.d.ts +3 -3
  53. package/types/src/extensions/Blocks/api/serialization.d.ts +2 -0
  54. package/types/src/extensions/Blocks/nodes/BlockContainer.d.ts +3 -3
  55. package/types/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.d.ts +1 -2
  56. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.d.ts +1 -2
  57. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.d.ts +1 -2
  58. package/types/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.d.ts +1 -2
  59. package/types/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.d.ts +7 -7
  60. package/types/src/extensions/DraggableBlocks/DraggableBlocksExtension.d.ts +5 -4
  61. package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +11 -10
  62. package/types/src/extensions/FormattingToolbar/FormattingToolbarExtension.d.ts +6 -5
  63. package/types/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.d.ts +4 -3
  64. package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +16 -19
  65. package/types/src/extensions/SlashMenu/BaseSlashMenuItem.d.ts +4 -3
  66. package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +5 -4
  67. package/types/src/extensions/SlashMenu/defaultSlashMenuItems.d.ts +66 -1
  68. package/types/src/extensions/SlashMenu/index.d.ts +2 -2
  69. package/types/src/index.d.ts +4 -0
  70. package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +5 -4
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.0",
6
+ "version": "0.8.0",
7
7
  "files": [
8
8
  "dist",
9
9
  "types",
@@ -30,6 +30,7 @@
30
30
  "module": "./dist/blocknote.js",
31
31
  "exports": {
32
32
  ".": {
33
+ "types": "./types/src/index.d.ts",
33
34
  "import": "./dist/blocknote.js",
34
35
  "require": "./dist/blocknote.umd.cjs"
35
36
  },
@@ -91,7 +92,7 @@
91
92
  "eslint-config-react-app": "^7.0.0",
92
93
  "jsdom": "^21.1.0",
93
94
  "prettier": "^2.7.1",
94
- "typescript": "^4.5.4",
95
+ "typescript": "^5.0.4",
95
96
  "vite": "^4.1.2",
96
97
  "vite-plugin-eslint": "^1.8.1",
97
98
  "vitest": "^0.28.5"
@@ -109,5 +110,5 @@
109
110
  "access": "public",
110
111
  "registry": "https://registry.npmjs.org/"
111
112
  },
112
- "gitHead": "c0fc174529fb2bcd23c53956818ad0f451c0e65c"
113
+ "gitHead": "cec8be26bbdf008846b907b52966ae3ef126cd64"
113
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,9 +145,10 @@ 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;
@@ -145,20 +158,34 @@ export class BlockNoteEditor {
145
158
  this._tiptapEditor.view.focus();
146
159
  }
147
160
 
148
- constructor(private readonly options: Partial<BlockNoteEditorOptions> = {}) {
161
+ constructor(
162
+ private readonly options: Partial<BlockNoteEditorOptions<BSchema>> = {}
163
+ ) {
149
164
  // apply defaults
150
- options = {
165
+ const newOptions: Omit<typeof options, "defaultStyles" | "blockSchema"> & {
166
+ defaultStyles: boolean;
167
+ blockSchema: BSchema;
168
+ } = {
151
169
  defaultStyles: true,
170
+ // TODO: There's a lot of annoying typing stuff to deal with here. If
171
+ // BSchema is specified, then options.blockSchema should also be required.
172
+ // If BSchema is not specified, then options.blockSchema should also not
173
+ // be defined. Unfortunately, trying to implement these constraints seems
174
+ // to be a huge pain, hence the `as any` casts.
175
+ blockSchema: options.blockSchema || (defaultBlockSchema as any),
152
176
  ...options,
153
177
  };
154
178
 
155
- const extensions = getBlockNoteExtensions({
179
+ const extensions = getBlockNoteExtensions<BSchema>({
156
180
  editor: this,
157
- uiFactories: options.uiFactories || {},
158
- slashCommands: options.slashCommands || defaultSlashMenuItems,
159
- collaboration: options.collaboration,
181
+ uiFactories: newOptions.uiFactories || {},
182
+ slashCommands: newOptions.slashCommands || defaultSlashMenuItems,
183
+ blockSchema: newOptions.blockSchema,
184
+ collaboration: newOptions.collaboration,
160
185
  });
161
186
 
187
+ this.schema = newOptions.blockSchema;
188
+
162
189
  const tiptapOptions: EditorOptions = {
163
190
  // TODO: This approach to setting initial content is "cleaner" but requires the PM editor schema, which is only
164
191
  // created after initializing the TipTap editor. Not sure it's feasible.
@@ -168,39 +195,39 @@ export class BlockNoteEditor {
168
195
  // blockToNode(block, this._tiptapEditor.schema).toJSON()
169
196
  // ),
170
197
  ...blockNoteTipTapOptions,
171
- ...options._tiptapOptions,
198
+ ...newOptions._tiptapOptions,
172
199
  onCreate: () => {
173
- options.onEditorReady?.(this);
174
- options.initialContent &&
175
- this.replaceBlocks(this.topLevelBlocks, options.initialContent);
200
+ newOptions.onEditorReady?.(this);
201
+ newOptions.initialContent &&
202
+ this.replaceBlocks(this.topLevelBlocks, newOptions.initialContent);
176
203
  },
177
204
  onUpdate: () => {
178
- options.onEditorContentChange?.(this);
205
+ newOptions.onEditorContentChange?.(this);
179
206
  },
180
207
  onSelectionUpdate: () => {
181
- options.onTextCursorPositionChange?.(this);
208
+ newOptions.onTextCursorPositionChange?.(this);
182
209
  },
183
210
  editable: options.editable === undefined ? true : options.editable,
184
211
  extensions:
185
- options.enableBlockNoteExtensions === false
186
- ? options._tiptapOptions?.extensions
187
- : [...(options._tiptapOptions?.extensions || []), ...extensions],
212
+ newOptions.enableBlockNoteExtensions === false
213
+ ? newOptions._tiptapOptions?.extensions
214
+ : [...(newOptions._tiptapOptions?.extensions || []), ...extensions],
188
215
  editorProps: {
189
216
  attributes: {
190
217
  "data-theme": options.theme || "light",
191
- ...(options.editorDOMAttributes || {}),
218
+ ...(newOptions.editorDOMAttributes || {}),
192
219
  class: [
193
220
  styles.bnEditor,
194
221
  styles.bnRoot,
195
- options.defaultStyles ? styles.defaultStyles : "",
196
- options.editorDOMAttributes?.class || "",
222
+ newOptions.defaultStyles ? styles.defaultStyles : "",
223
+ newOptions.editorDOMAttributes?.class || "",
197
224
  ].join(" "),
198
225
  },
199
226
  },
200
227
  };
201
228
 
202
- if (options.parentElement) {
203
- tiptapOptions.element = options.parentElement;
229
+ if (newOptions.parentElement) {
230
+ tiptapOptions.element = newOptions.parentElement;
204
231
  }
205
232
 
206
233
  this._tiptapEditor = new Editor(tiptapOptions) as Editor & {
@@ -212,11 +239,11 @@ export class BlockNoteEditor {
212
239
  * Gets a snapshot of all top-level (non-nested) blocks in the editor.
213
240
  * @returns A snapshot of all top-level (non-nested) blocks in the editor.
214
241
  */
215
- public get topLevelBlocks(): Block[] {
216
- const blocks: Block[] = [];
242
+ public get topLevelBlocks(): Block<BSchema>[] {
243
+ const blocks: Block<BSchema>[] = [];
217
244
 
218
245
  this._tiptapEditor.state.doc.firstChild!.descendants((node) => {
219
- blocks.push(nodeToBlock(node, this.blockCache));
246
+ blocks.push(nodeToBlock(node, this.schema, this.blockCache));
220
247
 
221
248
  return false;
222
249
  });
@@ -229,12 +256,14 @@ export class BlockNoteEditor {
229
256
  * @param blockIdentifier The identifier of an existing block that should be retrieved.
230
257
  * @returns The block that matches the identifier, or `undefined` if no matching block was found.
231
258
  */
232
- public getBlock(blockIdentifier: BlockIdentifier): Block | undefined {
259
+ public getBlock(
260
+ blockIdentifier: BlockIdentifier
261
+ ): Block<BSchema> | undefined {
233
262
  const id =
234
263
  typeof blockIdentifier === "string"
235
264
  ? blockIdentifier
236
265
  : blockIdentifier.id;
237
- let newBlock: Block | undefined = undefined;
266
+ let newBlock: Block<BSchema> | undefined = undefined;
238
267
 
239
268
  this._tiptapEditor.state.doc.firstChild!.descendants((node) => {
240
269
  if (typeof newBlock !== "undefined") {
@@ -245,7 +274,7 @@ export class BlockNoteEditor {
245
274
  return true;
246
275
  }
247
276
 
248
- newBlock = nodeToBlock(node, this.blockCache);
277
+ newBlock = nodeToBlock(node, this.schema, this.blockCache);
249
278
 
250
279
  return false;
251
280
  });
@@ -259,7 +288,7 @@ export class BlockNoteEditor {
259
288
  * @param reverse Whether the blocks should be traversed in reverse order.
260
289
  */
261
290
  public forEachBlock(
262
- callback: (block: Block) => boolean,
291
+ callback: (block: Block<BSchema>) => boolean,
263
292
  reverse: boolean = false
264
293
  ): void {
265
294
  const blocks = this.topLevelBlocks.slice();
@@ -268,7 +297,7 @@ export class BlockNoteEditor {
268
297
  blocks.reverse();
269
298
  }
270
299
 
271
- function traverseBlockArray(blockArray: Block[]): boolean {
300
+ function traverseBlockArray(blockArray: Block<BSchema>[]): boolean {
272
301
  for (const block of blockArray) {
273
302
  if (!callback(block)) {
274
303
  return false;
@@ -289,11 +318,19 @@ export class BlockNoteEditor {
289
318
  traverseBlockArray(blocks);
290
319
  }
291
320
 
321
+ /**
322
+ * Executes a callback whenever the editor's contents change.
323
+ * @param callback The callback to execute.
324
+ */
325
+ public onEditorContentChange(callback: () => void) {
326
+ this._tiptapEditor.on("update", callback);
327
+ }
328
+
292
329
  /**
293
330
  * Gets a snapshot of the current text cursor position.
294
331
  * @returns A snapshot of the current text cursor position.
295
332
  */
296
- public getTextCursorPosition(): TextCursorPosition {
333
+ public getTextCursorPosition(): TextCursorPosition<BSchema> {
297
334
  const { node, depth, startPos, endPos } = getBlockInfoFromPos(
298
335
  this._tiptapEditor.state.doc,
299
336
  this._tiptapEditor.state.selection.from
@@ -321,15 +358,15 @@ export class BlockNoteEditor {
321
358
  }
322
359
 
323
360
  return {
324
- block: nodeToBlock(node, this.blockCache),
361
+ block: nodeToBlock(node, this.schema, this.blockCache),
325
362
  prevBlock:
326
363
  prevNode === undefined
327
364
  ? undefined
328
- : nodeToBlock(prevNode, this.blockCache),
365
+ : nodeToBlock(prevNode, this.schema, this.blockCache),
329
366
  nextBlock:
330
367
  nextNode === undefined
331
368
  ? undefined
332
- : nodeToBlock(nextNode, this.blockCache),
369
+ : nodeToBlock(nextNode, this.schema, this.blockCache),
333
370
  };
334
371
  }
335
372
 
@@ -363,7 +400,7 @@ export class BlockNoteEditor {
363
400
  /**
364
401
  * Gets a snapshot of the current selection.
365
402
  */
366
- public getSelection(): Selection | undefined {
403
+ public getSelection(): Selection<BSchema> | undefined {
367
404
  if (
368
405
  this._tiptapEditor.state.selection.from ===
369
406
  this._tiptapEditor.state.selection.to
@@ -371,7 +408,7 @@ export class BlockNoteEditor {
371
408
  return undefined;
372
409
  }
373
410
 
374
- const blocks: Block[] = [];
411
+ const blocks: Block<BSchema>[] = [];
375
412
 
376
413
  this._tiptapEditor.state.doc.descendants((node, pos) => {
377
414
  if (node.type.spec.group !== "blockContent") {
@@ -388,6 +425,7 @@ export class BlockNoteEditor {
388
425
  blocks.push(
389
426
  nodeToBlock(
390
427
  this._tiptapEditor.state.doc.resolve(pos).node(),
428
+ this.schema,
391
429
  this.blockCache
392
430
  )
393
431
  );
@@ -423,7 +461,7 @@ export class BlockNoteEditor {
423
461
  * `referenceBlock`. Inserts the blocks at the start of the existing block's children if "nested" is used.
424
462
  */
425
463
  public insertBlocks(
426
- blocksToInsert: PartialBlock[],
464
+ blocksToInsert: PartialBlock<BSchema>[],
427
465
  referenceBlock: BlockIdentifier,
428
466
  placement: "before" | "after" | "nested" = "before"
429
467
  ): void {
@@ -437,7 +475,10 @@ export class BlockNoteEditor {
437
475
  * @param blockToUpdate The block that should be updated.
438
476
  * @param update A partial block which defines how the existing block should be changed.
439
477
  */
440
- public updateBlock(blockToUpdate: BlockIdentifier, update: PartialBlock) {
478
+ public updateBlock(
479
+ blockToUpdate: BlockIdentifier,
480
+ update: PartialBlock<BSchema>
481
+ ) {
441
482
  updateBlock(blockToUpdate, update, this._tiptapEditor);
442
483
  }
443
484
 
@@ -458,7 +499,7 @@ export class BlockNoteEditor {
458
499
  */
459
500
  public replaceBlocks(
460
501
  blocksToRemove: BlockIdentifier[],
461
- blocksToInsert: PartialBlock[]
502
+ blocksToInsert: PartialBlock<BSchema>[]
462
503
  ) {
463
504
  replaceBlocks(blocksToRemove, blocksToInsert, this._tiptapEditor);
464
505
  }
@@ -504,6 +545,8 @@ export class BlockNoteEditor {
504
545
  ]);
505
546
  const colorStyles = new Set<ColorStyle>(["textColor", "backgroundColor"]);
506
547
 
548
+ this._tiptapEditor.view.focus();
549
+
507
550
  for (const [style, value] of Object.entries(styles)) {
508
551
  if (toggleStyles.has(style as ToggledStyle)) {
509
552
  this._tiptapEditor.commands.setMark(style);
@@ -518,6 +561,8 @@ export class BlockNoteEditor {
518
561
  * @param styles The styles to remove.
519
562
  */
520
563
  public removeStyles(styles: Styles) {
564
+ this._tiptapEditor.view.focus();
565
+
521
566
  for (const style of Object.keys(styles)) {
522
567
  this._tiptapEditor.commands.unsetMark(style);
523
568
  }
@@ -537,6 +582,8 @@ export class BlockNoteEditor {
537
582
  ]);
538
583
  const colorStyles = new Set<ColorStyle>(["textColor", "backgroundColor"]);
539
584
 
585
+ this._tiptapEditor.view.focus();
586
+
540
587
  for (const [style, value] of Object.entries(styles)) {
541
588
  if (toggleStyles.has(style as ToggledStyle)) {
542
589
  this._tiptapEditor.commands.toggleMark(style);
@@ -632,7 +679,7 @@ export class BlockNoteEditor {
632
679
  * @param blocks An array of blocks that should be serialized into HTML.
633
680
  * @returns The blocks, serialized as an HTML string.
634
681
  */
635
- public async blocksToHTML(blocks: Block[]): Promise<string> {
682
+ public async blocksToHTML(blocks: Block<BSchema>[]): Promise<string> {
636
683
  return blocksToHTML(blocks, this._tiptapEditor.schema);
637
684
  }
638
685
 
@@ -643,8 +690,8 @@ export class BlockNoteEditor {
643
690
  * @param html The HTML string to parse blocks from.
644
691
  * @returns The blocks parsed from the HTML string.
645
692
  */
646
- public async HTMLToBlocks(html: string): Promise<Block[]> {
647
- return HTMLToBlocks(html, this._tiptapEditor.schema);
693
+ public async HTMLToBlocks(html: string): Promise<Block<BSchema>[]> {
694
+ return HTMLToBlocks(html, this.schema, this._tiptapEditor.schema);
648
695
  }
649
696
 
650
697
  /**
@@ -653,7 +700,7 @@ export class BlockNoteEditor {
653
700
  * @param blocks An array of blocks that should be serialized into Markdown.
654
701
  * @returns The blocks, serialized as a Markdown string.
655
702
  */
656
- public async blocksToMarkdown(blocks: Block[]): Promise<string> {
703
+ public async blocksToMarkdown(blocks: Block<BSchema>[]): Promise<string> {
657
704
  return blocksToMarkdown(blocks, this._tiptapEditor.schema);
658
705
  }
659
706
 
@@ -664,8 +711,8 @@ export class BlockNoteEditor {
664
711
  * @param markdown The Markdown string to parse blocks from.
665
712
  * @returns The blocks parsed from the Markdown string.
666
713
  */
667
- public async markdownToBlocks(markdown: string): Promise<Block[]> {
668
- return markdownToBlocks(markdown, this._tiptapEditor.schema);
714
+ public async markdownToBlocks(markdown: string): Promise<Block<BSchema>[]> {
715
+ return markdownToBlocks(markdown, this.schema, this._tiptapEditor.schema);
669
716
  }
670
717
 
671
718
  /**
@@ -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 () => {