@blocknote/core 0.25.2 → 0.27.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 (153) hide show
  1. package/dist/blocknote.cjs +11 -10
  2. package/dist/blocknote.cjs.map +1 -1
  3. package/dist/blocknote.js +3722 -9856
  4. package/dist/blocknote.js.map +1 -1
  5. package/dist/comments.cjs +1 -1
  6. package/dist/comments.cjs.map +1 -1
  7. package/dist/comments.js +45 -44
  8. package/dist/comments.js.map +1 -1
  9. package/dist/en-B7ycW7c8.js +360 -0
  10. package/dist/en-B7ycW7c8.js.map +1 -0
  11. package/dist/en-D4taoCs4.cjs +2 -0
  12. package/dist/en-D4taoCs4.cjs.map +1 -0
  13. package/dist/locales.cjs +2 -0
  14. package/dist/locales.cjs.map +1 -0
  15. package/dist/locales.js +6129 -0
  16. package/dist/locales.js.map +1 -0
  17. package/dist/style.css +1 -1
  18. package/dist/tsconfig.tsbuildinfo +1 -1
  19. package/dist/webpack-stats.json +1 -1
  20. package/package.json +36 -27
  21. package/src/api/clipboard/__snapshots__/internal/basicBlocks.html +1 -1
  22. package/src/api/clipboard/__snapshots__/internal/basicBlocksWithProps.html +1 -1
  23. package/src/api/clipboard/clipboardExternal.test.ts +1 -1
  24. package/src/api/clipboard/clipboardInternal.test.ts +1 -1
  25. package/src/api/clipboard/fromClipboard/acceptedMIMETypes.ts +1 -0
  26. package/src/api/clipboard/fromClipboard/pasteExtension.ts +96 -42
  27. package/src/api/exporters/html/__snapshots__/codeBlock/contains-newlines/external.html +1 -1
  28. package/src/api/exporters/html/__snapshots__/codeBlock/contains-newlines/internal.html +1 -1
  29. package/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/external.html +1 -1
  30. package/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/internal.html +1 -1
  31. package/src/api/exporters/html/__snapshots__/codeBlock/empty/external.html +1 -1
  32. package/src/api/exporters/html/__snapshots__/codeBlock/empty/internal.html +1 -1
  33. package/src/api/exporters/html/__snapshots__/codeBlock/python/external.html +1 -1
  34. package/src/api/exporters/html/__snapshots__/codeBlock/python/internal.html +1 -1
  35. package/src/api/exporters/html/htmlConversion.test.ts +2 -2
  36. package/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +4 -4
  37. package/src/api/exporters/markdown/__snapshots__/codeBlock/defaultLanguage/markdown.md +1 -1
  38. package/src/api/exporters/markdown/__snapshots__/codeBlock/empty/markdown.md +1 -1
  39. package/src/api/exporters/markdown/__snapshots__/complex/misc/markdown.md +1 -1
  40. package/src/api/exporters/markdown/__snapshots__/lists/basic/markdown.md +8 -6
  41. package/src/api/exporters/markdown/__snapshots__/lists/nested/markdown.md +6 -6
  42. package/src/api/exporters/markdown/markdownExporter.test.ts +2 -2
  43. package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +2 -2
  44. package/src/api/nodeConversions/nodeToBlock.ts +1 -0
  45. package/src/api/parsers/html/__snapshots__/parse-2-tables.json +129 -0
  46. package/src/api/parsers/html/__snapshots__/parse-codeblocks.json +1 -1
  47. package/src/api/parsers/html/parseHTML.test.ts +36 -1
  48. package/src/api/parsers/markdown/__snapshots__/pasted/whitespace bold.json +42 -0
  49. package/src/api/parsers/markdown/__snapshots__/whitespace bold.json +19 -0
  50. package/src/api/parsers/markdown/detectMarkdown.ts +60 -0
  51. package/src/api/parsers/markdown/parseMarkdown.test.ts +7 -2
  52. package/src/api/parsers/markdown/parseMarkdown.ts +19 -18
  53. package/src/api/testUtil/cases/defaultSchema.ts +13 -0
  54. package/src/blocks/CodeBlockContent/CodeBlockContent.ts +100 -69
  55. package/src/blocks/QuoteBlockContent/QuoteBlockContent.ts +98 -0
  56. package/src/blocks/TableBlockContent/TableExtension.ts +1 -1
  57. package/src/blocks/defaultBlocks.ts +2 -2
  58. package/src/comments/threadstore/yjs/YjsThreadStore.ts +1 -0
  59. package/src/comments/threadstore/yjs/yjsHelpers.ts +3 -1
  60. package/src/comments/types.ts +4 -0
  61. package/src/editor/Block.css +14 -1
  62. package/src/editor/BlockNoteEditor.ts +103 -11
  63. package/src/editor/BlockNoteExtensions.ts +18 -4
  64. package/src/editor/BlockNoteTipTapEditor.ts +18 -7
  65. package/src/extensions/Comments/CommentsPlugin.ts +77 -30
  66. package/src/extensions/HardBreak/HardBreak.ts +35 -0
  67. package/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +100 -3
  68. package/src/extensions/SideMenu/dragging.ts +13 -0
  69. package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +3 -1
  70. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +18 -2
  71. package/src/extensions/TableHandles/TableHandlesPlugin.ts +24 -23
  72. package/src/i18n/index.ts +2 -0
  73. package/src/i18n/locales/ar.ts +10 -0
  74. package/src/i18n/locales/de.ts +21 -1
  75. package/src/i18n/locales/en.ts +10 -0
  76. package/src/i18n/locales/es.ts +21 -1
  77. package/src/i18n/locales/fr.ts +10 -0
  78. package/src/i18n/locales/hr.ts +27 -4
  79. package/src/i18n/locales/is.ts +10 -0
  80. package/src/i18n/locales/it.ts +21 -1
  81. package/src/i18n/locales/ja.ts +10 -0
  82. package/src/i18n/locales/ko.ts +10 -0
  83. package/src/i18n/locales/nl.ts +10 -0
  84. package/src/i18n/locales/no.ts +10 -0
  85. package/src/i18n/locales/pl.ts +10 -0
  86. package/src/i18n/locales/pt.ts +10 -0
  87. package/src/i18n/locales/ru.ts +10 -0
  88. package/src/i18n/locales/uk.ts +10 -0
  89. package/src/i18n/locales/vi.ts +10 -0
  90. package/src/i18n/locales/zh.ts +10 -0
  91. package/src/index.ts +2 -3
  92. package/src/locales.ts +1 -0
  93. package/src/schema/blocks/types.ts +1 -0
  94. package/types/src/api/blockManipulation/commands/updateBlock/updateBlock.d.ts +1 -1
  95. package/types/src/api/blockManipulation/setupTestEnv.d.ts +34 -2
  96. package/types/src/api/clipboard/fromClipboard/acceptedMIMETypes.d.ts +1 -1
  97. package/types/src/api/clipboard/fromClipboard/fileDropExtension.d.ts +2 -2
  98. package/types/src/api/clipboard/fromClipboard/pasteExtension.d.ts +3 -3
  99. package/types/src/api/clipboard/testUtil.d.ts +66 -34
  100. package/types/src/api/clipboard/toClipboard/copyExtension.d.ts +1 -1
  101. package/types/src/api/exporters/html/externalHTMLExporter.d.ts +2 -2
  102. package/types/src/api/exporters/html/internalHTMLSerializer.d.ts +2 -2
  103. package/types/src/api/exporters/html/util/serializeBlocksExternalHTML.d.ts +1 -1
  104. package/types/src/api/exporters/html/util/serializeBlocksInternalHTML.d.ts +1 -1
  105. package/types/src/api/parsers/markdown/detectMarkdown.d.ts +6 -0
  106. package/types/src/api/parsers/markdown/parseMarkdown.d.ts +1 -0
  107. package/types/src/api/testUtil/cases/customBlocks.d.ts +72 -40
  108. package/types/src/api/testUtil/cases/customInlineContent.d.ts +34 -2
  109. package/types/src/api/testUtil/cases/customStyles.d.ts +34 -2
  110. package/types/src/blocks/AudioBlockContent/AudioBlockContent.d.ts +3 -3
  111. package/types/src/blocks/CodeBlockContent/CodeBlockContent.d.ts +46 -34
  112. package/types/src/blocks/FileBlockContent/FileBlockContent.d.ts +3 -3
  113. package/types/src/blocks/FileBlockContent/helpers/render/createFileBlockWrapper.d.ts +1 -1
  114. package/types/src/blocks/HeadingBlockContent/HeadingBlockContent.d.ts +2 -2
  115. package/types/src/blocks/ImageBlockContent/ImageBlockContent.d.ts +2 -2
  116. package/types/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.d.ts +2 -2
  117. package/types/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.d.ts +2 -2
  118. package/types/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.d.ts +2 -2
  119. package/types/src/blocks/PageBreakBlockContent/PageBreakBlockContent.d.ts +2 -2
  120. package/types/src/blocks/PageBreakBlockContent/schema.d.ts +13 -13
  121. package/types/src/blocks/ParagraphBlockContent/ParagraphBlockContent.d.ts +2 -2
  122. package/types/src/blocks/QuoteBlockContent/QuoteBlockContent.d.ts +52 -0
  123. package/types/src/blocks/TableBlockContent/TableBlockContent.d.ts +2 -2
  124. package/types/src/blocks/VideoBlockContent/VideoBlockContent.d.ts +2 -2
  125. package/types/src/blocks/defaultBlockHelpers.d.ts +2 -2
  126. package/types/src/blocks/defaultBlocks.d.ts +107 -44
  127. package/types/src/comments/threadstore/yjs/YjsThreadStore.d.ts +3 -3
  128. package/types/src/comments/types.d.ts +4 -0
  129. package/types/src/editor/BlockNoteEditor.d.ts +58 -0
  130. package/types/src/editor/BlockNoteExtensions.d.ts +3 -2
  131. package/types/src/editor/BlockNoteTipTapEditor.d.ts +2 -1
  132. package/types/src/exporter/mapping.d.ts +2 -2
  133. package/types/src/extensions/BackgroundColor/BackgroundColorMark.d.ts +1 -1
  134. package/types/src/extensions/Collaboration/createCollaborationExtensions.d.ts +1 -1
  135. package/types/src/extensions/Comments/CommentsPlugin.d.ts +16 -1
  136. package/types/src/extensions/HardBreak/HardBreak.d.ts +2 -0
  137. package/types/src/extensions/TableHandles/TableHandlesPlugin.d.ts +4 -4
  138. package/types/src/extensions/TextColor/TextColorMark.d.ts +1 -1
  139. package/types/src/i18n/index.d.ts +2 -0
  140. package/types/src/i18n/locales/de.d.ts +2 -304
  141. package/types/src/i18n/locales/en.d.ts +11 -1
  142. package/types/src/i18n/locales/es.d.ts +2 -269
  143. package/types/src/i18n/locales/hr.d.ts +2 -266
  144. package/types/src/i18n/locales/it.d.ts +2 -269
  145. package/types/src/index.d.ts +1 -2
  146. package/types/src/locales.d.ts +1 -0
  147. package/types/src/pm-nodes/BlockContainer.d.ts +2 -7
  148. package/types/src/pm-nodes/BlockGroup.d.ts +2 -7
  149. package/types/src/schema/blocks/types.d.ts +1 -0
  150. package/types/src/schema/inlineContent/internal.d.ts +1 -1
  151. package/README.md +0 -125
  152. package/src/blocks/CodeBlockContent/defaultSupportedLanguages.ts +0 -116
  153. package/types/src/blocks/CodeBlockContent/shiki.bundle.d.ts +0 -82
