@blocknote/core 0.42.3 → 0.43.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-CbSavEwr.js +270 -0
  6. package/dist/BlockNoteSchema-CbSavEwr.js.map +1 -0
  7. package/dist/BlockNoteSchema-D8TyvlfU.cjs +2 -0
  8. package/dist/BlockNoteSchema-D8TyvlfU.cjs.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-BUhuMJrB.js +2096 -0
  18. package/dist/TrailingNode-BUhuMJrB.js.map +1 -0
  19. package/dist/TrailingNode-CaT_wbho.cjs +2 -0
  20. package/dist/TrailingNode-CaT_wbho.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 +2402 -5594
  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/{BlockNoteSchema-Bi-eeHal.js → defaultBlocks-BJtxTOM2.js} +991 -1047
  36. package/dist/defaultBlocks-BJtxTOM2.js.map +1 -0
  37. package/dist/defaultBlocks-BxFclIGP.cjs +6 -0
  38. package/dist/defaultBlocks-BxFclIGP.cjs.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
@@ -0,0 +1,514 @@
1
+ import {
2
+ InputRule,
3
+ inputRules as inputRulesPlugin,
4
+ } from "@handlewithcare/prosemirror-inputrules";
5
+ import {
6
+ AnyExtension as AnyTiptapExtension,
7
+ Extension as TiptapExtension,
8
+ } from "@tiptap/core";
9
+ import { keymap } from "@tiptap/pm/keymap";
10
+ import { Plugin } from "prosemirror-state";
11
+ import { updateBlockTr } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js";
12
+ import { getBlockInfoFromTransaction } from "../../../api/getBlockInfoFromPos.js";
13
+ import { sortByDependencies } from "../../../util/topo-sort.js";
14
+ import type {
15
+ BlockNoteEditor,
16
+ BlockNoteEditorOptions,
17
+ } from "../../BlockNoteEditor.js";
18
+ import type {
19
+ Extension,
20
+ ExtensionFactoryInstance,
21
+ ExtensionFactory,
22
+ } from "../../BlockNoteExtension.js";
23
+ import { originalFactorySymbol } from "./symbol.js";
24
+ import {
25
+ getDefaultExtensions,
26
+ getDefaultTiptapExtensions,
27
+ } from "./extensions.js";
28
+
29
+ export class ExtensionManager {
30
+ /**
31
+ * A set of extension keys which are disabled by the options
32
+ */
33
+ private disabledExtensions = new Set<string>();
34
+ /**
35
+ * A list of all the extensions that are registered to the editor
36
+ */
37
+ private extensions: Extension[] = [];
38
+ /**
39
+ * A map of all the abort controllers for each extension that has an init method defined
40
+ */
41
+ private abortMap = new Map<Extension, AbortController>();
42
+ /**
43
+ * A map of all the extension factories that are registered to the editor
44
+ */
45
+ private extensionFactories = new Map<ExtensionFactory, Extension>();
46
+ /**
47
+ * Because a single blocknote extension can both have it's own prosemirror plugins & additional generated ones (e.g. keymap & input rules plugins)
48
+ * We need to keep track of all the plugins for each extension, so that we can remove them when the extension is unregistered
49
+ */
50
+ private extensionPlugins: Map<Extension, Plugin[]> = new Map();
51
+
52
+ constructor(
53
+ private editor: BlockNoteEditor<any, any, any>,
54
+ private options: BlockNoteEditorOptions<any, any, any>,
55
+ ) {
56
+ /**
57
+ * When the editor is first mounted, we need to initialize all the extensions
58
+ */
59
+ editor.onMount(() => {
60
+ for (const extension of this.extensions) {
61
+ // If the extension has an init function, we can initialize it, otherwise, it is already added to the editor
62
+ if (extension.mount) {
63
+ // We create an abort controller for each extension, so that we can abort the extension when the editor is unmounted
64
+ const abortController = new window.AbortController();
65
+ const unmountCallback = extension.mount({
66
+ dom: editor.prosemirrorView.dom,
67
+ root: editor.prosemirrorView.root,
68
+ signal: abortController.signal,
69
+ });
70
+ // If the extension returns a method to unmount it, we can register it to be called when the abort controller is aborted
71
+ if (unmountCallback) {
72
+ abortController.signal.addEventListener("abort", () => {
73
+ unmountCallback();
74
+ });
75
+ }
76
+ // Keep track of the abort controller for each extension, so that we can abort it when the editor is unmounted
77
+ this.abortMap.set(extension, abortController);
78
+ }
79
+ }
80
+ });
81
+
82
+ /**
83
+ * When the editor is unmounted, we need to abort all the extensions' abort controllers
84
+ */
85
+ editor.onUnmount(() => {
86
+ for (const [extension, abortController] of this.abortMap.entries()) {
87
+ // No longer track the abort controller for this extension
88
+ this.abortMap.delete(extension);
89
+ // Abort each extension's abort controller
90
+ abortController.abort();
91
+ }
92
+ });
93
+
94
+ // TODO do disabled extensions need to be only for editor base extensions? Or all of them?
95
+ this.disabledExtensions = new Set(options.disableExtensions || []);
96
+
97
+ // Add the default extensions
98
+ for (const extension of getDefaultExtensions(this.editor, this.options)) {
99
+ this.addExtension(extension);
100
+ }
101
+
102
+ // Add the extensions from the options
103
+ for (const extension of this.options.extensions ?? []) {
104
+ this.addExtension(extension);
105
+ }
106
+
107
+ // Add the extensions from blocks specs
108
+ for (const block of Object.values(this.editor.schema.blockSpecs)) {
109
+ for (const extension of block.extensions ?? []) {
110
+ this.addExtension(extension);
111
+ }
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Register one or more extensions to the editor after the editor is initialized.
117
+ *
118
+ * This allows users to switch on & off extensions "at runtime".
119
+ */
120
+ public registerExtension(
121
+ extension:
122
+ | Extension
123
+ | ExtensionFactoryInstance
124
+ | (Extension | ExtensionFactoryInstance)[],
125
+ ): void {
126
+ const extensions = ([] as (Extension | ExtensionFactoryInstance)[])
127
+ .concat(extension)
128
+ .filter(Boolean) as (Extension | ExtensionFactoryInstance)[];
129
+
130
+ if (!extensions.length) {
131
+ // eslint-disable-next-line no-console
132
+ console.warn(`No extensions found to register`, extension);
133
+ return;
134
+ }
135
+
136
+ const registeredExtensions = extensions
137
+ .map((extension) => this.addExtension(extension))
138
+ .filter(Boolean) as Extension[];
139
+
140
+ const pluginsToAdd = new Set<Plugin>();
141
+ for (const extension of registeredExtensions) {
142
+ if (extension?.tiptapExtensions) {
143
+ // This is necessary because this can only switch out prosemirror plugins at runtime,
144
+ // it can't switch out Tiptap extensions since that can have more widespread effects (since a Tiptap extension can even add/remove to the schema).
145
+
146
+ // eslint-disable-next-line no-console
147
+ console.warn(
148
+ `Extension ${extension.key} has tiptap extensions, but these cannot be changed after initializing the editor. Please separate the extension into multiple extensions if you want to add them, or re-initialize the editor.`,
149
+ extension,
150
+ );
151
+ }
152
+
153
+ if (extension?.inputRules?.length) {
154
+ // This is necessary because input rules are defined in a single prosemirror plugin which cannot be re-initialized.
155
+ // eslint-disable-next-line no-console
156
+ console.warn(
157
+ `Extension ${extension.key} has input rules, but these cannot be changed after initializing the editor. Please separate the extension into multiple extensions if you want to add them, or re-initialize the editor.`,
158
+ extension,
159
+ );
160
+ }
161
+
162
+ this.getProsemirrorPluginsFromExtension(extension).plugins.forEach(
163
+ (plugin) => {
164
+ pluginsToAdd.add(plugin);
165
+ },
166
+ );
167
+ }
168
+
169
+ // TODO there isn't a great way to do sorting right now. This is something that should be improved in the future.
170
+ // So, we just append to the end of the list for now.
171
+ this.updatePlugins((plugins) => [...plugins, ...pluginsToAdd]);
172
+ }
173
+
174
+ /**
175
+ * Register an extension to the editor
176
+ * @param extension - The extension to register
177
+ * @returns The extension instance
178
+ */
179
+ private addExtension(
180
+ extension: Extension | ExtensionFactoryInstance,
181
+ ): Extension | undefined {
182
+ let instance: Extension;
183
+ if (typeof extension === "function") {
184
+ instance = extension({ editor: this.editor });
185
+ } else {
186
+ instance = extension;
187
+ }
188
+
189
+ if (!instance || this.disabledExtensions.has(instance.key)) {
190
+ return undefined as any;
191
+ }
192
+
193
+ // Now that we know that the extension is not disabled, we can add it to the extension factories
194
+ if (typeof extension === "function") {
195
+ const originalFactory = (instance as any)[originalFactorySymbol] as (
196
+ ...args: any[]
197
+ ) => ExtensionFactoryInstance;
198
+
199
+ if (typeof originalFactory === "function") {
200
+ this.extensionFactories.set(originalFactory, instance);
201
+ }
202
+ }
203
+
204
+ this.extensions.push(instance);
205
+
206
+ return instance as any;
207
+ }
208
+
209
+ /**
210
+ * Resolve an extension or a list of extensions into a list of extension instances
211
+ * @param toResolve - The extension or list of extensions to resolve
212
+ * @returns A list of extension instances
213
+ */
214
+ private resolveExtensions(
215
+ toResolve:
216
+ | undefined
217
+ | string
218
+ | Extension
219
+ | ExtensionFactory
220
+ | (Extension | ExtensionFactory | string | undefined)[],
221
+ ): Extension[] {
222
+ const extensions = [] as Extension[];
223
+ if (typeof toResolve === "function") {
224
+ const instance = this.extensionFactories.get(toResolve);
225
+ if (instance) {
226
+ extensions.push(instance);
227
+ }
228
+ } else if (Array.isArray(toResolve)) {
229
+ for (const extension of toResolve) {
230
+ extensions.push(...this.resolveExtensions(extension));
231
+ }
232
+ } else if (typeof toResolve === "object" && "key" in toResolve) {
233
+ extensions.push(toResolve);
234
+ } else if (typeof toResolve === "string") {
235
+ const instance = this.extensions.find((e) => e.key === toResolve);
236
+ if (instance) {
237
+ extensions.push(instance);
238
+ }
239
+ }
240
+ return extensions;
241
+ }
242
+
243
+ /**
244
+ * Unregister an extension from the editor
245
+ * @param toUnregister - The extension to unregister
246
+ * @returns void
247
+ */
248
+ public unregisterExtension(
249
+ toUnregister:
250
+ | undefined
251
+ | string
252
+ | Extension
253
+ | ExtensionFactory
254
+ | (Extension | ExtensionFactory | string | undefined)[],
255
+ ): void {
256
+ const extensions = this.resolveExtensions(toUnregister);
257
+
258
+ if (!extensions.length) {
259
+ // eslint-disable-next-line no-console
260
+ console.warn(`No extensions found to unregister`, toUnregister);
261
+ return;
262
+ }
263
+ let didWarn = false;
264
+
265
+ const pluginsToRemove = new Set<Plugin>();
266
+ for (const extension of extensions) {
267
+ this.extensions = this.extensions.filter((e) => e !== extension);
268
+ this.extensionFactories.forEach((instance, factory) => {
269
+ if (instance === extension) {
270
+ this.extensionFactories.delete(factory);
271
+ }
272
+ });
273
+ this.abortMap.get(extension)?.abort();
274
+ this.abortMap.delete(extension);
275
+
276
+ const plugins = this.extensionPlugins.get(extension);
277
+ plugins?.forEach((plugin) => {
278
+ pluginsToRemove.add(plugin);
279
+ });
280
+ this.extensionPlugins.delete(extension);
281
+
282
+ if (extension.tiptapExtensions && !didWarn) {
283
+ didWarn = true;
284
+ // eslint-disable-next-line no-console
285
+ console.warn(
286
+ `Extension ${extension.key} has tiptap extensions, but they will not be removed. Please separate the extension into multiple extensions if you want to remove them, or re-initialize the editor.`,
287
+ toUnregister,
288
+ );
289
+ }
290
+ }
291
+
292
+ this.updatePlugins((plugins) =>
293
+ plugins.filter((plugin) => !pluginsToRemove.has(plugin)),
294
+ );
295
+ }
296
+
297
+ /**
298
+ * Allows resetting the current prosemirror state's plugins
299
+ * @param update - A function that takes the current plugins and returns the new plugins
300
+ * @returns void
301
+ */
302
+ private updatePlugins(update: (plugins: Plugin[]) => Plugin[]): void {
303
+ const currentState = this.editor.prosemirrorState;
304
+
305
+ const state = currentState.reconfigure({
306
+ plugins: update(currentState.plugins.slice()),
307
+ });
308
+
309
+ this.editor.prosemirrorView.updateState(state);
310
+ }
311
+
312
+ /**
313
+ * Get all the extensions that are registered to the editor
314
+ */
315
+ public getTiptapExtensions(): AnyTiptapExtension[] {
316
+ // Start with the default tiptap extensions
317
+ const tiptapExtensions = getDefaultTiptapExtensions(
318
+ this.editor,
319
+ this.options,
320
+ );
321
+ // TODO filter out the default extensions via the disabledExtensions set?
322
+
323
+ const getPriority = sortByDependencies(this.extensions);
324
+
325
+ const inputRulesByPriority = new Map<number, InputRule[]>();
326
+ for (const extension of this.extensions) {
327
+ if (extension.tiptapExtensions) {
328
+ tiptapExtensions.push(...extension.tiptapExtensions);
329
+ }
330
+
331
+ const priority = getPriority(extension.key);
332
+
333
+ const { plugins: prosemirrorPlugins, inputRules } =
334
+ this.getProsemirrorPluginsFromExtension(extension);
335
+ // Sometimes a blocknote extension might need to make additional prosemirror plugins, so we generate them here
336
+ if (prosemirrorPlugins.length) {
337
+ tiptapExtensions.push(
338
+ TiptapExtension.create({
339
+ name: extension.key,
340
+ priority,
341
+ addProseMirrorPlugins: () => prosemirrorPlugins,
342
+ }),
343
+ );
344
+ }
345
+ if (inputRules.length) {
346
+ if (!inputRulesByPriority.has(priority)) {
347
+ inputRulesByPriority.set(priority, []);
348
+ }
349
+ inputRulesByPriority.get(priority)!.push(...inputRules);
350
+ }
351
+ }
352
+
353
+ // Collect all input rules into 1 extension to reduce conflicts
354
+ tiptapExtensions.push(
355
+ TiptapExtension.create({
356
+ name: "blocknote-input-rules",
357
+ addProseMirrorPlugins() {
358
+ const rules = [] as InputRule[];
359
+ Array.from(inputRulesByPriority.keys())
360
+ // We sort the rules by their priority (the key)
361
+ .sort()
362
+ .reverse()
363
+ .forEach((priority) => {
364
+ // Append in reverse priority order
365
+ rules.push(...inputRulesByPriority.get(priority)!);
366
+ });
367
+ return [inputRulesPlugin({ rules })];
368
+ },
369
+ }),
370
+ );
371
+
372
+ // Add any tiptap extensions from the `_tiptapOptions`
373
+ for (const extension of this.options._tiptapOptions?.extensions ?? []) {
374
+ tiptapExtensions.push(extension);
375
+ }
376
+
377
+ return tiptapExtensions;
378
+ }
379
+
380
+ /**
381
+ * This maps a blocknote extension into an array of Prosemirror plugins if it has any of the following:
382
+ * - plugins
383
+ * - keyboard shortcuts
384
+ * - input rules
385
+ */
386
+ private getProsemirrorPluginsFromExtension(extension: Extension): {
387
+ plugins: Plugin[];
388
+ inputRules: InputRule[];
389
+ } {
390
+ const plugins: Plugin[] = [...(extension.prosemirrorPlugins ?? [])];
391
+ const inputRules: InputRule[] = [];
392
+ if (
393
+ !extension.prosemirrorPlugins?.length &&
394
+ !Object.keys(extension.keyboardShortcuts || {}).length &&
395
+ !extension.inputRules?.length
396
+ ) {
397
+ // We can bail out early if the extension has no features to add to the tiptap editor
398
+ return { plugins, inputRules };
399
+ }
400
+
401
+ this.extensionPlugins.set(extension, plugins);
402
+
403
+ if (extension.inputRules?.length) {
404
+ inputRules.push(
405
+ ...extension.inputRules.map((inputRule) => {
406
+ return new InputRule(inputRule.find, (state, match, start, end) => {
407
+ const replaceWith = inputRule.replace({
408
+ match,
409
+ range: { from: start, to: end },
410
+ editor: this.editor,
411
+ });
412
+ if (replaceWith) {
413
+ const cursorPosition = this.editor.getTextCursorPosition();
414
+
415
+ if (
416
+ this.editor.schema.blockSchema[cursorPosition.block.type]
417
+ .content !== "inline"
418
+ ) {
419
+ return null;
420
+ }
421
+
422
+ const blockInfo = getBlockInfoFromTransaction(state.tr);
423
+ const tr = state.tr.deleteRange(start, end);
424
+
425
+ updateBlockTr(tr, blockInfo.bnBlock.beforePos, replaceWith);
426
+ return tr;
427
+ }
428
+ return null;
429
+ });
430
+ }),
431
+ );
432
+ }
433
+
434
+ if (Object.keys(extension.keyboardShortcuts || {}).length) {
435
+ plugins.push(
436
+ keymap(
437
+ Object.fromEntries(
438
+ Object.entries(extension.keyboardShortcuts!).map(([key, value]) => [
439
+ key,
440
+ () => value({ editor: this.editor }),
441
+ ]),
442
+ ),
443
+ ),
444
+ );
445
+ }
446
+
447
+ return { plugins, inputRules };
448
+ }
449
+
450
+ /**
451
+ * Get all extensions
452
+ */
453
+ public getExtensions(): Map<string, Extension> {
454
+ return new Map(
455
+ this.extensions.map((extension) => [extension.key, extension]),
456
+ );
457
+ }
458
+
459
+ /**
460
+ * Get a specific extension by it's instance
461
+ */
462
+ public getExtension<
463
+ const Ext extends Extension | ExtensionFactory = Extension,
464
+ >(
465
+ extension: string,
466
+ ):
467
+ | (Ext extends Extension
468
+ ? Ext
469
+ : Ext extends ExtensionFactory
470
+ ? ReturnType<ReturnType<Ext>>
471
+ : never)
472
+ | undefined;
473
+ public getExtension<const T extends ExtensionFactory>(
474
+ extension: T,
475
+ ): ReturnType<ReturnType<T>> | undefined;
476
+ public getExtension<const T extends ExtensionFactory | string = string>(
477
+ extension: T,
478
+ ):
479
+ | (T extends ExtensionFactory
480
+ ? ReturnType<ReturnType<T>>
481
+ : T extends string
482
+ ? Extension
483
+ : never)
484
+ | undefined {
485
+ if (typeof extension === "string") {
486
+ const instance = this.extensions.find((e) => e.key === extension);
487
+ if (!instance) {
488
+ return undefined;
489
+ }
490
+ return instance as any;
491
+ } else if (typeof extension === "function") {
492
+ const instance = this.extensionFactories.get(extension);
493
+ if (!instance) {
494
+ return undefined;
495
+ }
496
+ return instance as any;
497
+ }
498
+ throw new Error(`Invalid extension type: ${typeof extension}`);
499
+ }
500
+
501
+ /**
502
+ * Check if an extension exists
503
+ */
504
+ public hasExtension(key: string | Extension | ExtensionFactory): boolean {
505
+ if (typeof key === "string") {
506
+ return this.extensions.some((e) => e.key === key);
507
+ } else if (typeof key === "object" && "key" in key) {
508
+ return this.extensions.some((e) => e.key === key.key);
509
+ } else if (typeof key === "function") {
510
+ return this.extensionFactories.has(key);
511
+ }
512
+ return false;
513
+ }
514
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Symbol used to track the original factory function for extensions.
3
+ * This allows us to retrieve the original factory for comparison and other operations.
4
+ */
5
+ export const originalFactorySymbol = Symbol("originalFactory");
6
+
@@ -109,6 +109,10 @@ export class SelectionManager<
109
109
  }
110
110
  }
111
111
 
112
- return posToDOMRect(this.editor.prosemirrorView, from, to);
112
+ return posToDOMRect(
113
+ this.editor.prosemirrorView,
114
+ from,
115
+ to,
116
+ ).toJSON() as DOMRect;
113
117
  }
