@blocknote/core 0.19.0 → 0.19.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 (97) hide show
  1. package/README.md +2 -0
  2. package/dist/blocknote.js +2549 -2497
  3. package/dist/blocknote.js.map +1 -1
  4. package/dist/blocknote.umd.cjs +6 -6
  5. package/dist/blocknote.umd.cjs.map +1 -1
  6. package/dist/src/api/blockManipulation/commands/moveBlock/moveBlock.test.js +5 -1
  7. package/dist/src/api/blockManipulation/commands/moveBlock/moveBlock.test.js.map +1 -1
  8. package/dist/src/api/blockManipulation/commands/removeBlocks/removeBlocks.js +2 -40
  9. package/dist/src/api/blockManipulation/commands/removeBlocks/removeBlocks.js.map +1 -1
  10. package/dist/src/api/blockManipulation/commands/removeBlocks/removeBlocks.test.js +4 -0
  11. package/dist/src/api/blockManipulation/commands/removeBlocks/removeBlocks.test.js.map +1 -1
  12. package/dist/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.js +51 -9
  13. package/dist/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.js.map +1 -1
  14. package/dist/src/api/blockManipulation/commands/splitBlock/splitBlock.js +2 -2
  15. package/dist/src/api/blockManipulation/commands/splitBlock/splitBlock.js.map +1 -1
  16. package/dist/src/api/clipboard/fromClipboard/acceptedMIMETypes.js +1 -1
  17. package/dist/src/api/clipboard/fromClipboard/acceptedMIMETypes.js.map +1 -1
  18. package/dist/src/api/clipboard/fromClipboard/handleFileInsertion.js +4 -2
  19. package/dist/src/api/clipboard/fromClipboard/handleFileInsertion.js.map +1 -1
  20. package/dist/src/api/getBlockInfoFromPos.js +19 -25
  21. package/dist/src/api/getBlockInfoFromPos.js.map +1 -1
  22. package/dist/src/blocks/HeadingBlockContent/HeadingBlockContent.js +8 -4
  23. package/dist/src/blocks/HeadingBlockContent/HeadingBlockContent.js.map +1 -1
  24. package/dist/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.js +5 -3
  25. package/dist/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.js.map +1 -1
  26. package/dist/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.js +12 -6
  27. package/dist/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.js.map +1 -1
  28. package/dist/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.js +5 -1
  29. package/dist/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.js.map +1 -1
  30. package/dist/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.js +4 -2
  31. package/dist/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.js.map +1 -1
  32. package/dist/src/blocks/ParagraphBlockContent/ParagraphBlockContent.js +2 -1
  33. package/dist/src/blocks/ParagraphBlockContent/ParagraphBlockContent.js.map +1 -1
  34. package/dist/src/blocks/TableBlockContent/TableBlockContent.js +0 -1
  35. package/dist/src/blocks/TableBlockContent/TableBlockContent.js.map +1 -1
  36. package/dist/src/editor/BlockNoteEditor.js +39 -43
  37. package/dist/src/editor/BlockNoteEditor.js.map +1 -1
  38. package/dist/src/editor/BlockNoteEditor.test.js +2 -2
  39. package/dist/src/editor/BlockNoteEditor.test.js.map +1 -1
  40. package/dist/src/editor/BlockNoteExtensions.js +52 -6
  41. package/dist/src/editor/BlockNoteExtensions.js.map +1 -1
  42. package/dist/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.js +36 -6
  43. package/dist/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.js.map +1 -1
  44. package/dist/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.js +35 -32
  45. package/dist/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.js.map +1 -1
  46. package/dist/src/extensions/Placeholder/PlaceholderPlugin.js +74 -71
  47. package/dist/src/extensions/Placeholder/PlaceholderPlugin.js.map +1 -1
  48. package/dist/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.js +153 -149
  49. package/dist/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.js.map +1 -1
  50. package/dist/tsconfig.tsbuildinfo +1 -1
  51. package/dist/webpack-stats.json +1 -1
  52. package/package.json +2 -2
  53. package/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap +0 -6
  54. package/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap +0 -5
  55. package/src/api/blockManipulation/commands/moveBlock/__snapshots__/moveBlock.test.ts.snap +0 -8
  56. package/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts +7 -3
  57. package/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap +439 -2
  58. package/src/api/blockManipulation/commands/removeBlocks/removeBlocks.test.ts +6 -0
  59. package/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts +2 -82
  60. package/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap +0 -8
  61. package/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts +96 -20
  62. package/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap +0 -6
  63. package/src/api/blockManipulation/commands/splitBlock/splitBlock.ts +2 -5
  64. package/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap +0 -490
  65. package/src/api/clipboard/fromClipboard/acceptedMIMETypes.ts +1 -1
  66. package/src/api/clipboard/fromClipboard/handleFileInsertion.ts +6 -5
  67. package/src/api/getBlockInfoFromPos.ts +20 -30
  68. package/src/api/parsers/html/__snapshots__/parse-notion-html.json +1 -2
  69. package/src/blocks/HeadingBlockContent/HeadingBlockContent.ts +16 -4
  70. package/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +9 -3
  71. package/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts +22 -6
  72. package/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts +5 -3
  73. package/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +8 -2
  74. package/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts +4 -1
  75. package/src/blocks/TableBlockContent/TableBlockContent.ts +0 -1
  76. package/src/editor/BlockNoteEditor.test.ts +2 -5
  77. package/src/editor/BlockNoteEditor.ts +71 -42
  78. package/src/editor/BlockNoteExtensions.ts +90 -14
  79. package/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +36 -9
  80. package/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.ts +45 -42
  81. package/src/extensions/Placeholder/PlaceholderPlugin.ts +94 -90
  82. package/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.ts +173 -169
  83. package/types/src/api/blockManipulation/commands/removeBlocks/removeBlocks.d.ts +0 -3
  84. package/types/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.d.ts +4 -0
  85. package/types/src/api/blockManipulation/setupTestEnv.d.ts +0 -6
  86. package/types/src/api/clipboard/fromClipboard/acceptedMIMETypes.d.ts +1 -1
  87. package/types/src/api/getBlockInfoFromPos.d.ts +9 -34
  88. package/types/src/api/testUtil/cases/customBlocks.d.ts +0 -6
  89. package/types/src/api/testUtil/cases/customInlineContent.d.ts +0 -6
  90. package/types/src/api/testUtil/cases/customStyles.d.ts +0 -6
  91. package/types/src/blocks/TableBlockContent/TableBlockContent.d.ts +0 -9
  92. package/types/src/blocks/defaultBlocks.d.ts +0 -12
  93. package/types/src/editor/BlockNoteEditor.d.ts +19 -2
  94. package/types/src/editor/BlockNoteExtensions.d.ts +14 -7
  95. package/types/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.d.ts +4 -1
  96. package/types/src/extensions/Placeholder/PlaceholderPlugin.d.ts +4 -1
  97. package/types/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.d.ts +4 -5
