@blocknote/core 0.24.2 → 0.25.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 (141) hide show
  1. package/dist/blocknote.cjs +12 -0
  2. package/dist/blocknote.cjs.map +1 -0
  3. package/dist/blocknote.js +4754 -3514
  4. package/dist/blocknote.js.map +1 -1
  5. package/dist/comments.cjs +2 -0
  6. package/dist/comments.cjs.map +1 -0
  7. package/dist/comments.js +593 -0
  8. package/dist/comments.js.map +1 -0
  9. package/dist/style.css +1 -1
  10. package/dist/tsconfig.tsbuildinfo +1 -1
  11. package/dist/webpack-stats.json +1 -1
  12. package/package.json +39 -26
  13. package/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap +1022 -378
  14. package/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap +730 -270
  15. package/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap +3100 -1260
  16. package/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap +438 -162
  17. package/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap +1168 -432
  18. package/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap +930 -378
  19. package/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap +2485 -1015
  20. package/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts +28 -1
  21. package/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +1 -1
  22. package/src/api/blockManipulation/selections/__snapshots__/selection.test.ts.snap +292 -108
  23. package/src/api/blockManipulation/setupTestEnv.ts +14 -1
  24. package/src/api/blockManipulation/tables/tables.test.ts +1987 -0
  25. package/src/api/blockManipulation/tables/tables.ts +887 -0
  26. package/src/api/clipboard/__snapshots__/external/pasteEndOfParagraph.html +66 -24
  27. package/src/api/clipboard/__snapshots__/external/pasteEndOfParagraphText.html +66 -24
  28. package/src/api/clipboard/__snapshots__/external/pasteImage.html +66 -24
  29. package/src/api/clipboard/__snapshots__/external/pasteParagraphInCustomBlock.html +66 -24
  30. package/src/api/clipboard/__snapshots__/external/pasteTable.html +132 -48
  31. package/src/api/clipboard/__snapshots__/external/pasteTableInExistingTable.html +136 -44
  32. package/src/api/clipboard/toClipboard/copyExtension.ts +2 -3
  33. package/src/api/exporters/html/__snapshots__/table/headerCols/external.html +1 -0
  34. package/src/api/exporters/html/__snapshots__/table/headerCols/internal.html +1 -0
  35. package/src/api/exporters/html/__snapshots__/table/headerRows/external.html +1 -0
  36. package/src/api/exporters/html/__snapshots__/table/headerRows/internal.html +1 -0
  37. package/src/api/exporters/html/__snapshots__/table/headersRows/external.html +1 -0
  38. package/src/api/exporters/html/__snapshots__/table/headersRows/internal.html +1 -0
  39. package/src/api/exporters/html/__snapshots__/table/mixedCellColors/external.html +1 -0
  40. package/src/api/exporters/html/__snapshots__/table/mixedCellColors/internal.html +1 -0
  41. package/src/api/exporters/html/__snapshots__/table/mixedRowspansAndColspans/external.html +1 -0
  42. package/src/api/exporters/html/__snapshots__/table/mixedRowspansAndColspans/internal.html +1 -0
  43. package/src/api/exporters/markdown/__snapshots__/table/headerCols/markdown.md +4 -0
  44. package/src/api/exporters/markdown/__snapshots__/table/headerRows/markdown.md +4 -0
  45. package/src/api/exporters/markdown/__snapshots__/table/mixedCellColors/markdown.md +5 -0
  46. package/src/api/exporters/markdown/__snapshots__/table/mixedRowspansAndColspans/markdown.md +5 -0
  47. package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +985 -20
  48. package/src/api/nodeConversions/blockToNode.ts +63 -20
  49. package/src/api/nodeConversions/nodeToBlock.ts +75 -13
  50. package/src/api/parsers/html/__snapshots__/parse-notion-html.json +145 -54
  51. package/src/api/testUtil/cases/defaultSchema.ts +782 -9
  52. package/src/api/testUtil/partialBlockTestUtil.ts +39 -4
  53. package/src/blocks/TableBlockContent/TableBlockContent.ts +11 -5
  54. package/src/blocks/defaultBlockTypeGuards.ts +8 -0
  55. package/src/comments/index.ts +9 -0
  56. package/src/comments/models/User.ts +8 -0
  57. package/src/comments/threadstore/DefaultThreadStoreAuth.ts +106 -0
  58. package/src/comments/threadstore/ThreadStore.ts +134 -0
  59. package/src/comments/threadstore/ThreadStoreAuth.ts +13 -0
  60. package/src/comments/threadstore/TipTapThreadStore.ts +292 -0
  61. package/src/comments/threadstore/yjs/RESTYjsThreadStore.ts +144 -0
  62. package/src/comments/threadstore/yjs/YjsThreadStore.test.ts +294 -0
  63. package/src/comments/threadstore/yjs/YjsThreadStore.ts +340 -0
  64. package/src/comments/threadstore/yjs/YjsThreadStoreBase.ts +48 -0
  65. package/src/comments/threadstore/yjs/yjsHelpers.ts +121 -0
  66. package/src/comments/types.ts +117 -0
  67. package/src/editor/Block.css +16 -8
  68. package/src/editor/BlockNoteEditor.ts +269 -92
  69. package/src/editor/BlockNoteExtensions.ts +24 -1
  70. package/src/editor/BlockNoteTipTapEditor.ts +5 -1
  71. package/src/editor/editor.css +17 -0
  72. package/src/extensions/BackgroundColor/BackgroundColorExtension.ts +1 -1
  73. package/src/extensions/Comments/CommentMark.ts +61 -0
  74. package/src/extensions/Comments/CommentsPlugin.ts +301 -0
  75. package/src/extensions/Comments/userstore/UserStore.ts +72 -0
  76. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +9 -5
  77. package/src/extensions/LinkToolbar/LinkToolbarPlugin.ts +3 -3
  78. package/src/extensions/ShowSelection/ShowSelectionPlugin.ts +52 -0
  79. package/src/extensions/TableHandles/TableHandlesPlugin.ts +409 -57
  80. package/src/extensions/TextAlignment/TextAlignmentExtension.ts +2 -0
  81. package/src/extensions/TextColor/TextColorExtension.ts +1 -1
  82. package/src/i18n/locales/ar.ts +23 -0
  83. package/src/i18n/locales/de.ts +15 -0
  84. package/src/i18n/locales/en.ts +25 -1
  85. package/src/i18n/locales/es.ts +16 -1
  86. package/src/i18n/locales/fr.ts +23 -0
  87. package/src/i18n/locales/hr.ts +18 -0
  88. package/src/i18n/locales/is.ts +24 -1
  89. package/src/i18n/locales/it.ts +15 -0
  90. package/src/i18n/locales/ja.ts +23 -0
  91. package/src/i18n/locales/ko.ts +23 -0
  92. package/src/i18n/locales/nl.ts +23 -0
  93. package/src/i18n/locales/no.ts +23 -0
  94. package/src/i18n/locales/pl.ts +23 -0
  95. package/src/i18n/locales/pt.ts +23 -0
  96. package/src/i18n/locales/ru.ts +23 -0
  97. package/src/i18n/locales/uk.ts +23 -0
  98. package/src/i18n/locales/vi.ts +23 -0
  99. package/src/i18n/locales/zh.ts +23 -0
  100. package/src/index.ts +6 -4
  101. package/src/schema/blocks/types.ts +32 -2
  102. package/src/util/browser.ts +1 -1
  103. package/src/util/table.ts +107 -0
  104. package/types/src/api/blockManipulation/tables/tables.d.ts +343 -0
  105. package/types/src/api/blockManipulation/tables/tables.test.d.ts +1 -0
  106. package/types/src/api/clipboard/toClipboard/copyExtension.d.ts +1 -1
  107. package/types/src/blocks/TableBlockContent/TableBlockContent.d.ts +1 -2
  108. package/types/src/blocks/defaultBlockTypeGuards.d.ts +3 -0
  109. package/types/src/comments/index.d.ts +9 -0
  110. package/types/src/comments/models/User.d.ts +8 -0
  111. package/types/src/comments/threadstore/DefaultThreadStoreAuth.d.ts +47 -0
  112. package/types/src/comments/threadstore/ThreadStore.d.ts +121 -0
  113. package/types/src/comments/threadstore/ThreadStoreAuth.d.ts +12 -0
  114. package/types/src/comments/threadstore/TipTapThreadStore.d.ts +97 -0
  115. package/types/src/comments/threadstore/yjs/RESTYjsThreadStore.d.ts +83 -0
  116. package/types/src/comments/threadstore/yjs/YjsThreadStore.d.ts +79 -0
  117. package/types/src/comments/threadstore/yjs/YjsThreadStore.test.d.ts +1 -0
  118. package/types/src/comments/threadstore/yjs/YjsThreadStoreBase.d.ts +15 -0
  119. package/types/src/comments/threadstore/yjs/yjsHelpers.d.ts +13 -0
  120. package/types/src/comments/types.d.ts +109 -0
  121. package/types/src/editor/BlockNoteEditor.d.ts +146 -66
  122. package/types/src/editor/BlockNoteExtensions.d.ts +4 -0
  123. package/types/src/extensions/Collaboration/createCollaborationExtensions.d.ts +1 -1
  124. package/types/src/extensions/Comments/CommentMark.d.ts +2 -0
  125. package/types/src/extensions/Comments/CommentsPlugin.d.ts +49 -0
  126. package/types/src/extensions/Comments/userstore/UserStore.d.ts +31 -0
  127. package/types/src/extensions/ShowSelection/ShowSelectionPlugin.d.ts +15 -0
  128. package/types/src/extensions/TableHandles/TableHandlesPlugin.d.ts +66 -1
  129. package/types/src/i18n/locales/de.d.ts +15 -0
  130. package/types/src/i18n/locales/en.d.ts +20 -0
  131. package/types/src/i18n/locales/es.d.ts +15 -0
  132. package/types/src/i18n/locales/hr.d.ts +18 -0
  133. package/types/src/i18n/locales/it.d.ts +15 -0
  134. package/types/src/index.d.ts +5 -4
  135. package/types/src/pm-nodes/BlockContainer.d.ts +2 -2
  136. package/types/src/pm-nodes/BlockGroup.d.ts +2 -2
  137. package/types/src/schema/blocks/types.d.ts +23 -2
  138. package/types/src/util/browser.d.ts +1 -1
  139. package/types/src/util/table.d.ts +12 -0
  140. package/dist/blocknote.umd.cjs +0 -11
  141. package/dist/blocknote.umd.cjs.map +0 -1
