@blocknote/core 0.23.6 → 0.24.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 (43) hide show
  1. package/dist/blocknote.js +690 -550
  2. package/dist/blocknote.js.map +1 -1
  3. package/dist/blocknote.umd.cjs +6 -6
  4. package/dist/blocknote.umd.cjs.map +1 -1
  5. package/dist/tsconfig.tsbuildinfo +1 -1
  6. package/dist/webpack-stats.json +1 -1
  7. package/package.json +2 -2
  8. package/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap +492 -0
  9. package/src/api/blockManipulation/commands/insertBlocks/insertBlocks.test.ts +6 -0
  10. package/src/api/nodeConversions/blockToNode.ts +5 -3
  11. package/src/api/nodeConversions/nodeToBlock.ts +11 -5
  12. package/src/editor/BlockNoteEditor.ts +21 -6
  13. package/src/editor/BlockNoteExtensions.ts +9 -4
  14. package/src/editor/BlockNoteTipTapEditor.ts +37 -36
  15. package/src/extensions/Collaboration/createCollaborationExtensions.ts +4 -2
  16. package/src/extensions/Placeholder/PlaceholderPlugin.ts +69 -54
  17. package/src/extensions/SideMenu/SideMenuPlugin.ts +49 -39
  18. package/src/i18n/locales/ar.ts +3 -0
  19. package/src/i18n/locales/de.ts +3 -0
  20. package/src/i18n/locales/en.ts +4 -1
  21. package/src/i18n/locales/es.ts +3 -0
  22. package/src/i18n/locales/fr.ts +3 -0
  23. package/src/i18n/locales/hr.ts +3 -0
  24. package/src/i18n/locales/is.ts +3 -0
  25. package/src/i18n/locales/it.ts +316 -314
  26. package/src/i18n/locales/ja.ts +3 -0
  27. package/src/i18n/locales/ko.ts +3 -0
  28. package/src/i18n/locales/nl.ts +3 -0
  29. package/src/i18n/locales/pl.ts +3 -0
  30. package/src/i18n/locales/pt.ts +3 -0
  31. package/src/i18n/locales/ru.ts +3 -0
  32. package/src/i18n/locales/uk.ts +337 -278
  33. package/src/i18n/locales/vi.ts +3 -0
  34. package/src/i18n/locales/zh.ts +3 -0
  35. package/types/src/editor/BlockNoteEditor.d.ts +2 -2
  36. package/types/src/editor/BlockNoteExtensions.d.ts +2 -2
  37. package/types/src/editor/BlockNoteTipTapEditor.d.ts +1 -2
  38. package/types/src/extensions/Placeholder/PlaceholderPlugin.d.ts +1 -1
  39. package/types/src/i18n/locales/de.d.ts +3 -0
  40. package/types/src/i18n/locales/en.d.ts +4 -7
  41. package/types/src/i18n/locales/es.d.ts +3 -0
  42. package/types/src/i18n/locales/hr.d.ts +3 -0
  43. package/types/src/i18n/locales/it.d.ts +3 -0
@@ -130,7 +130,10 @@ export type BlockNoteEditorOptions<
130
130
  /**
131
131
  * @deprecated, provide placeholders via dictionary instead
132
132
  */
133
- placeholders: Record<string | "default", string>;
133
+ placeholders: Record<
134
+ string | "default" | "emptyDocument",
135
+ string | undefined
136
+ >;
134
137
 
135
138
  /**
136
139
  * An object containing attributes that should be added to HTML elements of the editor.
@@ -486,7 +489,11 @@ export class BlockNoteEditor<
486
489
  this.resolveFileUrl = newOptions.resolveFileUrl;
487
490
  this.headless = newOptions._headless;
488
491
 
489
- if (newOptions.collaboration && newOptions.initialContent) {
492
+ const collaborationEnabled =
493
+ "collaboration" in this.extensions ||
494
+ "liveblocksExtension" in this.extensions;
495
+
496
+ if (collaborationEnabled && newOptions.initialContent) {
490
497
  // eslint-disable-next-line no-console
491
498
  console.warn(
492
499
  "When using Collaboration, initialContent might cause conflicts, because changes should come from the collaboration provider"
@@ -495,7 +502,7 @@ export class BlockNoteEditor<
495
502
 
496
503
  const initialContent =
497
504
  newOptions.initialContent ||
498
- (options.collaboration
505
+ (collaborationEnabled
499
506
  ? [
500
507
  {
501
508
  type: "paragraph",
@@ -589,8 +596,11 @@ export class BlockNoteEditor<
589
596
  *
590
597
  * @warning Not needed to call manually when using React, use BlockNoteView to take care of mounting
591
598
  */
