@blocknote/core 0.33.0 → 0.35.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 (42) hide show
  1. package/dist/blocknote.cjs +9 -9
  2. package/dist/blocknote.cjs.map +1 -1
  3. package/dist/blocknote.js +1822 -1688
  4. package/dist/blocknote.js.map +1 -1
  5. package/dist/tsconfig.tsbuildinfo +1 -1
  6. package/dist/webpack-stats.json +1 -1
  7. package/package.json +3 -3
  8. package/src/api/__snapshots__/blocks-indented-changed.json +129 -0
  9. package/src/api/__snapshots__/blocks-moved-deeper-into-nesting.json +164 -0
  10. package/src/api/__snapshots__/blocks-moved-multiple-in-same-transaction.json +188 -0
  11. package/src/api/__snapshots__/blocks-moved-to-different-parent.json +78 -0
  12. package/src/api/__snapshots__/blocks-moved-to-root-level.json +78 -0
  13. package/src/api/__snapshots__/blocks-outdented-changed.json +129 -0
  14. package/src/api/blockManipulation/commands/nestBlock/nestBlock.ts +58 -59
  15. package/src/api/clipboard/fromClipboard/handleFileInsertion.ts +12 -1
  16. package/src/api/nodeUtil.test.ts +228 -1
  17. package/src/api/nodeUtil.ts +139 -118
  18. package/src/api/parsers/markdown/detectMarkdown.test.ts +211 -0
  19. package/src/api/parsers/markdown/detectMarkdown.ts +3 -2
  20. package/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +2 -1
  21. package/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +2 -1
  22. package/src/blocks/QuoteBlockContent/QuoteBlockContent.ts +23 -1
  23. package/src/blocks/ToggleWrapper/createToggleWrapper.ts +2 -0
  24. package/src/blocks/defaultBlockTypeGuards.ts +30 -0
  25. package/src/editor/BlockNoteEditor.ts +105 -38
  26. package/src/editor/BlockNoteExtensions.ts +3 -5
  27. package/src/exporter/Exporter.ts +2 -0
  28. package/src/exporter/mapping.ts +1 -0
  29. package/src/extensions/BlockChange/BlockChangePlugin.ts +66 -0
  30. package/src/extensions/Collaboration/ForkYDocPlugin.ts +1 -1
  31. package/src/extensions/SideMenu/SideMenuPlugin.ts +288 -207
  32. package/src/schema/inlineContent/types.ts +8 -0
  33. package/types/src/api/nodeUtil.d.ts +19 -21
  34. package/types/src/api/parsers/markdown/detectMarkdown.test.d.ts +1 -0
  35. package/types/src/blocks/defaultBlockTypeGuards.d.ts +7 -1
  36. package/types/src/editor/BlockNoteEditor.d.ts +89 -36
  37. package/types/src/editor/BlockNoteExtensions.d.ts +0 -1
  38. package/types/src/exporter/Exporter.d.ts +1 -1
  39. package/types/src/exporter/mapping.d.ts +1 -1
  40. package/types/src/extensions/BlockChange/BlockChangePlugin.d.ts +15 -0
  41. package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +50 -9
  42. package/types/src/schema/inlineContent/types.d.ts +4 -0
@@ -1,9 +1,11 @@
1
1
  import { CellSelection } from "prosemirror-tables";
2
2
  import type { BlockNoteEditor } from "../editor/BlockNoteEditor.js";
3
3
  import {
4
+ BlockConfig,
4
5
  BlockFromConfig,
5
6
  BlockSchema,
6
7
  FileBlockConfig,
8
+ InlineContentConfig,
7
9
  InlineContentSchema,
8
10
  StyleSchema,
9
11
  } from "../schema/index.js";
@@ -35,6 +37,20 @@ export function checkDefaultBlockTypeInSchema<
35
37
  );
36
38
  }
37
39
 