@@ -1,6 +1,5 @@
1
1
  import { AnyExtension, Extension, extensions } from "@tiptap/core";
2
2
  import { Gapcursor } from "@tiptap/extension-gapcursor";
3
- import { HardBreak } from "@tiptap/extension-hard-break";
4
3
  import { History } from "@tiptap/extension-history";
5
4
  import { Link } from "@tiptap/extension-link";
6
5
  import { Text } from "@tiptap/extension-text";
@@ -17,6 +16,7 @@ import { CommentsPlugin } from "../extensions/Comments/CommentsPlugin.js";
17
16
  import type { ThreadStore } from "../comments/index.js";
18
17
  import { FilePanelProsemirrorPlugin } from "../extensions/FilePanel/FilePanelPlugin.js";
19
18
  import { FormattingToolbarProsemirrorPlugin } from "../extensions/FormattingToolbar/FormattingToolbarPlugin.js";
19
+ import { HardBreak } from "../extensions/HardBreak/HardBreak.js";
20
20
  import { KeyboardShortcutsExtension } from "../extensions/KeyboardShortcuts/KeyboardShortcutsExtension.js";
21
21
  import { LinkToolbarProsemirrorPlugin } from "../extensions/LinkToolbar/LinkToolbarPlugin.js";
22
22
  import {
@@ -44,7 +44,11 @@ import {
44
44
  StyleSchema,
45
45
  StyleSpecs,
46
46
  } from "../schema/index.js";
47
- import type { BlockNoteEditor, BlockNoteExtension } from "./BlockNoteEditor.js";
47
+ import type {
48
+ BlockNoteEditor,
49
+ BlockNoteEditorOptions,
50
+ BlockNoteExtension,
51
+ } from "./BlockNoteEditor.js";
48
52
 
49
53
  type ExtensionOptions<
50
54
  BSchema extends BlockSchema,
@@ -82,6 +86,7 @@ type ExtensionOptions<
82
86
  comments?: {
83
87
  threadStore: ThreadStore;
84
88
  };
89
+ pasteHandler: BlockNoteEditorOptions<any, any, any>["pasteHandler"];
85
90
  };
