@blocknote/core 0.30.1 → 0.31.1

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 (120) hide show
  1. package/dist/blocknote.cjs +9 -9
  2. package/dist/blocknote.cjs.map +1 -1
  3. package/dist/blocknote.js +2793 -2213
  4. package/dist/blocknote.js.map +1 -1
  5. package/dist/{en-D4taoCs4.cjs → en-BXVKCwYt.cjs} +2 -2
  6. package/dist/en-BXVKCwYt.cjs.map +1 -0
  7. package/dist/{en-B7ycW7c8.js → en-qGo6sk9V.js} +2 -3
  8. package/dist/en-qGo6sk9V.js.map +1 -0
  9. package/dist/locales.cjs +1 -1
  10. package/dist/locales.cjs.map +1 -1
  11. package/dist/locales.js +20 -39
  12. package/dist/locales.js.map +1 -1
  13. package/dist/style.css +1 -1
  14. package/dist/tsconfig.tsbuildinfo +1 -1
  15. package/dist/webpack-stats.json +1 -1
  16. package/package.json +5 -6
  17. package/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts +2 -3
  18. package/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts +1 -1
  19. package/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap +2816 -0
  20. package/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts +158 -0
  21. package/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +87 -17
  22. package/src/api/blockManipulation/selections/selection.ts +48 -1
  23. package/src/api/blockManipulation/selections/{textCursorPosition/textCursorPosition.ts → textCursorPosition.ts} +7 -7
  24. package/src/api/getBlockInfoFromPos.ts +1 -1
  25. package/src/api/nodeConversions/blockToNode.ts +5 -2
  26. package/src/api/nodeConversions/nodeToBlock.ts +203 -8
  27. package/src/api/pmUtil.ts +3 -3
  28. package/src/blocks/CodeBlockContent/CodeBlockContent.ts +6 -6
  29. package/src/blocks/FileBlockContent/helpers/render/createAddFileButton.ts +1 -1
  30. package/src/blocks/TableBlockContent/TableBlockContent.ts +32 -2
  31. package/src/editor/Block.css +27 -1
  32. package/src/editor/BlockNoteEditor.test.ts +7 -0
  33. package/src/editor/BlockNoteEditor.ts +124 -39
  34. package/src/editor/BlockNoteExtension.ts +26 -0
  35. package/src/editor/BlockNoteExtensions.ts +28 -12
  36. package/src/editor/BlockNoteTipTapEditor.ts +23 -2
  37. package/src/extensions/Collaboration/CursorPlugin.ts +13 -7
  38. package/src/extensions/Collaboration/ForkYDocPlugin.test.ts +166 -0
  39. package/src/extensions/Collaboration/ForkYDocPlugin.ts +174 -0
  40. package/src/extensions/Collaboration/SyncPlugin.ts +7 -4
  41. package/src/extensions/Collaboration/UndoPlugin.ts +7 -4
  42. package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor-forked.json +30 -0
  43. package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor.json +30 -0
  44. package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-forked.html +1 -0
  45. package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap.html +1 -0
  46. package/src/extensions/Comments/CommentsPlugin.ts +79 -70
  47. package/src/extensions/FilePanel/FilePanelPlugin.ts +54 -49
  48. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +60 -26
  49. package/src/extensions/LinkToolbar/LinkToolbarPlugin.ts +26 -21
  50. package/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.ts +49 -42
  51. package/src/extensions/Placeholder/PlaceholderPlugin.ts +115 -108
  52. package/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.ts +183 -170
  53. package/src/extensions/ShowSelection/ShowSelectionPlugin.ts +26 -19
  54. package/src/extensions/SideMenu/SideMenuPlugin.ts +23 -18
  55. package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +172 -168
  56. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +4 -4
  57. package/src/extensions/Suggestions/SuggestionMarks.ts +175 -0
  58. package/src/extensions/TableHandles/TableHandlesPlugin.ts +157 -150
  59. package/src/i18n/locales/ar.ts +0 -1
  60. package/src/i18n/locales/de.ts +0 -1
  61. package/src/i18n/locales/en.ts +0 -1
  62. package/src/i18n/locales/es.ts +0 -1
  63. package/src/i18n/locales/fr.ts +0 -1
  64. package/src/i18n/locales/hr.ts +0 -1
  65. package/src/i18n/locales/is.ts +0 -1
  66. package/src/i18n/locales/it.ts +0 -1
  67. package/src/i18n/locales/ja.ts +0 -1
  68. package/src/i18n/locales/ko.ts +0 -1
  69. package/src/i18n/locales/nl.ts +0 -1
  70. package/src/i18n/locales/no.ts +0 -1
  71. package/src/i18n/locales/pl.ts +0 -1
  72. package/src/i18n/locales/pt.ts +0 -1
  73. package/src/i18n/locales/ru.ts +0 -1
  74. package/src/i18n/locales/sk.ts +0 -1
  75. package/src/i18n/locales/uk.ts +0 -1
  76. package/src/i18n/locales/vi.ts +0 -1
  77. package/src/i18n/locales/zh-tw.ts +0 -1
  78. package/src/i18n/locales/zh.ts +0 -1
  79. package/src/index.ts +18 -8
  80. package/src/pm-nodes/BlockContainer.ts +1 -1
  81. package/src/pm-nodes/BlockGroup.ts +1 -1
  82. package/src/pm-nodes/Doc.ts +1 -0
  83. package/types/src/api/blockManipulation/commands/insertBlocks/insertBlocks.d.ts +1 -1
  84. package/types/src/api/blockManipulation/commands/updateBlock/updateBlock.d.ts +3 -1
  85. package/types/src/api/blockManipulation/selections/selection.d.ts +10 -0
  86. package/types/src/api/blockManipulation/selections/{textCursorPosition/textCursorPosition.d.ts → textCursorPosition.d.ts} +2 -2
  87. package/types/src/api/nodeConversions/nodeToBlock.d.ts +39 -2
  88. package/types/src/api/pmUtil.d.ts +3 -3
  89. package/types/src/blocks/TableBlockContent/TableBlockContent.d.ts +9 -1
  90. package/types/src/editor/BlockNoteEditor.d.ts +62 -10
  91. package/types/src/editor/BlockNoteExtension.d.ts +9 -0
  92. package/types/src/editor/BlockNoteExtensions.d.ts +2 -2
  93. package/types/src/editor/BlockNoteTipTapEditor.d.ts +2 -2
  94. package/types/src/extensions/Collaboration/CursorPlugin.d.ts +3 -3
  95. package/types/src/extensions/Collaboration/ForkYDocPlugin.d.ts +41 -0
  96. package/types/src/extensions/Collaboration/SyncPlugin.d.ts +3 -3
  97. package/types/src/extensions/Collaboration/UndoPlugin.d.ts +3 -3
  98. package/types/src/extensions/Comments/CommentsPlugin.d.ts +3 -4
  99. package/types/src/extensions/FilePanel/FilePanelPlugin.d.ts +4 -4
  100. package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +6 -5
  101. package/types/src/extensions/LinkToolbar/LinkToolbarPlugin.d.ts +4 -4
  102. package/types/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.d.ts +3 -3
  103. package/types/src/extensions/Placeholder/PlaceholderPlugin.d.ts +3 -3
  104. package/types/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.d.ts +3 -3
  105. package/types/src/extensions/ShowSelection/ShowSelectionPlugin.d.ts +3 -3
  106. package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +4 -4
  107. package/types/src/extensions/SuggestionMenu/SuggestionPlugin.d.ts +3 -4
  108. package/types/src/extensions/Suggestions/SuggestionMarks.d.ts +4 -0
  109. package/types/src/extensions/TableHandles/TableHandlesPlugin.d.ts +6 -6
  110. package/types/src/i18n/locales/en.d.ts +0 -1
  111. package/types/src/i18n/locales/sk.d.ts +0 -1
  112. package/types/src/index.d.ts +15 -8
  113. package/dist/en-B7ycW7c8.js.map +0 -1
  114. package/dist/en-D4taoCs4.cjs.map +0 -1
  115. package/src/api/blockManipulation/selections/__snapshots__/selection.test.ts.snap +0 -844
  116. package/src/api/blockManipulation/selections/selection.test.ts +0 -72
  117. package/src/api/blockManipulation/selections/textCursorPosition/__snapshots__/textCursorPosition.test.ts.snap +0 -316
  118. package/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.test.ts +0 -74
  119. package/types/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.test.d.ts +0 -1
  120. /package/types/src/{api/blockManipulation/selections/selection.test.d.ts → extensions/Collaboration/ForkYDocPlugin.test.d.ts} +0 -0
