@blocknote/core 0.39.1 → 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.
- package/dist/BlockNoteSchema-COA0fsXW.cjs +11 -0
- package/dist/BlockNoteSchema-COA0fsXW.cjs.map +1 -0
- package/dist/{BlockNoteSchema-oR047ACf.js → BlockNoteSchema-CYRHak18.js} +681 -581
- package/dist/BlockNoteSchema-CYRHak18.js.map +1 -0
- package/dist/blocknote.cjs +4 -4
- package/dist/blocknote.cjs.map +1 -1
- package/dist/blocknote.js +3663 -2816
- package/dist/blocknote.js.map +1 -1
- package/dist/blocks.cjs +1 -1
- package/dist/blocks.js +51 -49
- package/dist/en-Cl87Uuyf.cjs +2 -0
- package/dist/en-Cl87Uuyf.cjs.map +1 -0
- package/dist/{en-Bq3Es3Np.js → en-njEqD7AG.js} +9 -3
- package/dist/en-njEqD7AG.js.map +1 -0
- package/dist/locales.cjs +1 -1
- package/dist/locales.cjs.map +1 -1
- package/dist/locales.js +122 -2
- package/dist/locales.js.map +1 -1
- package/dist/style.css +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/webpack-stats.json +1 -1
- package/package.json +4 -3
- package/src/api/exporters/html/externalHTMLExporter.ts +1 -1
- package/src/api/exporters/html/util/serializeBlocksInternalHTML.ts +2 -1
- package/src/api/exporters/markdown/markdownExporter.ts +3 -1
- package/src/api/exporters/markdown/util/convertVideoToMarkdownRehypePlugin.ts +19 -0
- package/src/api/parsers/markdown/parseMarkdown.ts +31 -0
- package/src/blocks/Code/block.ts +14 -14
- package/src/blocks/Divider/block.ts +49 -0
- package/src/blocks/ListItem/BulletListItem/block.ts +9 -1
- package/src/blocks/ListItem/CheckListItem/block.ts +1 -0
- package/src/blocks/ListItem/NumberedListItem/block.ts +9 -1
- package/src/blocks/Table/block.ts +56 -1
- package/src/blocks/ToggleWrapper/createToggleWrapper.ts +1 -1
- package/src/blocks/defaultBlocks.ts +16 -14
- package/src/blocks/index.ts +1 -0
- package/src/editor/Block.css +14 -20
- package/src/editor/BlockNoteEditor.test.ts +40 -0
- package/src/editor/BlockNoteEditor.ts +257 -465
- package/src/editor/BlockNoteExtensions.ts +3 -1
- package/src/editor/managers/BlockManager.ts +251 -0
- package/src/editor/managers/CollaborationManager.ts +212 -0
- package/src/editor/managers/EventManager.ts +134 -0
- package/src/editor/managers/ExportManager.ts +137 -0
- package/src/editor/managers/ExtensionManager.ts +130 -0
- package/src/editor/managers/SelectionManager.ts +114 -0
- package/src/editor/managers/StateManager.ts +238 -0
- package/src/editor/managers/StyleManager.ts +182 -0
- package/src/editor/managers/index.ts +11 -0
- package/src/extensions/Comments/CommentsPlugin.ts +7 -4
- package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +10 -0
- package/src/i18n/locales/ar.ts +6 -0
- package/src/i18n/locales/de.ts +6 -0
- package/src/i18n/locales/en.ts +6 -0
- package/src/i18n/locales/es.ts +6 -0
- package/src/i18n/locales/fr.ts +6 -0
- package/src/i18n/locales/he.ts +6 -0
- package/src/i18n/locales/hr.ts +6 -0
- package/src/i18n/locales/is.ts +6 -0
- package/src/i18n/locales/it.ts +6 -0
- package/src/i18n/locales/ja.ts +6 -0
- package/src/i18n/locales/ko.ts +6 -0
- package/src/i18n/locales/nl.ts +6 -0
- package/src/i18n/locales/no.ts +6 -0
- package/src/i18n/locales/pl.ts +6 -0
- package/src/i18n/locales/pt.ts +6 -0
- package/src/i18n/locales/ru.ts +6 -0
- package/src/i18n/locales/sk.ts +6 -0
- package/src/i18n/locales/uk.ts +6 -0
- package/src/i18n/locales/vi.ts +6 -0
- package/src/i18n/locales/zh-tw.ts +6 -0
- package/src/i18n/locales/zh.ts +6 -0
- package/src/schema/blocks/createSpec.ts +1 -0
- package/src/util/string.ts +21 -0
- package/types/src/api/exporters/markdown/util/convertVideoToMarkdownRehypePlugin.d.ts +2 -0
- package/types/src/blocks/Divider/block.d.ts +3 -0
- package/types/src/blocks/Heading/block.d.ts +3 -3
- package/types/src/blocks/defaultBlocks.d.ts +2 -1
- package/types/src/blocks/index.d.ts +1 -0
- package/types/src/editor/BlockNoteEditor.d.ts +75 -48
- package/types/src/editor/BlockNoteExtensions.d.ts +2 -1
- package/types/src/editor/managers/BlockManager.d.ts +114 -0
- package/types/src/editor/managers/CollaborationManager.d.ts +115 -0
- package/types/src/editor/managers/EventManager.d.ts +58 -0
- package/types/src/editor/managers/ExportManager.d.ts +64 -0
- package/types/src/editor/managers/ExtensionManager.d.ts +68 -0
- package/types/src/editor/managers/SelectionManager.d.ts +54 -0
- package/types/src/editor/managers/StateManager.d.ts +115 -0
- package/types/src/editor/managers/StyleManager.d.ts +48 -0
- package/types/src/editor/managers/index.d.ts +8 -0
- package/types/src/extensions/Comments/CommentsPlugin.d.ts +4 -3
- package/types/src/i18n/locales/en.d.ts +6 -0
- package/types/src/i18n/locales/sk.d.ts +6 -0
- package/types/src/util/string.d.ts +1 -0
- package/dist/BlockNoteSchema-DmZ6UQfY.cjs +0 -11
- package/dist/BlockNoteSchema-DmZ6UQfY.cjs.map +0 -1
- package/dist/BlockNoteSchema-oR047ACf.js.map +0 -1
- package/dist/en-Bq3Es3Np.js.map +0 -1
- package/dist/en-D3B48eJ7.cjs +0 -2
- package/dist/en-D3B48eJ7.cjs.map +0 -1
- /package/src/api/exporters/markdown/{removeUnderlinesRehypePlugin.ts → util/removeUnderlinesRehypePlugin.ts} +0 -0
- /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
|
+
}
|