86
91
 
87
92
  /**
@@ -179,7 +184,7 @@ const getTipTapExtensions = <
179
184
  types: ["blockContainer", "columnList", "column"],
180
185
  setIdAttribute: opts.setIdAttribute,
181
186
  }),
182
- HardBreak.extend({ priority: 10 }),
187
+ HardBreak,
183
188
  // Comments,
184
189
 
185
190
  // basics:
@@ -258,7 +263,16 @@ const getTipTapExtensions = <
258
263
  ];
259
264
  }),
260
265
  createCopyToClipboardExtension(opts.editor),
261
- createPasteFromClipboardExtension(opts.editor),
266
+ createPasteFromClipboardExtension(
267
+ opts.editor,
268
+ opts.pasteHandler ||
269
+ ((context: {
270
+ defaultPasteHandler: (context?: {
271
+ prioritizeMarkdownOverHTML?: boolean;
272
+ plainTextAsMarkdown?: boolean;
273
+ }) => boolean | undefined;
274
+ }) => context.defaultPasteHandler())
275
+ ),
262
276
  createDropFileExtension(opts.editor),
263
277
 
264
278
  // This needs to be at the bottom of this list, because Key events (such as enter, when selecting a /command),
@@ -10,6 +10,7 @@ import { EditorState, Transaction } from "@tiptap/pm/state";
10
10
  import { blockToNode } from "../api/nodeConversions/blockToNode.js";
11
11
  import { PartialBlock } from "../blocks/defaultBlocks.js";
12
12
  import { StyleSchema } from "../schema/index.js";
13
+ import type { BlockNoteEditor } from "./BlockNoteEditor.js";
13
14
 
14
15
  export type BlockNoteTipTapEditorOptions = Partial<
15
16
  Omit<EditorOptions, "content">
@@ -149,7 +150,10 @@ export class BlockNoteTipTapEditor extends TiptapEditor {
149
150
  /**
150
151
  * Replace the default `createView` method with a custom one - which we call on mount
151
152
  */