592
- public mount = (parentElement?: HTMLElement | null) => {
593
- this._tiptapEditor.mount(parentElement);
599
+ public mount = (
600
+ parentElement?: HTMLElement | null,
601
+ contentComponent?: any
602
+ ) => {
603
+ this._tiptapEditor.mount(parentElement, contentComponent);
594
604
  };
595
605
 
596
606
  public get prosemirrorView() {
@@ -924,7 +934,12 @@ export class BlockNoteEditor<
924
934
  for (const mark of marks) {
925
935
  const config = this.schema.styleSchema[mark.type.name];
926
936
  if (!config) {
927
- if (mark.type.name !== "link") {
937
+ if (
938
+ // Links are not considered styles in blocknote
939
+ mark.type.name !== "link" &&
940
+ // "blocknoteIgnore" tagged marks (such as comments) are also not considered BlockNote "styles"
941
+ !mark.type.spec.blocknoteIgnore
942
+ ) {
928
943
  // eslint-disable-next-line no-console
929
944
  console.warn("mark not found in styleschema", mark.type.name);
930
945
  }
@@ -7,11 +7,11 @@ import { Text } from "@tiptap/extension-text";
7
7
  import { Plugin } from "prosemirror-state";
8
8
  import * as Y from "yjs";
9
9
 
10
- import type { BlockNoteEditor, BlockNoteExtension } from "./BlockNoteEditor.js";
11
10
  import { createDropFileExtension } from "../api/clipboard/fromClipboard/fileDropExtension.js";
12
11
  import { createPasteFromClipboardExtension } from "../api/clipboard/fromClipboard/pasteExtension.js";
13
12
  import { createCopyToClipboardExtension } from "../api/clipboard/toClipboard/copyExtension.js";
14
13
  import { BackgroundColorExtension } from "../extensions/BackgroundColor/BackgroundColorExtension.js";
14
+ import { createCollaborationExtensions } from "../extensions/Collaboration/createCollaborationExtensions.js";
15
15
  import { FilePanelProsemirrorPlugin } from "../extensions/FilePanel/FilePanelPlugin.js";
16
16
  import { FormattingToolbarProsemirrorPlugin } from "../extensions/FormattingToolbar/FormattingToolbarPlugin.js";
17
17
  import { KeyboardShortcutsExtension } from "../extensions/KeyboardShortcuts/KeyboardShortcutsExtension.js";
@@ -40,7 +40,7 @@ import {
40
40
  StyleSchema,
41
41
  StyleSpecs,
42
42
  } from "../schema/index.js";
43
- import { createCollaborationExtensions } from "../extensions/Collaboration/createCollaborationExtensions.js";
43
+ import type { BlockNoteEditor, BlockNoteExtension } from "./BlockNoteEditor.js";
44
44
 
45
45
  type ExtensionOptions<
46
46
  BSchema extends BlockSchema,
@@ -69,7 +69,10 @@ type ExtensionOptions<
69
69
  animations: boolean;
70
70
  tableHandles: boolean;
71
71
  dropCursor: (opts: any) => Plugin;
72
- placeholders: Record<string | "default", string>;
72
+ placeholders: Record<
73
+ string | "default" | "emptyDocument",
74
+ string | undefined
75
+ >;
73
76
  tabBehavior?: "prefer-navigate-ui" | "prefer-indent";
74
77
  sideMenuDetection: "viewport" | "editor";
75
78
  };
@@ -171,7 +174,9 @@ const getTipTapExtensions = <
171
174
  protocols: VALID_LINK_PROTOCOLS,
172
175
  }),
173
176
  ...Object.values(opts.styleSpecs).map((styleSpec) => {
174
- return styleSpec.implementation.mark;
177
+ return styleSpec.implementation.mark.configure({
178
+ editor: opts.editor as any,
179
+ });
175
180
  }),
176
181
 
177
182
  TextColorExtension,
@@ -21,10 +21,9 @@ export type BlockNoteTipTapEditorOptions = Partial<
21
21
  * Custom Editor class that extends TiptapEditor and separates
22
22
  * the creation of the view from the constructor.
23
23
  */
24
- // @ts-ignore
25
24
  export class BlockNoteTipTapEditor extends TiptapEditor {
26
25
  private _state: EditorState;
27
- private _creating = false;
26
+
28
27
  public static create = (
29
28
  options: BlockNoteTipTapEditorOptions,
30
29
  styleSchema: StyleSchema
@@ -150,40 +149,44 @@ export class BlockNoteTipTapEditor extends TiptapEditor {
150
149
  /**
151
150
  * Replace the default `createView` method with a custom one - which we call on mount
152
151
  */
153
- private createViewAlternative() {
154
- this._creating = true;
155
- // Without queueMicrotask, custom IC / styles will give a React FlushSync error
156
- queueMicrotask(() => {
157
- if (!this._creating) {
158
- return;
152
+ private createViewAlternative(contentComponent?: any) {
153
+ (this as any).contentComponent = contentComponent;
154
+
155
+ const markViews: any = {};
156
+ this.extensionManager.extensions.forEach((extension) => {
157
+ if (extension.type === "mark" && extension.config.addMarkView) {
158
+ // Note: migrate to using `addMarkView` from tiptap as soon as this lands
159
+ // (currently tiptap doesn't support markviews)
160
+ markViews[extension.name] = extension.config.addMarkView;
159
161
  }
160
- this.view = new EditorView(
161
- { mount: this.options.element as any }, // use mount option so that we reuse the existing element instead of creating a new one
162
- {
163
- ...this.options.editorProps,
164
- // @ts-ignore
165
- dispatchTransaction: this.dispatchTransaction.bind(this),
166
- state: this.state,
167
- }
168
- );
162
+ });
169
163
 
170
- // `editor.view` is not yet available at this time.
171
- // Therefore we will add all plugins and node views directly afterwards.
172
- const newState = this.state.reconfigure({
173
- plugins: this.extensionManager.plugins,
174
- });
164
+ this.view = new EditorView(
165
+ { mount: this.options.element as any }, // use mount option so that we reuse the existing element instead of creating a new one
166
+ {
167
+ ...this.options.editorProps,
168
+ // @ts-ignore
169
+ dispatchTransaction: this.dispatchTransaction.bind(this),
170
+ state: this.state,
171
+ markViews,
172
+ }
173
+ );
174
+
175
+ // `editor.view` is not yet available at this time.
176
+ // Therefore we will add all plugins and node views directly afterwards.
177
+ const newState = this.state.reconfigure({
178
+ plugins: this.extensionManager.plugins,
179
+ });
175
180
 
176
- this.view.updateState(newState);
181
+ this.view.updateState(newState);
177
182
 
178
- this.createNodeViews();
183
+ this.createNodeViews();
179
184
 
180
- // emit the created event, call here manually because we blocked the default call in the constructor
181
- // (https://github.com/ueberdosis/tiptap/blob/45bac803283446795ad1b03f43d3746fa54a68ff/packages/core/src/Editor.ts#L117)
182
- this.commands.focus(this.options.autofocus);
183
- this.emit("create", { editor: this });
184
- this.isInitialized = true;
185
- this._creating = false;
186
- });
185
+ // emit the created event, call here manually because we blocked the default call in the constructor
186
+ // (https://github.com/ueberdosis/tiptap/blob/45bac803283446795ad1b03f43d3746fa54a68ff/packages/core/src/Editor.ts#L117)
187
+ this.commands.focus(this.options.autofocus);
188
+ this.emit("create", { editor: this });
189
+ this.isInitialized = true;
187
190
  }
188
191
 
189
192
  /**
@@ -191,15 +194,12 @@ export class BlockNoteTipTapEditor extends TiptapEditor {
191
194
  *
192
195
  * @param element DOM element to mount to, ur null / undefined to destroy
193
196
  */
194
- public mount = (element?: HTMLElement | null) => {
197
+ public mount = (element?: HTMLElement | null, contentComponent?: any) => {
195
198
  if (!element) {
196
199
  this.destroy();
197
- // cancel pending microtask
198
- this._creating = false;
199
200
  } else {
200
201
  this.options.element = element;
201
- // @ts-ignore
202
- this.createViewAlternative();
202
+ this.createViewAlternative(contentComponent);
203
203
  }
204
204
  };
205
205
  }
@@ -210,5 +210,6 @@ export class BlockNoteTipTapEditor extends TiptapEditor {
210
210
  // We should call `createView` manually only when a DOM element is available
211
211
 
212
212
  // additional fix because onPaste and onDrop depend on installing plugins in constructor which we don't support
213
+ // (note: can probably be removed after tiptap upgrade fixed in 2.8.0)
213
214
  this.options.onPaste = this.options.onDrop = undefined;
214
215
  };
@@ -1,6 +1,6 @@
1
1
  import Collaboration from "@tiptap/extension-collaboration";
2
- import { Awareness } from "y-protocols/awareness";
3
2
  import CollaborationCursor from "@tiptap/extension-collaboration-cursor";
3
+ import { Awareness } from "y-protocols/awareness";
4
4
  import * as Y from "yjs";
5
5
 
6
6
  export const createCollaborationExtensions = (collaboration: {
@@ -93,7 +93,9 @@ export const createCollaborationExtensions = (collaboration: {
93
93
  );
94
94
 
95
95
  if (!clientState) {
96
- throw new Error("Could not find client state for user");
96
+ throw new Error(
97
+ "Could not find client state for user, " + JSON.stringify(user)
98
+ );
97
99
  }
98
100
 
99
101
  const clientID = clientState[0];
@@ -1,5 +1,6 @@
1
1
  import { Plugin, PluginKey } from "prosemirror-state";
2
2
  import { Decoration, DecorationSet } from "prosemirror-view";
3
+ import { v4 } from "uuid";
3
4
  import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
4
5
 
5
6
  const PLUGIN_KEY = new PluginKey(`blocknote-placeholder`);
@@ -8,16 +9,23 @@ export class PlaceholderPlugin {
8
9
  public readonly plugin: Plugin;
9
10
  constructor(
10
11
  editor: BlockNoteEditor<any, any, any>,
11
- placeholders: Record<string | "default", string>
12
+ placeholders: Record<
13
+ string | "default" | "emptyDocument",
14
+ string | undefined
15
+ >
12
16
  ) {
13
17
  this.plugin = new Plugin({
14
18
  key: PLUGIN_KEY,
15
- view: () => {
19
+ view: (view) => {
20
+ const uniqueEditorSelector = `placeholder-selector-${v4()}`;
21
+ view.dom.classList.add(uniqueEditorSelector);
16
22
  const styleEl = document.createElement("style");
23
+
17
24
  const nonce = editor._tiptapEditor.options.injectNonce;
18
25
  if (nonce) {
19
26
  styleEl.setAttribute("nonce", nonce);
20
27
  }
28
+
21
29
  if (editor.prosemirrorView?.root instanceof ShadowRoot) {
22
30
  editor.prosemirrorView.root.append(styleEl);
23
31
  } else {
@@ -26,54 +34,50 @@ export class PlaceholderPlugin {
26
34
 
27
35
  const styleSheet = styleEl.sheet!;
28
36
 
29
- const getBaseSelector = (additionalSelectors = "") =>
30
- `.bn-block-content${additionalSelectors} .bn-inline-content:has(> .ProseMirror-trailingBreak:only-child):before`;
37
+ const getSelector = (additionalSelectors = "") =>
38
+ `.${uniqueEditorSelector} .bn-block-content${additionalSelectors} .bn-inline-content:has(> .ProseMirror-trailingBreak:only-child):before`;
31
39
 
32
- const getSelector = (
33
- blockType: string | "default",
34
- mustBeFocused = true
35
- ) => {
36
- const mustBeFocusedSelector = mustBeFocused
37
- ? `[data-is-empty-and-focused]`
38
- : ``;
40
+ try {
41
+ // FIXME: the names "default" and "emptyDocument" are hardcoded
42
+ const {
43
+ default: defaultPlaceholder,
44
+ emptyDocument: emptyPlaceholder,
45
+ ...rest
46
+ } = placeholders;
39
47
 
40
- if (blockType === "default") {
41
- return getBaseSelector(mustBeFocusedSelector);
42
- }
43
-
44
- const blockTypeSelector = `[data-content-type="${blockType}"]`;
45
- return getBaseSelector(mustBeFocusedSelector + blockTypeSelector);
46
- };
48
+ // add block specific placeholders
49
+ for (const [blockType, placeholder] of Object.entries(rest)) {
50
+ const blockTypeSelector = `[data-content-type="${blockType}"]`;
47
51
 
48
- for (const [blockType, placeholder] of Object.entries(placeholders)) {
49
- const mustBeFocused = blockType === "default";
50
-
51
- try {
52
52
  styleSheet.insertRule(
53
- `${getSelector(
54
- blockType,
55
- mustBeFocused
56
- )} { content: ${JSON.stringify(placeholder)}; }`
57
- );
58
-
59
- // For some reason, the placeholders which show when the block is focused
60
- // take priority over ones which show depending on block type, so we need
61
- // to make sure the block specific ones are also used when the block is
62
- // focused.
63
- if (!mustBeFocused) {
64
- styleSheet.insertRule(
65
- `${getSelector(blockType, true)} { content: ${JSON.stringify(
66
- placeholder
67
- )}; }`
68
- );
69
- }
70
- } catch (e) {
71
- // eslint-disable-next-line no-console
72
- console.warn(
73
- `Failed to insert placeholder CSS rule - this is likely due to the browser not supporting certain CSS pseudo-element selectors (:has, :only-child:, or :before)`,
74
- e
53
+ `${getSelector(blockTypeSelector)} { content: ${JSON.stringify(
54
+ placeholder
55
+ )}; }`
75
56
  );
76
57
  }
58
+
59
+ const onlyBlockSelector = `[data-is-only-empty-block]`;
60
+ const mustBeFocusedSelector = `[data-is-empty-and-focused]`;
61
+
62
+ // placeholder for when there's only one empty block
63
+ styleSheet.insertRule(
64
+ `${getSelector(onlyBlockSelector)} { content: ${JSON.stringify(
65
+ emptyPlaceholder
66
+ )}; }`
67
+ );
68
+
69
+ // placeholder for default blocks, only when the cursor is in the block (mustBeFocused)
70
+ styleSheet.insertRule(
71
+ `${getSelector(mustBeFocusedSelector)} { content: ${JSON.stringify(
72
+ defaultPlaceholder
73
+ )}; }`
74
+ );
75
+ } catch (e) {
76
+ // eslint-disable-next-line no-console
77
+ console.warn(
78
+ `Failed to insert placeholder CSS rule - this is likely due to the browser not supporting certain CSS pseudo-element selectors (:has, :only-child:, or :before)`,
79
+ e
80
+ );
77
81
  }
78
82
 
79
83
  return {
@@ -87,7 +91,6 @@ export class PlaceholderPlugin {
87
91
  };
88
92
  },
89
93
  props: {
90
- // TODO: maybe also add placeholder for empty document ("e.g.: start writing..")
91
94
  decorations: (state) => {
92
95
  const { doc, selection } = state;
93
96
 
@@ -104,20 +107,32 @@ export class PlaceholderPlugin {
104
107
  return;
105
108
  }
106
109
 
107
- const $pos = selection.$anchor;
108
- const node = $pos.parent;
110
+ const decs = [];
109
111
 
110
- if (node.content.size > 0) {
111
- return null;
112
+ // decoration for when there's only one empty block
113
+ // positions are hardcoded for now
114
+ if (state.doc.content.size === 6) {
115
+ decs.push(
116
+ Decoration.node(2, 4, {
117
+ "data-is-only-empty-block": "true",
118
+ })
119
+ );
112
120
  }
113
121
 
114
- const before = $pos.before();
122
+ const $pos = selection.$anchor;
123
+ const node = $pos.parent;
124
+
125
+ if (node.content.size === 0) {
126
+ const before = $pos.before();
115
127
 
116
- const dec = Decoration.node(before, before + node.nodeSize, {
117
- "data-is-empty-and-focused": "true",
118
- });
128
+ decs.push(
129
+ Decoration.node(before, before + node.nodeSize, {
130
+ "data-is-empty-and-focused": "true",
131
+ })
132
+ );
133
+ }
119
134
 
120
- return DecorationSet.create(doc, [dec]);
135
+ return DecorationSet.create(doc, decs);
121
136
  },
122
137
  },
123
138
  });
@@ -283,47 +283,57 @@ export class SideMenuView<
283
283
 
284
284
  this.editor._tiptapEditor.commands.blur();
285
285
 
286
- // When ProseMirror handles a drop event on the editor while
287
- // `view.dragging` is set, it deletes the selected content. However, if
288
- // a block from a different editor is being dropped, this causes some
289
- // issues that the code below fixes:
290
- if (!this.isDragOrigin && this.pmView.dom.contains(event.target as Node)) {
291
- // 1. Because the editor selection is unrelated to the dragged content,
292
- // we don't want PM to delete its content. Therefore, we collapse the
293
- // selection.
294
- this.pmView.dispatch(
295
- this.pmView.state.tr.setSelection(
296
- TextSelection.create(
297
- this.pmView.state.tr.doc,
298
- this.pmView.state.tr.selection.to
286
+ // Finds the BlockNote editor element that the drop event occurred in (if
287
+ // any).
288
+ const parentEditorElement =
289
+ event.target instanceof Node
290
+ ? (event.target instanceof HTMLElement
291
+ ? event.target
292
+ : event.target.parentElement
293
+ )?.closest(".bn-editor") || null
294
+ : null;
295
+
296
+ // Drop event occurred within an editor.
297
+ if (parentEditorElement) {
298
+ // When ProseMirror handles a drop event on the editor while
299
+ // `view.dragging` is set, it deletes the selected content. However, if
300
+ // a block from a different editor is being dropped, this causes some
301
+ // issues that the code below fixes:
302
+ if (!this.isDragOrigin && this.pmView.dom === parentEditorElement) {
303
+ // 1. Because the editor selection is unrelated to the dragged content,
304
+ // we don't want PM to delete its content. Therefore, we collapse the
305
+ // selection.
306
+ this.pmView.dispatch(
307
+ this.pmView.state.tr.setSelection(
308
+ TextSelection.create(
309
+ this.pmView.state.tr.doc,
310
+ this.pmView.state.tr.selection.to
311
+ )
299
312
  )
300
- )
301
- );
302
- } else if (
303
- this.isDragOrigin &&
304
- !this.pmView.dom.contains(event.target as Node)
305
- ) {
306
- // 2. Because the editor from which the block originates doesn't get a
307
- // drop event on it, PM doesn't delete its selected content. Therefore, we
308
- // need to do so manually.
309
- //
310
- // Note: Deleting the selected content from the editor from which the
311
- // block originates, may change its height. This can cause the position of
312
- // the editor in which the block is being dropping to shift, before it
313
- // can handle the drop event. That in turn can cause the drop to happen
314
- // somewhere other than the user intended. To get around this, we delay
315
- // deleting the selected content until all editors have had the chance to
316
- // handle the event.
317
- setTimeout(
318
- () => this.pmView.dispatch(this.pmView.state.tr.deleteSelection()),
319
- 0
320
- );
313
+ );
314
+ } else if (this.isDragOrigin && this.pmView.dom !== parentEditorElement) {
315
+ // 2. Because the editor from which the block originates doesn't get a
316
+ // drop event on it, PM doesn't delete its selected content. Therefore, we
317
+ // need to do so manually.
318
+ //
319
+ // Note: Deleting the selected content from the editor from which the
320
+ // block originates, may change its height. This can cause the position of
321
+ // the editor in which the block is being dropping to shift, before it
322
+ // can handle the drop event. That in turn can cause the drop to happen
323
+ // somewhere other than the user intended. To get around this, we delay
324
+ // deleting the selected content until all editors have had the chance to
325
+ // handle the event.
326
+ setTimeout(
327
+ () => this.pmView.dispatch(this.pmView.state.tr.deleteSelection()),
328
+ 0
329
+ );
330
+ }
331
+ // 3. PM only clears `view.dragging` on the editor that the block was
332
+ // dropped, so we manually have to clear it on all the others. However,
333
+ // PM also needs to read `view.dragging` while handling the event, so we
334
+ // use a `setTimeout` to ensure it's only cleared after that.
335
+ setTimeout(() => (this.pmView.dragging = null), 0);
321
336
  }
322
- // 3. PM only clears `view.dragging` on the editor that the block was
323
- // dropped, so we manually have to clear it on all the others. However,
324
- // PM also needs to read `view.dragging` while handling the event, so we
325
- // use a `setTimeout` to ensure it's only cleared after that.
326
- setTimeout(() => (this.pmView.dragging = null), 0);
327
337
 
328
338
  if (
329
339
  this.sideMenuDetection === "editor" ||
@@ -261,6 +261,9 @@ export const ar: Dictionary = {
261
261
  align_justify: {
262
262
  tooltip: "ضبط النص",
263
263
  },
264
+ comment: {
265
+ tooltip: "إضافة ملاحظة",
266
+ },
264
267
  },
265
268
  file_panel: {
266
269
  upload: {
@@ -273,6 +273,9 @@ export const de = {
273
273
  align_justify: {
274
274
  tooltip: "Text Blocksatz",
275
275
  },
276
+ comment: {
277
+ tooltip: "Kommentar hinzufügen",
278
+ },
276
279
  },
277
280
  file_panel: {
278
281
  upload: {
@@ -129,7 +129,7 @@ export const en = {
129
129
  bulletListItem: "List",
130
130
  numberedListItem: "List",
131
131
  checkListItem: "List",
132
- },
132
+ } as Record<"default" | "emptyDocument" | string, string | undefined>,
133
133
  file_blocks: {
134
134
  image: {
135
135
  add_button_text: "Add image",
@@ -275,6 +275,9 @@ export const en = {
275
275
  align_justify: {
276
276
  tooltip: "Justify text",
277
277
  },
278
+ comment: {
279
+ tooltip: "Add comment",
280
+ },
278
281
  },
279
282
  file_panel: {
280
283
  upload: {
@@ -272,6 +272,9 @@ export const es = {
272
272
  align_justify: {
273
273
  tooltip: "Justificar texto",
274
274
  },
275
+ comment: {
276
+ tooltip: "Agregar comentario",
277
+ },
275
278
  },
276
279
  file_panel: {
277
280
  upload: {
@@ -300,6 +300,9 @@ export const fr: Dictionary = {
300
300
  align_justify: {
301
301
  tooltip: "Justifier le texte",
302
302
  },
303
+ comment: {
304
+ tooltip: "Ajouter un commentaire",
305
+ },
303
306
  },
304
307
  file_panel: {
305
308
  upload: {
@@ -281,6 +281,9 @@ export const hr = {
281
281
  align_justify: {
282
282
  tooltip: "Poravnaj tekst obostrano",
283
283
  },
284
+ comment: {
285
+ tooltip: "Dodaj komentar",
286
+ },
284
287
  },
285
288
  file_panel: {
286
289
  upload: {
@@ -268,6 +268,9 @@ export const is: Dictionary = {
268
268
  align_justify: {
269
269
  tooltip: "Jafna texta",
270
270
  },
271
+ comment: {
272
+ tooltip: "Bæta við athugun",
273
+ },
271
274
  },
272
275
  file_panel: {
273
276
  upload: {