@@ -1,6 +1,6 @@
1
- import { Extension, Extensions, extensions } from "@tiptap/core";
1
+ import { AnyExtension, Extension, extensions } from "@tiptap/core";
2
2
 
3
- import type { BlockNoteEditor } from "./BlockNoteEditor.js";
3
+ import type { BlockNoteEditor, BlockNoteExtension } from "./BlockNoteEditor.js";
4
4
 
5
5
  import Collaboration from "@tiptap/extension-collaboration";
6
6
  import CollaborationCursor from "@tiptap/extension-collaboration-cursor";
@@ -9,12 +9,22 @@ import { HardBreak } from "@tiptap/extension-hard-break";
9
9
  import { History } from "@tiptap/extension-history";
10
10
  import { Link } from "@tiptap/extension-link";
11
11
  import { Text } from "@tiptap/extension-text";
12
+ import { Plugin } from "prosemirror-state";
12
13
  import * as Y from "yjs";
13
14
  import { createDropFileExtension } from "../api/clipboard/fromClipboard/fileDropExtension.js";
14
15
  import { createPasteFromClipboardExtension } from "../api/clipboard/fromClipboard/pasteExtension.js";
15
16
  import { createCopyToClipboardExtension } from "../api/clipboard/toClipboard/copyExtension.js";
16
17
  import { BackgroundColorExtension } from "../extensions/BackgroundColor/BackgroundColorExtension.js";
18
+ import { FilePanelProsemirrorPlugin } from "../extensions/FilePanel/FilePanelPlugin.js";
19
+ import { FormattingToolbarProsemirrorPlugin } from "../extensions/FormattingToolbar/FormattingToolbarPlugin.js";
17
20
  import { KeyboardShortcutsExtension } from "../extensions/KeyboardShortcuts/KeyboardShortcutsExtension.js";