152
- private createViewAlternative(contentComponent?: any) {
153
+ private createViewAlternative(
154
+ blockNoteEditor: BlockNoteEditor<any, any, any>,
155
+ contentComponent?: any
156
+ ) {
153
157
  (this as any).contentComponent = contentComponent;
154
158
 
155
159
  const markViews: any = {};
@@ -157,7 +161,8 @@ export class BlockNoteTipTapEditor extends TiptapEditor {
157
161
  if (extension.type === "mark" && extension.config.addMarkView) {
158
162
  // Note: migrate to using `addMarkView` from tiptap as soon as this lands
159
163
  // (currently tiptap doesn't support markviews)
160
- markViews[extension.name] = extension.config.addMarkView;
164
+ markViews[extension.name] =
165
+ extension.config.addMarkView(blockNoteEditor);
161
166
  }
162
167
  });
163
168
 
@@ -169,19 +174,21 @@ export class BlockNoteTipTapEditor extends TiptapEditor {
169
174
  dispatchTransaction: this.dispatchTransaction.bind(this),
170
175
  state: this.state,
171
176
  markViews,
177
+ nodeViews: this.extensionManager.nodeViews,
172
178
  }
173
179
  );
174
180
 
175
181
  // `editor.view` is not yet available at this time.
176
- // Therefore we will add all plugins and node views directly afterwards.
182
+ // Therefore we will add all plugins directly afterwards.
183
+ //
184
+ // To research: this is the default tiptap behavior, but might actually not be necessary
185
+ // it feels like it's a workaround for plugins that don't account for the view not being available yet
177
186
  const newState = this.state.reconfigure({
178
187
  plugins: this.extensionManager.plugins,
179
188
  });
180
189
 
181
190
  this.view.updateState(newState);
182
191
 
183
- this.createNodeViews();
184
-
185
192
  // emit the created event, call here manually because we blocked the default call in the constructor
186
193
  // (https://github.com/ueberdosis/tiptap/blob/45bac803283446795ad1b03f43d3746fa54a68ff/packages/core/src/Editor.ts#L117)
