@blocknote/core 0.42.3 → 0.44.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 (200) hide show
  1. package/dist/BlockNoteExtension-BWw0r8Gy.cjs +2 -0
  2. package/dist/BlockNoteExtension-BWw0r8Gy.cjs.map +1 -0
  3. package/dist/BlockNoteExtension-C2X7LW-V.js +25 -0
  4. package/dist/BlockNoteExtension-C2X7LW-V.js.map +1 -0
  5. package/dist/BlockNoteSchema-B4gm-Qco.cjs +2 -0
  6. package/dist/BlockNoteSchema-B4gm-Qco.cjs.map +1 -0
  7. package/dist/BlockNoteSchema-C-l154WP.js +270 -0
  8. package/dist/BlockNoteSchema-C-l154WP.js.map +1 -0
  9. package/dist/EventEmitter-CLwfmbqG.cjs +2 -0
  10. package/dist/EventEmitter-CLwfmbqG.cjs.map +1 -0
  11. package/dist/EventEmitter-CjSwpTbz.js +27 -0
  12. package/dist/EventEmitter-CjSwpTbz.js.map +1 -0
  13. package/dist/ShowSelection-BW37oJ6h.cjs +2 -0
  14. package/dist/ShowSelection-BW37oJ6h.cjs.map +1 -0
  15. package/dist/ShowSelection-Dz-NEase.js +43 -0
  16. package/dist/ShowSelection-Dz-NEase.js.map +1 -0
  17. package/dist/TrailingNode-B_zPMWxw.js +2098 -0
  18. package/dist/TrailingNode-B_zPMWxw.js.map +1 -0
  19. package/dist/TrailingNode-CRHrgOnK.cjs +2 -0
  20. package/dist/TrailingNode-CRHrgOnK.cjs.map +1 -0
  21. package/dist/{blockToNode-DIfPWLH8.js → blockToNode-DBNbhwwC.js} +33 -33
  22. package/dist/blockToNode-DBNbhwwC.js.map +1 -0
  23. package/dist/blockToNode-w7H99R6p.cjs.map +1 -1
  24. package/dist/blocknote.cjs +4 -4
  25. package/dist/blocknote.cjs.map +1 -1
  26. package/dist/blocknote.js +2496 -5686
  27. package/dist/blocknote.js.map +1 -1
  28. package/dist/blocks.cjs +1 -1
  29. package/dist/blocks.js +71 -70
  30. package/dist/blocks.js.map +1 -1
  31. package/dist/comments.cjs +1 -1
  32. package/dist/comments.cjs.map +1 -1
  33. package/dist/comments.js +451 -137
  34. package/dist/comments.js.map +1 -1
  35. package/dist/defaultBlocks-DLJ4Q1_J.cjs +6 -0
  36. package/dist/defaultBlocks-DLJ4Q1_J.cjs.map +1 -0
  37. package/dist/{BlockNoteSchema-Bi-eeHal.js → defaultBlocks-DgA_mtQV.js} +974 -1027
  38. package/dist/defaultBlocks-DgA_mtQV.js.map +1 -0
  39. package/dist/extensions.cjs +2 -0
  40. package/dist/extensions.cjs.map +1 -0
  41. package/dist/extensions.js +57 -0
  42. package/dist/extensions.js.map +1 -0
  43. package/dist/tsconfig.tsbuildinfo +1 -1
  44. package/dist/webpack-stats.json +1 -1
  45. package/dist/yjs.js +1 -1
  46. package/package.json +9 -3
  47. package/src/api/nodeConversions/blockToNode.ts +1 -1
  48. package/src/api/nodeConversions/nodeToBlock.ts +1 -1
  49. package/src/blocks/Code/block.ts +4 -4
  50. package/src/blocks/Divider/block.ts +2 -2
  51. package/src/blocks/File/helpers/render/createAddFileButton.ts +7 -5
  52. package/src/blocks/Heading/block.ts +23 -20
  53. package/src/blocks/ListItem/BulletListItem/block.ts +2 -2
  54. package/src/blocks/ListItem/CheckListItem/block.ts +2 -2
  55. package/src/blocks/ListItem/NumberedListItem/block.ts +3 -3
  56. package/src/blocks/ListItem/ToggleListItem/block.ts +2 -2
  57. package/src/blocks/PageBreak/getPageBreakSlashMenuItems.ts +2 -2
  58. package/src/blocks/Paragraph/block.ts +2 -2
  59. package/src/blocks/Quote/block.ts +2 -2
  60. package/src/blocks/Table/block.ts +4 -3
  61. package/src/blocks/ToggleWrapper/createToggleWrapper.ts +2 -1
  62. package/src/comments/extension.ts +353 -0
  63. package/src/comments/index.ts +2 -1
  64. package/src/comments/types.ts +8 -0
  65. package/src/{extensions/Comments → comments}/userstore/UserStore.ts +2 -2
  66. package/src/editor/BlockNoteEditor.test.ts +2 -23
  67. package/src/editor/BlockNoteEditor.ts +60 -453
  68. package/src/editor/BlockNoteExtension.test.ts +103 -0
  69. package/src/editor/BlockNoteExtension.ts +174 -56
  70. package/src/editor/managers/EventManager.ts +64 -35
  71. package/src/editor/managers/ExtensionManager/extensions.ts +214 -0
  72. package/src/editor/managers/ExtensionManager/index.ts +514 -0
  73. package/src/editor/managers/ExtensionManager/symbol.ts +6 -0
  74. package/src/editor/managers/SelectionManager.ts +5 -1
  75. package/src/editor/managers/StateManager.ts +29 -17
  76. package/src/editor/managers/index.ts +1 -5
  77. package/src/extensions/BlockChange/{BlockChangePlugin.ts → BlockChange.ts} +27 -29
  78. package/src/extensions/Collaboration/{ForkYDocPlugin.test.ts → ForkYDoc.test.ts} +6 -5
  79. package/src/extensions/Collaboration/ForkYDoc.ts +158 -0
  80. package/src/extensions/Collaboration/YCursorPlugin.ts +183 -0
  81. package/src/extensions/Collaboration/YSync.ts +16 -0
  82. package/src/extensions/Collaboration/YUndo.ts +12 -0
  83. package/src/extensions/Collaboration/schemaMigration/SchemaMigration.ts +59 -0
  84. package/src/extensions/DropCursor/DropCursor.ts +26 -0
  85. package/src/extensions/FilePanel/FilePanel.ts +41 -0
  86. package/src/extensions/FormattingToolbar/FormattingToolbar.ts +119 -0
  87. package/src/extensions/History/History.ts +11 -0
  88. package/src/extensions/LinkToolbar/LinkToolbar.ts +121 -0
  89. package/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboard.ts +74 -0
  90. package/src/extensions/Placeholder/Placeholder.ts +148 -0
  91. package/src/extensions/PreviousBlockType/{PreviousBlockTypePlugin.ts → PreviousBlockType.ts} +9 -13
  92. package/src/extensions/ShowSelection/{ShowSelectionPlugin.ts → ShowSelection.ts} +27 -33
  93. package/src/extensions/SideMenu/{SideMenuPlugin.ts → SideMenu.ts} +63 -83
  94. package/src/extensions/SuggestionMenu/{SuggestionPlugin.ts → SuggestionMenu.ts} +71 -77
  95. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +29 -44
  96. package/src/extensions/TableHandles/{TableHandlesPlugin.ts → TableHandles.ts} +416 -437
  97. package/src/extensions/TrailingNode/{TrailingNodeExtension.ts → TrailingNode.ts} +8 -17
  98. package/src/extensions/index.ts +24 -0
  99. package/src/extensions/{BackgroundColor → tiptap-extensions/BackgroundColor}/BackgroundColorExtension.ts +1 -1
  100. package/src/extensions/{KeyboardShortcuts → tiptap-extensions/KeyboardShortcuts}/KeyboardShortcutsExtension.ts +21 -16
  101. package/src/extensions/{TextColor → tiptap-extensions/TextColor}/TextColorExtension.ts +1 -1
  102. package/src/extensions/tiptap-extensions/index.ts +31 -0
  103. package/src/index.ts +1 -13
  104. package/src/schema/blocks/createSpec.ts +14 -11
  105. package/src/schema/blocks/internal.ts +2 -2
  106. package/src/schema/blocks/types.ts +8 -5
  107. package/src/schema/schema.ts +11 -36
  108. package/src/util/topo-sort.ts +46 -0
  109. package/types/src/comments/extension.d.ts +70 -0
  110. package/types/src/comments/index.d.ts +2 -1
  111. package/types/src/comments/types.d.ts +8 -0
  112. package/types/src/{extensions/Comments → comments}/userstore/UserStore.d.ts +2 -2
  113. package/types/src/editor/BlockNoteEditor.d.ts +34 -105
  114. package/types/src/editor/BlockNoteExtension.d.ts +87 -22
  115. package/types/src/editor/managers/EventManager.d.ts +25 -16
  116. package/types/src/editor/managers/ExtensionManager/extensions.d.ts +8 -0
  117. package/types/src/editor/managers/ExtensionManager/index.d.ts +83 -0
  118. package/types/src/editor/managers/ExtensionManager/symbol.d.ts +5 -0
  119. package/types/src/editor/managers/StateManager.d.ts +1 -12
  120. package/types/src/editor/managers/index.d.ts +1 -2
  121. package/types/src/extensions/BlockChange/BlockChange.d.ts +16 -0
  122. package/types/src/extensions/Collaboration/ForkYDoc.d.ts +34 -0
  123. package/types/src/extensions/Collaboration/ForkYDoc.test.d.ts +1 -0
  124. package/types/src/extensions/Collaboration/YCursorPlugin.d.ts +24 -0
  125. package/types/src/extensions/Collaboration/YSync.d.ts +8 -0
  126. package/types/src/extensions/Collaboration/YUndo.d.ts +12 -0
  127. package/types/src/extensions/Collaboration/schemaMigration/SchemaMigration.d.ts +8 -0
  128. package/types/src/extensions/DropCursor/DropCursor.d.ts +5 -0
  129. package/types/src/extensions/FilePanel/FilePanel.d.ts +11 -0
  130. package/types/src/extensions/FormattingToolbar/FormattingToolbar.d.ts +9 -0
  131. package/types/src/extensions/History/History.d.ts +6 -0
  132. package/types/src/extensions/LinkToolbar/LinkToolbar.d.ts +24 -0
  133. package/types/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboard.d.ts +5 -0
  134. package/types/src/extensions/Placeholder/Placeholder.d.ts +6 -0
  135. package/types/src/extensions/PreviousBlockType/{PreviousBlockTypePlugin.d.ts → PreviousBlockType.d.ts} +9 -5
  136. package/types/src/extensions/ShowSelection/ShowSelection.d.ts +21 -0
  137. package/types/src/extensions/SideMenu/{SideMenuPlugin.d.ts → SideMenu.d.ts} +11 -15
  138. package/types/src/extensions/SuggestionMenu/SuggestionMenu.d.ts +54 -0
  139. package/types/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.d.ts +1 -1
  140. package/types/src/extensions/TableHandles/{TableHandlesPlugin.d.ts → TableHandles.d.ts} +28 -31
  141. package/types/src/extensions/TrailingNode/TrailingNode.d.ts +8 -0
  142. package/types/src/extensions/index.d.ts +24 -0
  143. package/types/src/extensions/{KeyboardShortcuts → tiptap-extensions/KeyboardShortcuts}/KeyboardShortcutsExtension.d.ts +1 -1
  144. package/types/src/extensions/tiptap-extensions/index.d.ts +11 -0
  145. package/types/src/index.d.ts +1 -13
  146. package/types/src/schema/blocks/createSpec.d.ts +4 -4
  147. package/types/src/schema/blocks/internal.d.ts +2 -2
  148. package/types/src/schema/blocks/types.d.ts +5 -5
  149. package/types/src/util/topo-sort.d.ts +8 -0
  150. package/dist/BlockNoteSchema-Bi-eeHal.js.map +0 -1
  151. package/dist/BlockNoteSchema-DjDaA2C3.cjs +0 -6
  152. package/dist/BlockNoteSchema-DjDaA2C3.cjs.map +0 -1
  153. package/dist/blockToNode-DIfPWLH8.js.map +0 -1
  154. package/src/comments/models/User.ts +0 -8
  155. package/src/editor/BlockNoteExtensions.ts +0 -325
  156. package/src/editor/managers/CollaborationManager.ts +0 -212
  157. package/src/editor/managers/ExtensionManager.ts +0 -130
  158. package/src/extensions/Collaboration/CursorPlugin.ts +0 -189
  159. package/src/extensions/Collaboration/ForkYDocPlugin.ts +0 -192
  160. package/src/extensions/Collaboration/SyncPlugin.ts +0 -18
  161. package/src/extensions/Collaboration/UndoPlugin.ts +0 -18
  162. package/src/extensions/Collaboration/schemaMigration/SchemaMigrationPlugin.ts +0 -59
  163. package/src/extensions/Comments/CommentsPlugin.ts +0 -392
  164. package/src/extensions/FilePanel/FilePanelPlugin.ts +0 -206
  165. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +0 -363
  166. package/src/extensions/LinkToolbar/LinkToolbarPlugin.ts +0 -380
  167. package/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.ts +0 -75
  168. package/src/extensions/Placeholder/PlaceholderPlugin.ts +0 -147
  169. package/types/src/comments/models/User.d.ts +0 -8
  170. package/types/src/editor/BlockNoteExtensions.d.ts +0 -43
  171. package/types/src/editor/managers/CollaborationManager.d.ts +0 -115
  172. package/types/src/editor/managers/ExtensionManager.d.ts +0 -68
  173. package/types/src/extensions/BlockChange/BlockChangePlugin.d.ts +0 -15
  174. package/types/src/extensions/Collaboration/CursorPlugin.d.ts +0 -37
  175. package/types/src/extensions/Collaboration/ForkYDocPlugin.d.ts +0 -41
  176. package/types/src/extensions/Collaboration/SyncPlugin.d.ts +0 -7
  177. package/types/src/extensions/Collaboration/UndoPlugin.d.ts +0 -9
  178. package/types/src/extensions/Collaboration/schemaMigration/SchemaMigrationPlugin.d.ts +0 -7
  179. package/types/src/extensions/Comments/CommentsPlugin.d.ts +0 -66
  180. package/types/src/extensions/FilePanel/FilePanelPlugin.d.ts +0 -31
  181. package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +0 -41
  182. package/types/src/extensions/LinkToolbar/LinkToolbarPlugin.d.ts +0 -42
  183. package/types/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.d.ts +0 -5
  184. package/types/src/extensions/Placeholder/PlaceholderPlugin.d.ts +0 -6
  185. package/types/src/extensions/ShowSelection/ShowSelectionPlugin.d.ts +0 -15
  186. package/types/src/extensions/SuggestionMenu/SuggestionPlugin.d.ts +0 -31
  187. package/types/src/extensions/TrailingNode/TrailingNodeExtension.d.ts +0 -13
  188. /package/src/{extensions/Comments/CommentMark.ts → comments/mark.ts} +0 -0
  189. /package/src/extensions/{HardBreak → tiptap-extensions/HardBreak}/HardBreak.ts +0 -0
  190. /package/src/extensions/{Suggestions → tiptap-extensions/Suggestions}/SuggestionMarks.ts +0 -0
  191. /package/src/extensions/{TextAlignment → tiptap-extensions/TextAlignment}/TextAlignmentExtension.ts +0 -0
  192. /package/src/extensions/{UniqueID → tiptap-extensions/UniqueID}/UniqueID.ts +0 -0
  193. /package/types/src/{extensions/Comments/CommentMark.d.ts → comments/mark.d.ts} +0 -0
  194. /package/types/src/{extensions/Collaboration/ForkYDocPlugin.test.d.ts → editor/BlockNoteExtension.test.d.ts} +0 -0
  195. /package/types/src/extensions/{BackgroundColor → tiptap-extensions/BackgroundColor}/BackgroundColorExtension.d.ts +0 -0
  196. /package/types/src/extensions/{HardBreak → tiptap-extensions/HardBreak}/HardBreak.d.ts +0 -0
  197. /package/types/src/extensions/{Suggestions → tiptap-extensions/Suggestions}/SuggestionMarks.d.ts +0 -0
  198. /package/types/src/extensions/{TextAlignment → tiptap-extensions/TextAlignment}/TextAlignmentExtension.d.ts +0 -0
  199. /package/types/src/extensions/{TextColor → tiptap-extensions/TextColor}/TextColorExtension.d.ts +0 -0
  200. /package/types/src/extensions/{UniqueID → tiptap-extensions/UniqueID}/UniqueID.d.ts +0 -0