@@ -3,7 +3,9 @@ import {
3
3
  EditorOptions,
4
4
  Extension,
5
5
  getSchema,
6
+ isNodeSelection,
6
7
  Mark,
8
+ posToDOMRect,
7
9
  Node as TipTapNode,
8
10
  } from "@tiptap/core";
9
11
  import { Node, Schema } from "prosemirror-model";
@@ -49,9 +51,11 @@ import {
49
51
  DefaultStyleSchema,
50
52
  PartialBlock,
51
53
  } from "../blocks/defaultBlocks.js";
54
+ import type { CommentsPlugin } from "../extensions/Comments/CommentsPlugin.js";
52
55
  import { FilePanelProsemirrorPlugin } from "../extensions/FilePanel/FilePanelPlugin.js";
53
56
  import { FormattingToolbarProsemirrorPlugin } from "../extensions/FormattingToolbar/FormattingToolbarPlugin.js";
54
57
  import { LinkToolbarProsemirrorPlugin } from "../extensions/LinkToolbar/LinkToolbarPlugin.js";
58
+ import { ShowSelectionPlugin } from "../extensions/ShowSelection/ShowSelectionPlugin.js";
55
59
  import { SideMenuProsemirrorPlugin } from "../extensions/SideMenu/SideMenuPlugin.js";
