@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
@@ -3,13 +3,13 @@ import { EditorState, Plugin, PluginKey, PluginView } from "prosemirror-state";
3
3
  import { EditorView } from "prosemirror-view";
4
4
 
5
5
  import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
6
+ import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
6
7
  import { UiElementPosition } from "../../extensions-shared/UiElementPosition.js";
7
8
  import {
8
9
  BlockSchema,
9
10
  InlineContentSchema,
10
11
  StyleSchema,
11
12
  } from "../../schema/index.js";
12
- import { EventEmitter } from "../../util/EventEmitter.js";
13
13
 
14
14
  export type FormattingToolbarState = UiElementPosition;
15
15
 
@@ -25,7 +25,7 @@ export class FormattingToolbarView implements PluginView {
25
25
  state: EditorState;
26
26
  from: number;
27
27
  to: number;
28
- }) => boolean = ({ state, from, to }) => {
28
+ }) => boolean = ({ view, state, from, to }) => {
29
29
  const { doc, selection } = state;
30
30
  const { empty } = selection;
31
31
 
@@ -43,7 +43,16 @@ export class FormattingToolbarView implements PluginView {
43
43
  return false;
44
44
  }
45
45
 
46
- return !(empty || isEmptyTextBlock);
46
+ if (empty || isEmptyTextBlock) {
47
+ return false;
48
+ }
49
+
50
+ const focusedElement = document.activeElement;
51
+ if (!this.isElementWithinEditorWrapper(focusedElement) && view.editable) {
52
+ // editable editors must have focus for the toolbar to show
53
+ return false;
54
+ }
55
+ return true;
47
56
  };
48
57
 
49
58
  constructor(
@@ -108,8 +117,22 @@ export class FormattingToolbarView implements PluginView {
108
117
  }
109
118
  };
110
119
 