40
+ export function checkBlockTypeInSchema<
41
+ BlockType extends string,
42
+ Config extends BlockConfig,
43
+ >(
44
+ blockType: BlockType,
45
+ blockConfig: Config,
46
+ editor: BlockNoteEditor<any, any, any>,
47
+ ): editor is BlockNoteEditor<{ [T in BlockType]: Config }, any, any> {
48
+ return (
49
+ blockType in editor.schema.blockSchema &&
50
+ editor.schema.blockSchema[blockType] === blockConfig
51
+ );
52
+ }
53
+
38
54
  export function checkDefaultInlineContentTypeInSchema<
39
55
  InlineContentType extends keyof DefaultInlineContentSchema,
40
56
  B extends BlockSchema,
@@ -54,6 +70,20 @@ export function checkDefaultInlineContentTypeInSchema<
54
70
  );
55
71
  }
56
72
 
73
+ export function checkInlineContentTypeInSchema<
74
+ InlineContentType extends string,
75
+ Config extends InlineContentConfig,
76
+ >(
77
+ inlineContentType: InlineContentType,
78
+ inlineContentConfig: Config,
79
+ editor: BlockNoteEditor<any, any, any>,
80
+ ): editor is BlockNoteEditor<any, { [T in InlineContentType]: Config }, any> {
81
+ return (
82
+ inlineContentType in editor.schema.inlineContentSchema &&
83
+ editor.schema.inlineContentSchema[inlineContentType] === inlineContentConfig
84
+ );
85
+ }
86
+
57
87
  export function checkBlockIsDefaultType<
58
88
  BlockType extends keyof DefaultBlockSchema,
59
89
  I extends InlineContentSchema,
@@ -120,6 +120,7 @@ import { EventEmitter } from "../util/EventEmitter.js";
120
120
  import { BlockNoteExtension } from "./BlockNoteExtension.js";
121
121
 
122
122
  import "../style.css";
123
+ import { BlockChangePlugin } from "../extensions/BlockChange/BlockChangePlugin.js";
123
124
 
124
125
  /**
125
126
  * A factory function that returns a BlockNoteExtension
@@ -155,8 +156,11 @@ export type BlockNoteEditorOptions<
155
156
 
156
157
  /**
157
158
  * When enabled, allows for collaboration between multiple users.
159
+ * See [Real-time Collaboration](https://www.blocknotejs.org/docs/advanced/real-time-collaboration) for more info.
160
+ *
161
+ * @remarks `CollaborationOptions`
158
162
  */