21
+ import { LinkToolbarProsemirrorPlugin } from "../extensions/LinkToolbar/LinkToolbarPlugin.js";
22
+ import { NodeSelectionKeyboardPlugin } from "../extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.js";
23
+ import { PlaceholderPlugin } from "../extensions/Placeholder/PlaceholderPlugin.js";
24
+ import { PreviousBlockTypePlugin } from "../extensions/PreviousBlockType/PreviousBlockTypePlugin.js";
25
+ import { SideMenuProsemirrorPlugin } from "../extensions/SideMenu/SideMenuPlugin.js";
26
+ import { SuggestionMenuProseMirrorPlugin } from "../extensions/SuggestionMenu/SuggestionPlugin.js";
27
+ import { TableHandlesProsemirrorPlugin } from "../extensions/TableHandles/TableHandlesPlugin.js";
18
28
  import { TextAlignmentExtension } from "../extensions/TextAlignment/TextAlignmentExtension.js";
19
29
  import { TextColorExtension } from "../extensions/TextColor/TextColorExtension.js";
20
30
  import { TrailingNode } from "../extensions/TrailingNode/TrailingNodeExtension.js";
@@ -30,14 +40,11 @@ import {
30
40
  StyleSpecs,
31
41
  } from "../schema/index.js";
32
42
 
33
- /**
34
- * Get all the Tiptap extensions BlockNote is configured with by default
35
- */
36
- export const getBlockNoteExtensions = <
43
+ type ExtensionOptions<
37
44
  BSchema extends BlockSchema,
38
45
  I extends InlineContentSchema,
39
46
  S extends StyleSchema