111
- viewMousedownHandler = () => {
112
- this.preventShow = true;
120
+ isElementWithinEditorWrapper = (element: Node | null) => {
121
+ if (!element) {
122
+ return false;
123
+ }
124
+ const editorWrapper = this.pmView.dom.parentElement;
125
+ if (!editorWrapper) {
126
+ return false;
127
+ }
128
+
129
+ return editorWrapper.contains(element);
130
+ };
131
+
132
+ viewMousedownHandler = (e: MouseEvent) => {
133
+ if (!this.isElementWithinEditorWrapper(e.target as Node)) {
134
+ this.preventShow = true;
135
+ }
113
136
  };
114
137
 
115
138
  mouseupHandler = () => {
@@ -172,12 +195,18 @@ export class FormattingToolbarView implements PluginView {
172
195
  // e.g. the download file button, should still be accessible. Therefore,
173
196
  // logic for hiding when the editor is non-editable is handled
174
197
  // individually in each button.
175
- this.state = {
198
+ const nextState = {
176
199
  show: true,
177
200
  referencePos: this.getSelectionBoundingBox(),
178
201
  };
179
202
 
180
- this.emitUpdate();
203
+ if (
204
+ nextState.show !== this.state?.show ||
205
+ nextState.referencePos.toJSON() !== this.state?.referencePos.toJSON()
206
+ ) {
207
+ this.state = nextState;
208
+ this.emitUpdate();
209
+ }
181
210
 
182
211
  return;
183
212
  }
@@ -236,30 +265,35 @@ export const formattingToolbarPluginKey = new PluginKey(
236
265
  "FormattingToolbarPlugin",
237
266
  );
238
267
 
239
- export class FormattingToolbarProsemirrorPlugin extends EventEmitter<any> {
268
+ export class FormattingToolbarProsemirrorPlugin extends BlockNoteExtension {
269
+ public static key() {
270
+ return "formattingToolbar";
271
+ }
272
+
240
273
  private view: FormattingToolbarView | undefined;
241
- public readonly plugin: Plugin;
242
274
 
243
275
  constructor(editor: BlockNoteEditor<any, any, any>) {
244
276
  super();
245
- this.plugin = new Plugin({
246
- key: formattingToolbarPluginKey,
247
- view: (editorView) => {
248
- this.view = new FormattingToolbarView(editor, editorView, (state) => {
249
- this.emit("update", state);
250
- });
251
- return this.view;
252
- },
253
- props: {
254
- handleKeyDown: (_view, event: KeyboardEvent) => {
255
- if (event.key === "Escape" && this.shown) {
256
- this.view!.closeMenu();
257
- return true;
258
- }
259
- return false;
277
+ this.addProsemirrorPlugin(
278
+ new Plugin({
279
+ key: formattingToolbarPluginKey,
280
+ view: (editorView) => {
281
+ this.view = new FormattingToolbarView(editor, editorView, (state) => {
282
+ this.emit("update", state);
283
+ });
284
+ return this.view;
260
285
  },
261
- },
262
- });
286
+ props: {
287
+ handleKeyDown: (_view, event: KeyboardEvent) => {
288
+ if (event.key === "Escape" && this.shown) {
289
+ this.view!.closeMenu();
290
+ return true;
291
+ }
292
+ return false;
293
+ },
294
+ },
295
+ }),
296
+ );
263
297
  }
264
298
 
265
299
  public get shown() {
@@ -4,15 +4,15 @@ import { EditorView } from "@tiptap/pm/view";
4
4
  import { Mark } from "prosemirror-model";
5
5
  import { EditorState, Plugin, PluginKey, PluginView } from "prosemirror-state";
6
6
 
7
+ import { getPmSchema } from "../../api/pmUtil.js";
7
8
  import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
9
+ import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
8
10
  import { UiElementPosition } from "../../extensions-shared/UiElementPosition.js";
9
11
  import {
10
12
  BlockSchema,
11
13
  InlineContentSchema,
12
14
  StyleSchema,
13
15
  } from "../../schema/index.js";
14
- import { EventEmitter } from "../../util/EventEmitter.js";
15
- import { getPmSchema } from "../../api/pmUtil.js";
16
16
 
17
17
  export type LinkToolbarState = UiElementPosition & {
18
18
  // The hovered link's URL, and the text it's displayed with in the
@@ -301,30 +301,35 @@ export class LinkToolbarProsemirrorPlugin<
301
301
  BSchema extends BlockSchema,
302
302
  I extends InlineContentSchema,
303
303
  S extends StyleSchema,
304
- > extends EventEmitter<any> {
304
+ > extends BlockNoteExtension {
305
+ public static key() {
306
+ return "linkToolbar";
307
+ }
308
+
305
309
  private view: LinkToolbarView | undefined;
306
- public readonly plugin: Plugin;
307
310
 
308
311
  constructor(editor: BlockNoteEditor<BSchema, I, S>) {
309
312
  super();
310
- this.plugin = new Plugin({
311
- key: linkToolbarPluginKey,
312
- view: (editorView) => {
313
- this.view = new LinkToolbarView(editor, editorView, (state) => {
314
- this.emit("update", state);
315
- });
316
- return this.view;
317
- },
318
- props: {
319
- handleKeyDown: (_view, event: KeyboardEvent) => {
320
- if (event.key === "Escape" && this.shown) {
321
- this.view!.closeMenu();
322
- return true;
323
- }
324
- return false;
313
+ this.addProsemirrorPlugin(
314
+ new Plugin({
315
+ key: linkToolbarPluginKey,
316
+ view: (editorView) => {
317
+ this.view = new LinkToolbarView(editor, editorView, (state) => {
318
+ this.emit("update", state);
319
+ });
320
+ return this.view;
325
321
  },
326
- },
327
- });
322
+ props: {
323
+ handleKeyDown: (_view, event: KeyboardEvent) => {
324
+ if (event.key === "Escape" && this.shown) {
325
+ this.view!.closeMenu();
326
+ return true;
327
+ }
328
+ return false;
329
+ },
330
+ },
331
+ }),
332
+ );
328
333
  }
329
334
 
330
335
  public onUpdate(callback: (state: LinkToolbarState) => void) {
@@ -1,4 +1,5 @@
1
1
  import { Plugin, PluginKey, TextSelection } from "prosemirror-state";
2
+ import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
2
3
 
3
4
  const PLUGIN_KEY = new PluginKey("node-selection-keyboard");
4
5
  // By default, typing with a node selection active will cause ProseMirror to
@@ -15,54 +16,60 @@ const PLUGIN_KEY = new PluginKey("node-selection-keyboard");
15
16
  // While a more elegant solution would probably process transactions instead of
16
17
  // keystrokes, this brings us most of the way to Notion's UX without much added
17
18
  // complexity.
18
- export class NodeSelectionKeyboardPlugin {
19
- public readonly plugin: Plugin;
19
+ export class NodeSelectionKeyboardPlugin extends BlockNoteExtension {
20
+ public static key() {
21
+ return "nodeSelectionKeyboard";
22
+ }
23
+
20
24
  constructor() {
21
- this.plugin = new Plugin({
22
- key: PLUGIN_KEY,
23
- props: {
24
- handleKeyDown: (view, event) => {
25
- // Checks for node selection
26
- if ("node" in view.state.selection) {
27
- // Checks if key press uses ctrl/meta modifier
28
- if (event.ctrlKey || event.metaKey) {
29
- return false;
30
- }
31
- // Checks if key press is alphanumeric
32
- if (event.key.length === 1) {
33
- event.preventDefault();
25
+ super();
26
+ this.addProsemirrorPlugin(
27
+ new Plugin({
28
+ key: PLUGIN_KEY,
29
+ props: {
30
+ handleKeyDown: (view, event) => {
31
+ // Checks for node selection
32
+ if ("node" in view.state.selection) {
33
+ // Checks if key press uses ctrl/meta modifier
34
+ if (event.ctrlKey || event.metaKey) {
35
+ return false;
36
+ }
37
+ // Checks if key press is alphanumeric
38
+ if (event.key.length === 1) {
39
+ event.preventDefault();
34
40
 
35
- return true;
36
- }
37
- // Checks if key press is Enter
38
- if (
39
- event.key === "Enter" &&
40
- !event.shiftKey &&
41
- !event.altKey &&
42
- !event.ctrlKey &&
43
- !event.metaKey
44
- ) {
45
- const tr = view.state.tr;
46
- view.dispatch(
47
- tr
48
- .insert(
49
- view.state.tr.selection.$to.after(),
50
- view.state.schema.nodes["paragraph"].createChecked(),
51
- )
52
- .setSelection(
53
- new TextSelection(
54
- tr.doc.resolve(view.state.tr.selection.$to.after() + 1),
41
+ return true;
42
+ }
43
+ // Checks if key press is Enter
44
+ if (
45
+ event.key === "Enter" &&
46
+ !event.shiftKey &&
47
+ !event.altKey &&
48
+ !event.ctrlKey &&
49
+ !event.metaKey
50
+ ) {
51
+ const tr = view.state.tr;
52
+ view.dispatch(
53
+ tr
54
+ .insert(
55
+ view.state.tr.selection.$to.after(),
56
+ view.state.schema.nodes["paragraph"].createChecked(),
57
+ )
58
+ .setSelection(
59
+ new TextSelection(
60
+ tr.doc.resolve(view.state.tr.selection.$to.after() + 1),
61
+ ),
55
62
  ),
56
- ),
57
- );
63
+ );
58
64
 
59
- return true;
65
+ return true;
66
+ }
60
67
  }
61
- }
62
68
 
63
- return false;
69
+ return false;
70
+ },
64
71
  },
65
- },
66
- });
72
+ }),
73
+ );
67
74
  }
68
75
  }
@@ -2,11 +2,15 @@ import { Plugin, PluginKey } from "prosemirror-state";
2
2
  import { Decoration, DecorationSet } from "prosemirror-view";
3
3
  import { v4 } from "uuid";
4
4
  import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
5
+ import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
5
6
 
6
7
  const PLUGIN_KEY = new PluginKey(`blocknote-placeholder`);
7
8
 
8
- export class PlaceholderPlugin {
9
- public readonly plugin: Plugin;
9
+ export class PlaceholderPlugin extends BlockNoteExtension {
10
+ public static key() {
11
+ return "placeholder";
12
+ }
13
+
10
14
  constructor(
11
15
  editor: BlockNoteEditor<any, any, any>,
12
16
  placeholders: Record<
@@ -14,127 +18,130 @@ export class PlaceholderPlugin {
14
18
  string | undefined
15
19
  >,
16
20
  ) {
17
- this.plugin = new Plugin({
18
- key: PLUGIN_KEY,
19
- view: (view) => {
20
- const uniqueEditorSelector = `placeholder-selector-${v4()}`;
21
- view.dom.classList.add(uniqueEditorSelector);
22
- const styleEl = document.createElement("style");
23
-
24
- const nonce = editor._tiptapEditor.options.injectNonce;
25
- if (nonce) {
26
- styleEl.setAttribute("nonce", nonce);
27
- }
28
-
29
- if (editor.prosemirrorView?.root instanceof ShadowRoot) {
30
- editor.prosemirrorView.root.append(styleEl);
31
- } else {
32
- editor.prosemirrorView?.root.head.appendChild(styleEl);
33
- }
34
-
35
- const styleSheet = styleEl.sheet!;
36
-
37
- const getSelector = (additionalSelectors = "") =>
38
- `.${uniqueEditorSelector} .bn-block-content${additionalSelectors} .bn-inline-content:has(> .ProseMirror-trailingBreak:only-child):before`;
39
-
40
- try {
41
- // FIXME: the names "default" and "emptyDocument" are hardcoded
42
- const {
43
- default: defaultPlaceholder,
44
- emptyDocument: emptyPlaceholder,
45
- ...rest
46
- } = placeholders;
47
-
48
- // add block specific placeholders
49
- for (const [blockType, placeholder] of Object.entries(rest)) {
50
- const blockTypeSelector = `[data-content-type="${blockType}"]`;
21
+ super();
22
+ this.addProsemirrorPlugin(
23
+ new Plugin({
24
+ key: PLUGIN_KEY,
25
+ view: (view) => {
26
+ const uniqueEditorSelector = `placeholder-selector-${v4()}`;
27
+ view.dom.classList.add(uniqueEditorSelector);
28
+ const styleEl = document.createElement("style");
29
+
30
+ const nonce = editor._tiptapEditor.options.injectNonce;
31
+ if (nonce) {
32
+ styleEl.setAttribute("nonce", nonce);
33
+ }
34
+
35
+ if (editor.prosemirrorView?.root instanceof ShadowRoot) {
36
+ editor.prosemirrorView.root.append(styleEl);
37
+ } else {
38
+ editor.prosemirrorView?.root.head.appendChild(styleEl);
39
+ }
40
+
41
+ const styleSheet = styleEl.sheet!;
42
+
43
+ const getSelector = (additionalSelectors = "") =>
44
+ `.${uniqueEditorSelector} .bn-block-content${additionalSelectors} .bn-inline-content:has(> .ProseMirror-trailingBreak:only-child):before`;
45
+
46
+ try {
47
+ // FIXME: the names "default" and "emptyDocument" are hardcoded
48
+ const {
49
+ default: defaultPlaceholder,
50
+ emptyDocument: emptyPlaceholder,
51
+ ...rest
52
+ } = placeholders;
53
+
54
+ // add block specific placeholders
55
+ for (const [blockType, placeholder] of Object.entries(rest)) {
56
+ const blockTypeSelector = `[data-content-type="${blockType}"]`;
57
+
58
+ styleSheet.insertRule(
59
+ `${getSelector(blockTypeSelector)} { content: ${JSON.stringify(
60
+ placeholder,
61
+ )}; }`,
62
+ );
63
+ }
64
+
65
+ const onlyBlockSelector = `[data-is-only-empty-block]`;
66
+ const mustBeFocusedSelector = `[data-is-empty-and-focused]`;
67
+
68
+ // placeholder for when there's only one empty block
69
+ styleSheet.insertRule(
70
+ `${getSelector(onlyBlockSelector)} { content: ${JSON.stringify(
71
+ emptyPlaceholder,
72
+ )}; }`,
73
+ );
51
74
 
75
+ // placeholder for default blocks, only when the cursor is in the block (mustBeFocused)
52
76
  styleSheet.insertRule(
53
- `${getSelector(blockTypeSelector)} { content: ${JSON.stringify(
54
- placeholder,
77
+ `${getSelector(mustBeFocusedSelector)} { content: ${JSON.stringify(
78
+ defaultPlaceholder,
55
79
  )}; }`,
56
80
  );
81
+ } catch (e) {
82
+ // eslint-disable-next-line no-console
83
+ console.warn(
84
+ `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)`,
85
+ e,
86
+ );
57
87
  }
58
88
 
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
- );
81
- }
82
-
83
- return {
84
- destroy: () => {
85
- if (editor.prosemirrorView?.root instanceof ShadowRoot) {
86
- editor.prosemirrorView.root.removeChild(styleEl);
87
- } else {
88
- editor.prosemirrorView?.root.head.removeChild(styleEl);
89
+ return {
90
+ destroy: () => {
91
+ if (editor.prosemirrorView?.root instanceof ShadowRoot) {
92
+ editor.prosemirrorView.root.removeChild(styleEl);
93
+ } else {
94
+ editor.prosemirrorView?.root.head.removeChild(styleEl);
95
+ }
96
+ },
97
+ };
98
+ },
99
+ props: {
100
+ decorations: (state) => {
101
+ const { doc, selection } = state;
102
+
103
+ if (!editor.isEditable) {
104
+ return;
89
105
  }
90
- },
91
- };
92
- },
93
- props: {
94
- decorations: (state) => {
95
- const { doc, selection } = state;
96
-
97
- if (!editor.isEditable) {
98
- return;
99
- }
100
106
 
101
- if (!selection.empty) {
102
- return;
103
- }
107
+ if (!selection.empty) {
108
+ return;
109
+ }
104
110
 
105
- // Don't show placeholder when the cursor is inside a code block
106
- if (selection.$from.parent.type.spec.code) {
107
- return;
108
- }
111
+ // Don't show placeholder when the cursor is inside a code block
112
+ if (selection.$from.parent.type.spec.code) {
113
+ return;
114
+ }
109
115
 
110
- const decs = [];
116
+ const decs = [];
111
117
 
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
- );
120
- }
118
+ // decoration for when there's only one empty block
119
+ // positions are hardcoded for now
120
+ if (state.doc.content.size === 6) {
121
+ decs.push(
122
+ Decoration.node(2, 4, {
123
+ "data-is-only-empty-block": "true",
124
+ }),
125
+ );
126
+ }
121
127
 
122
- const $pos = selection.$anchor;
123
- const node = $pos.parent;
128
+ const $pos = selection.$anchor;
129
+ const node = $pos.parent;
124
130
 
125
- if (node.content.size === 0) {
126
- const before = $pos.before();
131
+ if (node.content.size === 0) {
132
+ const before = $pos.before();
127
133
 
128
- decs.push(
129
- Decoration.node(before, before + node.nodeSize, {
130
- "data-is-empty-and-focused": "true",
131
- }),
132
- );
133
- }
134
+ decs.push(
135
+ Decoration.node(before, before + node.nodeSize, {
136
+ "data-is-empty-and-focused": "true",
137
+ }),
138
+ );
139
+ }
134
140
 
135
- return DecorationSet.create(doc, decs);
141
+ return DecorationSet.create(doc, decs);
142
+ },
136
143
  },
137
- },
138
- });
144
+ }),
145
+ );
139
146
  }
140
147
  }