@@ -2,6 +2,7 @@ import { expect, it } from "vitest";
2
2
  import * as Y from "yjs";
3
3
  import { Awareness } from "y-protocols/awareness";
4
4
  import { BlockNoteEditor } from "../../index.js";
5
+ import { ForkYDocExtension } from "./ForkYDoc.js";
5
6
 
6
7
  /**
7
8
  * @vitest-environment jsdom
@@ -36,7 +37,7 @@ it("can fork a document", async () => {
36
37
  "__snapshots__/fork-yjs-snap-editor.json",
37
38
  );
38
39
 
39
- editor.forkYDocPlugin!.fork();
40
+ editor.getExtension(ForkYDocExtension)!.fork();
40
41
 
41
42
  editor.replaceBlocks(editor.document, [
42
43
  {
@@ -83,7 +84,7 @@ it("can merge a document", async () => {
83
84
  "__snapshots__/fork-yjs-snap-editor.json",
84
85
  );
85
86
 
86
- editor.forkYDocPlugin!.fork();
87
+ editor.getExtension(ForkYDocExtension)!.fork();
87
88
 
88
89
  editor.replaceBlocks(editor.document, [
89
90
  {
@@ -99,7 +100,7 @@ it("can merge a document", async () => {
99
100
  "__snapshots__/fork-yjs-snap-editor-forked.json",
100
101
  );
101
102
 
102
- editor.forkYDocPlugin!.merge({ keepChanges: false });
103
+ editor.getExtension(ForkYDocExtension)!.merge({ keepChanges: false });
103
104
 
104
105
  await expect(fragment.toJSON()).toMatchFileSnapshot(
105
106
  "__snapshots__/fork-yjs-snap.html",
@@ -139,7 +140,7 @@ it("can fork an keep the changes to the original document", async () => {
139
140
  "__snapshots__/fork-yjs-snap-editor.json",
140
141
  );
141
142
 
142
- editor.forkYDocPlugin!.fork();
143
+ editor.getExtension(ForkYDocExtension)!.fork();
143
144
 
144
145
  editor.replaceBlocks(editor.document, [
145
146
  {
@@ -155,7 +156,7 @@ it("can fork an keep the changes to the original document", async () => {
155
156
  "__snapshots__/fork-yjs-snap-editor-forked.json",
156
157
  );
157
158
 
158
- editor.forkYDocPlugin!.merge({ keepChanges: true });
159
+ editor.getExtension(ForkYDocExtension)!.merge({ keepChanges: true });
159
160
 
160
161
  await expect(fragment.toJSON()).toMatchFileSnapshot(
161
162
  "__snapshots__/fork-yjs-snap-forked.html",
@@ -0,0 +1,158 @@
1
+ import { yUndoPluginKey } from "y-prosemirror";
2
+ import * as Y from "yjs";
3
+ import {
4
+ createExtension,
5
+ createStore,
6
+ ExtensionOptions,
7
+ } from "../../editor/BlockNoteExtension.js";
8
+ import { YCursorExtension } from "./YCursorPlugin.js";
9
+ import { YSyncExtension } from "./YSync.js";
10
+ import { YUndoExtension } from "./YUndo.js";
11
+ import { BlockNoteEditorOptions } from "../../editor/BlockNoteEditor.js";
12
+
13
+ /**
14
+ * To find a fragment in another ydoc, we need to search for it.
15
+ */
16
+ function findTypeInOtherYdoc<T extends Y.AbstractType<any>>(
17
+ ytype: T,
18
+ otherYdoc: Y.Doc,
19
+ ): T {
20
+ const ydoc = ytype.doc!;
21
+ if (ytype._item === null) {
22
+ /**
23
+ * If is a root type, we need to find the root key in the original ydoc
24
+ * and use it to get the type in the other ydoc.
25
+ */
26
+ const rootKey = Array.from(ydoc.share.keys()).find(
27
+ (key) => ydoc.share.get(key) === ytype,
28
+ );
29
+ if (rootKey == null) {
30
+ throw new Error("type does not exist in other ydoc");
31
+ }
32
+ return otherYdoc.get(rootKey, ytype.constructor as new () => T) as T;
33
+ } else {
34
+ /**
35
+ * If it is a sub type, we use the item id to find the history type.
36
+ */
37
+ const ytypeItem = ytype._item;
38
+ const otherStructs = otherYdoc.store.clients.get(ytypeItem.id.client) ?? [];
39
+ const itemIndex = Y.findIndexSS(otherStructs, ytypeItem.id.clock);
40
+ const otherItem = otherStructs[itemIndex] as Y.Item;
41
+ const otherContent = otherItem.content as Y.ContentType;
42
+ return otherContent.type as T;
43
+ }
44
+ }
45
+
46
+ export const ForkYDocExtension = createExtension(
47
+ ({
48
+ editor,
49
+ options,
50
+ }: ExtensionOptions<
51
+ NonNullable<BlockNoteEditorOptions<any, any, any>["collaboration"]>
52
+ >) => {
53
+ let forkedState:
54
+ | {
55
+ originalFragment: Y.XmlFragment;
56
+ undoStack: Y.UndoManager["undoStack"];
57
+ forkedFragment: Y.XmlFragment;
58
+ }
59
+ | undefined = undefined;
60
+
61
+ const store = createStore({ isForked: false });
62
+
63
+ return {
64
+ key: "yForkDoc",
65
+ store,
66
+ /**
67
+ * Fork the Y.js document from syncing to the remote,
68
+ * allowing modifications to the document without affecting the remote.
69
+ * These changes can later be rolled back or applied to the remote.
70
+ */
71
+ fork() {
72
+ if (forkedState) {
73
+ return;
74
+ }
75
+
76
+ const originalFragment = options.fragment;
77
+
78
+ if (!originalFragment) {
79
+ throw new Error("No fragment to fork from");
80
+ }
81
+
82
+ const doc = new Y.Doc();
83
+ // Copy the original document to a new Yjs document
84
+ Y.applyUpdate(doc, Y.encodeStateAsUpdate(originalFragment.doc!));
85
+
86
+ // Find the forked fragment in the new Yjs document
87
+ const forkedFragment = findTypeInOtherYdoc(originalFragment, doc);
88
+
89
+ forkedState = {
90
+ undoStack: yUndoPluginKey.getState(editor.prosemirrorState)!
91
+ .undoManager.undoStack,
92
+ originalFragment,
93
+ forkedFragment,
94
+ };
95
+
96
+ // Need to reset all the yjs plugins
97
+ editor.unregisterExtension([
98
+ YUndoExtension,
99
+ YCursorExtension,
100
+ YSyncExtension,
101
+ ]);
102
+ const newOptions = {
103
+ ...options,
104
+ fragment: forkedFragment,
105
+ };
106
+ // Register them again, based on the new forked fragment
107
+ editor.registerExtension([
108
+ YSyncExtension(newOptions),
109
+ // No need to register the cursor plugin again, it's a local fork
110
+ YUndoExtension({}),
111
+ ]);
112
+
113
+ // Tell the store that the editor is now forked
114
+ store.setState({ isForked: true });
115
+ },
116
+
117
+ /**
118
+ * Resume syncing the Y.js document to the remote
119
+ * If `keepChanges` is true, any changes that have been made to the forked document will be applied to the original document.
120
+ * Otherwise, the original document will be restored and the changes will be discarded.
121
+ */
122
+ merge({ keepChanges }: { keepChanges: boolean }) {
123
+ if (!forkedState) {
124
+ return;
125
+ }
126
+ // Remove the forked fragment's plugins
127
+ editor.unregisterExtension(["ySync", "yCursor", "yUndo"]);
128
+
129
+ const { originalFragment, forkedFragment, undoStack } = forkedState;
130
+ // Register the plugins again, based on the original fragment (which is still in the original options)
131
+ editor.registerExtension([
132
+ YSyncExtension(options),
133
+ YCursorExtension(options),
134
+ YUndoExtension({}),
135
+ ]);
136
+
137
+ // Reset the undo stack to the original undo stack
138
+ yUndoPluginKey.getState(
139
+ editor.prosemirrorState,
140
+ )!.undoManager.undoStack = undoStack;
141
+
142
+ if (keepChanges) {
143
+ // Apply any changes that have been made to the fork, onto the original doc
144
+ const update = Y.encodeStateAsUpdate(
145
+ forkedFragment.doc!,
146
+ Y.encodeStateVector(originalFragment.doc!),
147
+ );
148
+ // Applying this change will add to the undo stack, allowing it to be undone normally
149
+ Y.applyUpdate(originalFragment.doc!, update, editor);
150
+ }
151
+ // Reset the forked state
152
+ forkedState = undefined;
153
+ // Tell the store that the editor is no longer forked
154
+ store.setState({ isForked: false });
155
+ },
156
+ } as const;
157
+ },
158
+ );
@@ -0,0 +1,183 @@
1
+ import { defaultSelectionBuilder, yCursorPlugin } from "y-prosemirror";
2
+ import {
3
+ createExtension,
4
+ ExtensionOptions,
5
+ } from "../../editor/BlockNoteExtension.js";
6
+ import { BlockNoteEditorOptions } from "../../editor/BlockNoteEditor.js";
7
+
8
+ export type CollaborationUser = {
9
+ name: string;
10
+ color: string;
11
+ [key: string]: string;
12
+ };
13
+
14
+ /**
15
+ * Determine whether the foreground color should be white or black based on a provided background color
16
+ * Inspired by: https://stackoverflow.com/a/3943023
17
+ */
18
+ function isDarkColor(bgColor: string): boolean {
19
+ const color = bgColor.charAt(0) === "#" ? bgColor.substring(1, 7) : bgColor;
20
+ const r = parseInt(color.substring(0, 2), 16); // hexToR
21
+ const g = parseInt(color.substring(2, 4), 16); // hexToG
22
+ const b = parseInt(color.substring(4, 6), 16); // hexToB
23
+ const uicolors = [r / 255, g / 255, b / 255];
24
+ const c = uicolors.map((col) => {
25
+ if (col <= 0.03928) {
26
+ return col / 12.92;
27
+ }
28
+ return Math.pow((col + 0.055) / 1.055, 2.4);
29
+ });
30
+ const L = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
31
+ return L <= 0.179;
32
+ }
33
+
34
+ function defaultCursorRender(user: CollaborationUser) {
35
+ const cursorElement = document.createElement("span");
36
+
37
+ cursorElement.classList.add("bn-collaboration-cursor__base");
38
+
39
+ const caretElement = document.createElement("span");
40
+ caretElement.setAttribute("contentedEditable", "false");
41
+ caretElement.classList.add("bn-collaboration-cursor__caret");
42
+ caretElement.setAttribute(
43
+ "style",
44
+ `background-color: ${user.color}; color: ${
45
+ isDarkColor(user.color) ? "white" : "black"
46
+ }`,
47
+ );
48
+
49
+ const labelElement = document.createElement("span");
50
+
51
+ labelElement.classList.add("bn-collaboration-cursor__label");
52
+ labelElement.setAttribute(
53
+ "style",
54
+ `background-color: ${user.color}; color: ${
55
+ isDarkColor(user.color) ? "white" : "black"
56
+ }`,
57
+ );
58
+ labelElement.insertBefore(document.createTextNode(user.name), null);
59
+
60
+ caretElement.insertBefore(labelElement, null);
61
+
62
+ cursorElement.insertBefore(document.createTextNode("\u2060"), null); // Non-breaking space
63
+ cursorElement.insertBefore(caretElement, null);
64
+ cursorElement.insertBefore(document.createTextNode("\u2060"), null); // Non-breaking space
65
+
66
+ return cursorElement;
67
+ }
68
+
69
+ export const YCursorExtension = createExtension(
70
+ ({
71
+ options,
72
+ }: ExtensionOptions<
73
+ NonNullable<BlockNoteEditorOptions<any, any, any>["collaboration"]>
74
+ >) => {
75
+ const recentlyUpdatedCursors = new Map();
76
+
77
+ if (
78
+ options.provider &&
79
+ "awareness" in options.provider &&
80
+ typeof options.provider.awareness === "object"
81
+ ) {
82
+ if (
83
+ "setLocalStateField" in options.provider.awareness &&
84
+ typeof options.provider.awareness.setLocalStateField === "function"
85
+ ) {
86
+ options.provider.awareness.setLocalStateField("user", options.user);
87
+ }
88
+ if (
89
+ "on" in options.provider.awareness &&
90
+ typeof options.provider.awareness.on === "function"
91
+ ) {
92
+ if (options.showCursorLabels !== "always") {
93
+ options.provider.awareness.on(
94
+ "change",
95
+ ({
96
+ updated,
97
+ }: {
98
+ added: Array<number>;
99
+ updated: Array<number>;
100
+ removed: Array<number>;
101
+ }) => {
102
+ for (const clientID of updated) {
103
+ const cursor = recentlyUpdatedCursors.get(clientID);
104
+
105
+ if (cursor) {
106
+ cursor.element.setAttribute("data-active", "");
107
+
108
+ if (cursor.hideTimeout) {
109
+ clearTimeout(cursor.hideTimeout);
110
+ }
111
+
112
+ recentlyUpdatedCursors.set(clientID, {
113
+ element: cursor.element,
114
+ hideTimeout: setTimeout(() => {
115
+ cursor.element.removeAttribute("data-active");
116
+ }, 2000),
117
+ });
118
+ }
119
+ }
120
+ },
121
+ );
122
+ }
123
+ }
124
+ }
125
+
126
+ return {
127
+ key: "yCursor",
128
+ prosemirrorPlugins: [
129
+ yCursorPlugin(options.provider.awareness, {
130
+ selectionBuilder: defaultSelectionBuilder,
131
+ cursorBuilder(user: CollaborationUser, clientID: number) {
132
+ let cursorData = recentlyUpdatedCursors.get(clientID);
133
+
134
+ if (!cursorData) {
135
+ const cursorElement = (
136
+ options.renderCursor ?? defaultCursorRender
137
+ )(user);
138
+
139
+ if (options.showCursorLabels !== "always") {
140
+ cursorElement.addEventListener("mouseenter", () => {
141
+ const cursor = recentlyUpdatedCursors.get(clientID)!;
142
+ cursor.element.setAttribute("data-active", "");
143
+
144
+ if (cursor.hideTimeout) {
145
+ clearTimeout(cursor.hideTimeout);
146
+ recentlyUpdatedCursors.set(clientID, {
147
+ element: cursor.element,
148
+ hideTimeout: undefined,
149
+ });
150
+ }
151
+ });
152
+
153
+ cursorElement.addEventListener("mouseleave", () => {
154
+ const cursor = recentlyUpdatedCursors.get(clientID)!;
155
+
156
+ recentlyUpdatedCursors.set(clientID, {
157
+ element: cursor.element,
158
+ hideTimeout: setTimeout(() => {
159
+ cursor.element.removeAttribute("data-active");
160
+ }, 2000),
161
+ });
162
+ });
163
+ }
164
+
165
+ cursorData = {
166
+ element: cursorElement,
167
+ hideTimeout: undefined,
168
+ };
169
+
170
+ recentlyUpdatedCursors.set(clientID, cursorData);
171
+ }
172
+
173
+ return cursorData.element;
174
+ },
175
+ }),
176
+ ],
177
+ dependsOn: ["ySync"],
178
+ updateUser(user: { name: string; color: string; [key: string]: string }) {
179
+ options.provider.awareness.setLocalStateField("user", user);
180
+ },
181
+ } as const;
182
+ },
183
+ );
@@ -0,0 +1,16 @@
1
+ import { ySyncPlugin } from "y-prosemirror";
2
+ import { XmlFragment } from "yjs";
3
+ import {
4
+ ExtensionOptions,
5
+ createExtension,
6
+ } from "../../editor/BlockNoteExtension.js";
7
+
8
+ export const YSyncExtension = createExtension(
9
+ ({ options }: ExtensionOptions<{ fragment: XmlFragment }>) => {
10
+ return {
11
+ key: "ySync",
12
+ prosemirrorPlugins: [ySyncPlugin(options.fragment)],
13
+ runsBefore: ["default"],
14
+ } as const;
15
+ },
16
+ );
@@ -0,0 +1,12 @@
1
+ import { redoCommand, undoCommand, yUndoPlugin } from "y-prosemirror";
2
+ import { createExtension } from "../../editor/BlockNoteExtension.js";
3
+
4
+ export const YUndoExtension = createExtension(({ editor }) => {
5
+ return {
6
+ key: "yUndo",
7
+ prosemirrorPlugins: [yUndoPlugin({ trackedOrigins: [editor] })],
8
+ dependsOn: ["yCursor", "ySync"],
9
+ undoCommand: undoCommand,
10
+ redoCommand: redoCommand,
11
+ } as const;
12
+ });
@@ -0,0 +1,59 @@
1
+ import { Plugin, PluginKey } from "@tiptap/pm/state";
2
+ import * as Y from "yjs";
3
+
4
+ import {
5
+ createExtension,
6
+ ExtensionOptions,
7
+ } from "../../../editor/BlockNoteExtension.js";
8
+ import migrationRules from "./migrationRules/index.js";
9
+
10
+ // This plugin allows us to update collaboration YDocs whenever BlockNote's
11
+ // underlying ProseMirror schema changes. The plugin reads the current Yjs
12
+ // fragment and dispatches additional transactions to the ProseMirror state, in
13
+ // case things are found in the fragment that don't adhere to the editor schema
14
+ // and need to be fixed. These fixes are defined as `MigrationRule`s within the
15
+ // `migrationRules` directory.
16
+ export const SchemaMigration = createExtension(
17
+ ({ options }: ExtensionOptions<{ fragment: Y.XmlFragment }>) => {
18
+ let migrationDone = false;
19
+ const pluginKey = new PluginKey("schemaMigration");
20
+
21
+ return {
22
+ key: "schemaMigration",
23
+ prosemirrorPlugins: [
24
+ new Plugin({
25
+ key: pluginKey,
26
+ appendTransaction: (transactions, _oldState, newState) => {
27
+ if (migrationDone) {
28
+ return undefined;
29
+ }
30
+
31
+ if (
32
+ // If any of the transactions are not due to a yjs sync, we don't need to run the migration
33
+ !transactions.some((tr) => tr.getMeta("y-sync$")) ||
34
+ // If none of the transactions result in a document change, we don't need to run the migration
35
+ transactions.every((tr) => !tr.docChanged) ||
36
+ // If the fragment is still empty, we can't run the migration (since it has not yet been applied to the Y.Doc)
37
+ !options.fragment.firstChild
38
+ ) {
39
+ return undefined;
40
+ }
41
+
42
+ const tr = newState.tr;
43
+ for (const migrationRule of migrationRules) {
44
+ migrationRule(options.fragment, tr);
45
+ }
46
+
47
+ migrationDone = true;
48
+
49
+ if (!tr.docChanged) {
50
+ return undefined;
51
+ }
52
+
53
+ return tr;
54
+ },
55
+ }),
56
+ ],
57
+ } as const;
58
+ },
59
+ );
@@ -0,0 +1,26 @@
1
+ import { dropCursor } from "prosemirror-dropcursor";
2
+ import {
3
+ createExtension,
4
+ ExtensionOptions,
5
+ } from "../../editor/BlockNoteExtension.js";
6
+ import { BlockNoteEditorOptions } from "../../editor/BlockNoteEditor.js";
7
+
8
+ export const DropCursorExtension = createExtension(
9
+ ({
10
+ editor,
11
+ options,
12
+ }: ExtensionOptions<
13
+ Pick<BlockNoteEditorOptions<any, any, any>, "dropCursor">
14
+ >) => {
15
+ return {
16
+ key: "dropCursor",
17
+ prosemirrorPlugins: [
18
+ (options.dropCursor ?? dropCursor)({
19
+ width: 5,
20
+ color: "#ddeeff",
21
+ editor: editor,
22
+ }),
23
+ ],
24
+ } as const;
25
+ },
26
+ );
@@ -0,0 +1,41 @@
1
+ import {
2
+ createExtension,
3
+ createStore,
4
+ } from "../../editor/BlockNoteExtension.js";
5
+
6
+ export const FilePanelExtension = createExtension(({ editor }) => {
7
+ const store = createStore<string | undefined>(undefined);
8
+
9
+ function closeMenu() {
10
+ store.setState(undefined);
11
+ }
12
+
13
+ return {
14
+ key: "filePanel",
15
+ store,
16
+ mount({ signal }) {
17
+ // Reset the menu when the document changes.
18
+ const unsubscribeOnChange = editor.onChange(
19
+ closeMenu,
20
+ // Don't trigger if the changes are caused by a remote user.
21
+ false,
22
+ );
23
+
24
+ // reset the menu when the selection changes
25
+ const unsubscribeOnSelectionChange = editor.onSelectionChange(
26
+ closeMenu,
27
+ // Don't trigger if the changes are caused by a remote user.
28
+ false,
29
+ );
30
+
31
+ signal.addEventListener("abort", () => {
32
+ unsubscribeOnChange();
33
+ unsubscribeOnSelectionChange();
34
+ });
35
+ },
36
+ closeMenu,
37
+ showMenu(blockId: string) {
38
+ store.setState(blockId);
39
+ },
40
+ } as const;
41
+ });
@@ -0,0 +1,119 @@
1
+ import { NodeSelection, TextSelection } from "prosemirror-state";
2
+
3
+ import {
4
+ createExtension,
5
+ createStore,
6
+ } from "../../editor/BlockNoteExtension.js";
7
+
8
+ export const FormattingToolbarExtension = createExtension(({ editor }) => {
9
+ const store = createStore(false);
10
+
11
+ const shouldShow = () => {
12
+ return editor.transact((tr) => {
13
+ // Don't show if the selection is empty, or is a text selection with no
14
+ // text.
15
+ if (tr.selection.empty) {
16
+ return false;
17
+ }
18
+
19
+ // Don't show if a block with inline content is selected.
20
+ if (
21
+ tr.selection instanceof NodeSelection &&
22
+ (tr.selection.node.type.spec.content === "inline*" ||
23
+ tr.selection.node.firstChild?.type.spec.content === "inline*")
24
+ ) {
25
+ return false;
26
+ }
27
+
28
+ // Don't show if the selection is a text selection but contains no text.
29
+ if (
30
+ tr.selection instanceof TextSelection &&
31
+ tr.doc.textBetween(tr.selection.from, tr.selection.to).length === 0
32
+ ) {
33
+ return false;
34
+ }
35
+
36
+ // Searches the content of the selection to see if it spans a node with a
37
+ // code spec.
38
+ let spansCode = false;
39
+ tr.selection.content().content.descendants((node) => {
40
+ if (node.type.spec.code) {
41
+ spansCode = true;
42
+ }
43
+ return !spansCode; // keep descending if we haven't found a code block
44
+ });
45
+
46
+ // Don't show if the selection spans a code block.
47
+ if (spansCode) {
48
+ return false;
49
+ }
50
+
51
+ // Show toolbar otherwise.
52
+ return true;
53
+ });
54
+ };
55
+
56
+ return {
57
+ key: "formattingToolbar",
58
+ store,
59
+ mount({ dom, signal }) {
60
+ /**
61
+ * We want to mimic the Notion behavior of not showing the toolbar while the user is holding down the mouse button (to create a selection)
62
+ */
63
+ let preventShowWhileMouseDown = false;
64
+
65
+ const unsubscribeOnChange = editor.onChange(() => {
66
+ if (preventShowWhileMouseDown) {
67
+ return;
68
+ }
69
+ // re-evaluate whether the toolbar should be shown
70
+ store.setState(shouldShow());
71
+ });
72
+ const unsubscribeOnSelectionChange = editor.onSelectionChange(() => {
73
+ if (preventShowWhileMouseDown) {
74
+ return;
75
+ }
76
+ // re-evaluate whether the toolbar should be shown
77
+ store.setState(shouldShow());
78
+ });
79
+
80
+ // To mimic Notion's behavior, we listen to the mouse down event to set the `preventShowWhileMouseDown` flag
81
+ dom.addEventListener(
82
+ "pointerdown",
83
+ () => {
84
+ preventShowWhileMouseDown = true;
85
+ store.setState(false);
86
+ },
87
+ { signal },
88
+ );
89
+ // To mimic Notion's behavior, we listen to the mouse up event to reset the `preventShowWhileMouseDown` flag and show the toolbar (if it should)
90
+ editor.prosemirrorView.root.addEventListener(
91
+ "pointerup",
92
+ () => {
93
+ preventShowWhileMouseDown = false;
94
+ // We only want to re-show the toolbar if the mouse made the selection
95
+ if (editor.isFocused()) {
96
+ store.setState(shouldShow());
97
+ }
98
+ },
99
+ { signal, capture: true },
100
+ );
101
+ // If the pointer gets cancelled, we don't want to be stuck in the `preventShowWhileMouseDown` state
102
+ dom.addEventListener(
103
+ "pointercancel",
104
+ () => {
105
+ preventShowWhileMouseDown = false;
106
+ },
107
+ {
108
+ signal,
109
+ capture: true,
110
+ },
111
+ );
112
+
113
+ signal.addEventListener("abort", () => {
114
+ unsubscribeOnChange();
115
+ unsubscribeOnSelectionChange();
116
+ });
117
+ },
118
+ } as const;
119
+ });
@@ -0,0 +1,11 @@
1
+ import { history, redo, undo } from "@tiptap/pm/history";
2
+ import { createExtension } from "../../editor/BlockNoteExtension.js";
3
+
4
+ export const HistoryExtension = createExtension(() => {
5
+ return {
6
+ key: "history",
7
+ prosemirrorPlugins: [history()],
8
+ undoCommand: undo,
9
+ redoCommand: redo,
10
+ } as const;
11
+ });