187
194
  this.commands.focus(
@@ -198,12 +205,16 @@ export class BlockNoteTipTapEditor extends TiptapEditor {
198
205
  *
199
206
  * @param element DOM element to mount to, ur null / undefined to destroy
200
207
  */
201
- public mount = (element?: HTMLElement | null, contentComponent?: any) => {
208
+ public mount = (
209
+ blockNoteEditor: BlockNoteEditor<any, any, any>,
210
+ element?: HTMLElement | null,
211
+ contentComponent?: any
212
+ ) => {
202
213
  if (!element) {
203
214
  this.destroy();
204
215
  } else {
205
216
  this.options.element = element;
206
- this.createViewAlternative(contentComponent);
217
+ this.createViewAlternative(blockNoteEditor, contentComponent);
207
218
  }
208
219
  };
209
220
  }
@@ -16,11 +16,6 @@ const PLUGIN_KEY = new PluginKey(`blocknote-comments`);
16
16
  const SET_SELECTED_THREAD_ID = "SET_SELECTED_THREAD_ID";
17
17
 
18
18
  type CommentsPluginState = {
19
- /**
20
- * Store the positions of all threads in the document.
21
- * this can be used later to implement a floating sidebar
22
- */
23
- threadPositions: Map<string, { from: number; to: number }>;
24
19
  /**
25
20
  * Decorations to be rendered, specifically to indicate the selected thread
26
21
  */
@@ -28,15 +23,11 @@ type CommentsPluginState = {
28
23
  };
29
24
 
30
25
  /**
31
- * Get a new state (theadPositions and decorations) from the current document state
26
+ * Calculate the thread positions from the current document state
32
27
  */
33
- function updateState(
34
- doc: Node,
35
- selectedThreadId: string | undefined,
36
- markType: string
37
- ): CommentsPluginState {
28
+ function getUpdatedThreadPositions(doc: Node, markType: string) {
38
29
  const threadPositions = new Map<string, { from: number; to: number }>();
39
- const decorations: Decoration[] = [];
30
+
40
31
  // find all thread marks and store their position + create decoration for selected thread
41
32
  doc.descendants((node, pos) => {
42
33
  node.marks.forEach((mark) => {
@@ -59,34 +50,38 @@ function updateState(
59
50
  from: Math.min(from, currentPosition.from),
60
51
  to: Math.max(to, currentPosition.to),
61
52
  });
62
-
63
- if (selectedThreadId === thisThreadId) {
64
- decorations.push(
65
- Decoration.inline(from, to, {
66
- class: "bn-thread-mark-selected",
67
- })
68
- );
69
- }
70
53
  }
71
54
  });
72
55
  });
73
- return {
74
- decorations: DecorationSet.create(doc, decorations),
75
- threadPositions,
76
- };
56
+ return threadPositions;
77
57
  }
78
58
 
79
59
  export class CommentsPlugin extends EventEmitter<any> {
80
60
  public readonly plugin: Plugin;
81
61
  public readonly userStore: UserStore<User>;
82
62
 
63
+ /**
64
+ * Whether a comment is currently being composed
65
+ */
83
66
  private pendingComment = false;
67
+
68
+ /**
69
+ * The currently selected thread id
70
+ */
84
71
  private selectedThreadId: string | undefined;
85
72
 
73
+ /**
74
+ * Store the positions of all threads in the document.
75
+ * this can be used later to implement a floating sidebar
76
+ */
77
+ private threadPositions: Map<string, { from: number; to: number }> =
78
+ new Map();
79
+
86
80
  private emitStateUpdate() {
87
81
  this.emit("update", {
88
82
  selectedThreadId: this.selectedThreadId,
89
83
  pendingComment: this.pendingComment,
84
+ threadPositions: this.threadPositions,
90
85
  });
91
86
  }
92
87
 
@@ -111,7 +106,7 @@ export class CommentsPlugin extends EventEmitter<any> {
111
106
  pos + node.nodeSize,
112
107
  ttEditor.state.doc.content.size - 1
113
108
  );
114
- tr.removeMark(trimmedFrom, trimmedTo, markType);
109
+ tr.removeMark(trimmedFrom, trimmedTo, mark);
115
110
  tr.addMark(
116
111
  trimmedFrom,
117
112
  trimmedTo,
@@ -168,18 +163,51 @@ export class CommentsPlugin extends EventEmitter<any> {
168
163
  state: {
169
164
  init() {
170
165
  return {
171
- threadPositions: new Map<string, { from: number; to: number }>(),
172
166
  decorations: DecorationSet.empty,
173
167
  };
174
168
  },
175
169
  apply(tr, state) {
176
170
  const action = tr.getMeta(PLUGIN_KEY);
171
+
177
172
  if (!tr.docChanged && !action) {
178
173
  return state;
179
174
  }
180
175
 
181
- // The doc changed or the selected thread changed
182
- return updateState(tr.doc, self.selectedThreadId, markType);
176
+ // only update threadPositions if the doc changed
177
+ const threadPositions = tr.docChanged
178
+ ? getUpdatedThreadPositions(tr.doc, self.markType)
179
+ : self.threadPositions;
180
+
181
+ if (threadPositions.size > 0 || self.threadPositions.size > 0) {
182
+ // small optimization; don't emit event if threadPositions before / after were both empty
183
+ self.threadPositions = threadPositions;
184
+ self.emitStateUpdate();
185
+ }
186
+
187
+ // update decorations if doc or selected thread changed
188
+ const decorations = [];
189
+
190
+ if (self.selectedThreadId) {
191
+ const selectedThreadPosition = threadPositions.get(
192
+ self.selectedThreadId
193
+ );
194
+
195
+ if (selectedThreadPosition) {
196
+ decorations.push(
197
+ Decoration.inline(
198
+ selectedThreadPosition.from,
199
+ selectedThreadPosition.to,
200
+ {
201
+ class: "bn-thread-mark-selected",
202
+ }
203
+ )
204
+ );
205
+ }
206
+ }
207
+
208
+ return {
209
+ decorations: DecorationSet.create(tr.doc, decorations),
210
+ };
183
211
  },
184
212
  },
185
213
  props: {
@@ -206,7 +234,7 @@ export class CommentsPlugin extends EventEmitter<any> {
206
234
  );
207
235
 
208
236
  const threadId = commentMark?.attrs.threadId as string | undefined;
209
- self.selectThread(threadId);
237
+ self.selectThread(threadId, false);
210
238
  },
211
239
  },
212
240
  });
@@ -219,6 +247,7 @@ export class CommentsPlugin extends EventEmitter<any> {
219
247
  callback: (state: {
220
248
  pendingComment: boolean;
221
249
  selectedThreadId: string | undefined;
250
+ threadPositions: Map<string, { from: number; to: number }>;
222
251
  }) => void
223
252
  ) {
224
253
  return this.on("update", callback);
@@ -227,7 +256,7 @@ export class CommentsPlugin extends EventEmitter<any> {
227
256
  /**
228
257
  * Set the selected thread
229
258
  */
230
- public selectThread(threadId: string | undefined) {
259
+ public selectThread(threadId: string | undefined, scrollToThread = true) {
231
260
  if (this.selectedThreadId === threadId) {
232
261
  return;
233
262
  }
@@ -238,6 +267,24 @@ export class CommentsPlugin extends EventEmitter<any> {
238
267
  name: SET_SELECTED_THREAD_ID,
239
268
  })
240
269
  );
270
+
271
+ if (threadId && scrollToThread) {
272
+ const selectedThreadPosition = this.threadPositions.get(threadId);
273
+
274
+ if (!selectedThreadPosition) {
275
+ return;
276
+ }
277
+
278
+ // When a new thread is selected, scrolls the page to its reference text in
279
+ // the editor.
280
+ (
281
+ this.editor.prosemirrorView?.domAtPos(selectedThreadPosition.from)
282
+ .node as Element | undefined
283
+ )?.scrollIntoView({
284
+ behavior: "smooth",
285
+ block: "center",
286
+ });
287
+ }
241
288
  }
242
289
 
243
290
  /**
@@ -0,0 +1,35 @@
1
+ // Stripped down version of the TipTap HardBreak extension:
2
+ // https://github.com/ueberdosis/tiptap/blob/f3258d9ee5fb7979102fe63434f6ea4120507311/packages/extension-hard-break/src/hard-break.ts#L80
3
+ // Changes:
4
+ // - Removed options
5
+ // - Removed keyboard shortcuts & moved them to the `KeyboardShortcutsExtension`
6
+ // - Removed `setHardBreak` command (added a simpler version in the Shift+Enter
7
+ // handler in `KeyboardShortcutsExtension`).
8
+ // - Added priority
9
+ import { mergeAttributes, Node } from "@tiptap/core";
10
+
11
+ export const HardBreak = Node.create({
12
+ name: "hardBreak",
13
+
14
+ inline: true,
15
+
16
+ group: "inline",
17
+
18
+ selectable: false,
19
+
20
+ linebreakReplacement: true,
21
+
22
+ priority: 10,
23
+
24
+ parseHTML() {
25
+ return [{ tag: "br" }];
26
+ },
27
+
28
+ renderHTML({ HTMLAttributes }) {
29
+ return ["br", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
30
+ },
31
+
32
+ renderText() {
33
+ return "\n";
34
+ },
35
+ });
@@ -260,6 +260,73 @@ export const KeyboardShortcutsExtension = Extension.create<{
260
260
 
261
261
  return true;
262
262
  }),
263
+ // Deletes the current block if it's an empty block with inline content,
264
+ // and moves the selection to the previous block.
265
+ () =>
266
+ commands.command(({ state }) => {
267
+ const blockInfo = getBlockInfoFromSelection(state);
268
+ if (!blockInfo.isBlockContainer) {
269
+ return false;
270
+ }
271
+
272
+ const blockEmpty =
273
+ blockInfo.blockContent.node.childCount === 0 &&
274
+ blockInfo.blockContent.node.type.spec.content === "inline*";
275
+
276
+ if (blockEmpty) {
277
+ const prevBlockInfo = getPrevBlockInfo(
278
+ state.doc,
279
+ blockInfo.bnBlock.beforePos
280
+ );
281
+ if (!prevBlockInfo || !prevBlockInfo.isBlockContainer) {
282
+ return false;
283
+ }
284
+
285
+ let chainedCommands = chain();
286
+
287
+ if (
288
+ prevBlockInfo.blockContent.node.type.spec.content ===
289
+ "tableRow+"
290
+ ) {
291
+ const tableBlockEndPos = blockInfo.bnBlock.beforePos - 1;
292
+ const tableBlockContentEndPos = tableBlockEndPos - 1;
293
+ const lastRowEndPos = tableBlockContentEndPos - 1;
294
+ const lastCellEndPos = lastRowEndPos - 1;
295
+ const lastCellParagraphEndPos = lastCellEndPos - 1;
296
+
297
+ chainedCommands = chainedCommands.setTextSelection(
298
+ lastCellParagraphEndPos
299
+ );
300
+ } else if (
301
+ prevBlockInfo.blockContent.node.type.spec.content === ""
302
+ ) {
303
+ const nonEditableBlockContentStartPos =
304
+ prevBlockInfo.blockContent.afterPos -
305
+ prevBlockInfo.blockContent.node.nodeSize;
306
+
307
+ chainedCommands = chainedCommands.setNodeSelection(
308
+ nonEditableBlockContentStartPos
309
+ );
310
+ } else {
311
+ const blockContentStartPos =
312
+ prevBlockInfo.blockContent.afterPos -
313
+ prevBlockInfo.blockContent.node.nodeSize;
314
+
315
+ chainedCommands =
316
+ chainedCommands.setTextSelection(blockContentStartPos);
317
+ }
318
+
319
+ return chainedCommands
320
+ .deleteRange({
321
+ from: blockInfo.bnBlock.beforePos,
322
+ to: blockInfo.bnBlock.afterPos,
323
+ })
324
+ .scrollIntoView()
325
+ .run();
326
+ }
327
+
328
+ return false;
329
+ }),
263
330
  // Deletes previous block if it contains no content and isn't a table,
264
331
  // when the selection is empty and at the start of the block. Moves the
265
332
  // current block into the deleted block's place.
@@ -370,8 +437,8 @@ export const KeyboardShortcutsExtension = Extension.create<{
370
437
  }),
371
438
  ]);
372
439
 
373
- const handleEnter = () =>
374
- this.editor.commands.first(({ commands }) => [
440
+ const handleEnter = (withShift = false) => {
441
+ return this.editor.commands.first(({ commands }) => [
375
442
  // Removes a level of nesting if the block is empty & indented, while the selection is also empty & at the start
376
443
  // of the block.
377
444
  () =>
@@ -400,6 +467,34 @@ export const KeyboardShortcutsExtension = Extension.create<{
400
467
  return commands.liftListItem("blockContainer");
401
468
  }
402
469
 
470
+ return false;
471
+ }),
472
+ // Creates a hard break if block is configured to do so.
473
+ () =>
474
+ commands.command(({ state }) => {
475
+ const blockInfo = getBlockInfoFromSelection(state);
476
+
477
+ const blockHardBreakShortcut: "shift+enter" | "enter" | "none" =
478
+ this.options.editor.schema.blockSchema[blockInfo.blockNoteType]
479
+ .hardBreakShortcut ?? "shift+enter";
480
+
481
+ if (blockHardBreakShortcut === "none") {
482
+ return false;
483
+ }
484
+
485
+ if (
486
+ // If shortcut is not configured, or is configured as "shift+enter",
487
+ // create a hard break for shift+enter, but not for enter.
488
+ (blockHardBreakShortcut === "shift+enter" && withShift) ||
489
+ // If shortcut is configured as "enter", create a hard break for
490
+ // both enter and shift+enter.
491
+ blockHardBreakShortcut === "enter"
492
+ ) {
493
+ return commands.insertContent({
494
+ type: "hardBreak",
495
+ });
496
+ }
497
+
403
498
  return false;
404
499
  }),
405
500
  // Creates a new block and moves the selection to it if the current one is empty, while the selection is also
@@ -471,11 +566,13 @@ export const KeyboardShortcutsExtension = Extension.create<{
471
566
  return false;
472
567
  }),
473
568
  ]);
569
+ };
474
570
 
475
571
  return {
476
572
  Backspace: handleBackspace,
477
573
  Delete: handleDelete,
478
- Enter: handleEnter,
574
+ Enter: () => handleEnter(),
575
+ "Shift-Enter": () => handleEnter(true),
479
576
  // Always returning true for tab key presses ensures they're not captured by the browser. Otherwise, they blur the
480
577
  // editor since the browser will try to use tab for keyboard navigation.
481
578
  Tab: () => {
@@ -99,6 +99,19 @@ function setDragImage(view: EditorView, from: number, to = from) {
99
99
  unsetDragImage(view.root);
100
100
  dragImageElement = parentClone;
101
101
 
102
+ // Browsers may have CORS policies which prevents iframes from being
103
+ // manipulated, so better to stay on the safe side and remove them from the
104
+ // drag preview. The drag preview doesn't work with iframes anyway.
105
+ const iframes = dragImageElement.getElementsByTagName("iframe");
106
+ for (let i = 0; i < iframes.length; i++) {
107
+ const iframe = iframes[i];
108
+ const parent = iframe.parentElement;
109
+
110
+ if (parent) {
111
+ parent.removeChild(iframe);
112
+ }
113
+ }
114
+
102
115
  // TODO: This is hacky, need a better way of assigning classes to the editor so that they can also be applied to the
103
116
  // drag preview.
104
117
  const classes = view.dom.className.split(" ");
@@ -86,7 +86,9 @@ class SuggestionMenuView<
86
86
  this.pluginState = stopped ? prev : next;
87
87
 
88
88
  if (stopped || !this.editor.isEditable) {
89
- this.state!.show = false;
89
+ if (this.state) {
90
+ this.state.show = false;
91
+ }
90
92
  this.emitUpdate(this.pluginState!.triggerCharacter);
91
93
 
92
94
  return;
@@ -20,11 +20,15 @@ function setSelectionToNextContentEditableBlock<
20
20
  I extends InlineContentSchema,
21
21
  S extends StyleSchema
22
22
  >(editor: BlockNoteEditor<BSchema, I, S>) {
23
- let block = editor.getTextCursorPosition().block;
23
+ let block: Block<BSchema, I, S> | undefined =
24
+ editor.getTextCursorPosition().block;
24
25
  let contentType = editor.schema.blockSchema[block.type].content;
25
26
 
26
27
  while (contentType === "none") {
27
- block = editor.getTextCursorPosition().nextBlock!;
28
+ block = editor.getTextCursorPosition().nextBlock;
29
+ if (block === undefined) {
30
+ return;
31
+ }
28
32
  contentType = editor.schema.blockSchema[block.type].content as
29
33
  | "inline"
30
34
  | "table"
@@ -121,6 +125,18 @@ export function getDefaultSlashMenuItems<
121
125
  );
122
126
  }
123
127
 
128
+ if (checkDefaultBlockTypeInSchema("quote", editor)) {
129
+ items.push({
130
+ onItemClick: () => {
131
+ insertOrUpdateBlock(editor, {
132
+ type: "quote",
133
+ });
134
+ },
135
+ key: "quote",
136
+ ...editor.dictionary.slash_menu.quote,
137
+ });
138
+ }
139
+
124
140
  if (checkDefaultBlockTypeInSchema("numberedListItem", editor)) {
125
141
  items.push({
126
142
  onItemClick: () => {
@@ -1,10 +1,10 @@
1
1
  import { Plugin, PluginKey, PluginView } from "prosemirror-state";
2
2
  import {
3
+ CellSelection,
3
4
  addColumnAfter,
4
5
  addColumnBefore,
5
6
  addRowAfter,
6
7
  addRowBefore,
7
- CellSelection,
8
8
  deleteColumn,
9
9
  deleteRow,
10
10
  mergeCells,
@@ -12,6 +12,7 @@ import {
12
12
  } from "prosemirror-tables";
13
13
  import { Decoration, DecorationSet, EditorView } from "prosemirror-view";
14
14
  import {
15
+ RelativeCellIndices,
15
16
  addRowsOrColumns,
16
17
  areInSameColumn,
17
18
  canColumnBeDraggedInto,
@@ -22,7 +23,6 @@ import {
22
23
  getDimensionsOfTable,
23
24
  moveColumn,
24
25
  moveRow,
25
- RelativeCellIndices,
26
26
  } from "../../api/blockManipulation/tables/tables.js";
27
27
  import { nodeToBlock } from "../../api/nodeConversions/nodeToBlock.js";
28
28
  import { getNodeById } from "../../api/nodeUtil.js";
@@ -539,7 +539,12 @@ export class TableHandlesView<
539
539
 
540
540
  // Hide handles if the table block has been removed.
541
541
  this.state.block = this.editor.getBlock(this.state.block.id)!;
542
- if (!this.state.block) {
542
+ if (
543
+ !this.state.block ||
544
+ // when collaborating, the table element might be replaced and out of date
545
+ // because yjs replaces the element when for example you change the color via the side menu
546
+ !this.tableElement?.isConnected
547
+ ) {
543
548
  this.state.show = false;
544
549
  this.state.showAddOrRemoveRowsButton = false;
545
550
  this.state.showAddOrRemoveColumnsButton = false;
@@ -569,6 +574,7 @@ export class TableHandlesView<
569
574
 
570
575
  // Update bounding boxes.
571
576
  const tableBody = this.tableElement!.querySelector("tbody");
577
+
572
578
  if (!tableBody) {
573
579
  throw new Error(
574
580
  "Table block does not contain a 'tbody' HTML element. This should never happen."
@@ -656,32 +662,27 @@ export class TableHandlesProsemirrorPlugin<
656
662
  }
657
663
 
658
664
  const decorations: Decoration[] = [];
659
-
660
- if (newIndex === this.view.state.draggingState.originalIndex) {
661
- return DecorationSet.create(state.doc, decorations);
662
- } else if (
663
- this.view.state.draggingState.draggedCellOrientation === "row" &&
664
- !canRowBeDraggedInto(
665
- this.view.state.block,
666
- this.view.state.draggingState.originalIndex,
667
- newIndex
668
- )
669
- ) {
670
- return DecorationSet.create(state.doc, decorations);
671
- } else if (
672
- this.view.state.draggingState.draggedCellOrientation === "col" &&
673
- !canColumnBeDraggedInto(
674
- this.view.state.block,
675
- this.view.state.draggingState.originalIndex,
676
- newIndex
677
- )
665
+ const { block, draggingState } = this.view.state;
666
+ const { originalIndex, draggedCellOrientation } = draggingState;
667
+
668
+ // Return empty decorations if:
669
+ // - Dragging to same position
670
+ // - No block exists
671
+ // - Row drag not allowed
672
+ // - Column drag not allowed
673
+ if (
674
+ newIndex === originalIndex ||
675
+ !block ||
676
+ (draggedCellOrientation === "row" &&
677
+ !canRowBeDraggedInto(block, originalIndex, newIndex)) ||
678
+ (draggedCellOrientation === "col" &&
679
+ !canColumnBeDraggedInto(block, originalIndex, newIndex))
678
680
  ) {
679
681
  return DecorationSet.create(state.doc, decorations);
680
682
  }
681
683
 
682
684
  // Gets the table to show the drop cursor in.
683
685
  const tableResolvedPos = state.doc.resolve(this.view.tablePos + 1);
684
- const originalIndex = this.view.state.draggingState.originalIndex;
685
686
 
686
687
  if (this.view.state.draggingState.draggedCellOrientation === "row") {
687
688
  const cellsInRow = getCellsAtRowHandle(
@@ -0,0 +1,2 @@
1
+ export * from "./locales/index.js";
2
+ export * from "./dictionary.js";