40
- >(opts: {
47
+ > = {
41
48
  editor: BlockNoteEditor<BSchema, I, S>;
42
49
  domAttributes: Partial<BlockNoteDOMAttributes>;
43
50
  blockSpecs: BlockSpecs;
@@ -56,8 +63,77 @@ export const getBlockNoteExtensions = <
56
63
  };
57
64
  disableExtensions: string[] | undefined;
58
65
  setIdAttribute?: boolean;
59
- }) => {
60
- const ret: Extensions = [
66
+ animations: boolean;
67
+ tableHandles: boolean;
68
+ dropCursor: (opts: any) => Plugin;
69
+ placeholders: Record<string | "default", string>;
70
+ };
71
+
72
+ /**
73
+ * Get all the Tiptap extensions BlockNote is configured with by default
74
+ */
75
+ export const getBlockNoteExtensions = <
76
+ BSchema extends BlockSchema,
77
+ I extends InlineContentSchema,
78
+ S extends StyleSchema
79
+ >(
80
+ opts: ExtensionOptions<BSchema, I, S>
81
+ ) => {
82
+ const ret: Record<string, BlockNoteExtension> = {};
83
+ const tiptapExtensions = getTipTapExtensions(opts);
84
+
85
+ for (const ext of tiptapExtensions) {
86
+ ret[ext.name] = ext;
87
+ }
88
+
89
+ // Note: this is pretty hardcoded and will break when user provides plugins with same keys.
90
+ // Define name on plugins instead and not make this a map?
91
+ ret["formattingToolbar"] = new FormattingToolbarProsemirrorPlugin(
92
+ opts.editor
93
+ );
94
+ ret["linkToolbar"] = new LinkToolbarProsemirrorPlugin(opts.editor);
95
+ ret["sideMenu"] = new SideMenuProsemirrorPlugin(opts.editor);
96
+ ret["suggestionMenus"] = new SuggestionMenuProseMirrorPlugin(opts.editor);
97
+ ret["filePanel"] = new FilePanelProsemirrorPlugin(opts.editor as any);
98
+ ret["placeholder"] = new PlaceholderPlugin(opts.editor, opts.placeholders);
99
+
100
+ if (opts.animations ?? true) {
101
+ ret["animations"] = new PreviousBlockTypePlugin();
102
+ }
103
+
104
+ if (opts.tableHandles) {
105
+ ret["tableHandles"] = new TableHandlesProsemirrorPlugin(opts.editor as any);
106
+ }
107
+
108
+ ret["dropCursor"] = {
109
+ plugin: opts.dropCursor({
110
+ width: 5,
111
+ color: "#ddeeff",
112
+ editor: opts.editor,
113
+ }),
114
+ };
115
+
116
+ ret["nodeSelectionKeyboard"] = new NodeSelectionKeyboardPlugin();
117
+
118
+ const disableExtensions: string[] = opts.disableExtensions || [];
119
+ for (const ext of Object.keys(disableExtensions)) {
120
+ delete ret[ext];
121
+ }
122
+
123
+ return ret;
124
+ };
125
+
126
+ /**
127
+ * Get all the Tiptap extensions BlockNote is configured with by default
128
+ */
129
+ const getTipTapExtensions = <
130
+ BSchema extends BlockSchema,
131
+ I extends InlineContentSchema,
132
+ S extends StyleSchema
133
+ >(
134
+ opts: ExtensionOptions<BSchema, I, S>
135
+ ) => {
136
+ const tiptapExtensions: AnyExtension[] = [
61
137
  extensions.ClipboardTextSerializer,
62
138
  extensions.Commands,
63
139
  extensions.Editable,
@@ -81,6 +157,7 @@ export const getBlockNoteExtensions = <
81
157
 
82
158
  // marks:
83
159
  Link.extend({
160
+ inclusive: false,
84
161
  addKeyboardShortcuts() {
85
162
  return {
86
163
  "Mod-k": () => {
@@ -163,7 +240,7 @@ export const getBlockNoteExtensions = <
163
240
  ];
164
241
 
165
242
  if (opts.collaboration) {
166
- ret.push(
243
+ tiptapExtensions.push(
167
244
  Collaboration.configure({
168
245
  fragment: opts.collaboration.fragment,
169
246
  })
@@ -188,7 +265,7 @@ export const getBlockNoteExtensions = <
188
265
  cursor.insertBefore(nonbreakingSpace2, null);
189
266
  return cursor;
190
267
  };
191
- ret.push(
268
+ tiptapExtensions.push(
192
269
  CollaborationCursor.configure({
193
270
  user: opts.collaboration.user,
194
271
  render: opts.collaboration.renderCursor || defaultRender,
@@ -198,9 +275,8 @@ export const getBlockNoteExtensions = <
198
275
  }
199
276
  } else {
200
277
  // disable history extension when collaboration is enabled as Yjs takes care of undo / redo
201
- ret.push(History);
278
+ tiptapExtensions.push(History);
202
279
  }
203
280
 
204
- const disableExtensions: string[] = opts.disableExtensions || [];
205
- return ret.filter((ex) => !disableExtensions.includes(ex.name));
281
+ return tiptapExtensions;
206
282
  };
@@ -33,6 +33,9 @@ export const KeyboardShortcutsExtension = Extension.create<{
33
33
  () =>
34
34
  commands.command(({ state }) => {
35
35
  const blockInfo = getBlockInfoFromSelection(state);
36
+ if (!blockInfo.isBlockContainer) {
37
+ return false;
38
+ }
36
39
 
37
40
  const selectionAtBlockStart =
38
41
  state.selection.from === blockInfo.blockContent.beforePos + 1;
@@ -57,7 +60,11 @@ export const KeyboardShortcutsExtension = Extension.create<{
57
60
  // Removes a level of nesting if the block is indented if the selection is at the start of the block.
58
61
  () =>
59
62
  commands.command(({ state }) => {
60
- const { blockContent } = getBlockInfoFromSelection(state);
63
+ const blockInfo = getBlockInfoFromSelection(state);
64
+ if (!blockInfo.isBlockContainer) {
65
+ return false;
66
+ }
67
+ const { blockContent } = blockInfo;
61
68
 
62
69
  const selectionAtBlockStart =
63
70
  state.selection.from === blockContent.beforePos + 1;
@@ -72,8 +79,11 @@ export const KeyboardShortcutsExtension = Extension.create<{
72
79
  // block. The target block for merging must contain inline content.
73
80
  () =>
74
81
  commands.command(({ state }) => {
75
- const { bnBlock: blockContainer, blockContent } =
76
- getBlockInfoFromSelection(state);
82
+ const blockInfo = getBlockInfoFromSelection(state);
83
+ if (!blockInfo.isBlockContainer) {
84
+ return false;
85
+ }
86
+ const { bnBlock: blockContainer, blockContent } = blockInfo;
77
87
 
78
88
  const selectionAtBlockStart =
79
89
  state.selection.from === blockContent.beforePos + 1;
@@ -94,6 +104,9 @@ export const KeyboardShortcutsExtension = Extension.create<{
94
104
  commands.command(({ state, dispatch }) => {
95
105
  // when at the start of a first block in a column
96
106
  const blockInfo = getBlockInfoFromSelection(state);
107
+ if (!blockInfo.isBlockContainer) {
108
+ return false;
109
+ }
97
110
 
98
111
  const selectionAtBlockStart =
99
112
  state.selection.from === blockInfo.blockContent.beforePos + 1;
@@ -315,11 +328,15 @@ export const KeyboardShortcutsExtension = Extension.create<{
315
328
  () =>
316
329
  commands.command(({ state }) => {
317
330
  // TODO: Change this to not rely on offsets & schema assumptions
331
+ const blockInfo = getBlockInfoFromSelection(state);
332
+ if (!blockInfo.isBlockContainer) {
333
+ return false;
334
+ }
318
335
  const {
319
336
  bnBlock: blockContainer,
320
337
  blockContent,
321
338
  childContainer,
322
- } = getBlockInfoFromSelection(state);
339
+ } = blockInfo;
323
340
 
324
341
  const { depth } = state.doc.resolve(blockContainer.beforePos);
325
342
  const blockAtDocEnd =
@@ -358,8 +375,11 @@ export const KeyboardShortcutsExtension = Extension.create<{
358
375
  // of the block.
359
376
  () =>
360
377
  commands.command(({ state }) => {
361
- const { blockContent, bnBlock: blockContainer } =
362
- getBlockInfoFromSelection(state);
378
+ const blockInfo = getBlockInfoFromSelection(state);
379
+ if (!blockInfo.isBlockContainer) {
380
+ return false;
381
+ }
382
+ const { bnBlock: blockContainer, blockContent } = blockInfo;
363
383
 
364
384
  const { depth } = state.doc.resolve(blockContainer.beforePos);
365
385
 
@@ -385,8 +405,11 @@ export const KeyboardShortcutsExtension = Extension.create<{
385
405
  // empty & at the start of the block.
386
406
  () =>
387
407
  commands.command(({ state, dispatch }) => {
388
- const { bnBlock: blockContainer, blockContent } =
389
- getBlockInfoFromSelection(state);
408
+ const blockInfo = getBlockInfoFromSelection(state);
409
+ if (!blockInfo.isBlockContainer) {
410
+ return false;
411
+ }
412
+ const { bnBlock: blockContainer, blockContent } = blockInfo;
390
413
 
391
414
  const selectionAtBlockStart =
392
415
  state.selection.$anchor.parentOffset === 0;
@@ -419,7 +442,11 @@ export const KeyboardShortcutsExtension = Extension.create<{
419
442
  // deletes the selection beforehand, if it's not empty.
420
443
  () =>
421
444
  commands.command(({ state, chain }) => {
422
- const { blockContent } = getBlockInfoFromSelection(state);
445
+ const blockInfo = getBlockInfoFromSelection(state);
446
+ if (!blockInfo.isBlockContainer) {
447
+ return false;
448
+ }
449
+ const { blockContent } = blockInfo;
423
450
 
424
451
  const selectionAtBlockStart =
425
452
  state.selection.$anchor.parentOffset === 0;
@@ -15,51 +15,54 @@ const PLUGIN_KEY = new PluginKey("node-selection-keyboard");
15
15
  // While a more elegant solution would probably process transactions instead of
16
16
  // keystrokes, this brings us most of the way to Notion's UX without much added
17
17
  // complexity.
18
- export const NodeSelectionKeyboardPlugin = () => {
19
- return new Plugin({
20
- key: PLUGIN_KEY,
21
- props: {
22
- handleKeyDown: (view, event) => {
23
- // Checks for node selection
24
- if ("node" in view.state.selection) {
25
- // Checks if key press uses ctrl/meta modifier
26
- if (event.ctrlKey || event.metaKey) {
27
- return false;
28
- }
29
- // Checks if key press is alphanumeric
30
- if (event.key.length === 1) {
31
- event.preventDefault();
18
+ export class NodeSelectionKeyboardPlugin {
19
+ public readonly plugin: Plugin;
20
+ 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();
32
34
 
33
- return true;
34
- }
35
- // Checks if key press is Enter
36
- if (
37
- event.key === "Enter" &&
38
- !event.shiftKey &&
39
- !event.altKey &&
40
- !event.ctrlKey &&
41
- !event.metaKey
42
- ) {
43
- const tr = view.state.tr;
44
- view.dispatch(
45
- tr
46
- .insert(
47
- view.state.tr.selection.$to.after(),
48
- view.state.schema.nodes["paragraph"].createChecked()
49
- )
50
- .setSelection(
51
- new TextSelection(
52
- tr.doc.resolve(view.state.tr.selection.$to.after() + 1)
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)
55
+ )
53
56
  )
54
- )
55
- );
57
+ );
56
58
 
57
- return true;
59
+ return true;
60
+ }
58
61
  }
59
- }
60
62
 
61
- return false;
63
+ return false;
64
+ },
62
65
  },
63
- },
64
- });
65
- };
66
+ });
67
+ }
68
+ }
@@ -4,110 +4,114 @@ import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
4
4
 
5
5
  const PLUGIN_KEY = new PluginKey(`blocknote-placeholder`);
6
6
 
7
- export const PlaceholderPlugin = (
8
- editor: BlockNoteEditor<any, any, any>,
9
- placeholders: Record<string | "default", string>
10
- ) => {
11
- return new Plugin({
12
- key: PLUGIN_KEY,
13
- view: () => {
14
- const styleEl = document.createElement("style");
15
- const nonce = editor._tiptapEditor.options.injectNonce;
16
- if (nonce) {
17
- styleEl.setAttribute("nonce", nonce);
18
- }
19
- if (editor._tiptapEditor.view.root instanceof ShadowRoot) {
20
- editor._tiptapEditor.view.root.append(styleEl);
21
- } else {
22
- editor._tiptapEditor.view.root.head.appendChild(styleEl);
23
- }
24
-
25
- const styleSheet = styleEl.sheet!;
26
-
27
- const getBaseSelector = (additionalSelectors = "") =>
28
- `.bn-block-content${additionalSelectors} .bn-inline-content:has(> .ProseMirror-trailingBreak:only-child):before`;
29
-
30
- const getSelector = (
31
- blockType: string | "default",
32
- mustBeFocused = true
33
- ) => {
34
- const mustBeFocusedSelector = mustBeFocused
35
- ? `[data-is-empty-and-focused]`
36
- : ``;
37
-
38
- if (blockType === "default") {
39
- return getBaseSelector(mustBeFocusedSelector);
7
+ export class PlaceholderPlugin {
8
+ public readonly plugin: Plugin;
9
+ constructor(
10
+ editor: BlockNoteEditor<any, any, any>,
11
+ placeholders: Record<string | "default", string>
12
+ ) {
13
+ this.plugin = new Plugin({
14
+ key: PLUGIN_KEY,
15
+ view: () => {
16
+ const styleEl = document.createElement("style");
17
+ const nonce = editor._tiptapEditor.options.injectNonce;
18
+ if (nonce) {
19
+ styleEl.setAttribute("nonce", nonce);
40
20
  }
21
+ if (editor._tiptapEditor.view.root instanceof ShadowRoot) {
22
+ editor._tiptapEditor.view.root.append(styleEl);
23
+ } else {
24
+ editor._tiptapEditor.view.root.head.appendChild(styleEl);
25
+ }
26
+
27
+ const styleSheet = styleEl.sheet!;
28
+
29
+ const getBaseSelector = (additionalSelectors = "") =>
30
+ `.bn-block-content${additionalSelectors} .bn-inline-content:has(> .ProseMirror-trailingBreak:only-child):before`;
41
31
 
42
- const blockTypeSelector = `[data-content-type="${blockType}"]`;
43
- return getBaseSelector(mustBeFocusedSelector + blockTypeSelector);
44
- };
32
+ const getSelector = (
33
+ blockType: string | "default",
34
+ mustBeFocused = true
35
+ ) => {
36
+ const mustBeFocusedSelector = mustBeFocused
37
+ ? `[data-is-empty-and-focused]`
38
+ : ``;
39
+
40
+ if (blockType === "default") {
41
+ return getBaseSelector(mustBeFocusedSelector);
42
+ }
45
43
 
46
- for (const [blockType, placeholder] of Object.entries(placeholders)) {
47
- const mustBeFocused = blockType === "default";
44
+ const blockTypeSelector = `[data-content-type="${blockType}"]`;
45
+ return getBaseSelector(mustBeFocusedSelector + blockTypeSelector);
46
+ };
48
47
 
49
- styleSheet.insertRule(
50
- `${getSelector(blockType, mustBeFocused)}{ content: ${JSON.stringify(
51
- placeholder
52
- )}; }`
53
- );
48
+ for (const [blockType, placeholder] of Object.entries(placeholders)) {
49
+ const mustBeFocused = blockType === "default";
54
50
 
55
- // For some reason, the placeholders which show when the block is focused
56
- // take priority over ones which show depending on block type, so we need
57
- // to make sure the block specific ones are also used when the block is
58
- // focused.
59
- if (!mustBeFocused) {
60
51
  styleSheet.insertRule(
61
- `${getSelector(blockType, true)}{ content: ${JSON.stringify(
62
- placeholder
63
- )}; }`
52
+ `${getSelector(
53
+ blockType,
54
+ mustBeFocused
55
+ )}{ content: ${JSON.stringify(placeholder)}; }`
64
56
  );
65
- }
66
- }
67
-
68
- return {
69
- destroy: () => {
70
- if (editor._tiptapEditor.view.root instanceof ShadowRoot) {
71
- editor._tiptapEditor.view.root.removeChild(styleEl);
72
- } else {
73
- editor._tiptapEditor.view.root.head.removeChild(styleEl);
57
+
58
+ // For some reason, the placeholders which show when the block is focused
59
+ // take priority over ones which show depending on block type, so we need
60
+ // to make sure the block specific ones are also used when the block is
61
+ // focused.
62
+ if (!mustBeFocused) {
63
+ styleSheet.insertRule(
64
+ `${getSelector(blockType, true)}{ content: ${JSON.stringify(
65
+ placeholder
66
+ )}; }`
67
+ );
74
68
  }
75
- },
76
- };
77
- },
78
- props: {
79
- // TODO: maybe also add placeholder for empty document ("e.g.: start writing..")
80
- decorations: (state) => {
81
- const { doc, selection } = state;
82
-
83
- if (!editor.isEditable) {
84
- return;
85
69
  }
86
70
 
87
- if (!selection.empty) {
88
- return;
89
- }
71
+ return {
72
+ destroy: () => {
73
+ if (editor._tiptapEditor.view.root instanceof ShadowRoot) {
74
+ editor._tiptapEditor.view.root.removeChild(styleEl);
75
+ } else {
76
+ editor._tiptapEditor.view.root.head.removeChild(styleEl);
77
+ }
78
+ },
79
+ };
80
+ },
81
+ props: {
82
+ // TODO: maybe also add placeholder for empty document ("e.g.: start writing..")
83
+ decorations: (state) => {
84
+ const { doc, selection } = state;
90
85
 
91
- // Don't show placeholder when the cursor is inside a code block
92
- if (selection.$from.parent.type.spec.code) {
93
- return;
94
- }
86
+ if (!editor.isEditable) {
87
+ return;
88
+ }
95
89
 
96
- const $pos = selection.$anchor;
97
- const node = $pos.parent;
90
+ if (!selection.empty) {
91
+ return;
92
+ }
98
93
 
99
- if (node.content.size > 0) {
100
- return null;
101
- }
94
+ // Don't show placeholder when the cursor is inside a code block
95
+ if (selection.$from.parent.type.spec.code) {
96
+ return;
97
+ }
102
98
 
103
- const before = $pos.before();
99
+ const $pos = selection.$anchor;
100
+ const node = $pos.parent;
104
101
 
105
- const dec = Decoration.node(before, before + node.nodeSize, {
106
- "data-is-empty-and-focused": "true",
107
- });
102
+ if (node.content.size > 0) {
103
+ return null;
104
+ }
105
+
106
+ const before = $pos.before();
108
107
 
109
- return DecorationSet.create(doc, [dec]);
108
+ const dec = Decoration.node(before, before + node.nodeSize, {
109
+ "data-is-empty-and-focused": "true",
110
+ });
111
+
112
+ return DecorationSet.create(doc, [dec]);
113
+ },
110
114
  },
111
- },
112
- });
113
- };
115
+ });
116
+ }
117
+ }