56
60
  import { SuggestionMenuProseMirrorPlugin } from "../extensions/SuggestionMenu/SuggestionPlugin.js";
57
61
  import { TableHandlesProsemirrorPlugin } from "../extensions/TableHandles/TableHandlesPlugin.js";
@@ -90,10 +94,13 @@ import { en } from "../i18n/locales/index.js";
90
94
  import { Plugin, Transaction } from "@tiptap/pm/state";
91
95
  import { dropCursor } from "prosemirror-dropcursor";
92
96
  import { EditorView } from "prosemirror-view";
97
+ import { ySyncPluginKey } from "y-prosemirror";
93
98
  import { createInternalHTMLSerializer } from "../api/exporters/html/internalHTMLSerializer.js";
94
99
  import { inlineContentToNodes } from "../api/nodeConversions/blockToNode.js";
95
100
  import { nodeToBlock } from "../api/nodeConversions/nodeToBlock.js";
101
+ import type { ThreadStore, User } from "../comments/index.js";
96
102
  import "../style.css";
103
+ import { EventEmitter } from "../util/EventEmitter.js";
97
104
 
98
105
  export type BlockNoteExtensionFactory = (
99
106
  editor: BlockNoteEditor<any, any, any>
@@ -117,69 +124,6 @@ export type BlockNoteEditorOptions<
117
124
  */
118
125
  animations?: boolean;
119
126
 
120
- /**
121
- * Disable internal extensions (based on keys / extension name)
122
- */
123
- disableExtensions: string[];
124
-
125
- /**
126
- * A dictionary object containing translations for the editor.
127
- */
128
- dictionary?: Dictionary & Record<string, any>;
129
-
130
- /**
131
- * @deprecated, provide placeholders via dictionary instead
132
- */
133
- placeholders: Record<
134
- string | "default" | "emptyDocument",
135
- string | undefined
136
- >;
137
-
138
- /**
139
- * An object containing attributes that should be added to HTML elements of the editor.
140
- *
141
- * @example { editor: { class: "my-editor-class" } }
142
- */
143
- domAttributes: Partial<BlockNoteDOMAttributes>;
144
-
145
- /**
146
- * The content that should be in the editor when it's created, represented as an array of partial block objects.
147
- */
148
- initialContent: PartialBlock<
149
- NoInfer<BSchema>,
150
- NoInfer<ISchema>,
151
- NoInfer<SSchema>
152
- >[];
153
- /**
154
- * Use default BlockNote font and reset the styles of <p> <li> <h1> elements etc., that are used in BlockNote.
155
- *
156
- * @default true
157
- */
158
- defaultStyles: boolean;
159
-
160
- schema: BlockNoteSchema<BSchema, ISchema, SSchema>;
161
-
162
- /**
163
- * The `uploadFile` method is what the editor uses when files need to be uploaded (for example when selecting an image to upload).
164
- * This method should set when creating the editor as this is application-specific.
165
- *
166
- * `undefined` means the application doesn't support file uploads.
167
- *
168
- * @param file The file that should be uploaded.
169
- * @returns The URL of the uploaded file OR an object containing props that should be set on the file block (such as an id)
170
- */
171
- uploadFile: (
172
- file: File,
173
- blockId?: string
174
- ) => Promise<string | Record<string, any>>;
175
-
176
- /**
177
- * Resolve a URL of a file block to one that can be displayed or downloaded. This can be used for creating authenticated URL or
178
- * implementing custom protocols / schemes
179
- * @returns The URL that's
180
- */
181
- resolveFileUrl: (url: string) => Promise<string>;
182
-
183
127
  /**
184
128
  * When enabled, allows for collaboration between multiple users.
185
129
  */
@@ -212,26 +156,72 @@ export type BlockNoteEditorOptions<
212
156
  showCursorLabels?: "always" | "activity";
213
157
  };
