@blocknote/core 0.44.2 → 0.46.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 (92) hide show
  1. package/dist/BlockNoteExtension-BWw0r8Gy.cjs.map +1 -1
  2. package/dist/BlockNoteExtension-C2X7LW-V.js.map +1 -1
  3. package/dist/{BlockNoteSchema-BsTi0fNS.js → BlockNoteSchema-DsMVJZv4.js} +2 -2
  4. package/dist/{BlockNoteSchema-BsTi0fNS.js.map → BlockNoteSchema-DsMVJZv4.js.map} +1 -1
  5. package/dist/{BlockNoteSchema-CBNkNhkw.cjs → BlockNoteSchema-qt4Czo0-.cjs} +2 -2
  6. package/dist/{BlockNoteSchema-CBNkNhkw.cjs.map → BlockNoteSchema-qt4Czo0-.cjs.map} +1 -1
  7. package/dist/ShowSelection-B0ch3unP.js +51 -0
  8. package/dist/ShowSelection-B0ch3unP.js.map +1 -0
  9. package/dist/ShowSelection-BxnbRvy4.cjs +2 -0
  10. package/dist/ShowSelection-BxnbRvy4.cjs.map +1 -0
  11. package/dist/{TrailingNode-CG2a-HDA.js → TrailingNode-C-Kyrtf1.js} +715 -709
  12. package/dist/TrailingNode-C-Kyrtf1.js.map +1 -0
  13. package/dist/TrailingNode-W7GJVng5.cjs +2 -0
  14. package/dist/TrailingNode-W7GJVng5.cjs.map +1 -0
  15. package/dist/{blockToNode-DBNbhwwC.js → blockToNode-BNoNIXU7.js} +2 -2
  16. package/dist/{blockToNode-DBNbhwwC.js.map → blockToNode-BNoNIXU7.js.map} +1 -1
  17. package/dist/{blockToNode-w7H99R6p.cjs → blockToNode-CumVjgem.cjs} +2 -2
  18. package/dist/{blockToNode-w7H99R6p.cjs.map → blockToNode-CumVjgem.cjs.map} +1 -1
  19. package/dist/blocknote.cjs +4 -4
  20. package/dist/blocknote.cjs.map +1 -1
  21. package/dist/blocknote.js +1118 -1077
  22. package/dist/blocknote.js.map +1 -1
  23. package/dist/blocks.cjs +1 -1
  24. package/dist/blocks.js +2 -2
  25. package/dist/comments.cjs +1 -1
  26. package/dist/comments.cjs.map +1 -1
  27. package/dist/comments.js +3 -3
  28. package/dist/comments.js.map +1 -1
  29. package/dist/{defaultBlocks-B63ufZ5N.js → defaultBlocks-CXOCngjC.js} +273 -312
  30. package/dist/defaultBlocks-CXOCngjC.js.map +1 -0
  31. package/dist/defaultBlocks-IsUGVZIq.cjs +6 -0
  32. package/dist/defaultBlocks-IsUGVZIq.cjs.map +1 -0
  33. package/dist/extensions.cjs +1 -1
  34. package/dist/extensions.js +4 -4
  35. package/dist/style.css +1 -1
  36. package/dist/tsconfig.tsbuildinfo +1 -1
  37. package/dist/webpack-stats.json +1 -1
  38. package/dist/yjs.cjs +1 -1
  39. package/dist/yjs.js +1 -1
  40. package/package.json +18 -18
  41. package/src/api/blockManipulation/selections/selection.ts +9 -4
  42. package/src/api/blockManipulation/tables/tables.test.ts +140 -0
  43. package/src/api/blockManipulation/tables/tables.ts +1 -1
  44. package/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +17 -0
  45. package/src/api/parsers/markdown/parseMarkdown.ts +11 -0
  46. package/src/blocks/ListItem/BulletListItem/block.ts +1 -1
  47. package/src/blocks/ListItem/CheckListItem/block.ts +6 -4
  48. package/src/blocks/ListItem/NumberedListItem/block.ts +6 -2
  49. package/src/comments/extension.ts +6 -2
  50. package/src/editor/Block.css +1 -1
  51. package/src/editor/BlockNoteEditor.test.ts +0 -1
  52. package/src/editor/BlockNoteEditor.ts +9 -39
  53. package/src/editor/BlockNoteExtension.ts +5 -0
  54. package/src/editor/managers/EventManager.ts +1 -1
  55. package/src/editor/managers/ExtensionManager/extensions.ts +3 -13
  56. package/src/editor/managers/ExtensionManager/index.ts +7 -2
  57. package/src/editor/managers/SelectionManager.ts +10 -10
  58. package/src/extensions/BlockChange/BlockChange.ts +2 -2
  59. package/src/extensions/Collaboration/Collaboration.ts +55 -0
  60. package/src/extensions/Collaboration/ForkYDoc.ts +4 -9
  61. package/src/extensions/Collaboration/YCursorPlugin.ts +56 -60
  62. package/src/extensions/Collaboration/YSync.ts +2 -2
  63. package/src/extensions/Collaboration/YUndo.ts +2 -2
  64. package/src/extensions/LinkToolbar/LinkToolbar.ts +1 -1
  65. package/src/extensions/ShowSelection/ShowSelection.ts +14 -4
  66. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +40 -68
  67. package/src/extensions/TableHandles/TableHandles.ts +9 -5
  68. package/src/index.ts +2 -1
  69. package/src/schema/blocks/createSpec.ts +3 -0
  70. package/src/util/expandToWords.ts +38 -0
  71. package/types/src/api/blockManipulation/selections/selection.d.ts +1 -1
  72. package/types/src/editor/BlockNoteEditor.d.ts +5 -34
  73. package/types/src/editor/BlockNoteExtension.d.ts +4 -0
  74. package/types/src/editor/managers/SelectionManager.d.ts +4 -4
  75. package/types/src/extensions/Collaboration/Collaboration.d.ts +76 -0
  76. package/types/src/extensions/Collaboration/ForkYDoc.d.ts +2 -11
  77. package/types/src/extensions/Collaboration/YCursorPlugin.d.ts +3 -11
  78. package/types/src/extensions/Collaboration/YSync.d.ts +2 -4
  79. package/types/src/extensions/Collaboration/YUndo.d.ts +1 -1
  80. package/types/src/extensions/ShowSelection/ShowSelection.d.ts +10 -4
  81. package/types/src/index.d.ts +2 -1
  82. package/types/src/util/expandToWords.d.ts +13 -0
  83. package/dist/ShowSelection-BW37oJ6h.cjs +0 -2
  84. package/dist/ShowSelection-BW37oJ6h.cjs.map +0 -1
  85. package/dist/ShowSelection-Dz-NEase.js +0 -43
  86. package/dist/ShowSelection-Dz-NEase.js.map +0 -1
  87. package/dist/TrailingNode-CG2a-HDA.js.map +0 -1
  88. package/dist/TrailingNode-Du4SNHun.cjs +0 -2
  89. package/dist/TrailingNode-Du4SNHun.cjs.map +0 -1
  90. package/dist/defaultBlocks-B63ufZ5N.js.map +0 -1
  91. package/dist/defaultBlocks-BX6UxQa8.cjs +0 -6
  92. package/dist/defaultBlocks-BX6UxQa8.cjs.map +0 -1