159
- collaboration: {
163
+ collaboration?: {
160
164
  /**
161
165
  * The Yjs XML fragment that's used for collaboration.
162
166
  */
@@ -190,7 +194,13 @@ export type BlockNoteEditorOptions<
190
194
  */
191
195
  codeBlock?: CodeBlockOptions;
192
196
 
193
- comments: {
197
+ /**
198
+ * Configuration for the comments feature, requires a `threadStore`.
199
+ *
200
+ * See [Comments](https://www.blocknotejs.org/docs/features/collaboration/comments) for more info.
201
+ * @remarks `CommentsOptions`
202
+ */
203
+ comments?: {
194
204
  threadStore: ThreadStore;
195
205
  };
196
206
 
@@ -199,25 +209,38 @@ export type BlockNoteEditorOptions<
199
209
  *
200
210
  * @default true
201
211
  */
202
- defaultStyles: boolean;
212
+ defaultStyles?: boolean;
203
213
 
204
214
  /**
205
215
  * A dictionary object containing translations for the editor.
216
+ *
217
+ * See [Localization / i18n](https://www.blocknotejs.org/docs/advanced/localization) for more info.
218
+ *
219
+ * @remarks `Dictionary` is a type that contains all the translations for the editor.
206
220
  */
207
221
  dictionary?: Dictionary & Record<string, any>;
208
222
 
209
223
  /**
210
224
  * Disable internal extensions (based on keys / extension name)
225
+ *
226
+ * @note Advanced
211
227
  */
212
- disableExtensions: string[];
228
+ disableExtensions?: string[];
213
229
 
214
230
  /**
215
231
  * An object containing attributes that should be added to HTML elements of the editor.
216
232
  *
233
+ * See [Adding DOM Attributes](https://www.blocknotejs.org/docs/theming#adding-dom-attributes) for more info.
234
+ *
217
235
  * @example { editor: { class: "my-editor-class" } }
236
+ * @remarks `Record<string, Record<string, string>>`
218
237
  */
219
- domAttributes: Partial<BlockNoteDOMAttributes>;
238
+ domAttributes?: Partial<BlockNoteDOMAttributes>;
220
239
 
240
+ /**
241
+ * A replacement indicator to use when dragging and dropping blocks. Uses the [ProseMirror drop cursor](https://github.com/ProseMirror/prosemirror-dropcursor), or a modified version when [Column Blocks](https://www.blocknotejs.org/docs/document-structure#column-blocks) are enabled.
242
+ * @remarks `() => Plugin`
243
+ */
221
244
  dropCursor?: (opts: {
222
245
  editor: BlockNoteEditor<
223
246
  NoInfer<BSchema>,
@@ -242,9 +265,13 @@ export type BlockNoteEditorOptions<
242
265
  };
243
266
 
244
267
  /**
245
- * The content that should be in the editor when it's created, represented as an array of partial block objects.
268
+ * The content that should be in the editor when it's created, represented as an array of {@link PartialBlock} objects.
269
+ *
270
+ * See [Partial Blocks](https://www.blocknotejs.org/docs/editor-api/manipulating-blocks#partial-blocks) for more info.
271
+ *
272
+ * @remarks `PartialBlock[]`
246
273
  */
247
- initialContent: PartialBlock<
274
+ initialContent?: PartialBlock<
248
275
  NoInfer<BSchema>,
249
276
  NoInfer<ISchema>,
250
277
  NoInfer<SSchema>
@@ -252,14 +279,19 @@ export type BlockNoteEditorOptions<
252
279
 
253
280
  /**
254
281
  * @deprecated, provide placeholders via dictionary instead
282
+ * @internal
255
283
  */
256
- placeholders: Record<
284
+ placeholders?: Record<
257
285
  string | "default" | "emptyDocument",
258
286
  string | undefined
259
287
  >;
260
288
 
261
289
  /**
262
290
  * Custom paste handler that can be used to override the default paste behavior.
291
+ *
292
+ * See [Paste Handling](https://www.blocknotejs.org/docs/advanced/paste-handling) for more info.
293
+ *
294
+ * @remarks `PasteHandler`
263
295
  * @returns The function should return `true` if the paste event was handled, otherwise it should return `false` if it should be canceled or `undefined` if it should be handled by another handler.
264
296
  *
265
297
  * @example
@@ -296,10 +328,21 @@ export type BlockNoteEditorOptions<
296
328
  * implementing custom protocols / schemes
297
329
  * @returns The URL that's
298
330
  */
299
- resolveFileUrl: (url: string) => Promise<string>;
331
+ resolveFileUrl?: (url: string) => Promise<string>;
300
332
 
301
- resolveUsers: (userIds: string[]) => Promise<User[]>;
333
+ /**
334
+ * Resolve user information for comments.
335
+ *
336
+ * See [Comments](https://www.blocknotejs.org/docs/features/collaboration/comments) for more info.
337
+ */
338
+ resolveUsers?: (userIds: string[]) => Promise<User[]>;
302
339
 
340
+ /**
341
+ * The schema of the editor. The schema defines which Blocks, InlineContent, and Styles are available in the editor.
342
+ *
343
+ * See [Custom Schemas](https://www.blocknotejs.org/docs/custom-schemas) for more info.
344
+ * @remarks `BlockNoteSchema`
345
+ */
303
346
  schema: BlockNoteSchema<BSchema, ISchema, SSchema>;
304
347
 
305
348
  /**
@@ -313,31 +356,19 @@ export type BlockNoteEditorOptions<
313
356
  setIdAttribute?: boolean;
314
357
 
315
358
  /**
316
- * The detection mode for showing the side menu - "viewport" always shows the
317
- * side menu for the block next to the mouse cursor, while "editor" only shows
318
- * it when hovering the editor or the side menu itself.
319
- *
320
- * @default "viewport"
321
- */
322
- sideMenuDetection: "viewport" | "editor";
323
-
324
- /**
325
- Select desired behavior when pressing `Tab` (or `Shift-Tab`). Specifically,
326
- what should happen when a user has selected multiple blocks while a toolbar
327
- is open:
328
- - `"prefer-navigate-ui"`: Change focus to the toolbar. The user needs to
329
- first press `Escape` to close the toolbar, and can then indent multiple
330
- blocks. Better for keyboard accessibility.
331
- - `"prefer-indent"`: Regardless of whether toolbars are open, indent the
332
- selection of blocks. In this case, it's not possible to navigate toolbars
333
- with the keyboard.
334
-
335
- @default "prefer-navigate-ui"
359
+ * Determines behavior when pressing Tab (or Shift-Tab) while multiple blocks are selected and a toolbar is open.
360
+ * - `"prefer-navigate-ui"`: Changes focus to the toolbar. User must press Escape to close toolbar before indenting blocks. Better for keyboard accessibility.
361
+ * - `"prefer-indent"`: Always indents selected blocks, regardless of toolbar state. Keyboard navigation of toolbars not possible.
362
+ * @default "prefer-navigate-ui"
336
363
  */
337
- tabBehavior: "prefer-navigate-ui" | "prefer-indent";
364
+ tabBehavior?: "prefer-navigate-ui" | "prefer-indent";
338
365
 
339
366
  /**
340
367
  * Allows enabling / disabling features of tables.
368
+ *
369
+ * See [Tables](https://www.blocknotejs.org/docs/editor-basics/document-structure#tables) for more info.
370
+ *
371
+ * @remarks `TableConfig`
341
372
  */
342
373
  tables?: {
343
374
  /**
@@ -366,6 +397,11 @@ export type BlockNoteEditorOptions<
366
397
  headers?: boolean;
367
398
  };
368
399
 
400
+ /**
401
+ * An option which user can pass with `false` value to disable the automatic creation of a trailing new block on the next line when the user types or edits any block.
402
+ *
403
+ * @default true
404
+ */
369
405
  trailingBlock?: boolean;
370
406
 
371
407
  /**
@@ -376,23 +412,26 @@ export type BlockNoteEditorOptions<
376
412
  *
377
413
  * @param file The file that should be uploaded.
378
414
  * @returns The URL of the uploaded file OR an object containing props that should be set on the file block (such as an id)
415
+ * @remarks `(file: File) => Promise<UploadFileResult>`
379
416
  */
380
- uploadFile: (
417
+ uploadFile?: (
381
418
  file: File,
382
419
  blockId?: string,
383
420
  ) => Promise<string | Record<string, any>>;
384
421
 
385
422
  /**
386
423
  * additional tiptap options, undocumented
424
+ * @internal
387
425
  */
388
- _tiptapOptions: Partial<EditorOptions>;
426
+ _tiptapOptions?: Partial<EditorOptions>;
389
427
 
390
428
  /**
391
429
  * (experimental) add extra extensions to the editor
392
430
  *
393
431
  * @deprecated, should use `extensions` instead
432
+ * @internal
394
433
  */
395
- _extensions: Record<
434
+ _extensions?: Record<
396
435
  string,
397
436
  | { plugin: Plugin; priority?: number }
398
437
  | ((editor: BlockNoteEditor<any, any, any>) => {
@@ -402,9 +441,11 @@ export type BlockNoteEditorOptions<
402
441
  >;
403
442
 
404
443
  /**
405
- * Register
444
+ * Register extensions to the editor.
445
+ *
446
+ * @internal
406
447
  */
407
- extensions: Array<BlockNoteExtension | BlockNoteExtensionFactory>;
448
+ extensions?: Array<BlockNoteExtension | BlockNoteExtensionFactory>;
408
449
 
409
450
  /**
410
451
  * Boolean indicating whether the editor is in headless mode.
@@ -412,8 +453,9 @@ export type BlockNoteEditorOptions<
412
453
  * but there's no underlying editor (UI) instantiated.
413
454
  *
414
455
  * You probably don't need to set this manually, but use the `server-util` package instead that uses this option internally
456
+ * @internal
415
457
  */
416
- _headless: boolean;
458
+ _headless?: boolean;
417
459
  };
418
460
 
419
461
  const blockNoteTipTapOptions = {
@@ -639,7 +681,6 @@ export class BlockNoteEditor<
639
681
  dropCursor: this.options.dropCursor ?? dropCursor,
640
682
  placeholders: newOptions.placeholders,
641
683
  tabBehavior: newOptions.tabBehavior,
642
- sideMenuDetection: newOptions.sideMenuDetection || "viewport",
643
684
  comments: newOptions.comments,
644
685
  pasteHandler: newOptions.pasteHandler,
645
686
  });
@@ -1606,6 +1647,32 @@ export class BlockNoteEditor<
1606
1647
  (this.extensions["yCursorPlugin"] as CursorPlugin).updateUser(user);
1607
1648
  }
1608
1649
 
1650
+ /**
1651
+ * Registers a callback which will be called before any change is applied to the editor, allowing you to cancel the change.
1652
+ */
1653
+ public onBeforeChange(
1654
+ /**
1655
+ * If the callback returns `false`, the change will be canceled & not applied to the editor.
1656
+ */
1657
+ callback: (
1658
+ editor: BlockNoteEditor<BSchema, ISchema, SSchema>,
1659
+ context: {
1660
+ getChanges: () => BlocksChanged<BSchema, ISchema, SSchema>;
1661
+ tr: Transaction;
1662
+ },
1663
+ ) => boolean | void,
1664
+ ): () => void {
1665
+ if (this.headless) {
1666
+ return () => {
1667
+ // noop
1668
+ };
1669
+ }
1670
+
1671
+ return (this.extensions["blockChange"] as BlockChangePlugin).subscribe(
1672
+ (context) => callback(this, context),
1673
+ );
1674
+ }
1675
+
1609
1676
  /**
1610
1677
  * A callback function that runs whenever the editor's contents change.
1611
1678
  *
@@ -11,6 +11,7 @@ import { createPasteFromClipboardExtension } from "../api/clipboard/fromClipboar
11
11
  import { createCopyToClipboardExtension } from "../api/clipboard/toClipboard/copyExtension.js";
12
12
  import type { ThreadStore } from "../comments/index.js";
13
13
  import { BackgroundColorExtension } from "../extensions/BackgroundColor/BackgroundColorExtension.js";
14
+ import { BlockChangePlugin } from "../extensions/BlockChange/BlockChangePlugin.js";
14
15
  import { CursorPlugin } from "../extensions/Collaboration/CursorPlugin.js";
15
16
  import { SyncPlugin } from "../extensions/Collaboration/SyncPlugin.js";
16
17
  import { UndoPlugin } from "../extensions/Collaboration/UndoPlugin.js";
@@ -90,7 +91,6 @@ type ExtensionOptions<
90
91
  string | undefined
91
92
  >;
92
93
  tabBehavior?: "prefer-navigate-ui" | "prefer-indent";
93
- sideMenuDetection: "viewport" | "editor";
94
94
  comments?: {
95
95
  threadStore: ThreadStore;
96
96
  };
@@ -133,10 +133,7 @@ export const getBlockNoteExtensions = <
133
133
  opts.editor,
134
134
  );
135
135
  ret["linkToolbar"] = new LinkToolbarProsemirrorPlugin(opts.editor);
136
- ret["sideMenu"] = new SideMenuProsemirrorPlugin(
137
- opts.editor,
138
- opts.sideMenuDetection,
139
- );
136
+ ret["sideMenu"] = new SideMenuProsemirrorPlugin(opts.editor);
140
137
  ret["suggestionMenus"] = new SuggestionMenuProseMirrorPlugin(opts.editor);
141
138
  ret["filePanel"] = new FilePanelProsemirrorPlugin(opts.editor as any);
142
139
  ret["placeholder"] = new PlaceholderPlugin(opts.editor, opts.placeholders);
@@ -150,6 +147,7 @@ export const getBlockNoteExtensions = <
150
147
  }
151
148
 
152
149
  ret["nodeSelectionKeyboard"] = new NodeSelectionKeyboardPlugin();
150
+ ret["blockChange"] = new BlockChangePlugin();
153
151
 
154
152
  ret["showSelection"] = new ShowSelectionPlugin(opts.editor);
155
153
 
@@ -90,12 +90,14 @@ export abstract class Exporter<
90
90
  block: BlockFromConfig<B[keyof B], I, S>,
91
91
  nestingLevel: number,
92
92
  numberedListIndex: number,
93
+ children?: Array<Awaited<RB>>,
93
94
  ) {
94
95
  return this.mappings.blockMapping[block.type](
95
96
  block,
96
97
  this,
97
98
  nestingLevel,
98
99
  numberedListIndex,
100
+ children,
99
101
  );
100
102
  }
101
103
  }
@@ -27,6 +27,7 @@ export type BlockMapping<
27
27
  exporter: Exporter<any, any, any, RB, RI, any, any>,
28
28
  nestingLevel: number,
29
29
  numberedListIndex?: number,
30
+ children?: Array<Awaited<RB>>,
30
31
  ) => RB | Promise<RB>;
31
32
  };
32
33
 
@@ -0,0 +1,66 @@
1
+ import { Plugin, Transaction } from "prosemirror-state";
2
+ import { getBlocksChangedByTransaction } from "../../api/nodeUtil.js";
3
+ import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
4
+ import { BlocksChanged } from "../../index.js";
5
+
6
+ /**
7
+ * This plugin can filter transactions before they are applied to the editor, but with a higher-level API than `filterTransaction` from prosemirror.
8
+ */
9
+ export class BlockChangePlugin extends BlockNoteExtension {
10
+ public static key() {
11
+ return "blockChange";
12
+ }
13
+
14
+ private beforeChangeCallbacks: ((context: {
15
+ getChanges: () => BlocksChanged<any, any, any>;
16
+ tr: Transaction;
17
+ }) => boolean | void)[] = [];
18
+
19
+ constructor() {
20
+ super();
21
+
22
+ this.addProsemirrorPlugin(
23
+ new Plugin({
24
+ filterTransaction: (tr) => {
25
+ let changes:
26
+ | ReturnType<typeof getBlocksChangedByTransaction>
27
+ | undefined = undefined;
28
+
29
+ return this.beforeChangeCallbacks.reduce((acc, cb) => {
30
+ if (acc === false) {
31
+ // We only care that we hit a `false` result, so we can stop iterating.
32
+ return acc;
33
+ }
34
+ return (
35
+ cb({
36
+ getChanges() {
37
+ if (changes) {
38
+ return changes;
39
+ }
40
+ changes = getBlocksChangedByTransaction(tr);
41
+ return changes;
42
+ },
43
+ tr,
44
+ }) !== false
45
+ );
46
+ }, true);
47
+ },
48
+ }),
49
+ );
50
+ }
51
+
52
+ public subscribe(
53
+ callback: (context: {
54
+ getChanges: () => BlocksChanged<any, any, any>;
55
+ tr: Transaction;
56
+ }) => boolean | void,
57
+ ) {
58
+ this.beforeChangeCallbacks.push(callback);
59
+
60
+ return () => {
61
+ this.beforeChangeCallbacks = this.beforeChangeCallbacks.filter(
62
+ (cb) => cb !== callback,
63
+ );
64
+ };
65
+ }
66
+ }
@@ -101,7 +101,7 @@ export class ForkYDocPlugin extends BlockNoteExtension<{
101
101
  return;
102
102
  }
103
103
 
104
- const originalFragment = this.collaboration.fragment;
104
+ const originalFragment = this.collaboration?.fragment;
105
105
 
106
106
  if (!originalFragment) {
107
107
  throw new Error("No fragment to fork from");