214
158
 
159
+ comments: {
160
+ threadStore: ThreadStore;
161
+ };
162
+
215
163
  /**
216
- * additional tiptap options, undocumented
164
+ * Use default BlockNote font and reset the styles of <p> <li> <h1> elements etc., that are used in BlockNote.
165
+ *
166
+ * @default true
217
167
  */
218
- _tiptapOptions: Partial<EditorOptions>;
168
+ defaultStyles: boolean;
219
169
 
220
170
  /**
221
- * (experimental) add extra prosemirror plugins or tiptap extensions to the editor
171
+ * A dictionary object containing translations for the editor.
222
172
  */
223
- _extensions: Record<string, BlockNoteExtension | BlockNoteExtensionFactory>;
173
+ dictionary?: Dictionary & Record<string, any>;
224
174
 
225
- trailingBlock?: boolean;
175
+ /**
176
+ * Disable internal extensions (based on keys / extension name)
177
+ */
178
+ disableExtensions: string[];
226
179
 
227
180
  /**
228
- * Boolean indicating whether the editor is in headless mode.
229
- * Headless mode means we can use features like importing / exporting blocks,
230
- * but there's no underlying editor (UI) instantiated.
181
+ * An object containing attributes that should be added to HTML elements of the editor.
231
182
  *
232
- * You probably don't need to set this manually, but use the `server-util` package instead that uses this option internally
183
+ * @example { editor: { class: "my-editor-class" } }
233
184
  */
234
- _headless: boolean;
185
+ domAttributes: Partial<BlockNoteDOMAttributes>;
186
+
187
+ dropCursor?: (opts: {
188
+ editor: BlockNoteEditor<
189
+ NoInfer<BSchema>,
190
+ NoInfer<ISchema>,
191
+ NoInfer<SSchema>
192
+ >;
193
+ color?: string | false;
194
+ width?: number;
195
+ class?: string;
196
+ }) => Plugin;
197
+
198
+ /**
199
+ * The content that should be in the editor when it's created, represented as an array of partial block objects.
200
+ */
201
+ initialContent: PartialBlock<
202
+ NoInfer<BSchema>,
203
+ NoInfer<ISchema>,
204
+ NoInfer<SSchema>
205
+ >[];
206
+
207
+ /**
208
+ * @deprecated, provide placeholders via dictionary instead
209
+ */
210
+ placeholders: Record<
211
+ string | "default" | "emptyDocument",
212
+ string | undefined
213
+ >;
214
+
215
+ /**
216
+ * Resolve a URL of a file block to one that can be displayed or downloaded. This can be used for creating authenticated URL or
217
+ * implementing custom protocols / schemes
218
+ * @returns The URL that's
219
+ */
220
+ resolveFileUrl: (url: string) => Promise<string>;
221
+
222
+ resolveUsers: (userIds: string[]) => Promise<User[]>;
223
+
224
+ schema: BlockNoteSchema<BSchema, ISchema, SSchema>;
235
225
 
236
226
  /**
237
227
  * A flag indicating whether to set an HTML ID for every block
@@ -243,7 +233,14 @@ export type BlockNoteEditorOptions<
243
233
  */
244
234
  setIdAttribute?: boolean;
245
235
 
246
- dropCursor?: (opts: any) => Plugin;
236
+ /**
237
+ * The detection mode for showing the side menu - "viewport" always shows the
238
+ * side menu for the block next to the mouse cursor, while "editor" only shows
239
+ * it when hovering the editor or the side menu itself.
240
+ *
241
+ * @default "viewport"
242
+ */
243
+ sideMenuDetection: "viewport" | "editor";
247
244
 