@@ -7,8 +7,6 @@ import {
7
7
  } from "@tiptap/core";
8
8
  import { type Command, type Plugin, type Transaction } from "@tiptap/pm/state";
9
9
  import { Node, Schema } from "prosemirror-model";
10
- import * as Y from "yjs";
11
-
12
10
  import type { BlocksChanged } from "../api/getBlocksChangedByTransaction.js";
13
11
  import { blockToNode } from "../api/nodeConversions/blockToNode.js";
14
12
  import {
@@ -19,6 +17,8 @@ import {
19
17
  DefaultStyleSchema,
20
18
  PartialBlock,
21
19
  } from "../blocks/index.js";
20
+ import type { CollaborationOptions } from "../extensions/Collaboration/Collaboration.js";
21
+ import { BlockChangeExtension } from "../extensions/index.js";
22
22
  import { UniqueID } from "../extensions/tiptap-extensions/UniqueID/UniqueID.js";
23
23
  import type { Dictionary } from "../i18n/dictionary.js";
24
24
  import { en } from "../i18n/locales/index.js";
@@ -52,7 +52,6 @@ import {
52
52
  } from "./managers/index.js";
53
53
  import type { Selection } from "./selectionTypes.js";
54
54
  import { transformPasted } from "./transformPasted.js";
55
- import { BlockChangeExtension } from "../extensions/index.js";
56
55
 
57
56
  export type BlockCache<
58
57
  BSchema extends BlockSchema = any,
@@ -82,37 +81,8 @@ export interface BlockNoteEditorOptions<
82
81
  /**
83
82
  * When enabled, allows for collaboration between multiple users.
84
83
  * See [Real-time Collaboration](https://www.blocknotejs.org/docs/advanced/real-time-collaboration) for more info.
85
- *
86
- * @remarks `CollaborationOptions`
87
84
  */
88
- collaboration?: {
89
- /**
90
- * The Yjs XML fragment that's used for collaboration.
91
- */
92
- fragment: Y.XmlFragment;
93
- /**
94
- * The user info for the current user that's shown to other collaborators.
95
- */
96
- user: {
97
- name: string;
98
- color: string;
99
- };
100
- /**
101
- * A Yjs provider (used for awareness / cursor information)
102
- */
103
- provider: any;
104
- /**
105
- * Optional function to customize how cursors of users are rendered
106
- */
107
- renderCursor?: (user: any) => HTMLElement;
108
- /**
109
- * Optional flag to set when the user label should be shown with the default
110
- * collaboration cursor. Setting to "always" will always show the label,
111
- * while "activity" will only show the label when the user moves the cursor
112
- * or types. Defaults to "activity".
113
- */
114
- showCursorLabels?: "always" | "activity";
115
- };
85
+ collaboration?: CollaborationOptions;
116
86
 
117
87
  /**
118
88
  * Use default BlockNote font and reset the styles of <p> <li> <h1> elements etc., that are used in BlockNote.
@@ -912,13 +882,13 @@ export class BlockNoteEditor<
912
882
  */
913
883
  public onBeforeChange(
914
884
  callback: (context: {
915
- getChanges: () => BlocksChanged<any, any, any>;
885
+ getChanges: () => BlocksChanged<BSchema, ISchema, SSchema>;
916
886
  tr: Transaction;
917
887
  }) => boolean | void,
918
- ) {
888
+ ): () => void {
919
889
  return this._extensionManager
920
- .getExtension(BlockChangeExtension)
921
- ?.subscribe(callback);
890
+ .getExtension(BlockChangeExtension)!
891
+ .subscribe(callback);
922
892
  }
923
893
 
924
894
  /**
@@ -963,8 +933,8 @@ export class BlockNoteEditor<
963
933
  * If the selection starts / ends halfway through a block, the returned block will be
964
934
  * only the part of the block that is included in the selection.
965
935
  */
966
- public getSelectionCutBlocks() {
967
- return this._selectionManager.getSelectionCutBlocks();
936
+ public getSelectionCutBlocks(expandToWords = false) {
937
+ return this._selectionManager.getSelectionCutBlocks(expandToWords);
968
938
  }
969
939
 
970
940
  /**
@@ -89,6 +89,11 @@ export interface Extension<State = any, Key extends string = string> {
89
89
  * Add additional tiptap extensions to the editor.
90
90
  */
91
91
  readonly tiptapExtensions?: ReadonlyArray<AnyExtension>;
92
+
93
+ /**
94
+ * Add additional BlockNote extensions to the editor.
95
+ */
96
+ readonly blockNoteExtensions?: ReadonlyArray<ExtensionFactoryInstance>;
92
97
  }
93
98
 
94
99
  /**
@@ -89,7 +89,7 @@ export class EventManager<
89
89
  }
90
90
  callback(this.editor, {
91
91
  getChanges() {
92
- return getBlocksChangedByTransaction(
92
+ return getBlocksChangedByTransaction<BSchema, I, S>(
93
93
  transaction,
94
94
  appendedTransactions,
95
95
  );
@@ -4,7 +4,7 @@ import {
4
4
  Node,
5
5
  Extension as TiptapExtension,
6
6
  } from "@tiptap/core";
7
- import { Gapcursor } from "@tiptap/extension-gapcursor";
7
+ import { Gapcursor } from "@tiptap/extensions/gap-cursor";
8
8
  import { Link } from "@tiptap/extension-link";
9
9
  import { Text } from "@tiptap/extension-text";
10
10
  import { createDropFileExtension } from "../../../api/clipboard/fromClipboard/fileDropExtension.js";
@@ -14,22 +14,17 @@ import {
14
14
  BlockChangeExtension,
15
15
  DropCursorExtension,
16
16
  FilePanelExtension,
17
- ForkYDocExtension,
18
17
  FormattingToolbarExtension,
19
18
  HistoryExtension,
20
19
  LinkToolbarExtension,
21
20
  NodeSelectionKeyboardExtension,
22
21
  PlaceholderExtension,
23
22
  PreviousBlockTypeExtension,
24
- SchemaMigration,
25
23
  ShowSelectionExtension,
26
24
  SideMenuExtension,
27
25
  SuggestionMenu,
28
26
  TableHandlesExtension,
29
27
  TrailingNodeExtension,
30
- YCursorExtension,
31
- YSyncExtension,
32
- YUndoExtension,
33
28
  } from "../../../extensions/index.js";
34
29
  import {
35
30
  DEFAULT_LINK_PROTOCOL,
@@ -52,6 +47,7 @@ import {
52
47
  BlockNoteEditorOptions,
53
48
  } from "../../BlockNoteEditor.js";
54
49
  import { ExtensionFactoryInstance } from "../../BlockNoteExtension.js";
50
+ import { CollaborationExtension } from "../../../extensions/Collaboration/Collaboration.js";
55
51
 
56
52
  // TODO remove linkify completely by vendoring the link extension & dropping linkifyjs as a dependency
57
53
  let LINKIFY_INITIALIZED = false;
@@ -190,13 +186,7 @@ export function getDefaultExtensions(
190
186
  ] as ExtensionFactoryInstance[];
191
187
 
192
188
  if (options.collaboration) {
193
- extensions.push(ForkYDocExtension(options.collaboration));
194
- if (options.collaboration.provider?.awareness) {
195
- extensions.push(YCursorExtension(options.collaboration));
196
- }
197
- extensions.push(YSyncExtension(options.collaboration));
198
- extensions.push(YUndoExtension(options.collaboration));
199
- extensions.push(SchemaMigration(options.collaboration));
189
+ extensions.push(CollaborationExtension(options.collaboration));
200
190
  } else {
201
191
  // YUndo is not compatible with ProseMirror's history plugin
202
192
  extensions.push(HistoryExtension());
@@ -203,6 +203,12 @@ export class ExtensionManager {
203
203
 
204
204
  this.extensions.push(instance);
205
205
 
206
+ if (instance.blockNoteExtensions) {
207
+ for (const extension of instance.blockNoteExtensions) {
208
+ this.addExtension(extension);
209
+ }
210
+ }
211
+
206
212
  return instance as any;
207
213
  }
208
214
 
@@ -317,8 +323,7 @@ export class ExtensionManager {
317
323
  const tiptapExtensions = getDefaultTiptapExtensions(
318
324
  this.editor,
319
325
  this.options,
320
- );
321
- // TODO filter out the default extensions via the disabledExtensions set?
326
+ ).filter((extension) => !this.disabledExtensions.has(extension.name));
322
327
 
323
328
  const getPriority = sortByDependencies(this.extensions);
324
329
 
@@ -1,3 +1,4 @@
1
+ import { isNodeSelection, posToDOMRect } from "@tiptap/core";
1
2
  import {
2
3
  getSelection,
3
4
  getSelectionCutBlocks,
@@ -7,21 +8,20 @@ import {
7
8
  getTextCursorPosition,
8
9
  setTextCursorPosition,
9
10
  } from "../../api/blockManipulation/selections/textCursorPosition.js";
10
- import { isNodeSelection, posToDOMRect } from "@tiptap/core";
11
+ import {
12
+ DefaultBlockSchema,
13
+ DefaultInlineContentSchema,
14
+ DefaultStyleSchema,
15
+ } from "../../blocks/defaultBlocks.js";
11
16
  import {
12
17
  BlockIdentifier,
13
18
  BlockSchema,
14
19
  InlineContentSchema,
15
20
  StyleSchema,
16
21
  } from "../../schema/index.js";
17
- import {
18
- DefaultBlockSchema,
19
- DefaultInlineContentSchema,
20
- DefaultStyleSchema,
21
- } from "../../blocks/defaultBlocks.js";
22
- import { Selection } from "../selectionTypes.js";
23
- import { TextCursorPosition } from "../cursorPositionTypes.js";
24
22
  import { BlockNoteEditor } from "../BlockNoteEditor.js";
23
+ import { TextCursorPosition } from "../cursorPositionTypes.js";
24
+ import { Selection } from "../selectionTypes.js";
25
25
 
26
26
  export class SelectionManager<
27
27
  BSchema extends BlockSchema = DefaultBlockSchema,
@@ -47,8 +47,8 @@ export class SelectionManager<
47
47
  * If the selection starts / ends halfway through a block, the returned block will be
48
48
  * only the part of the block that is included in the selection.
49
49
  */
50
- public getSelectionCutBlocks() {
51
- return this.editor.transact((tr) => getSelectionCutBlocks(tr));
50
+ public getSelectionCutBlocks(expandToWords = false) {
51
+ return this.editor.transact((tr) => getSelectionCutBlocks(tr, expandToWords));
52
52
  }
53
53
 
54
54
  /**
@@ -20,7 +20,7 @@ export const BlockChangeExtension = createExtension(() => {
20
20
  key: new PluginKey("blockChange"),
21
21
  filterTransaction: (tr) => {
22
22
  let changes:
23
- | ReturnType<typeof getBlocksChangedByTransaction>
23
+ | ReturnType<typeof getBlocksChangedByTransaction<any, any, any>>
24
24
  | undefined = undefined;
25
25
 
26
26
  return beforeChangeCallbacks.reduce((acc, cb) => {
@@ -34,7 +34,7 @@ export const BlockChangeExtension = createExtension(() => {
34
34
  if (changes) {
35
35
  return changes;
36
36
  }
37
- changes = getBlocksChangedByTransaction(tr);
37
+ changes = getBlocksChangedByTransaction<any, any, any>(tr);
38
38
  return changes;
39
39
  },
40
40
  tr,
@@ -0,0 +1,55 @@
1
+ import type * as Y from "yjs";
2
+ import type { Awareness } from "y-protocols/awareness";
3
+ import {
4
+ createExtension,
5
+ ExtensionOptions,
6
+ } from "../../editor/BlockNoteExtension.js";
7
+ import { ForkYDocExtension } from "./ForkYDoc.js";
8
+ import { SchemaMigration } from "./schemaMigration/SchemaMigration.js";
9
+ import { YCursorExtension } from "./YCursorPlugin.js";
10
+ import { YSyncExtension } from "./YSync.js";
11
+ import { YUndoExtension } from "./YUndo.js";
12
+
13
+ export type CollaborationOptions = {
14
+ /**
15
+ * The Yjs XML fragment that's used for collaboration.
16
+ */
17
+ fragment: Y.XmlFragment;
18
+ /**
19
+ * The user info for the current user that's shown to other collaborators.
20
+ */
21
+ user: {
22
+ name: string;
23
+ color: string;
24
+ };
25
+ /**
26
+ * A Yjs provider (used for awareness / cursor information)
27
+ */
28
+ provider?: { awareness?: Awareness };
29
+ /**
30
+ * Optional function to customize how cursors of users are rendered
31
+ */
32
+ renderCursor?: (user: any) => HTMLElement;
33
+ /**
34
+ * Optional flag to set when the user label should be shown with the default
35
+ * collaboration cursor. Setting to "always" will always show the label,
36
+ * while "activity" will only show the label when the user moves the cursor
37
+ * or types. Defaults to "activity".
38
+ */
39
+ showCursorLabels?: "always" | "activity";
40
+ };
41
+
42
+ export const CollaborationExtension = createExtension(
43
+ ({ options }: ExtensionOptions<CollaborationOptions>) => {
44
+ return {
45
+ key: "collaboration",
46
+ blockNoteExtensions: [
47
+ ForkYDocExtension(options),
48
+ YCursorExtension(options),
49
+ YSyncExtension(options),
50
+ YUndoExtension(),
51
+ SchemaMigration(options),
52
+ ],
53
+ } as const;
54
+ },
55
+ );
@@ -5,10 +5,10 @@ import {
5
5
  createStore,
6
6
  ExtensionOptions,
7
7
  } from "../../editor/BlockNoteExtension.js";
8
+ import { CollaborationOptions } from "./Collaboration.js";
8
9
  import { YCursorExtension } from "./YCursorPlugin.js";
9
10
  import { YSyncExtension } from "./YSync.js";
10
11
  import { YUndoExtension } from "./YUndo.js";
11
- import { BlockNoteEditorOptions } from "../../editor/BlockNoteEditor.js";
12
12
 
13
13
  /**
14
14
  * To find a fragment in another ydoc, we need to search for it.
@@ -44,12 +44,7 @@ function findTypeInOtherYdoc<T extends Y.AbstractType<any>>(
44
44
  }
45
45
 
46
46
  export const ForkYDocExtension = createExtension(
47
- ({
48
- editor,
49
- options,
50
- }: ExtensionOptions<
51
- NonNullable<BlockNoteEditorOptions<any, any, any>["collaboration"]>
52
- >) => {
47
+ ({ editor, options }: ExtensionOptions<CollaborationOptions>) => {
53
48
  let forkedState:
54
49
  | {
55
50
  originalFragment: Y.XmlFragment;
@@ -107,7 +102,7 @@ export const ForkYDocExtension = createExtension(
107
102
  editor.registerExtension([
108
103
  YSyncExtension(newOptions),
109
104
  // No need to register the cursor plugin again, it's a local fork
110
- YUndoExtension({}),
105
+ YUndoExtension(),
111
106
  ]);
112
107
 
113
108
  // Tell the store that the editor is now forked
@@ -131,7 +126,7 @@ export const ForkYDocExtension = createExtension(
131
126
  editor.registerExtension([
132
127
  YSyncExtension(options),
133
128
  YCursorExtension(options),
134
- YUndoExtension({}),
129
+ YUndoExtension(),
135
130
  ]);
136
131
 
137
132
  // Reset the undo stack to the original undo stack
@@ -3,7 +3,7 @@ import {
3
3
  createExtension,
4
4
  ExtensionOptions,
5
5
  } from "../../editor/BlockNoteExtension.js";
6
- import { BlockNoteEditorOptions } from "../../editor/BlockNoteEditor.js";
6
+ import { CollaborationOptions } from "./Collaboration.js";
7
7
 
8
8
  export type CollaborationUser = {
9
9
  name: string;
@@ -67,30 +67,24 @@ function defaultCursorRender(user: CollaborationUser) {
67
67
  }
68
68
 
69
69
  export const YCursorExtension = createExtension(
70
- ({
71
- options,
72
- }: ExtensionOptions<
73
- NonNullable<BlockNoteEditorOptions<any, any, any>["collaboration"]>
74
- >) => {
70
+ ({ options }: ExtensionOptions<CollaborationOptions>) => {
75
71
  const recentlyUpdatedCursors = new Map();
76
-
77
- if (
72
+ const awareness =
78
73
  options.provider &&
79
74
  "awareness" in options.provider &&
80
75
  typeof options.provider.awareness === "object"
81
- ) {
76
+ ? options.provider.awareness
77
+ : undefined;
78
+ if (awareness) {
82
79
  if (
83
- "setLocalStateField" in options.provider.awareness &&
84
- typeof options.provider.awareness.setLocalStateField === "function"
80
+ "setLocalStateField" in awareness &&
81
+ typeof awareness.setLocalStateField === "function"
85
82
  ) {
86
- options.provider.awareness.setLocalStateField("user", options.user);
83
+ awareness.setLocalStateField("user", options.user);
87
84
  }
88
- if (
89
- "on" in options.provider.awareness &&
90
- typeof options.provider.awareness.on === "function"
91
- ) {
85
+ if ("on" in awareness && typeof awareness.on === "function") {
92
86
  if (options.showCursorLabels !== "always") {
93
- options.provider.awareness.on(
87
+ awareness.on(
94
88
  "change",
95
89
  ({
96
90
  updated,
@@ -126,57 +120,59 @@ export const YCursorExtension = createExtension(
126
120
  return {
127
121
  key: "yCursor",
128
122
  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,
123
+ awareness
124
+ ? yCursorPlugin(awareness, {
125
+ selectionBuilder: defaultSelectionBuilder,
126
+ cursorBuilder(user: CollaborationUser, clientID: number) {
127
+ let cursorData = recentlyUpdatedCursors.get(clientID);
128
+
129
+ if (!cursorData) {
130
+ const cursorElement = (
131
+ options.renderCursor ?? defaultCursorRender
132
+ )(user);
133
+
134
+ if (options.showCursorLabels !== "always") {
135
+ cursorElement.addEventListener("mouseenter", () => {
136
+ const cursor = recentlyUpdatedCursors.get(clientID)!;
137
+ cursor.element.setAttribute("data-active", "");
138
+
139
+ if (cursor.hideTimeout) {
140
+ clearTimeout(cursor.hideTimeout);
141
+ recentlyUpdatedCursors.set(clientID, {
142
+ element: cursor.element,
143
+ hideTimeout: undefined,
144
+ });
145
+ }
149
146
  });
150
- }
151
- });
152
147
 
153
- cursorElement.addEventListener("mouseleave", () => {
154
- const cursor = recentlyUpdatedCursors.get(clientID)!;
148
+ cursorElement.addEventListener("mouseleave", () => {
149
+ const cursor = recentlyUpdatedCursors.get(clientID)!;
155
150
 
156
- recentlyUpdatedCursors.set(clientID, {
157
- element: cursor.element,
158
- hideTimeout: setTimeout(() => {
159
- cursor.element.removeAttribute("data-active");
160
- }, 2000),
161
- });
162
- });
163
- }
151
+ recentlyUpdatedCursors.set(clientID, {
152
+ element: cursor.element,
153
+ hideTimeout: setTimeout(() => {
154
+ cursor.element.removeAttribute("data-active");
155
+ }, 2000),
156
+ });
157
+ });
158
+ }
164
159
 
165
- cursorData = {
166
- element: cursorElement,
167
- hideTimeout: undefined,
168
- };
160
+ cursorData = {
161
+ element: cursorElement,
162
+ hideTimeout: undefined,
163
+ };
169
164
 
170
- recentlyUpdatedCursors.set(clientID, cursorData);
171
- }
165
+ recentlyUpdatedCursors.set(clientID, cursorData);
166
+ }
172
167
 
173
- return cursorData.element;
174
- },
175
- }),
176
- ],
168
+ return cursorData.element;
169
+ },
170
+ })
171
+ : undefined,
172
+ ].filter(Boolean),
177
173
  dependsOn: ["ySync"],
178
174
  updateUser(user: { name: string; color: string; [key: string]: string }) {
179
- options.provider.awareness.setLocalStateField("user", user);
175
+ awareness?.setLocalStateField("user", user);
180
176
  },
181
177
  } as const;
182
178
  },
@@ -1,12 +1,12 @@
1
1
  import { ySyncPlugin } from "y-prosemirror";
2
- import { XmlFragment } from "yjs";
3
2
  import {
4
3
  ExtensionOptions,
5
4
  createExtension,
6
5
  } from "../../editor/BlockNoteExtension.js";
6
+ import { CollaborationOptions } from "./Collaboration.js";
7
7
 
8
8
  export const YSyncExtension = createExtension(
9
- ({ options }: ExtensionOptions<{ fragment: XmlFragment }>) => {
9
+ ({ options }: ExtensionOptions<Pick<CollaborationOptions, "fragment">>) => {
10
10
  return {
11
11
  key: "ySync",
12
12
  prosemirrorPlugins: [ySyncPlugin(options.fragment)],
@@ -1,10 +1,10 @@
1
1
  import { redoCommand, undoCommand, yUndoPlugin } from "y-prosemirror";
2
2
  import { createExtension } from "../../editor/BlockNoteExtension.js";
3
3
 
4
- export const YUndoExtension = createExtension(({ editor }) => {
4
+ export const YUndoExtension = createExtension(() => {
5
5
  return {
6
6
  key: "yUndo",
7
- prosemirrorPlugins: [yUndoPlugin({ trackedOrigins: [editor] })],
7
+ prosemirrorPlugins: [yUndoPlugin()],
8
8
  dependsOn: ["yCursor", "ySync"],
9
9
  undoCommand: undoCommand,
10
10
  redoCommand: redoCommand,
@@ -1,6 +1,6 @@
1
1
  import { getMarkRange, posToDOMRect } from "@tiptap/core";
2
- import { createExtension } from "../../editor/BlockNoteExtension.js";
3
2
  import { getPmSchema } from "../../api/pmUtil.js";
3
+ import { createExtension } from "../../editor/BlockNoteExtension.js";
4
4
 
5
5
  export const LinkToolbarExtension = createExtension(({ editor }) => {
6
6
  function getLinkElementAtPos(pos: number) {
@@ -14,7 +14,7 @@ const PLUGIN_KEY = new PluginKey(`blocknote-show-selection`);
14
14
  */
15
15
  export const ShowSelectionExtension = createExtension(({ editor }) => {
16
16
  const store = createStore(
17
- { enabled: false },
17
+ { enabledSet: new Set<string>() },
18
18
  {
19
19
  onUpdate() {
20
20
  editor.transact((tr) => tr.setMeta(PLUGIN_KEY, {}));
@@ -30,7 +30,7 @@ export const ShowSelectionExtension = createExtension(({ editor }) => {
30
30
  props: {
31
31
  decorations: (state) => {
32
32
  const { doc, selection } = state;
33
- if (!store.state.enabled) {
33
+ if (store.state.enabledSet.size === 0) {
34
34
  return DecorationSet.empty;
35
35
  }
36
36
  const dec = Decoration.inline(selection.from, selection.to, {
@@ -43,9 +43,19 @@ export const ShowSelectionExtension = createExtension(({ editor }) => {
43
43
  ],
44
44
  /**
45
45
  * Show or hide the selection decoration
46
+ *
47
+ * @param shouldShow - Whether to show the selection decoration
48
+ * @param key - The key of the selection to show or hide,
49
+ * this is necessary to prevent disabling ShowSelection from one place
50
+ * will interfere with other parts of the code that need to show the selection decoration
51
+ * (e.g.: CreateLinkButton and AIExtension)
46
52
  */
47
- showSelection(shouldShow: boolean) {
48
- store.setState({ enabled: shouldShow });
53
+ showSelection(shouldShow: boolean, key: string) {
54
+ store.setState({
55
+ enabledSet: shouldShow
56
+ ? new Set([...store.state.enabledSet, key])
57
+ : new Set([...store.state.enabledSet].filter((k) => k !== key)),
58
+ });
49
59
  },
50
60
  } as const;
51
61
  });