114
118
  }
@@ -1,21 +1,10 @@
1
- import { redo, undo } from "@tiptap/pm/history";
2
1
  import { Command, Transaction } from "prosemirror-state";
2
+ import type { YUndoExtension } from "../../extensions/Collaboration/YUndo.js";
3
+ import type { HistoryExtension } from "../../extensions/History/History.js";
3
4
  import { BlockNoteEditor } from "../BlockNoteEditor.js";
4
5
 
5
6
  export class StateManager {
6
- constructor(
7
- private editor: BlockNoteEditor,
8
- private options?: {
9
- /**
10
- * Swap the default undo command with a custom command.
11
- */
12
- undo?: typeof undo;
13
- /**
14
- * Swap the default redo command with a custom command.
15
- */
16
- redo?: typeof redo;
17
- },
18
- ) {}
7
+ constructor(private editor: BlockNoteEditor<any, any, any>) {}
19
8
 
20
9
  /**
21
10
  * Stores the currently active transaction, which is the accumulated transaction from all {@link dispatch} calls during a {@link transact} calls
@@ -225,14 +214,37 @@ export class StateManager {
225
214
  /**
226
215
  * Undo the last action.
227
216
  */
228
- public undo() {
229
- return this.exec(this.options?.undo ?? undo);
217
+ public undo(): boolean {
218
+ // Purposefully not using the UndoPlugin to not import y-prosemirror when not needed
219
+ const undoPlugin = this.editor.getExtension<typeof YUndoExtension>("yUndo");
220
+ if (undoPlugin) {
221
+ return this.exec(undoPlugin.undoCommand);
222
+ }
223
+
224
+ const historyPlugin =
225
+ this.editor.getExtension<typeof HistoryExtension>("history");
226
+ if (historyPlugin) {
227
+ return this.exec(historyPlugin.undoCommand);
228
+ }
229
+
230
+ throw new Error("No undo plugin found");
230
231
  }
231
232
 
232
233
  /**
233
234
  * Redo the last action.
234
235
  */
235
236
  public redo() {
236
- return this.exec(this.options?.redo ?? redo);
237
+ const undoPlugin = this.editor.getExtension<typeof YUndoExtension>("yUndo");
238
+ if (undoPlugin) {
239
+ return this.exec(undoPlugin.redoCommand);
240
+ }
241
+
242
+ const historyPlugin =
243
+ this.editor.getExtension<typeof HistoryExtension>("history");
244
+ if (historyPlugin) {
245
+ return this.exec(historyPlugin.redoCommand);
246
+ }
247
+
248
+ throw new Error("No redo plugin found");
237
249
  }
238
250
  }
@@ -1,11 +1,7 @@
1
1
  export { BlockManager } from "./BlockManager.js";
2
- export {
3
- CollaborationManager,
4
- type CollaborationOptions,
5
- } from "./CollaborationManager.js";
6
2
  export { EventManager } from "./EventManager.js";
7
3
  export { ExportManager } from "./ExportManager.js";
8
- export { ExtensionManager } from "./ExtensionManager.js";
4
+ export { ExtensionManager } from "./ExtensionManager/index.js";
9
5
  export { SelectionManager } from "./SelectionManager.js";
10
6
  export { StateManager } from "./StateManager.js";
11
7
  export { StyleManager } from "./StyleManager.js";
@@ -3,25 +3,19 @@ import {
3
3
  BlocksChanged,
4
4
  getBlocksChangedByTransaction,
5
5
  } from "../../api/getBlocksChangedByTransaction.js";
6
- import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
6
+ import { createExtension } from "../../editor/BlockNoteExtension.js";
7
7
 
8
8
  /**
9
9
  * This plugin can filter transactions before they are applied to the editor, but with a higher-level API than `filterTransaction` from prosemirror.
10
10
  */
11
- export class BlockChangePlugin extends BlockNoteExtension {
12
- public static key() {
13
- return "blockChange";
14
- }
15
-
16
- private beforeChangeCallbacks: ((context: {
11
+ export const BlockChangeExtension = createExtension(() => {
12
+ const beforeChangeCallbacks: ((context: {
17
13
  getChanges: () => BlocksChanged<any, any, any>;
18
14
  tr: Transaction;
19
15
  }) => boolean | void)[] = [];
20
-
21
- constructor() {
22
- super();
23
-
24
- this.addProsemirrorPlugin(
16
+ return {
17
+ key: "blockChange",
18
+ prosemirrorPlugins: [
25
19
  new Plugin({
26
20
  key: new PluginKey("blockChange"),
27
21
  filterTransaction: (tr) => {
@@ -29,7 +23,7 @@ export class BlockChangePlugin extends BlockNoteExtension {
29
23
  | ReturnType<typeof getBlocksChangedByTransaction>
30
24
  | undefined = undefined;
31
25
 
32
- return this.beforeChangeCallbacks.reduce((acc, cb) => {
26
+ return beforeChangeCallbacks.reduce((acc, cb) => {
33
27
  if (acc === false) {
34
28
  // We only care that we hit a `false` result, so we can stop iterating.
35
29
  return acc;
@@ -49,21 +43,25 @@ export class BlockChangePlugin extends BlockNoteExtension {
49
43
  }, true);
50
44
  },
51
45
  }),
52
- );
53
- }
46
+ ],
54
47
 
55
- public subscribe(
56
- callback: (context: {
57
- getChanges: () => BlocksChanged<any, any, any>;
58
- tr: Transaction;
59
- }) => boolean | void,
60
- ) {
61
- this.beforeChangeCallbacks.push(callback);
48
+ /**
49
+ * Subscribe to the block change events.
50
+ */
51
+ subscribe(
52
+ callback: (context: {
53
+ getChanges: () => BlocksChanged<any, any, any>;
54
+ tr: Transaction;
55
+ }) => boolean | void,
56
+ ) {
57
+ beforeChangeCallbacks.push(callback);
62
58
 
63
- return () => {
64
- this.beforeChangeCallbacks = this.beforeChangeCallbacks.filter(
65
- (cb) => cb !== callback,
66
- );
67
- };
68
- }
69
- }
59
+ return () => {
60
+ beforeChangeCallbacks.splice(
61
+ beforeChangeCallbacks.indexOf(callback),
62
+ 1,
63
+ );
64
+ };
65
+ },
66
+ } as const;
67
+ });