@blocknote/core 0.40.0 → 0.41.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 (102) hide show
  1. package/dist/BlockNoteSchema-COA0fsXW.cjs +11 -0
  2. package/dist/BlockNoteSchema-COA0fsXW.cjs.map +1 -0
  3. package/dist/{BlockNoteSchema-oR047ACf.js → BlockNoteSchema-CYRHak18.js} +681 -581
  4. package/dist/BlockNoteSchema-CYRHak18.js.map +1 -0
  5. package/dist/blocknote.cjs +4 -4
  6. package/dist/blocknote.cjs.map +1 -1
  7. package/dist/blocknote.js +3663 -2817
  8. package/dist/blocknote.js.map +1 -1
  9. package/dist/blocks.cjs +1 -1
  10. package/dist/blocks.js +51 -49
  11. package/dist/en-Cl87Uuyf.cjs +2 -0
  12. package/dist/en-Cl87Uuyf.cjs.map +1 -0
  13. package/dist/{en-Bq3Es3Np.js → en-njEqD7AG.js} +9 -3
  14. package/dist/en-njEqD7AG.js.map +1 -0
  15. package/dist/locales.cjs +1 -1
  16. package/dist/locales.cjs.map +1 -1
  17. package/dist/locales.js +122 -2
  18. package/dist/locales.js.map +1 -1
  19. package/dist/style.css +1 -1
  20. package/dist/tsconfig.tsbuildinfo +1 -1
  21. package/dist/webpack-stats.json +1 -1
  22. package/package.json +4 -3
  23. package/src/api/exporters/html/externalHTMLExporter.ts +1 -1
  24. package/src/api/exporters/html/util/serializeBlocksInternalHTML.ts +2 -1
  25. package/src/api/exporters/markdown/markdownExporter.ts +3 -1
  26. package/src/api/exporters/markdown/util/convertVideoToMarkdownRehypePlugin.ts +19 -0
  27. package/src/api/parsers/markdown/parseMarkdown.ts +31 -0
  28. package/src/blocks/Code/block.ts +14 -14
  29. package/src/blocks/Divider/block.ts +49 -0
  30. package/src/blocks/ListItem/BulletListItem/block.ts +9 -1
  31. package/src/blocks/ListItem/CheckListItem/block.ts +1 -0
  32. package/src/blocks/ListItem/NumberedListItem/block.ts +9 -1
  33. package/src/blocks/Table/block.ts +56 -1
  34. package/src/blocks/ToggleWrapper/createToggleWrapper.ts +1 -1
  35. package/src/blocks/defaultBlocks.ts +16 -14
  36. package/src/blocks/index.ts +1 -0
  37. package/src/editor/Block.css +14 -20
  38. package/src/editor/BlockNoteEditor.test.ts +40 -0
  39. package/src/editor/BlockNoteEditor.ts +248 -465
  40. package/src/editor/BlockNoteExtensions.ts +3 -1
  41. package/src/editor/managers/BlockManager.ts +251 -0
  42. package/src/editor/managers/CollaborationManager.ts +212 -0
  43. package/src/editor/managers/EventManager.ts +134 -0
  44. package/src/editor/managers/ExportManager.ts +137 -0
  45. package/src/editor/managers/ExtensionManager.ts +130 -0
  46. package/src/editor/managers/SelectionManager.ts +114 -0
  47. package/src/editor/managers/StateManager.ts +238 -0
  48. package/src/editor/managers/StyleManager.ts +182 -0
  49. package/src/editor/managers/index.ts +11 -0
  50. package/src/extensions/Comments/CommentsPlugin.ts +7 -4
  51. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +10 -0
  52. package/src/i18n/locales/ar.ts +6 -0
  53. package/src/i18n/locales/de.ts +6 -0
  54. package/src/i18n/locales/en.ts +6 -0
  55. package/src/i18n/locales/es.ts +6 -0
  56. package/src/i18n/locales/fr.ts +6 -0
  57. package/src/i18n/locales/he.ts +6 -0
  58. package/src/i18n/locales/hr.ts +6 -0
  59. package/src/i18n/locales/is.ts +6 -0
  60. package/src/i18n/locales/it.ts +6 -0
  61. package/src/i18n/locales/ja.ts +6 -0
  62. package/src/i18n/locales/ko.ts +6 -0
  63. package/src/i18n/locales/nl.ts +6 -0
  64. package/src/i18n/locales/no.ts +6 -0
  65. package/src/i18n/locales/pl.ts +6 -0
  66. package/src/i18n/locales/pt.ts +6 -0
  67. package/src/i18n/locales/ru.ts +6 -0
  68. package/src/i18n/locales/sk.ts +6 -0
  69. package/src/i18n/locales/uk.ts +6 -0
  70. package/src/i18n/locales/vi.ts +6 -0
  71. package/src/i18n/locales/zh-tw.ts +6 -0
  72. package/src/i18n/locales/zh.ts +6 -0
  73. package/src/schema/blocks/createSpec.ts +1 -0
  74. package/src/util/string.ts +21 -0
  75. package/types/src/api/exporters/markdown/util/convertVideoToMarkdownRehypePlugin.d.ts +2 -0
  76. package/types/src/blocks/Divider/block.d.ts +3 -0
  77. package/types/src/blocks/Heading/block.d.ts +3 -3
  78. package/types/src/blocks/defaultBlocks.d.ts +2 -1
  79. package/types/src/blocks/index.d.ts +1 -0
  80. package/types/src/editor/BlockNoteEditor.d.ts +68 -47
  81. package/types/src/editor/BlockNoteExtensions.d.ts +2 -1
  82. package/types/src/editor/managers/BlockManager.d.ts +114 -0
  83. package/types/src/editor/managers/CollaborationManager.d.ts +115 -0
  84. package/types/src/editor/managers/EventManager.d.ts +58 -0
  85. package/types/src/editor/managers/ExportManager.d.ts +64 -0
  86. package/types/src/editor/managers/ExtensionManager.d.ts +68 -0
  87. package/types/src/editor/managers/SelectionManager.d.ts +54 -0
  88. package/types/src/editor/managers/StateManager.d.ts +115 -0
  89. package/types/src/editor/managers/StyleManager.d.ts +48 -0
  90. package/types/src/editor/managers/index.d.ts +8 -0
  91. package/types/src/extensions/Comments/CommentsPlugin.d.ts +4 -3
  92. package/types/src/i18n/locales/en.d.ts +6 -0
  93. package/types/src/i18n/locales/sk.d.ts +6 -0
  94. package/types/src/util/string.d.ts +1 -0
  95. package/dist/BlockNoteSchema-DmZ6UQfY.cjs +0 -11
  96. package/dist/BlockNoteSchema-DmZ6UQfY.cjs.map +0 -1
  97. package/dist/BlockNoteSchema-oR047ACf.js.map +0 -1
  98. package/dist/en-Bq3Es3Np.js.map +0 -1
  99. package/dist/en-D3B48eJ7.cjs +0 -2
  100. package/dist/en-D3B48eJ7.cjs.map +0 -1
  101. /package/src/api/exporters/markdown/{removeUnderlinesRehypePlugin.ts → util/removeUnderlinesRehypePlugin.ts} +0 -0
  102. /package/types/src/api/exporters/markdown/{removeUnderlinesRehypePlugin.d.ts → util/removeUnderlinesRehypePlugin.d.ts} +0 -0