@@ -2,15 +2,15 @@ import { findParentNode } from "@tiptap/core";
2
2
  import { EditorState, Plugin, PluginKey } from "prosemirror-state";
3
3
  import { Decoration, DecorationSet, EditorView } from "prosemirror-view";
4
4
 
5
+ import { trackPosition } from "../../api/positionMapping.js";
5
6
  import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
7
+ import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
6
8
  import { UiElementPosition } from "../../extensions-shared/UiElementPosition.js";
7
9
  import {
8
10
  BlockSchema,
9
11
  InlineContentSchema,
10
12
  StyleSchema,
11
13
  } from "../../schema/index.js";
12
- import { EventEmitter } from "../../util/EventEmitter.js";
13
- import { trackPosition } from "../../api/positionMapping.js";
14
14
 
15
15
  const findBlock = findParentNode((node) => node.type.name === "blockContainer");
16
16
 
@@ -166,190 +166,194 @@ export class SuggestionMenuProseMirrorPlugin<
166
166
  BSchema extends BlockSchema,
167
167
  I extends InlineContentSchema,
168
168
  S extends StyleSchema,
169
- > extends EventEmitter<any> {
170
- private view: SuggestionMenuView<BSchema, I, S> | undefined;
171
- public readonly plugin: Plugin;
169
+ > extends BlockNoteExtension {
170
+ public static key() {
171
+ return "suggestionMenu";
172
+ }
172
173
 
174
+ private view: SuggestionMenuView<BSchema, I, S> | undefined;
173
175
  private triggerCharacters: string[] = [];
174
176
 
175
177
  constructor(editor: BlockNoteEditor<BSchema, I, S>) {
176
178
  super();
177
179
  const triggerCharacters = this.triggerCharacters;
178
- this.plugin = new Plugin({
179
- key: suggestionMenuPluginKey,
180
-
181
- view: () => {
182
- this.view = new SuggestionMenuView<BSchema, I, S>(
183
- editor,
184
- (triggerCharacter, state) => {
185
- this.emit(`update ${triggerCharacter}`, state);
186
- },
187
- );
188
- return this.view;
189
- },
190
-
191
- state: {
192
- // Initialize the plugin's internal state.
193
- init(): SuggestionPluginState {
194
- return undefined;
180
+ this.addProsemirrorPlugin(
181
+ new Plugin({
182
+ key: suggestionMenuPluginKey,
183
+
184
+ view: () => {
185
+ this.view = new SuggestionMenuView<BSchema, I, S>(
186
+ editor,
187
+ (triggerCharacter, state) => {
188
+ this.emit(`update ${triggerCharacter}`, state);
189
+ },
190
+ );
191
+ return this.view;
195
192
  },
196
193
 
197
- // Apply changes to the plugin state from an editor transaction.
198
- apply: (
199
- transaction,
200
- prev,
201
- _oldState,
202
- newState,
203
- ): SuggestionPluginState => {
204
- // TODO: More clearly define which transactions should be ignored.
205
- if (transaction.getMeta("orderedListIndexing") !== undefined) {
206
- return prev;
207
- }
208
-
209
- // Ignore transactions in code blocks.
210
- if (transaction.selection.$from.parent.type.spec.code) {
211
- return prev;
212
- }
213
-
214
- // Either contains the trigger character if the menu should be shown,
215
- // or null if it should be hidden.
216
- const suggestionPluginTransactionMeta: {
217
- triggerCharacter: string;
218
- deleteTriggerCharacter?: boolean;
219
- ignoreQueryLength?: boolean;
220
- } | null = transaction.getMeta(suggestionMenuPluginKey);
221
-
222
- if (
223
- typeof suggestionPluginTransactionMeta === "object" &&
224
- suggestionPluginTransactionMeta !== null
225
- ) {
226
- if (prev) {
227
- // Close the previous menu if it exists
228
- this.closeMenu();
229
- }
230
- const trackedPosition = trackPosition(
231
- editor,
232
- newState.selection.from -
233
- // Need to account for the trigger char that was inserted, so we offset the position by the length of the trigger character.
234
- suggestionPluginTransactionMeta.triggerCharacter.length,
235
- );
236
- return {
237
- triggerCharacter:
238
- suggestionPluginTransactionMeta.triggerCharacter,
239
- deleteTriggerCharacter:
240
- suggestionPluginTransactionMeta.deleteTriggerCharacter !==
241
- false,
242
- // When reading the queryStartPos, we offset the result by the length of the trigger character, to make it easy on the caller
243
- queryStartPos: () =>
244
- trackedPosition() +
245
- suggestionPluginTransactionMeta.triggerCharacter.length,
246
- query: "",
247
- decorationId: `id_${Math.floor(Math.random() * 0xffffffff)}`,
248
- ignoreQueryLength:
249
- suggestionPluginTransactionMeta?.ignoreQueryLength,
250
- };
251
- }
252
-
253
- // Checks if the menu is hidden, in which case it doesn't need to be hidden or updated.
254
- if (prev === undefined) {
255
- return prev;
256
- }
257
-
258
- // Checks if the menu should be hidden.
259
- if (
260
- // Highlighting text should hide the menu.
261
- newState.selection.from !== newState.selection.to ||
262
- // Transactions with plugin metadata should hide the menu.
263
- suggestionPluginTransactionMeta === null ||
264
- // Certain mouse events should hide the menu.
265
- // TODO: Change to global mousedown listener.
266
- transaction.getMeta("focus") ||
267
- transaction.getMeta("blur") ||
268
- transaction.getMeta("pointer") ||
269
- // Moving the caret before the character which triggered the menu should hide it.
270
- (prev.triggerCharacter !== undefined &&
271
- newState.selection.from < prev.queryStartPos()) ||
272
- // Moving the caret to a new block should hide the menu.
273
- !newState.selection.$from.sameParent(
274
- newState.doc.resolve(prev.queryStartPos()),
275
- )
276
- ) {
194
+ state: {
195
+ // Initialize the plugin's internal state.
196
+ init(): SuggestionPluginState {
277
197
  return undefined;
278
- }
198
+ },
279
199
 
280
- const next = { ...prev };
200
+ // Apply changes to the plugin state from an editor transaction.
201
+ apply: (
202
+ transaction,
203
+ prev,
204
+ _oldState,
205
+ newState,
206
+ ): SuggestionPluginState => {
207
+ // TODO: More clearly define which transactions should be ignored.
208
+ if (transaction.getMeta("orderedListIndexing") !== undefined) {
209
+ return prev;
210
+ }
281
211
 
282
- // Updates the current query.
283
- next.query = newState.doc.textBetween(
284
- prev.queryStartPos(),
285
- newState.selection.from,
286
- );
212
+ // Ignore transactions in code blocks.
213
+ if (transaction.selection.$from.parent.type.spec.code) {
214
+ return prev;
215
+ }
287
216
 
288
- return next;
289
- },
290
- },
291
-
292
- props: {
293
- handleTextInput(view, _from, _to, text) {
294
- if (triggerCharacters.includes(text)) {
295
- view.dispatch(view.state.tr.insertText(text));
296
- view.dispatch(
297
- view.state.tr
298
- .setMeta(suggestionMenuPluginKey, {
299
- triggerCharacter: text,
300
- })
301
- .scrollIntoView(),
217
+ // Either contains the trigger character if the menu should be shown,
218
+ // or null if it should be hidden.
219
+ const suggestionPluginTransactionMeta: {
220
+ triggerCharacter: string;
221
+ deleteTriggerCharacter?: boolean;
222
+ ignoreQueryLength?: boolean;
223
+ } | null = transaction.getMeta(suggestionMenuPluginKey);
224
+
225
+ if (
226
+ typeof suggestionPluginTransactionMeta === "object" &&
227
+ suggestionPluginTransactionMeta !== null
228
+ ) {
229
+ if (prev) {
230
+ // Close the previous menu if it exists
231
+ this.closeMenu();
232
+ }
233
+ const trackedPosition = trackPosition(
234
+ editor,
235
+ newState.selection.from -
236
+ // Need to account for the trigger char that was inserted, so we offset the position by the length of the trigger character.
237
+ suggestionPluginTransactionMeta.triggerCharacter.length,
238
+ );
239
+ return {
240
+ triggerCharacter:
241
+ suggestionPluginTransactionMeta.triggerCharacter,
242
+ deleteTriggerCharacter:
243
+ suggestionPluginTransactionMeta.deleteTriggerCharacter !==
244
+ false,
245
+ // When reading the queryStartPos, we offset the result by the length of the trigger character, to make it easy on the caller
246
+ queryStartPos: () =>
247
+ trackedPosition() +
248
+ suggestionPluginTransactionMeta.triggerCharacter.length,
249
+ query: "",
250
+ decorationId: `id_${Math.floor(Math.random() * 0xffffffff)}`,
251
+ ignoreQueryLength:
252
+ suggestionPluginTransactionMeta?.ignoreQueryLength,
253
+ };
254
+ }
255
+
256
+ // Checks if the menu is hidden, in which case it doesn't need to be hidden or updated.
257
+ if (prev === undefined) {
258
+ return prev;
259
+ }
260
+
261
+ // Checks if the menu should be hidden.
262
+ if (
263
+ // Highlighting text should hide the menu.
264
+ newState.selection.from !== newState.selection.to ||
265
+ // Transactions with plugin metadata should hide the menu.
266
+ suggestionPluginTransactionMeta === null ||
267
+ // Certain mouse events should hide the menu.
268
+ // TODO: Change to global mousedown listener.
269
+ transaction.getMeta("focus") ||
270
+ transaction.getMeta("blur") ||
271
+ transaction.getMeta("pointer") ||
272
+ // Moving the caret before the character which triggered the menu should hide it.
273
+ (prev.triggerCharacter !== undefined &&
274
+ newState.selection.from < prev.queryStartPos()) ||
275
+ // Moving the caret to a new block should hide the menu.
276
+ !newState.selection.$from.sameParent(
277
+ newState.doc.resolve(prev.queryStartPos()),
278
+ )
279
+ ) {
280
+ return undefined;
281
+ }
282
+
283
+ const next = { ...prev };
284
+
285
+ // Updates the current query.
286
+ next.query = newState.doc.textBetween(
287
+ prev.queryStartPos(),
288
+ newState.selection.from,
302
289
  );
303
290
 
304
- return true;
305
- }
306
- return false;
291
+ return next;
292
+ },
307
293
  },
308
294
 
309
- // Setup decorator on the currently active suggestion.
310
- decorations(state) {
311
- const suggestionPluginState: SuggestionPluginState = (
312
- this as Plugin
313
- ).getState(state);
314
-
315
- if (suggestionPluginState === undefined) {
316
- return null;
317
- }
318
-
319
- // If the menu was opened programmatically by another extension, it may not use a trigger character. In this
320
- // case, the decoration is set on the whole block instead, as the decoration range would otherwise be empty.
321
- if (!suggestionPluginState.deleteTriggerCharacter) {
322
- const blockNode = findBlock(state.selection);
323
- if (blockNode) {
324
- return DecorationSet.create(state.doc, [
325
- Decoration.node(
326
- blockNode.pos,
327
- blockNode.pos + blockNode.node.nodeSize,
328
- {
329
- nodeName: "span",
330
- class: "bn-suggestion-decorator",
331
- "data-decoration-id": suggestionPluginState.decorationId,
332
- },
333
- ),
334
- ]);
295
+ props: {
296
+ handleTextInput(view, _from, _to, text) {
297
+ if (triggerCharacters.includes(text)) {
298
+ view.dispatch(view.state.tr.insertText(text));
299
+ view.dispatch(
300
+ view.state.tr
301
+ .setMeta(suggestionMenuPluginKey, {
302
+ triggerCharacter: text,
303
+ })
304
+ .scrollIntoView(),
305
+ );
306
+
307
+ return true;
335
308
  }
336
- }
337
- // Creates an inline decoration around the trigger character.
338
- return DecorationSet.create(state.doc, [
339
- Decoration.inline(
340
- suggestionPluginState.queryStartPos() -
341
- suggestionPluginState.triggerCharacter!.length,
342
- suggestionPluginState.queryStartPos(),
343
- {
344
- nodeName: "span",
345
- class: "bn-suggestion-decorator",
346
- "data-decoration-id": suggestionPluginState.decorationId,
347
- },
348
- ),
349
- ]);
309
+ return false;
310
+ },
311
+
312
+ // Setup decorator on the currently active suggestion.
313
+ decorations(state) {
314
+ const suggestionPluginState: SuggestionPluginState = (
315
+ this as Plugin
316
+ ).getState(state);
317
+
318
+ if (suggestionPluginState === undefined) {
319
+ return null;
320
+ }
321
+
322
+ // If the menu was opened programmatically by another extension, it may not use a trigger character. In this
323
+ // case, the decoration is set on the whole block instead, as the decoration range would otherwise be empty.
324
+ if (!suggestionPluginState.deleteTriggerCharacter) {
325
+ const blockNode = findBlock(state.selection);
326
+ if (blockNode) {
327
+ return DecorationSet.create(state.doc, [
328
+ Decoration.node(
329
+ blockNode.pos,
330
+ blockNode.pos + blockNode.node.nodeSize,
331
+ {
332
+ nodeName: "span",
333
+ class: "bn-suggestion-decorator",
334
+ "data-decoration-id": suggestionPluginState.decorationId,
335
+ },
336
+ ),
337
+ ]);
338
+ }
339
+ }
340
+ // Creates an inline decoration around the trigger character.
341
+ return DecorationSet.create(state.doc, [
342
+ Decoration.inline(
343
+ suggestionPluginState.queryStartPos() -
344
+ suggestionPluginState.triggerCharacter!.length,
345
+ suggestionPluginState.queryStartPos(),
346
+ {
347
+ nodeName: "span",
348
+ class: "bn-suggestion-decorator",
349
+ "data-decoration-id": suggestionPluginState.decorationId,
350
+ },
351
+ ),
352
+ ]);
353
+ },
350
354
  },
351
- },
352
- });
355
+ }),
356
+ );
353
357
  }
354
358
 
355
359
  public onUpdate(
@@ -235,7 +235,7 @@ export function getDefaultSlashMenuItems<
235
235
 
236
236
  // Immediately open the file toolbar
237
237
  editor.transact((tr) =>
238
- tr.setMeta(editor.filePanel!.plugin, {
238
+ tr.setMeta(editor.filePanel!.plugins[0], {
239
239
  block: insertedBlock,
240
240
  }),
241
241
  );
@@ -254,7 +254,7 @@ export function getDefaultSlashMenuItems<
254
254
 
255
255
  // Immediately open the file toolbar
256
256
  editor.transact((tr) =>
257
- tr.setMeta(editor.filePanel!.plugin, {
257
+ tr.setMeta(editor.filePanel!.plugins[0], {
258
258
  block: insertedBlock,
259
259
  }),
260
260
  );
@@ -273,7 +273,7 @@ export function getDefaultSlashMenuItems<
273
273
 
274
274
  // Immediately open the file toolbar
275
275
  editor.transact((tr) =>
276
- tr.setMeta(editor.filePanel!.plugin, {
276
+ tr.setMeta(editor.filePanel!.plugins[0], {
277
277
  block: insertedBlock,
278
278
  }),
279
279
  );
@@ -292,7 +292,7 @@ export function getDefaultSlashMenuItems<
292
292
 
293
293
  // Immediately open the file toolbar
294
294
  editor.transact((tr) =>
295
- tr.setMeta(editor.filePanel!.plugin, {
295
+ tr.setMeta(editor.filePanel!.plugins[0], {
296
296
  block: insertedBlock,
297
297
  }),
298
298
  );
@@ -0,0 +1,175 @@
1
+ import { Mark } from "@tiptap/core";
2
+ import { MarkSpec } from "prosemirror-model";
3
+
4
+ // This copies the marks from @handlewithcare/prosemirror-suggest-changes,
5
+ // but uses the Tiptap Mark API instead so we can use them in BlockNote
6
+
7
+ // The ideal solution would be to not depend on tiptap nodes / marks, but be able to use prosemirror nodes / marks directly
8
+ // this way we could directly use the exported marks from @handlewithcare/prosemirror-suggest-changes
9
+ export const SuggestionAddMark = Mark.create({
10
+ name: "insertion",
11
+ inclusive: false,
12
+ excludes: "deletion modification insertion",
13
+ addAttributes() {
14
+ return {
15
+ id: { default: null, validate: "number" }, // note: validate is supported in prosemirror but not in tiptap, so this doesn't actually work (considered not critical)
16
+ };
17
+ },
18
+ extendMarkSchema(extension) {
19
+ if (extension.name !== "insertion") {
20
+ return {};
21
+ }
22
+ return {
23
+ blocknoteIgnore: true,
24
+ inclusive: false,
25
+
26
+ toDOM(mark, inline) {
27
+ return [
28
+ "ins",
29
+ {
30
+ "data-id": String(mark.attrs["id"]),
31
+ "data-inline": String(inline),
32
+ ...(!inline && { style: "display: contents" }), // changed to "contents" to make this work for table rows
33
+ },
34
+ 0,
35
+ ];
36
+ },
37
+ parseDOM: [
38
+ {
39
+ tag: "ins",
40
+ getAttrs(node) {
41
+ if (!node.dataset["id"]) {
42
+ return false;
43
+ }
44
+ return {
45
+ id: parseInt(node.dataset["id"], 10),
46
+ };
47
+ },
48
+ },
49
+ ],
50
+ } satisfies MarkSpec;
51
+ },
52
+ });
53
+
54
+ export const SuggestionDeleteMark = Mark.create({
55
+ name: "deletion",
56
+ inclusive: false,
57
+ excludes: "insertion modification deletion",
58
+ addAttributes() {
59
+ return {
60
+ id: { default: null, validate: "number" }, // note: validate is supported in prosemirror but not in tiptap
61
+ };
62
+ },
63
+ extendMarkSchema(extension) {
64
+ if (extension.name !== "deletion") {
65
+ return {};
66
+ }
67
+ return {
68
+ blocknoteIgnore: true,
69
+ inclusive: false,
70
+
71
+ // attrs: {
72
+ // id: { validate: "number" },
73
+ // },
74
+ toDOM(mark, inline) {
75
+ return [
76
+ "del",
77
+ {
78
+ "data-id": String(mark.attrs["id"]),
79
+ "data-inline": String(inline),
80
+ ...(!inline && { style: "display: contents" }), // changed to "contents" to make this work for table rows
81
+ },
82
+ 0,
83
+ ];
84
+ },
85
+ parseDOM: [
86
+ {
87
+ tag: "del",
88
+ getAttrs(node) {
89
+ if (!node.dataset["id"]) {
90
+ return false;
91
+ }
92
+ return {
93
+ id: parseInt(node.dataset["id"], 10),
94
+ };
95
+ },
96
+ },
97
+ ],
98
+ } satisfies MarkSpec;
99
+ },
100
+ });
101
+
102
+ export const SuggestionModificationMark = Mark.create({
103
+ name: "modification",
104
+ inclusive: false,
105
+ excludes: "deletion insertion",
106
+ addAttributes() {
107
+ // note: validate is supported in prosemirror but not in tiptap
108
+ return {
109
+ id: { default: null, validate: "number" },
110
+ type: { validate: "string" },
111
+ attrName: { default: null, validate: "string|null" },
112
+ previousValue: { default: null },
113
+ newValue: { default: null },
114
+ };
115
+ },
116
+ extendMarkSchema(extension) {
117
+ if (extension.name !== "modification") {
118
+ return {};
119
+ }
120
+ return {
121
+ blocknoteIgnore: true,
122
+ inclusive: false,
123
+ // attrs: {
124
+ // id: { validate: "number" },
125
+ // type: { validate: "string" },
126
+ // attrName: { default: null, validate: "string|null" },
127
+ // previousValue: { default: null },
128
+ // newValue: { default: null },
129
+ // },
130
+ toDOM(mark, inline) {
131
+ return [
132
+ inline ? "span" : "div",
133
+ {
134
+ "data-type": "modification",
135
+ "data-id": String(mark.attrs["id"]),
136
+ "data-mod-type": mark.attrs["type"] as string,
137
+ "data-mod-prev-val": JSON.stringify(mark.attrs["previousValue"]),
138
+ // TODO: Try to serialize marks with toJSON?
139
+ "data-mod-new-val": JSON.stringify(mark.attrs["newValue"]),
140
+ },
141
+ 0,
142
+ ];
143
+ },
144
+ parseDOM: [
145
+ {
146
+ tag: "span[data-type='modification']",
147
+ getAttrs(node) {
148
+ if (!node.dataset["id"]) {
149
+ return false;
150
+ }
151
+ return {
152
+ id: parseInt(node.dataset["id"], 10),
153
+ type: node.dataset["modType"],
154
+ previousValue: node.dataset["modPrevVal"],
155
+ newValue: node.dataset["modNewVal"],
156
+ };
157
+ },
158
+ },
159
+ {
160
+ tag: "div[data-type='modification']",
161
+ getAttrs(node) {
162
+ if (!node.dataset["id"]) {
163
+ return false;
164
+ }
165
+ return {
166
+ id: parseInt(node.dataset["id"], 10),
167
+ type: node.dataset["modType"],
168
+ previousValue: node.dataset["modPrevVal"],
169
+ };
170
+ },
171
+ },
172
+ ],
173
+ } satisfies MarkSpec;
174
+ },
175
+ });