248
245
  /**
249
246
  Select desired behavior when pressing `Tab` (or `Shift-Tab`). Specifically,
@@ -261,13 +258,69 @@ export type BlockNoteEditorOptions<
261
258
  tabBehavior: "prefer-navigate-ui" | "prefer-indent";
262
259
 
263
260
  /**
264
- * The detection mode for showing the side menu - "viewport" always shows the
265
- * side menu for the block next to the mouse cursor, while "editor" only shows
266
- * it when hovering the editor or the side menu itself.
261
+ * Allows enabling / disabling features of tables.
262
+ */
263
+ tables?: {
264
+ /**
265
+ * Whether to allow splitting and merging cells within a table.
266
+ *
267
+ * @default false
268
+ */
269
+ splitCells?: boolean;
270
+ /**
271
+ * Whether to allow changing the background color of cells.
272
+ *
273
+ * @default false
274
+ */
275
+ cellBackgroundColor?: boolean;
276
+ /**
277
+ * Whether to allow changing the text color of cells.
278
+ *
279
+ * @default false
280
+ */
281
+ cellTextColor?: boolean;
282
+ /**
283
+ * Whether to allow changing cells into headers.
284
+ *
285
+ * @default false
286
+ */
287
+ headers?: boolean;
288
+ };
289
+
290
+ trailingBlock?: boolean;
291
+
292
+ /**
293
+ * The `uploadFile` method is what the editor uses when files need to be uploaded (for example when selecting an image to upload).
294
+ * This method should set when creating the editor as this is application-specific.
267
295
  *
268
- * @default "viewport"
296
+ * `undefined` means the application doesn't support file uploads.
297
+ *
298
+ * @param file The file that should be uploaded.
299
+ * @returns The URL of the uploaded file OR an object containing props that should be set on the file block (such as an id)
269
300
  */
270
- sideMenuDetection: "viewport" | "editor";
301
+ uploadFile: (
302
+ file: File,
303
+ blockId?: string
304
+ ) => Promise<string | Record<string, any>>;
305
+
306
+ /**
307
+ * additional tiptap options, undocumented
308
+ */
309
+ _tiptapOptions: Partial<EditorOptions>;
310
+
311
+ /**
312
+ * (experimental) add extra prosemirror plugins or tiptap extensions to the editor
313
+ */
314
+ _extensions: Record<string, BlockNoteExtension | BlockNoteExtensionFactory>;
315
+
316
+ /**
317
+ * Boolean indicating whether the editor is in headless mode.
318
+ * Headless mode means we can use features like importing / exporting blocks,
319
+ * but there's no underlying editor (UI) instantiated.
320
+ *
321
+ * You probably don't need to set this manually, but use the `server-util` package instead that uses this option internally
322
+ */
323
+ _headless: boolean;
271
324
  };
272
325
 
273
326
  const blockNoteTipTapOptions = {
@@ -280,8 +333,13 @@ export class BlockNoteEditor<
280
333
  BSchema extends BlockSchema = DefaultBlockSchema,
281
334
  ISchema extends InlineContentSchema = DefaultInlineContentSchema,
282
335
  SSchema extends StyleSchema = DefaultStyleSchema
283
- > {
284
- private readonly _pmSchema: Schema;
336
+ > extends EventEmitter<{
337
+ create: void;
338
+ }> {
339
+ /**
340
+ * The underlying prosemirror schema
341
+ */
342
+ public readonly pmSchema: Schema;
285
343
 
286
344
  /**
287
345
  * extensions that are added to the editor, can be tiptap extensions or prosemirror plugins
@@ -352,6 +410,9 @@ export class BlockNoteEditor<
352
410
  ISchema,
353
411
  SSchema
354
412
  >;
413
+ public readonly comments?: CommentsPlugin;
414
+
415
+ private readonly showSelectionPlugin: ShowSelectionPlugin;
355
416
 
356
417
  /**
357
418
  * The `uploadFile` method is what the editor uses when files need to be uploaded (for example when selecting an image to upload).
@@ -370,10 +431,18 @@ export class BlockNoteEditor<
370
431
  private onUploadEndCallbacks: ((blockId?: string) => void)[] = [];
371
432
 
372
433
  public readonly resolveFileUrl?: (url: string) => Promise<string>;
373
-
374
- public get pmSchema() {
375
- return this._pmSchema;
376
- }
434
+ public readonly resolveUsers?: (userIds: string[]) => Promise<User[]>;
435
+ /**
436
+ * Editor settings
437
+ */
438
+ public readonly settings: {
439
+ tables: {
440
+ splitCells: boolean;
441
+ cellBackgroundColor: boolean;
442
+ cellTextColor: boolean;
443
+ headers: boolean;
444
+ };
445
+ };
377
446
 
378
447
  public static create<
379
448
  BSchema extends BlockSchema = DefaultBlockSchema,
@@ -386,6 +455,7 @@ export class BlockNoteEditor<
386
455
  protected constructor(
387
456
  protected readonly options: Partial<BlockNoteEditorOptions<any, any, any>>
388
457
  ) {
458
+ super();
389
459
  const anyOpts = options as any;
390
460
  if (anyOpts.onEditorContentChange) {
391
461
  throw new Error(
@@ -412,6 +482,14 @@ export class BlockNoteEditor<
412
482
  }
413
483
 
414
484
  this.dictionary = options.dictionary || en;
485
+ this.settings = {
486
+ tables: {
487
+ splitCells: options?.tables?.splitCells ?? false,
488
+ cellBackgroundColor: options?.tables?.cellBackgroundColor ?? false,
489
+ cellTextColor: options?.tables?.cellTextColor ?? false,
490
+ headers: options?.tables?.headers ?? false,
491
+ },
492
+ };
415
493
 
416
494
  // apply defaults
417
495
  const newOptions = {
@@ -425,6 +503,12 @@ export class BlockNoteEditor<
425
503
  },
426
504
  };
427
505
 
506
+ if (newOptions.comments && !newOptions.resolveUsers) {
507
+ throw new Error("resolveUsers is required when using comments");
508
+ }
509
+
510
+ this.resolveUsers = newOptions.resolveUsers;
511
+
428
512
  // @ts-ignore
429
513
  this.schema = newOptions.schema;
430
514
  this.blockImplementations = newOptions.schema.blockSpecs;
@@ -447,6 +531,7 @@ export class BlockNoteEditor<
447
531
  placeholders: newOptions.placeholders,
448
532
  tabBehavior: newOptions.tabBehavior,
449
533
  sideMenuDetection: newOptions.sideMenuDetection || "viewport",
534
+ comments: newOptions.comments,
450
535
  });
451
536
 
452
537
  // add extensions from _tiptapOptions
@@ -469,6 +554,8 @@ export class BlockNoteEditor<
469
554
  this.suggestionMenus = this.extensions["suggestionMenus"] as any;
470
555
  this.filePanel = this.extensions["filePanel"] as any;
471
556
  this.tableHandles = this.extensions["tableHandles"] as any;
557
+ this.comments = this.extensions["comments"] as any;
558
+ this.showSelectionPlugin = this.extensions["showSelection"] as any;
472
559
 
473
560
  if (newOptions.uploadFile) {
474
561
  const uploadFile = newOptions.uploadFile;
@@ -500,6 +587,12 @@ export class BlockNoteEditor<
500
587
  );
501
588
  }
502
589
 
590
+ if (newOptions.comments && !collaborationEnabled) {
591
+ throw new Error(
592
+ "Comments are only supported when collaboration is enabled, please set the collaboration option"
593
+ );
594
+ }
595
+
503
596
  const initialContent =
504
597
  newOptions.initialContent ||
505
598
  (collaborationEnabled
@@ -579,17 +672,18 @@ export class BlockNoteEditor<
579
672
  view: any;
580
673
  contentComponent: any;
581
674
  };
582
- this._pmSchema = this._tiptapEditor.schema;
675
+ this.pmSchema = this._tiptapEditor.schema;
583
676
  } else {
584
677
  // In headless mode, we don't instantiate an underlying TipTap editor,
585
678
  // but we still need the schema
586
- this._pmSchema = getSchema(tiptapOptions.extensions!);
679
+ this.pmSchema = getSchema(tiptapOptions.extensions!);
587
680
  }