@@ -9,7 +9,7 @@ import * as Y from "yjs";
9
9
  import { createDropFileExtension } from "../api/clipboard/fromClipboard/fileDropExtension.js";
10
10
  import { createPasteFromClipboardExtension } from "../api/clipboard/fromClipboard/pasteExtension.js";
11
11
  import { createCopyToClipboardExtension } from "../api/clipboard/toClipboard/copyExtension.js";
12
- import type { ThreadStore } from "../comments/index.js";
12
+ import type { ThreadStore, User } from "../comments/index.js";
13
13
  import { BackgroundColorExtension } from "../extensions/BackgroundColor/BackgroundColorExtension.js";
14
14
  import { BlockChangePlugin } from "../extensions/BlockChange/BlockChangePlugin.js";
15
15
  import { CursorPlugin } from "../extensions/Collaboration/CursorPlugin.js";
@@ -96,6 +96,7 @@ type ExtensionOptions<
96
96
  comments?: {
97
97
  schema?: BlockNoteSchema<any, any, any>;
98
98
  threadStore: ThreadStore;
99
+ resolveUsers?: (userIds: string[]) => Promise<User[]>;
99
100
  };
100
101
  pasteHandler: BlockNoteEditorOptions<any, any, any>["pasteHandler"];
101
102
  };
@@ -162,6 +163,7 @@ export const getBlockNoteExtensions = <
162
163
  opts.editor,
163
164
  opts.comments.threadStore,
164
165
  CommentMark.name,
166
+ opts.comments.resolveUsers,
165
167
  opts.comments.schema,
166
168
  );
167
169
  }
@@ -0,0 +1,251 @@
1
+ import { insertBlocks } from "../../api/blockManipulation/commands/insertBlocks/insertBlocks.js";
2
+ import {
3
+ moveBlocksDown,
4
+ moveBlocksUp,
5
+ } from "../../api/blockManipulation/commands/moveBlocks/moveBlocks.js";
6
+ import {
7
+ canNestBlock,
8
+ canUnnestBlock,
9
+ nestBlock,
10
+ unnestBlock,
11
+ } from "../../api/blockManipulation/commands/nestBlock/nestBlock.js";
12
+ import { removeAndInsertBlocks } from "../../api/blockManipulation/commands/replaceBlocks/replaceBlocks.js";
13
+ import { updateBlock } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js";
14
+ import {
15
+ getBlock,
16
+ getNextBlock,
17
+ getParentBlock,
18
+ getPrevBlock,
19
+ } from "../../api/blockManipulation/getBlock/getBlock.js";
20
+ import { docToBlocks } from "../../api/nodeConversions/nodeToBlock.js";
21
+ import {
22
+ Block,
23
+ DefaultBlockSchema,
24
+ DefaultInlineContentSchema,
25
+ DefaultStyleSchema,
26
+ PartialBlock,
27
+ } from "../../blocks/defaultBlocks.js";
28
+ import {
29
+ BlockIdentifier,
30
+ BlockSchema,
31
+ InlineContentSchema,
32
+ StyleSchema,
33
+ } from "../../schema/index.js";
34
+ import { BlockNoteEditor } from "../BlockNoteEditor.js";
35
+
36
+ export class BlockManager<
37
+ BSchema extends BlockSchema = DefaultBlockSchema,
38
+ ISchema extends InlineContentSchema = DefaultInlineContentSchema,
39
+ SSchema extends StyleSchema = DefaultStyleSchema,
40
+ > {
41
+ constructor(private editor: BlockNoteEditor<BSchema, ISchema, SSchema>) {}
42
+
43
+ /**
44
+ * Gets a snapshot of all top-level (non-nested) blocks in the editor.
45
+ * @returns A snapshot of all top-level (non-nested) blocks in the editor.
46
+ */
47
+ public get document(): Block<BSchema, ISchema, SSchema>[] {
48
+ return this.editor.transact((tr) => {
49
+ return docToBlocks(tr.doc, this.editor.pmSchema);
50
+ });
51
+ }
52
+
53
+ /**
54
+ * Gets a snapshot of an existing block from the editor.
55
+ * @param blockIdentifier The identifier of an existing block that should be
56
+ * retrieved.
57
+ * @returns The block that matches the identifier, or `undefined` if no
58
+ * matching block was found.
59
+ */
60
+ public getBlock(
61
+ blockIdentifier: BlockIdentifier,
62
+ ): Block<BSchema, ISchema, SSchema> | undefined {
63
+ return this.editor.transact((tr) => getBlock(tr.doc, blockIdentifier));
64
+ }
65
+
66
+ /**
67
+ * Gets a snapshot of the previous sibling of an existing block from the
68
+ * editor.
69
+ * @param blockIdentifier The identifier of an existing block for which the
70
+ * previous sibling should be retrieved.
71
+ * @returns The previous sibling of the block that matches the identifier.
72
+ * `undefined` if no matching block was found, or it's the first child/block
73
+ * in the document.
74
+ */
75
+ public getPrevBlock(
76
+ blockIdentifier: BlockIdentifier,
77
+ ): Block<BSchema, ISchema, SSchema> | undefined {
78
+ return this.editor.transact((tr) => getPrevBlock(tr.doc, blockIdentifier));
79
+ }
80
+
81
+ /**
82
+ * Gets a snapshot of the next sibling of an existing block from the editor.
83
+ * @param blockIdentifier The identifier of an existing block for which the
84
+ * next sibling should be retrieved.
85
+ * @returns The next sibling of the block that matches the identifier.
86
+ * `undefined` if no matching block was found, or it's the last child/block in
87
+ * the document.
88
+ */
89
+ public getNextBlock(
90
+ blockIdentifier: BlockIdentifier,
91
+ ): Block<BSchema, ISchema, SSchema> | undefined {
92
+ return this.editor.transact((tr) => getNextBlock(tr.doc, blockIdentifier));
93
+ }
94
+
95
+ /**
96
+ * Gets a snapshot of the parent of an existing block from the editor.
97
+ * @param blockIdentifier The identifier of an existing block for which the
98
+ * parent should be retrieved.
99
+ * @returns The parent of the block that matches the identifier. `undefined`
100
+ * if no matching block was found, or the block isn't nested.
101
+ */
102
+ public getParentBlock(
103
+ blockIdentifier: BlockIdentifier,
104
+ ): Block<BSchema, ISchema, SSchema> | undefined {
105
+ return this.editor.transact((tr) =>
106
+ getParentBlock(tr.doc, blockIdentifier),
107
+ );
108
+ }
109
+
110
+ /**
111
+ * Traverses all blocks in the editor depth-first, and executes a callback for each.
112
+ * @param callback The callback to execute for each block. Returning `false` stops the traversal.
113
+ * @param reverse Whether the blocks should be traversed in reverse order.
114
+ */
115
+ public forEachBlock(
116
+ callback: (block: Block<BSchema, ISchema, SSchema>) => boolean,
117
+ reverse = false,
118
+ ): void {
119
+ const blocks = this.document.slice();
120
+
121
+ if (reverse) {
122
+ blocks.reverse();
123
+ }
124
+
125
+ function traverseBlockArray(
126
+ blockArray: Block<BSchema, ISchema, SSchema>[],
127
+ ): boolean {
128
+ for (const block of blockArray) {
129
+ if (callback(block) === false) {
130
+ return false;
131
+ }
132
+
133
+ const children = reverse
134
+ ? block.children.slice().reverse()
135
+ : block.children;
136
+
137
+ if (!traverseBlockArray(children)) {
138
+ return false;
139
+ }
140
+ }
141
+
142
+ return true;
143
+ }
144
+
145
+ traverseBlockArray(blocks);
146
+ }
147
+
148
+ /**
149
+ * Inserts new blocks into the editor. If a block's `id` is undefined, BlockNote generates one automatically. Throws an
150
+ * error if the reference block could not be found.
151
+ * @param blocksToInsert An array of partial blocks that should be inserted.
152
+ * @param referenceBlock An identifier for an existing block, at which the new blocks should be inserted.
153
+ * @param placement Whether the blocks should be inserted just before, just after, or nested inside the
154
+ * `referenceBlock`.
155
+ */
156
+ public insertBlocks(
157
+ blocksToInsert: PartialBlock<BSchema, ISchema, SSchema>[],
158
+ referenceBlock: BlockIdentifier,
159
+ placement: "before" | "after" = "before",
160
+ ) {
161
+ return this.editor.transact((tr) =>
162
+ insertBlocks(tr, blocksToInsert, referenceBlock, placement),
163
+ );
164
+ }
165
+
166
+ /**
167
+ * Updates an existing block in the editor. Since updatedBlock is a PartialBlock object, some fields might not be
168
+ * defined. These undefined fields are kept as-is from the existing block. Throws an error if the block to update could
169
+ * not be found.
170
+ * @param blockToUpdate The block that should be updated.
171
+ * @param update A partial block which defines how the existing block should be changed.
172
+ */
173
+ public updateBlock(
174
+ blockToUpdate: BlockIdentifier,
175
+ update: PartialBlock<BSchema, ISchema, SSchema>,
176
+ ) {
177
+ return this.editor.transact((tr) => updateBlock(tr, blockToUpdate, update));
178
+ }
179
+
180
+ /**
181
+ * Removes existing blocks from the editor. Throws an error if any of the blocks could not be found.
182
+ * @param blocksToRemove An array of identifiers for existing blocks that should be removed.
183
+ */
184
+ public removeBlocks(blocksToRemove: BlockIdentifier[]) {
185
+ return this.editor.transact(
186
+ (tr) => removeAndInsertBlocks(tr, blocksToRemove, []).removedBlocks,
187
+ );
188
+ }
189
+
190
+ /**
191
+ * Replaces existing blocks in the editor with new blocks. If the blocks that should be removed are not adjacent or
192
+ * are at different nesting levels, `blocksToInsert` will be inserted at the position of the first block in
193
+ * `blocksToRemove`. Throws an error if any of the blocks to remove could not be found.
194
+ * @param blocksToRemove An array of blocks that should be replaced.
195
+ * @param blocksToInsert An array of partial blocks to replace the old ones with.
196
+ */
197
+ public replaceBlocks(
198
+ blocksToRemove: BlockIdentifier[],
199
+ blocksToInsert: PartialBlock<BSchema, ISchema, SSchema>[],
200
+ ) {
201
+ return this.editor.transact((tr) =>
202
+ removeAndInsertBlocks(tr, blocksToRemove, blocksToInsert),
203
+ );
204
+ }
205
+
206
+ /**
207
+ * Checks if the block containing the text cursor can be nested.
208
+ */
209
+ public canNestBlock() {
210
+ return canNestBlock(this.editor);
211
+ }
212
+
213
+ /**
214
+ * Nests the block containing the text cursor into the block above it.
215
+ */
216
+ public nestBlock() {
217
+ nestBlock(this.editor);
218
+ }
219
+
220
+ /**
221
+ * Checks if the block containing the text cursor is nested.
222
+ */
223
+ public canUnnestBlock() {
224
+ return canUnnestBlock(this.editor);
225
+ }
226
+
227
+ /**
228
+ * Lifts the block containing the text cursor out of its parent.
229
+ */
230
+ public unnestBlock() {
231
+ unnestBlock(this.editor);
232
+ }
233
+
234
+ /**
235
+ * Moves the selected blocks up. If the previous block has children, moves
236
+ * them to the end of its children. If there is no previous block, but the
237
+ * current blocks share a common parent, moves them out of & before it.
238
+ */
239
+ public moveBlocksUp() {
240
+ return moveBlocksUp(this.editor);
241
+ }
242
+
243
+ /**
244
+ * Moves the selected blocks down. If the next block has children, moves
245
+ * them to the start of its children. If there is no next block, but the
246
+ * current blocks share a common parent, moves them out of & after it.
247
+ */
248
+ public moveBlocksDown() {
249
+ return moveBlocksDown(this.editor);
250
+ }
251
+ }
@@ -0,0 +1,212 @@
1
+ import * as Y from "yjs";
2
+ import { redoCommand, undoCommand } from "y-prosemirror";
3
+ import { CommentsPlugin } from "../../extensions/Comments/CommentsPlugin.js";
4
+ import { CommentMark } from "../../extensions/Comments/CommentMark.js";
5
+ import { ForkYDocPlugin } from "../../extensions/Collaboration/ForkYDocPlugin.js";
6
+ import { SyncPlugin } from "../../extensions/Collaboration/SyncPlugin.js";
7
+ import { UndoPlugin } from "../../extensions/Collaboration/UndoPlugin.js";
8
+ import { CursorPlugin } from "../../extensions/Collaboration/CursorPlugin.js";
9
+ import type { ThreadStore, User } from "../../comments/index.js";
10
+ import type { BlockNoteEditor } from "../BlockNoteEditor.js";
11
+ import { CustomBlockNoteSchema } from "../../schema/schema.js";
12
+
13
+ export interface CollaborationOptions {
14
+ /**
15
+ * The Yjs XML fragment that's used for collaboration.
16
+ */
17
+ fragment: Y.XmlFragment;
18
+ /**
19
+ * The user info for the current user that's shown to other collaborators.
20
+ */
21
+ user: {
22
+ name: string;
23
+ color: string;
24
+ };
25
+ /**
26
+ * A Yjs provider (used for awareness / cursor information)
27
+ * Can be null for comments-only mode
28
+ */
29
+ provider: any;
30
+ /**
31
+ * Optional function to customize how cursors of users are rendered
32
+ */
33
+ renderCursor?: (user: any) => HTMLElement;
34
+ /**
35
+ * Optional flag to set when the user label should be shown with the default
36
+ * collaboration cursor. Setting to "always" will always show the label,
37
+ * while "activity" will only show the label when the user moves the cursor
38
+ * or types. Defaults to "activity".
39
+ */
40
+ showCursorLabels?: "always" | "activity";
41
+ /**
42
+ * Comments configuration - can be used with or without collaboration
43
+ */
44
+ comments?: {
45
+ schema?: CustomBlockNoteSchema<any, any, any>;
46
+ threadStore: ThreadStore;
47
+ };
48
+ /**
49
+ * Function to resolve user IDs to user objects - required for comments
50
+ */
51
+ resolveUsers?: (userIds: string[]) => Promise<User[]>;
52
+ }
53
+
54
+ /**
55
+ * CollaborationManager handles all collaboration-related functionality
56
+ * This manager is completely optional and can be tree-shaken if not used
57
+ */
58
+ export class CollaborationManager {
59
+ private editor: BlockNoteEditor;
60
+ private options: CollaborationOptions;
61
+ private _commentsPlugin?: CommentsPlugin;
62
+ private _forkYDocPlugin?: ForkYDocPlugin;
63
+ private _syncPlugin?: SyncPlugin;
64
+ private _undoPlugin?: UndoPlugin;
65
+ private _cursorPlugin?: CursorPlugin;
66
+
67
+ constructor(editor: BlockNoteEditor, options: CollaborationOptions) {
68
+ this.editor = editor;
69
+ this.options = options;
70
+ }
71
+
72
+ /**
73
+ * Get the sync plugin instance
74
+ */
75
+ public get syncPlugin(): SyncPlugin | undefined {
76
+ return this._syncPlugin;
77
+ }
78
+
79
+ /**
80
+ * Get the undo plugin instance
81
+ */
82
+ public get undoPlugin(): UndoPlugin | undefined {
83
+ return this._undoPlugin;
84
+ }
85
+
86
+ /**
87
+ * Get the cursor plugin instance
88
+ */
89
+ public get cursorPlugin(): CursorPlugin | undefined {
90
+ return this._cursorPlugin;
91
+ }
92
+
93
+ /**
94
+ * Get the fork YDoc plugin instance
95
+ */
96
+ public get forkYDocPlugin(): ForkYDocPlugin | undefined {
97
+ return this._forkYDocPlugin;
98
+ }
99
+
100
+ // Initialize collaboration plugins
101
+ public initExtensions(): Record<string, unknown> {
102
+ // Only create collaboration plugins when real-time collaboration is enabled
103
+ const extensions: Record<string, unknown> = {};
104
+
105
+ // Initialize sync plugin
106
+ this._syncPlugin = new SyncPlugin(this.options.fragment);
107
+ extensions.ySyncPlugin = this._syncPlugin;
108
+
109
+ // Initialize undo plugin
110
+ this._undoPlugin = new UndoPlugin({ editor: this.editor });
111
+ extensions.yUndoPlugin = this._undoPlugin;
112
+
113
+ // Initialize cursor plugin if provider has awareness
114
+ if (this.options.provider?.awareness) {
115
+ this._cursorPlugin = new CursorPlugin(this.options);
116
+ extensions.yCursorPlugin = this._cursorPlugin;
117
+ }
118
+
119
+ // Initialize fork YDoc plugin
120
+ this._forkYDocPlugin = new ForkYDocPlugin({
121
+ editor: this.editor,
122
+ collaboration: this.options,
123
+ });
124
+ extensions.forkYDocPlugin = this._forkYDocPlugin;
125
+
126
+ if (this.options.comments) {
127
+ if (!this.options.resolveUsers) {
128
+ throw new Error("resolveUsers is required when using comments");
129
+ }
130
+
131
+ // Create CommentsPlugin instance and add it to editor extensions
132
+ this._commentsPlugin = new CommentsPlugin(
133
+ this.editor,
134
+ this.options.comments.threadStore,
135
+ CommentMark.name,
136
+ this.options.resolveUsers,
137
+ this.options.comments.schema,
138
+ );
139
+
140
+ // Add the comments plugin to the editor's extensions
141
+ extensions.comments = this._commentsPlugin;
142
+ extensions.commentMark = CommentMark;
143
+ }
144
+ return extensions;
145
+ }
146
+
147
+ /**
148
+ * Update the user info for the current user that's shown to other collaborators
149
+ */
150
+ public updateUserInfo(user: { name: string; color: string }) {
151
+ const cursor = this.cursorPlugin;
152
+ if (!cursor) {
153
+ throw new Error(
154
+ "Cannot update collaboration user info when collaboration is disabled.",
155
+ );
156
+ }
157
+ cursor.updateUser(user);
158
+ }
159
+
160
+ /**
161
+ * Get the collaboration undo command
162
+ */
163
+ public getUndoCommand() {
164
+ return undoCommand;
165
+ }
166
+
167
+ /**
168
+ * Get the collaboration redo command
169
+ */
170
+ public getRedoCommand() {
171
+ return redoCommand;
172
+ }
173
+
174
+ /**
175
+ * Check if initial content should be avoided due to collaboration
176
+ */
177
+ public shouldAvoidInitialContent(): boolean {
178
+ // Only avoid initial content when real-time collaboration is enabled
179
+ // (i.e., when we have a provider)
180
+ return !!this.options.provider;
181
+ }
182
+
183
+ /**
184
+ * Get the collaboration options
185
+ */
186
+ public getOptions(): CollaborationOptions {
187
+ return this.options;
188
+ }
189
+
190
+ /**
191
+ * Get the comments plugin if available
192
+ */
193
+ public get comments(): CommentsPlugin | undefined {
194
+ return this._commentsPlugin;
195
+ }
196
+
197
+ /**
198
+ * Check if comments are enabled
199
+ */
200
+ public get hasComments(): boolean {
201
+ return !!this.options.comments;
202
+ }
203
+
204
+ /**
205
+ * Get the resolveUsers function
206
+ */
207
+ public get resolveUsers():
208
+ | ((userIds: string[]) => Promise<User[]>)
209
+ | undefined {
210
+ return this.options.resolveUsers;
211
+ }
212
+ }
@@ -0,0 +1,134 @@
1
+ import type { BlockNoteEditor } from "../BlockNoteEditor.js";
2
+ import {
3
+ getBlocksChangedByTransaction,
4
+ type BlocksChanged,
5
+ } from "../../api/getBlocksChangedByTransaction.js";
6
+ import { Transaction } from "prosemirror-state";
7
+ import { EventEmitter } from "../../util/EventEmitter.js";
8
+
9
+ /**
10
+ * A function that can be used to unsubscribe from an event.
11
+ */
12
+ export type Unsubscribe = () => void;
13
+
14
+ /**
15
+ * EventManager is a class which manages the events of the editor
16
+ */
17
+ export class EventManager<Editor extends BlockNoteEditor> extends EventEmitter<{
18
+ onChange: [
19
+ editor: Editor,
20
+ ctx: {
21
+ getChanges(): BlocksChanged<
22
+ Editor["schema"]["blockSchema"],
23
+ Editor["schema"]["inlineContentSchema"],
24
+ Editor["schema"]["styleSchema"]
25
+ >;
26
+ },
27
+ ];
28
+ onSelectionChange: [ctx: { editor: Editor; transaction: Transaction }];
29
+ onMount: [ctx: { editor: Editor }];
30
+ onUnmount: [ctx: { editor: Editor }];
31
+ }> {
32
+ constructor(private editor: Editor) {
33
+ super();
34
+ // We register tiptap events only once the editor is finished initializing
35
+ // otherwise we would be trying to register events on a tiptap editor which does not exist yet
36
+ editor.onCreate(() => {
37
+ editor._tiptapEditor.on(
38
+ "update",
39
+ ({ transaction, appendedTransactions }) => {
40
+ this.emit("onChange", editor, {
41
+ getChanges() {
42
+ return getBlocksChangedByTransaction(
43
+ transaction,
44
+ appendedTransactions,
45
+ );
46
+ },
47
+ });
48
+ },
49
+ );
50
+ editor._tiptapEditor.on("selectionUpdate", ({ transaction }) => {
51
+ this.emit("onSelectionChange", { editor, transaction });
52
+ });
53
+ editor._tiptapEditor.on("mount", () => {
54
+ this.emit("onMount", { editor });
55
+ });
56
+ editor._tiptapEditor.on("unmount", () => {
57
+ this.emit("onUnmount", { editor });
58
+ });
59
+ });
60
+ }
61
+
62
+ /**
63
+ * Register a callback that will be called when the editor changes.
64
+ */
65
+ public onChange(
66
+ callback: (
67
+ editor: Editor,
68
+ ctx: {
69
+ getChanges(): BlocksChanged<
70
+ Editor["schema"]["blockSchema"],
71
+ Editor["schema"]["inlineContentSchema"],
72
+ Editor["schema"]["styleSchema"]
73
+ >;
74
+ },
75
+ ) => void,
76
+ ): Unsubscribe {
77
+ this.on("onChange", callback);
78
+
79
+ return () => {
80
+ this.off("onChange", callback);
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Register a callback that will be called when the selection changes.
86
+ */
87
+ public onSelectionChange(
88
+ callback: (editor: Editor) => void,
89
+ /**
90
+ * If true, the callback will be triggered when the selection changes due to a yjs sync (i.e.: other user was typing)
91
+ */
92
+ includeSelectionChangedByRemote = false,
93
+ ): Unsubscribe {
94
+ const cb = (e: { transaction: Transaction }) => {
95
+ if (
96
+ e.transaction.getMeta("$y-sync") &&
97
+ !includeSelectionChangedByRemote
98
+ ) {
99
+ // selection changed because of a yjs sync (i.e.: other user was typing)
100
+ // we don't want to trigger the callback in this case
101
+ return;
102
+ }
103
+ callback(this.editor);
104
+ };
105
+
106
+ this.on("onSelectionChange", cb);
107
+
108
+ return () => {
109
+ this.off("onSelectionChange", cb);
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Register a callback that will be called when the editor is mounted.
115
+ */
116
+ public onMount(callback: (ctx: { editor: Editor }) => void): Unsubscribe {
117
+ this.on("onMount", callback);
118
+
119
+ return () => {
120
+ this.off("onMount", callback);
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Register a callback that will be called when the editor is unmounted.
126
+ */
127
+ public onUnmount(callback: (ctx: { editor: Editor }) => void): Unsubscribe {
128
+ this.on("onUnmount", callback);
129
+
130
+ return () => {
131
+ this.off("onUnmount", callback);
132
+ };
133
+ }
134
+ }