681
+ this.emit("create");
588
682
  }
589
683
 
590
- dispatch(tr: Transaction) {
684
+ dispatch = (tr: Transaction) => {
591
685
  this._tiptapEditor.dispatch(tr);
592
- }
686
+ };
593
687
 
594
688
  /**
595
689
  * Mount the editor to a parent DOM element. Call mount(undefined) to clean up
@@ -603,10 +697,20 @@ export class BlockNoteEditor<
603
697
  this._tiptapEditor.mount(parentElement, contentComponent);
604
698
  };
605
699
 
700
+ /**
701
+ * Get the underlying prosemirror view
702
+ */
606
703
  public get prosemirrorView() {
607
704
  return this._tiptapEditor.view;
608
705
  }
609
706
 
707
+ /**
708
+ * Get the underlying prosemirror state
709
+ */
710
+ public get prosemirrorState() {
711
+ return this._tiptapEditor.state;
712
+ }
713
+
610
714
  public get domElement() {
611
715
  return this.prosemirrorView?.dom as HTMLDivElement | undefined;
612
716
  }
@@ -655,7 +759,7 @@ export class BlockNoteEditor<
655
759
  public get document(): Block<BSchema, ISchema, SSchema>[] {
656
760
  const blocks: Block<BSchema, ISchema, SSchema>[] = [];
657
761
 
658
- this._tiptapEditor.state.doc.firstChild!.descendants((node) => {
762
+ this.prosemirrorState.doc.firstChild!.descendants((node) => {
659
763
  blocks.push(
660
764
  nodeToBlock(
661
765
  node,
@@ -768,6 +872,8 @@ export class BlockNoteEditor<
768
872
  /**
769
873
  * Executes a callback whenever the editor's contents change.
770
874
  * @param callback The callback to execute.
875
+ *
876
+ * @deprecated use `onChange` instead
771
877
  */
772
878
  public onEditorContentChange(callback: () => void) {
773
879
  this._tiptapEditor.on("update", callback);
@@ -776,6 +882,8 @@ export class BlockNoteEditor<
776
882
  /**
777
883
  * Executes a callback whenever the editor's selection changes.
778
884
  * @param callback The callback to execute.
885
+ *
886
+ * @deprecated use `onSelectionChange` instead
779
887
  */
780
888
  public onEditorSelectionChange(callback: () => void) {
781
889
  this._tiptapEditor.on("selectionUpdate", callback);
@@ -1217,13 +1325,22 @@ export class BlockNoteEditor<
1217
1325
  * @returns A function to remove the callback.
1218
1326
  */
1219
1327
  public onSelectionChange(
1220
- callback: (editor: BlockNoteEditor<BSchema, ISchema, SSchema>) => void
1328
+ callback: (editor: BlockNoteEditor<BSchema, ISchema, SSchema>) => void,
1329
+ includeSelectionChangedByRemote?: boolean
1221
1330
  ) {
1222
1331
  if (this.headless) {
1223
1332
  return;
1224
1333
  }
1225
1334
 
1226
- const cb = () => {
1335
+ const cb = (e: { transaction: Transaction }) => {
1336
+ if (
1337
+ e.transaction.getMeta(ySyncPluginKey) &&
1338
+ !includeSelectionChangedByRemote
1339
+ ) {
1340
+ // selection changed because of a yjs sync (i.e.: other user was typing)
1341
+ // we don't want to trigger the callback in this case
1342
+ return;
1343
+ }
1227
1344
  callback(this);
1228
1345
  };
1229
1346
 
@@ -1234,6 +1351,53 @@ export class BlockNoteEditor<
1234
1351
  };
1235
1352
  }
1236
1353
 
1354
+ /**
1355
+ * A callback function that runs when the editor has been initialized.
1356
+ *
1357
+ * This can be useful for plugins to initialize themselves after the editor has been initialized.
1358
+ */
1359
+ public onCreate(callback: () => void) {
1360
+ this.on("create", callback);
1361
+
1362
+ return () => {
1363
+ this.off("create", callback);
1364
+ };
1365
+ }
1366
+
1367
+ public getSelectionBoundingBox() {
1368
+ if (!this.prosemirrorView) {
1369
+ return undefined;
1370
+ }
1371
+ const state = this.prosemirrorView?.state;
1372
+ const { selection } = state;
1373
+
1374
+ // support for CellSelections
1375
+ const { ranges } = selection;
1376
+ const from = Math.min(...ranges.map((range) => range.$from.pos));
1377
+ const to = Math.max(...ranges.map((range) => range.$to.pos));
1378
+
1379
+ if (isNodeSelection(selection)) {
1380
+ const node = this.prosemirrorView.nodeDOM(from) as HTMLElement;
1381
+ if (node) {
1382
+ return node.getBoundingClientRect();
1383
+ }
1384
+ }
1385
+
1386
+ return posToDOMRect(this.prosemirrorView, from, to);
1387
+ }
1388
+
1389
+ public get isEmpty() {
1390
+ const doc = this.document;
1391
+ // Note: only works for paragraphs as default blocks (but for now this is default in blocknote)
1392
+ // checking prosemirror directly might be faster
1393
+ return (
1394
+ doc.length === 0 ||
1395
+ (doc.length === 1 &&
1396
+ doc[0].type === "paragraph" &&
1397
+ (doc[0].content as any).length === 0)
1398
+ );
1399
+ }
1400
+
1237
1401
  public openSuggestionMenu(
1238
1402
  triggerCharacter: string,
1239
1403
  pluginState?: {
@@ -1260,4 +1424,17 @@ export class BlockNoteEditor<
1260
1424
  })
1261
1425
  );
1262
1426
  }
1427
+
1428
+ // `forceSelectionVisible` determines whether the editor selection is shows
1429
+ // even when the editor is not focused. This is useful for e.g. creating new
1430
+ // links, so the user still sees the affected content when an input field is
1431
+ // focused.
1432
+ // TODO: Reconsider naming?
1433
+ public getForceSelectionVisible() {
1434
+ return this.showSelectionPlugin.getEnabled();
1435
+ }
1436
+
1437
+ public setForceSelectionVisible(forceSelectionVisible: boolean) {
1438
+ this.showSelectionPlugin.setEnabled(forceSelectionVisible);
1439
+ }
1263
1440
  }
@@ -12,6 +12,9 @@ import { createPasteFromClipboardExtension } from "../api/clipboard/fromClipboar
12
12
  import { createCopyToClipboardExtension } from "../api/clipboard/toClipboard/copyExtension.js";
13
13
  import { BackgroundColorExtension } from "../extensions/BackgroundColor/BackgroundColorExtension.js";
14
14
  import { createCollaborationExtensions } from "../extensions/Collaboration/createCollaborationExtensions.js";
15
+ import { CommentMark } from "../extensions/Comments/CommentMark.js";
16
+ import { CommentsPlugin } from "../extensions/Comments/CommentsPlugin.js";
17
+ import type { ThreadStore } from "../comments/index.js";
15
18
  import { FilePanelProsemirrorPlugin } from "../extensions/FilePanel/FilePanelPlugin.js";
16
19
  import { FormattingToolbarProsemirrorPlugin } from "../extensions/FormattingToolbar/FormattingToolbarPlugin.js";
17
20
  import { KeyboardShortcutsExtension } from "../extensions/KeyboardShortcuts/KeyboardShortcutsExtension.js";
@@ -23,6 +26,7 @@ import {
23
26
  import { NodeSelectionKeyboardPlugin } from "../extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.js";
24
27
  import { PlaceholderPlugin } from "../extensions/Placeholder/PlaceholderPlugin.js";
25
28
  import { PreviousBlockTypePlugin } from "../extensions/PreviousBlockType/PreviousBlockTypePlugin.js";
29
+ import { ShowSelectionPlugin } from "../extensions/ShowSelection/ShowSelectionPlugin.js";
26
30
  import { SideMenuProsemirrorPlugin } from "../extensions/SideMenu/SideMenuPlugin.js";
27
31
  import { SuggestionMenuProseMirrorPlugin } from "../extensions/SuggestionMenu/SuggestionPlugin.js";
28
32
  import { TableHandlesProsemirrorPlugin } from "../extensions/TableHandles/TableHandlesPlugin.js";
@@ -75,6 +79,9 @@ type ExtensionOptions<
75
79
  >;
76
80
  tabBehavior?: "prefer-navigate-ui" | "prefer-indent";
77
81
  sideMenuDetection: "viewport" | "editor";
82
+ comments?: {
83
+ threadStore: ThreadStore;
84
+ };
78
85
  };
79
86
 
80
87
  /**
@@ -126,6 +133,16 @@ export const getBlockNoteExtensions = <
126
133
 
127
134
  ret["nodeSelectionKeyboard"] = new NodeSelectionKeyboardPlugin();
128
135
 
136
+ ret["showSelection"] = new ShowSelectionPlugin(opts.editor);
137
+
138
+ if (opts.comments) {
139
+ ret["comments"] = new CommentsPlugin(
140
+ opts.editor,
141
+ opts.comments.threadStore,
142
+ CommentMark.name
143
+ );
144
+ }
145
+
129
146
  const disableExtensions: string[] = opts.disableExtensions || [];
130
147
  for (const ext of disableExtensions) {
131
148
  delete ret[ext];
@@ -134,6 +151,8 @@ export const getBlockNoteExtensions = <
134
151
  return ret;
135
152
  };
136
153
 
154
+ let LINKIFY_INITIALIZED = false;
155
+
137
156
  /**
138
157
  * Get all the Tiptap extensions BlockNote is configured with by default
139
158
  */
@@ -171,7 +190,8 @@ const getTipTapExtensions = <
171
190
  inclusive: false,
172
191
  }).configure({
173
192
  defaultProtocol: DEFAULT_LINK_PROTOCOL,
174
- protocols: VALID_LINK_PROTOCOLS,
193
+ // only call this once if we have multiple editors installed. Or fix https://github.com/ueberdosis/tiptap/issues/5450
194
+ protocols: LINKIFY_INITIALIZED ? [] : VALID_LINK_PROTOCOLS,
175
195
  }),
176
196
  ...Object.values(opts.styleSpecs).map((styleSpec) => {
177
197
  return styleSpec.implementation.mark.configure({
@@ -246,8 +266,11 @@ const getTipTapExtensions = <
246
266
  ...(opts.trailingBlock === undefined || opts.trailingBlock
247
267
  ? [TrailingNode]
248
268
  : []),
269
+ ...(opts.comments ? [CommentMark] : []),
249
270
  ];
250
271
 
272
+ LINKIFY_INITIALIZED = true;
273
+
251
274
  if (opts.collaboration) {
252
275
  tiptapExtensions.push(...createCollaborationExtensions(opts.collaboration));
253
276
  } else {
@@ -184,7 +184,11 @@ export class BlockNoteTipTapEditor extends TiptapEditor {
184
184
 
185
185
  // emit the created event, call here manually because we blocked the default call in the constructor
186
186
  // (https://github.com/ueberdosis/tiptap/blob/45bac803283446795ad1b03f43d3746fa54a68ff/packages/core/src/Editor.ts#L117)
187
- this.commands.focus(this.options.autofocus);
187
+ this.commands.focus(
188
+ this.options.autofocus ||
189
+ this.options.element.getAttribute("data-bn-autofocus") === "true",
190
+ { scrollIntoView: false }
191
+ );
188
192
  this.emit("create", { editor: this });
189
193
  this.isInitialized = true;
190
194
  }
@@ -10,6 +10,15 @@
10
10
  --N40: #dfe1e6; /* Light neutral used for subtle borders and text on dark background */
11
11
  }
12
12
 
13
+ .bn-comment-editor {
14
+ width: 100%;
15
+ padding: 0;
16
+ }
17
+
18
+ .bn-comment-editor .bn-editor {
19
+ padding: 0;
20
+ }
21
+
13
22
  /*
14
23
  bn-root should be applied to all top-level elements
15
24
 
@@ -179,3 +188,11 @@ Tippy popups that are appended to document.body directly
179
188
  .prosemirror-dropcursor-vertical {
180
189
  transition-property: left, right;
181
190
  }
191
+
192
+ /*
193
+ For the ShowSelectionPlugin
194
+ */
195
+ [data-show-selection] {
196
+ background-color: highlight;
197
+ padding: 2px 0;
198
+ }
@@ -7,7 +7,7 @@ export const BackgroundColorExtension = Extension.create({
7
7
  addGlobalAttributes() {
8
8
  return [
9
9
  {
10
- types: ["blockContainer"],
10
+ types: ["blockContainer", "tableCell", "tableHeader"],
11
11
  attributes: {
12
12
  backgroundColor: {
13
13
  default: defaultProps.backgroundColor.default,