@blocknote/core 0.19.0 → 0.19.2

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 (119) hide show
  1. package/README.md +2 -0
  2. package/dist/blocknote.js +2990 -2898
  3. package/dist/blocknote.js.map +1 -1
  4. package/dist/blocknote.umd.cjs +7 -7
  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/clipboard/fromClipboard/handleVSCodePaste.js +3 -3
  21. package/dist/src/api/clipboard/fromClipboard/handleVSCodePaste.js.map +1 -1
  22. package/dist/src/api/getBlockInfoFromPos.js +19 -25
  23. package/dist/src/api/getBlockInfoFromPos.js.map +1 -1
  24. package/dist/src/blocks/CodeBlockContent/CodeBlockContent.js +15 -7
  25. package/dist/src/blocks/CodeBlockContent/CodeBlockContent.js.map +1 -1
  26. package/dist/src/blocks/CodeBlockContent/defaultSupportedLanguages.js +38 -18
  27. package/dist/src/blocks/CodeBlockContent/defaultSupportedLanguages.js.map +1 -1
  28. package/dist/src/blocks/HeadingBlockContent/HeadingBlockContent.js +8 -4
  29. package/dist/src/blocks/HeadingBlockContent/HeadingBlockContent.js.map +1 -1
  30. package/dist/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.js +5 -3
  31. package/dist/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.js.map +1 -1
  32. package/dist/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.js +12 -6
  33. package/dist/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.js.map +1 -1
  34. package/dist/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.js +5 -1
  35. package/dist/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.js.map +1 -1
  36. package/dist/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.js +4 -2
  37. package/dist/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.js.map +1 -1
  38. package/dist/src/blocks/ParagraphBlockContent/ParagraphBlockContent.js +2 -1
  39. package/dist/src/blocks/ParagraphBlockContent/ParagraphBlockContent.js.map +1 -1
  40. package/dist/src/blocks/TableBlockContent/TableBlockContent.js +0 -1
  41. package/dist/src/blocks/TableBlockContent/TableBlockContent.js.map +1 -1
  42. package/dist/src/editor/BlockNoteEditor.js +42 -43
  43. package/dist/src/editor/BlockNoteEditor.js.map +1 -1
  44. package/dist/src/editor/BlockNoteEditor.test.js +2 -2
  45. package/dist/src/editor/BlockNoteEditor.test.js.map +1 -1
  46. package/dist/src/editor/BlockNoteExtensions.js +52 -6
  47. package/dist/src/editor/BlockNoteExtensions.js.map +1 -1
  48. package/dist/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.js +36 -6
  49. package/dist/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.js.map +1 -1
  50. package/dist/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.js +35 -32
  51. package/dist/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.js.map +1 -1
  52. package/dist/src/extensions/Placeholder/PlaceholderPlugin.js +74 -71
  53. package/dist/src/extensions/Placeholder/PlaceholderPlugin.js.map +1 -1
  54. package/dist/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.js +153 -149
  55. package/dist/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.js.map +1 -1
  56. package/dist/src/extensions/SideMenu/SideMenuPlugin.js +5 -1
  57. package/dist/src/extensions/SideMenu/SideMenuPlugin.js.map +1 -1
  58. package/dist/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.js +0 -3
  59. package/dist/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.js.map +1 -1
  60. package/dist/src/index.js +1 -0
  61. package/dist/src/index.js.map +1 -1
  62. package/dist/style.css +1 -1
  63. package/dist/tsconfig.tsbuildinfo +1 -1
  64. package/dist/webpack-stats.json +1 -1
  65. package/package.json +2 -2
  66. package/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap +0 -6
  67. package/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap +0 -5
  68. package/src/api/blockManipulation/commands/moveBlock/__snapshots__/moveBlock.test.ts.snap +0 -8
  69. package/src/api/blockManipulation/commands/moveBlock/moveBlock.test.ts +7 -3
  70. package/src/api/blockManipulation/commands/removeBlocks/__snapshots__/removeBlocks.test.ts.snap +439 -2
  71. package/src/api/blockManipulation/commands/removeBlocks/removeBlocks.test.ts +6 -0
  72. package/src/api/blockManipulation/commands/removeBlocks/removeBlocks.ts +2 -82
  73. package/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap +0 -8
  74. package/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts +96 -20
  75. package/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap +0 -6
  76. package/src/api/blockManipulation/commands/splitBlock/splitBlock.ts +2 -5
  77. package/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap +0 -490
  78. package/src/api/clipboard/fromClipboard/acceptedMIMETypes.ts +1 -1
  79. package/src/api/clipboard/fromClipboard/handleFileInsertion.ts +6 -5
  80. package/src/api/clipboard/fromClipboard/handleVSCodePaste.ts +4 -4
  81. package/src/api/getBlockInfoFromPos.ts +20 -30
  82. package/src/api/parsers/html/__snapshots__/parse-notion-html.json +1 -2
  83. package/src/blocks/CodeBlockContent/CodeBlockContent.ts +18 -8
  84. package/src/blocks/CodeBlockContent/defaultSupportedLanguages.ts +38 -18
  85. package/src/blocks/HeadingBlockContent/HeadingBlockContent.ts +16 -4
  86. package/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +9 -3
  87. package/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts +22 -6
  88. package/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts +5 -3
  89. package/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +8 -2
  90. package/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts +4 -1
  91. package/src/blocks/TableBlockContent/TableBlockContent.ts +0 -1
  92. package/src/editor/Block.css +3 -0
  93. package/src/editor/BlockNoteEditor.test.ts +2 -5
  94. package/src/editor/BlockNoteEditor.ts +77 -42
  95. package/src/editor/BlockNoteExtensions.ts +90 -14
  96. package/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +36 -9
  97. package/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.ts +45 -42
  98. package/src/extensions/Placeholder/PlaceholderPlugin.ts +94 -90
  99. package/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.ts +173 -169
  100. package/src/extensions/SideMenu/SideMenuPlugin.ts +5 -1
  101. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +0 -5
  102. package/src/index.ts +1 -0
  103. package/types/src/api/blockManipulation/commands/removeBlocks/removeBlocks.d.ts +0 -3
  104. package/types/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.d.ts +4 -0
  105. package/types/src/api/blockManipulation/setupTestEnv.d.ts +0 -6
  106. package/types/src/api/clipboard/fromClipboard/acceptedMIMETypes.d.ts +1 -1
  107. package/types/src/api/getBlockInfoFromPos.d.ts +9 -34
  108. package/types/src/api/testUtil/cases/customBlocks.d.ts +0 -6
  109. package/types/src/api/testUtil/cases/customInlineContent.d.ts +0 -6
  110. package/types/src/api/testUtil/cases/customStyles.d.ts +0 -6
  111. package/types/src/blocks/TableBlockContent/TableBlockContent.d.ts +0 -9
  112. package/types/src/blocks/defaultBlocks.d.ts +0 -12
  113. package/types/src/editor/BlockNoteEditor.d.ts +19 -2
  114. package/types/src/editor/BlockNoteExtensions.d.ts +14 -7
  115. package/types/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.d.ts +4 -1
  116. package/types/src/extensions/Placeholder/PlaceholderPlugin.d.ts +4 -1
  117. package/types/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.d.ts +4 -5
  118. package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +1 -1
  119. package/types/src/index.d.ts +1 -0
@@ -45,22 +45,21 @@ export type BlockInfo = {
45
45
  );
46
46
 
47
47
  /**
48
- * Retrieves the position just before the nearest blockContainer node in a
49
- * ProseMirror doc, relative to a position. If the position is within a
50
- * blockContainer node or its descendants, the position just before it is
51
- * returned. If the position is not within a blockContainer node or its
52
- * descendants, the position just before the next closest blockContainer node
53
- * is returned. If the position is beyond the last blockContainer, the position
54
- * just before the last blockContainer is returned.
48
+ * Retrieves the position just before the nearest block node in a ProseMirror
49
+ * doc, relative to a position. If the position is within a block node or its
50
+ * descendants, the position just before it is returned. If the position is not
51
+ * within a block node or its descendants, the position just before the next
52
+ * closest block node is returned. If the position is beyond the last block, the
53
+ * position just before the last block is returned.
55
54
  * @param doc The ProseMirror doc.
56
55
  * @param pos An integer position in the document.
57
56
  * @returns The position just before the nearest blockContainer node.
58
57
  */
59
- export function getNearestBlockContainerPos(doc: Node, pos: number) {
58
+ export function getNearestBlockPos(doc: Node, pos: number) {
60
59
  const $pos = doc.resolve(pos);
61
60
 
62
- // Checks if the position provided is already just before a blockContainer
63
- // node, in which case we return the position.
61
+ // Checks if the position provided is already just before a block node, in
62
+ // which case we return the position.
64
63
  if ($pos.nodeAfter && $pos.nodeAfter.type.isInGroup("bnBlock")) {
65
64
  return {
66
65
  posBeforeNode: $pos.pos,
@@ -69,7 +68,7 @@ export function getNearestBlockContainerPos(doc: Node, pos: number) {
69
68
  }
70
69
 
71
70
  // Checks the node containing the position and its ancestors until a
72
- // blockContainer node is found and returned.
71
+ // block node is found and returned.
73
72
  let depth = $pos.depth;
74
73
  let node = $pos.node(depth);
75
74
  while (depth > 0) {
@@ -84,13 +83,12 @@ export function getNearestBlockContainerPos(doc: Node, pos: number) {
84
83
  node = $pos.node(depth);
85
84
  }
86
85
 
87
- // If the position doesn't lie within a blockContainer node, we instead find
88
- // the position of the next closest one. If the position is beyond the last
89
- // blockContainer, we return the position of the last blockContainer. While
90
- // running `doc.descendants` is expensive, this case should be very rarely
91
- // triggered. However, it's possible for the position to sometimes be beyond
92
- // the last blockContainer node. This is a problem specifically when using the
93
- // collaboration plugin.
86
+ // If the position doesn't lie within a block node, we instead find the
87
+ // position of the next closest one. If the position is beyond the last block,
88
+ // we return the position of the last block. While running `doc.descendants`
89
+ // is expensive, this case should be very rarely triggered. However, it's
90
+ // possible for the position to sometimes be beyond the last block node. This
91
+ // is a problem specifically when using the collaboration plugin.
94
92
  const allBlockContainerPositions: number[] = [];
95
93
  doc.descendants((node, pos) => {
96
94
  if (node.type.isInGroup("bnBlock")) {
@@ -119,7 +117,7 @@ export function getNearestBlockContainerPos(doc: Node, pos: number) {
119
117
  * the ProseMirror positions just before & after each node.
120
118
  * @param node The main `blockContainer` node that the block information should
121
119
  * be retrieved from,
122
- * @param blockContainerBeforePosOffset the position just before the
120
+ * @param bnBlockBeforePosOffset the position just before the
123
121
  * `blockContainer` node in the document.
124
122
  */
125
123
  export function getBlockInfoWithManualOffset(
@@ -237,15 +235,7 @@ export function getBlockInfoFromResolvedPos(resolvedPos: ResolvedPos) {
237
235
  * @param state The ProseMirror editor state.
238
236
  */
239
237
  export function getBlockInfoFromSelection(state: EditorState) {
240
- const posInfo = getNearestBlockContainerPos(
241
- state.doc,
242
- state.selection.anchor
243
- );
244
- const ret = getBlockInfo(posInfo);
245
- if (!ret.isBlockContainer) {
246
- throw new Error(
247
- `selection always expected to return blockContainer ${state.selection.anchor}`
248
- );
249
- }
250
- return ret;
238
+ const posInfo = getNearestBlockPos(state.doc, state.selection.anchor);
239
+
240
+ return getBlockInfo(posInfo);
251
241
  }
@@ -365,8 +365,7 @@
365
365
  "id": "19",
366
366
  "type": "table",
367
367
  "props": {
368
- "textColor": "default",
369
- "backgroundColor": "default"
368
+ "textColor": "default"
370
369
  },
371
370
  "content": {
372
371
  "type": "tableContent",
@@ -47,11 +47,15 @@ const CodeBlockContent = createStronglyTypedTiptapNode({
47
47
  };
48
48
  },
49
49
  addAttributes() {
50
+ const supportedLanguages = this.options
51
+ .supportedLanguages as SupportedLanguageConfig[];
52
+
50
53
  return {
51
54
  language: {
52
55
  default: this.options.defaultLanguage,
53
56
  parseHTML: (inputElement) => {
54
57
  let element = inputElement as HTMLElement | null;
58
+ let language: string | null = null;
55
59
 
56
60
  if (
57
61
  element?.tagName === "DIV" &&
@@ -67,20 +71,26 @@ const CodeBlockContent = createStronglyTypedTiptapNode({
67
71
  const dataLanguage = element?.getAttribute("data-language");
68
72
 
69
73
  if (dataLanguage) {
70
- return dataLanguage.toLowerCase();
74
+ language = dataLanguage.toLowerCase();
75
+ } else {
76
+ const classNames = [...(element?.className.split(" ") || [])];
77
+ const languages = classNames
78
+ .filter((className) => className.startsWith("language-"))
79
+ .map((className) => className.replace("language-", ""));
80
+ const [classLanguage] = languages;
81
+
82
+ language = classLanguage.toLowerCase();
71
83
  }
72
84
 
73
- const classNames = [...(element?.className.split(" ") || [])];
74
- const languages = classNames
75
- .filter((className) => className.startsWith("language-"))
76
- .map((className) => className.replace("language-", ""));
77
- const [language] = languages;
78
-
79
85
  if (!language) {
80
86
  return null;
81
87
  }
82
88
 
83
- return language.toLowerCase();
89
+ return (
90
+ supportedLanguages.find(({ match }) => {
91
+ return match.includes(language);
92
+ })?.id || this.options.defaultLanguage
93
+ );
84
94
  },
85
95
  renderHTML: (attributes) => {
86
96
  return attributes.language && attributes.language !== "text"
@@ -1,4 +1,4 @@
1
- import { bundledLanguagesInfo } from "shiki/bundle/web";
1
+ import { bundledLanguagesInfo } from "shiki";
2
2
 
3
3
  export type SupportedLanguageConfig = {
4
4
  id: string;
@@ -14,23 +14,42 @@ export const defaultSupportedLanguages: SupportedLanguageConfig[] = [
14
14
  },
15
15
  ...bundledLanguagesInfo
16
16
  .filter((lang) => {
17
- return ![
18
- "angular-html",
19
- "angular-ts",
20
- "astro",
21
- "blade",
22
- "coffee",
23
- "handlebars",
24
- "html-derivative",
25
- "http",
26
- "imba",
27
- "jinja",
28
- "jison",
29
- "json5",
30
- "marko",
31
- "mdc",
32
- "stylus",
33
- "ts-tags",
17
+ return [
18
+ "c",
19
+ "cpp",
20
+ "css",
21
+ "glsl",
22
+ "graphql",
23
+ "haml",
24
+ "html",
25
+ "java",
26
+ "javascript",
27
+ "json",
28
+ "jsonc",
29
+ "jsonl",
30
+ "jsx",
31
+ "julia",
32
+ "less",
33
+ "markdown",
34
+ "mdx",
35
+ "php",
36
+ "postcss",
37
+ "pug",
38
+ "python",
39
+ "r",
40
+ "regexp",
41
+ "sass",
42
+ "scss",
43
+ "shellscript",
44
+ "sql",
45
+ "svelte",
46
+ "typescript",
47
+ "vue",
48
+ "vue-html",
49
+ "wasm",
50
+ "wgsl",
51
+ "xml",
52
+ "yaml",
34
53
  ].includes(lang.id);
35
54
  })
36
55
  .map((lang) => ({
@@ -38,6 +57,7 @@ export const defaultSupportedLanguages: SupportedLanguageConfig[] = [
38
57
  id: lang.id,
39
58
  name: lang.name,
40
59
  })),
60
+ { id: "tsx", name: "TSX", match: ["tsx", "typescriptreact"] },
41
61
  {
42
62
  id: "haskell",
43
63
  name: "Haskell",
@@ -48,7 +48,10 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({
48
48
  find: new RegExp(`^(#{${level}})\\s$`),
49
49
  handler: ({ state, chain, range }) => {
50
50
  const blockInfo = getBlockInfoFromSelection(state);
51
- if (blockInfo.blockContent.node.type.spec.content !== "inline*") {
51
+ if (
52
+ !blockInfo.isBlockContainer ||
53
+ blockInfo.blockContent.node.type.spec.content !== "inline*"
54
+ ) {
52
55
  return;
53
56
  }
54
57
 
@@ -78,7 +81,10 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({
78
81
  return {
79
82
  "Mod-Alt-1": () => {
80
83
  const blockInfo = getBlockInfoFromSelection(this.editor.state);
81
- if (blockInfo.blockContent.node.type.spec.content !== "inline*") {
84
+ if (
85
+ !blockInfo.isBlockContainer ||
86
+ blockInfo.blockContent.node.type.spec.content !== "inline*"
87
+ ) {
82
88
  return true;
83
89
  }
84
90
 
@@ -94,7 +100,10 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({
94
100
  },
95
101
  "Mod-Alt-2": () => {
96
102
  const blockInfo = getBlockInfoFromSelection(this.editor.state);
97
- if (blockInfo.blockContent.node.type.spec.content !== "inline*") {
103
+ if (
104
+ !blockInfo.isBlockContainer ||
105
+ blockInfo.blockContent.node.type.spec.content !== "inline*"
106
+ ) {
98
107
  return true;
99
108
  }
100
109
 
@@ -109,7 +118,10 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({
109
118
  },
110
119
  "Mod-Alt-3": () => {
111
120
  const blockInfo = getBlockInfoFromSelection(this.editor.state);
112
- if (blockInfo.blockContent.node.type.spec.content !== "inline*") {
121
+ if (
122
+ !blockInfo.isBlockContainer ||
123
+ blockInfo.blockContent.node.type.spec.content !== "inline*"
124
+ ) {
113
125
  return true;
114
126
  }
115
127
 
@@ -28,7 +28,10 @@ const BulletListItemBlockContent = createStronglyTypedTiptapNode({
28
28
  find: new RegExp(`^[-+*]\\s$`),
29
29
  handler: ({ state, chain, range }) => {
30
30
  const blockInfo = getBlockInfoFromSelection(state);
31
- if (blockInfo.blockContent.node.type.spec.content !== "inline*") {
31
+ if (
32
+ !blockInfo.isBlockContainer ||
33
+ blockInfo.blockContent.node.type.spec.content !== "inline*"
34
+ ) {
32
35
  return;
33
36
  }
34
37
 
@@ -55,11 +58,14 @@ const BulletListItemBlockContent = createStronglyTypedTiptapNode({
55
58
  Enter: () => handleEnter(this.options.editor),
56
59
  "Mod-Shift-8": () => {
57
60
  const blockInfo = getBlockInfoFromSelection(this.editor.state);
58
- if (blockInfo.blockContent.node.type.spec.content !== "inline*") {
61
+ if (
62
+ !blockInfo.isBlockContainer ||
63
+ blockInfo.blockContent.node.type.spec.content !== "inline*"
64
+ ) {
59
65
  return true;
60
66
  }
61
67
 
62
- return this.options.editor.commands.command(
68
+ return this.editor.commands.command(
63
69
  updateBlockCommand(this.options.editor, blockInfo.bnBlock.beforePos, {
64
70
  type: "bulletListItem",
65
71
  props: {},
@@ -2,7 +2,7 @@ import { InputRule } from "@tiptap/core";
2
2
  import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js";
3
3
  import {
4
4
  getBlockInfoFromSelection,
5
- getNearestBlockContainerPos,
5
+ getNearestBlockPos,
6
6
  } from "../../../api/getBlockInfoFromPos.js";
7
7
  import {
8
8
  PropSchema,
@@ -49,7 +49,10 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({
49
49
  find: new RegExp(`\\[\\s*\\]\\s$`),
50
50
  handler: ({ state, chain, range }) => {
51
51
  const blockInfo = getBlockInfoFromSelection(state);
52
- if (blockInfo.blockContent.node.type.spec.content !== "inline*") {
52
+ if (
53
+ !blockInfo.isBlockContainer ||
54
+ blockInfo.blockContent.node.type.spec.content !== "inline*"
55
+ ) {
53
56
  return;
54
57
  }
55
58
 
@@ -75,7 +78,10 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({
75
78
  handler: ({ state, chain, range }) => {
76
79
  const blockInfo = getBlockInfoFromSelection(state);
77
80
 
78
- if (blockInfo.blockContent.node.type.spec.content !== "inline*") {
81
+ if (
82
+ !blockInfo.isBlockContainer ||
83
+ blockInfo.blockContent.node.type.spec.content !== "inline*"
84
+ ) {
79
85
  return;
80
86
  }
81
87
 
@@ -103,8 +109,11 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({
103
109
  return {
104
110
  Enter: () => handleEnter(this.options.editor),
105
111
  "Mod-Shift-9": () => {
106
- const blockInfo = getBlockInfoFromSelection(this.options.editor.state);
107
- if (blockInfo.blockContent.node.type.spec.content !== "inline*") {
112
+ const blockInfo = getBlockInfoFromSelection(this.editor.state);
113
+ if (
114
+ !blockInfo.isBlockContainer ||
115
+ blockInfo.blockContent.node.type.spec.content !== "inline*"
116
+ ) {
108
117
  return true;
109
118
  }
110
119
 
@@ -232,10 +241,17 @@ const checkListItemBlockContent = createStronglyTypedTiptapNode({
232
241
 
233
242
  // TODO: test
234
243
  if (typeof getPos !== "boolean") {
235
- const beforeBlockContainerPos = getNearestBlockContainerPos(
244
+ const beforeBlockContainerPos = getNearestBlockPos(
236
245
  editor.state.doc,
237
246
  getPos()
238
247
  );
248
+
249
+ if (beforeBlockContainerPos.node.type.name !== "blockContainer") {
250
+ throw new Error(
251
+ `Expected blockContainer node, got ${beforeBlockContainerPos.node.type.name}`
252
+ );
253
+ }
254
+
239
255
  this.editor.commands.command(
240
256
  updateBlockCommand(
241
257
  this.options.editor,
@@ -5,9 +5,11 @@ import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
5
5
 
6
6
  export const handleEnter = (editor: BlockNoteEditor<any, any, any>) => {
7
7
  const ttEditor = editor._tiptapEditor;
8
- const { blockContent, bnBlock: blockContainer } = getBlockInfoFromSelection(
9
- ttEditor.state
10
- );
8
+ const blockInfo = getBlockInfoFromSelection(ttEditor.state);
9
+ if (!blockInfo.isBlockContainer) {
10
+ return false;
11
+ }
12
+ const { bnBlock: blockContainer, blockContent } = blockInfo;
11
13
 
12
14
  const selectionEmpty =
13
15
  ttEditor.state.selection.anchor === ttEditor.state.selection.head;
@@ -41,7 +41,10 @@ const NumberedListItemBlockContent = createStronglyTypedTiptapNode({
41
41
  find: new RegExp(`^1\\.\\s$`),
42
42
  handler: ({ state, chain, range }) => {
43
43
  const blockInfo = getBlockInfoFromSelection(state);
44
- if (blockInfo.blockContent.node.type.spec.content !== "inline*") {
44
+ if (
45
+ !blockInfo.isBlockContainer ||
46
+ blockInfo.blockContent.node.type.spec.content !== "inline*"
47
+ ) {
45
48
  return;
46
49
  }
47
50
 
@@ -68,7 +71,10 @@ const NumberedListItemBlockContent = createStronglyTypedTiptapNode({
68
71
  Enter: () => handleEnter(this.options.editor),
69
72
  "Mod-Shift-7": () => {
70
73
  const blockInfo = getBlockInfoFromSelection(this.editor.state);
71
- if (blockInfo.blockContent.node.type.spec.content !== "inline*") {
74
+ if (
75
+ !blockInfo.isBlockContainer ||
76
+ blockInfo.blockContent.node.type.spec.content !== "inline*"
77
+ ) {
72
78
  return true;
73
79
  }
74
80
 
@@ -20,7 +20,10 @@ export const ParagraphBlockContent = createStronglyTypedTiptapNode({
20
20
  return {
21
21
  "Mod-Alt-0": () => {
22
22
  const blockInfo = getBlockInfoFromSelection(this.editor.state);
23
- if (blockInfo.blockContent.node.type.spec.content !== "inline*") {
23
+ if (
24
+ !blockInfo.isBlockContainer ||
25
+ blockInfo.blockContent.node.type.spec.content !== "inline*"
26
+ ) {
24
27
  return true;
25
28
  }
26
29
 
@@ -15,7 +15,6 @@ import { defaultProps } from "../defaultProps.js";
15
15
  import { EMPTY_CELL_WIDTH, TableExtension } from "./TableExtension.js";
16
16
 
17
17
  export const tablePropSchema = {
18
- backgroundColor: defaultProps.backgroundColor,
19
18
  textColor: defaultProps.textColor,
20
19
  };
21
20
 
@@ -299,6 +299,9 @@ NESTED BLOCKS
299
299
  transition: opacity 0.3s;
300
300
  transition-delay: 1s;
301
301
  }
302
+ .bn-block-content[data-content-type="codeBlock"] > div > select > option {
303
+ color: black;
304
+ }
302
305
  .bn-block-content[data-content-type="codeBlock"]:hover > div > select,
303
306
  .bn-block-content[data-content-type="codeBlock"] > div > select:focus {
304
307
  opacity: 0.5;
@@ -1,7 +1,7 @@
1
1
  import { expect, it } from "vitest";
2
2
  import {
3
3
  getBlockInfo,
4
- getNearestBlockContainerPos,
4
+ getNearestBlockPos,
5
5
  } from "../api/getBlockInfoFromPos.js";
6
6
  import { BlockNoteEditor } from "./BlockNoteEditor.js";
7
7
 
@@ -10,10 +10,7 @@ import { BlockNoteEditor } from "./BlockNoteEditor.js";
10
10
  */
11
11
  it("creates an editor", () => {
12
12
  const editor = BlockNoteEditor.create();
13
- const posInfo = getNearestBlockContainerPos(
14
- editor._tiptapEditor.state.doc,
15
- 2
16
- );
13
+ const posInfo = getNearestBlockPos(editor._tiptapEditor.state.doc, 2);
17
14
  const info = getBlockInfo(posInfo);
18
15
  expect(info.blockNoteType).toEqual("paragraph");
19
16
  });
@@ -1,4 +1,11 @@
1
- import { EditorOptions, Extension, getSchema } from "@tiptap/core";
1
+ import {
2
+ AnyExtension,
3
+ EditorOptions,
4
+ Extension,
5
+ getSchema,
6
+ Mark,
7
+ Node as TipTapNode,
8
+ } from "@tiptap/core";
2
9
  import { Node, Schema } from "prosemirror-model";
3
10
  // import "./blocknote.css";
4
11
  import * as Y from "yjs";
@@ -47,9 +54,9 @@ import {
47
54
  InlineContentSchema,
48
55
  InlineContentSpecs,
49
56
  PartialInlineContent,
57
+ Styles,
50
58
  StyleSchema,
51
59
  StyleSpecs,
52
- Styles,
53
60
  } from "../schema/index.js";
54
61
  import { mergeCSSClasses } from "../util/browser.js";
55
62
  import { NoInfer, UnreachableCaseError } from "../util/typescript.js";
@@ -67,7 +74,6 @@ import {
67
74
  BlockNoteTipTapEditorOptions,
68
75
  } from "./BlockNoteTipTapEditor.js";
69
76
 
70
- import { PlaceholderPlugin } from "../extensions/Placeholder/PlaceholderPlugin.js";
71
77
  import { Dictionary } from "../i18n/dictionary.js";
72
78
  import { en } from "../i18n/locales/index.js";
73
79
 
@@ -76,10 +82,14 @@ import { dropCursor } from "prosemirror-dropcursor";
76
82
  import { createInternalHTMLSerializer } from "../api/exporters/html/internalHTMLSerializer.js";
77
83
  import { inlineContentToNodes } from "../api/nodeConversions/blockToNode.js";
78
84
  import { nodeToBlock } from "../api/nodeConversions/nodeToBlock.js";
79
- import { NodeSelectionKeyboardPlugin } from "../extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.js";
80
- import { PreviousBlockTypePlugin } from "../extensions/PreviousBlockType/PreviousBlockTypePlugin.js";
81
85
  import "../style.css";
82
86
 
87
+ export type BlockNoteExtension =
88
+ | AnyExtension
89
+ | {
90
+ plugin: Plugin;
91
+ };
92
+
83
93
  export type BlockNoteEditorOptions<
84
94
  BSchema extends BlockSchema,
85
95
  ISchema extends InlineContentSchema,
@@ -92,7 +102,11 @@ export type BlockNoteEditorOptions<
92
102
  */
93
103
  animations?: boolean;
94
104
 
105
+ /**
106
+ * Disable internal extensions (based on keys / extension name)
107
+ */
95
108
  disableExtensions: string[];
109
+
96
110
  /**
97
111
  * A dictionary object containing translations for the editor.
98
112
  */
@@ -173,9 +187,16 @@ export type BlockNoteEditorOptions<
173
187
  renderCursor?: (user: any) => HTMLElement;
174
188
  };
175
189
 
176
- // tiptap options, undocumented
190
+ /**
191
+ * additional tiptap options, undocumented
192
+ */
177
193
  _tiptapOptions: Partial<EditorOptions>;
178
194
 
195
+ /**
196
+ * (experimental) add extra prosemirror plugins or tiptap extensions to the editor
197
+ */
198
+ _extensions: Record<string, BlockNoteExtension>;
199
+
179
200
  trailingBlock?: boolean;
180
201
 
181
202
  /**
@@ -213,6 +234,11 @@ export class BlockNoteEditor<
213
234
  > {
214
235
  private readonly _pmSchema: Schema;
215
236
 
237
+ /**
238
+ * extensions that are added to the editor, can be tiptap extensions or prosemirror plugins
239
+ */
240
+ public readonly extensions: Record<string, BlockNoteExtension> = {};
241
+
216
242
  /**
217
243
  * Boolean indicating whether the editor is in headless mode.
218
244
  * Headless mode means we can use features like importing / exporting blocks,
@@ -355,17 +381,7 @@ export class BlockNoteEditor<
355
381
  this.inlineContentImplementations = newOptions.schema.inlineContentSpecs;
356
382
  this.styleImplementations = newOptions.schema.styleSpecs;
357
383
 
358
- this.formattingToolbar = new FormattingToolbarProsemirrorPlugin(this);
359
- this.linkToolbar = new LinkToolbarProsemirrorPlugin(this);
360
- this.sideMenu = new SideMenuProsemirrorPlugin(this);
361
- this.suggestionMenus = new SuggestionMenuProseMirrorPlugin(this);
362
- this.filePanel = new FilePanelProsemirrorPlugin(this as any);
363
-
364
- if (checkDefaultBlockTypeInSchema("table", this)) {
365
- this.tableHandles = new TableHandlesProsemirrorPlugin(this as any);
366
- }
367
-
368
- const extensions = getBlockNoteExtensions({
384
+ this.extensions = getBlockNoteExtensions({
369
385
  editor: this,
370
386
  domAttributes: newOptions.domAttributes || {},
371
387
  blockSpecs: this.schema.blockSpecs,
@@ -375,30 +391,28 @@ export class BlockNoteEditor<
375
391
  trailingBlock: newOptions.trailingBlock,
376
392
  disableExtensions: newOptions.disableExtensions,
377
393
  setIdAttribute: newOptions.setIdAttribute,
394
+ animations: newOptions.animations ?? true,
395
+ tableHandles: checkDefaultBlockTypeInSchema("table", this),
396
+ dropCursor: this.options.dropCursor ?? dropCursor,
397
+ placeholders: newOptions.placeholders,
378
398
  });
379
399
 
380
- const dropCursorPlugin: any = this.options.dropCursor ?? dropCursor;
381
- const blockNoteUIExtension = Extension.create({
382
- name: "BlockNoteUIExtension",
383
-
384
- addProseMirrorPlugins: () => {
385
- return [
386
- this.formattingToolbar.plugin,
387
- this.linkToolbar.plugin,
388
- this.sideMenu.plugin,
389
- this.suggestionMenus.plugin,
390
- ...(this.filePanel ? [this.filePanel.plugin] : []),
391
- ...(this.tableHandles ? [this.tableHandles.plugin] : []),
392
- dropCursorPlugin({ width: 5, color: "#ddeeff", editor: this }),
393
- PlaceholderPlugin(this, newOptions.placeholders),
394
- NodeSelectionKeyboardPlugin(),
395
- ...(this.options.animations ?? true
396
- ? [PreviousBlockTypePlugin()]
397
- : []),
398
- ];
399
- },
400
+ // add extensions from _tiptapOptions
401
+ (newOptions._tiptapOptions?.extensions || []).forEach((ext) => {
402
+ this.extensions[ext.name] = ext;
403
+ });
404
+
405
+ // add extensions from options
406
+ Object.entries(newOptions._extensions || {}).forEach(([key, ext]) => {
407
+ this.extensions[key] = ext;
400
408
  });
401
- extensions.push(blockNoteUIExtension);
409
+
410
+ this.formattingToolbar = this.extensions["formattingToolbar"] as any;
411
+ this.linkToolbar = this.extensions["linkToolbar"] as any;
412
+ this.sideMenu = this.extensions["sideMenu"] as any;
413
+ this.suggestionMenus = this.extensions["suggestionMenus"] as any;
414
+ this.filePanel = this.extensions["filePanel"] as any;
415
+ this.tableHandles = this.extensions["tableHandles"] as any;
402
416
 
403
417
  if (newOptions.uploadFile) {
404
418
  const uploadFile = newOptions.uploadFile;
@@ -449,14 +463,35 @@ export class BlockNoteEditor<
449
463
  );
450
464
  }
451
465
 
466
+ const tiptapExtensions = [
467
+ ...Object.entries(this.extensions).map(([key, ext]) => {
468
+ if (
469
+ ext instanceof Extension ||
470
+ ext instanceof TipTapNode ||
471
+ ext instanceof Mark
472
+ ) {
473
+ // tiptap extension
474
+ return ext;
475
+ }
476
+
477
+ if (!ext.plugin) {
478
+ throw new Error(
479
+ "Extension should either be a TipTap extension or a ProseMirror plugin in a plugin property"
480
+ );
481
+ }
482
+
483
+ // "blocknote" extensions (prosemirror plugins)
484
+ return Extension.create({
485
+ name: key,
486
+ addProseMirrorPlugins: () => [ext.plugin],
487
+ });
488
+ }),
489
+ ];
452
490
  const tiptapOptions: BlockNoteTipTapEditorOptions = {
453
491
  ...blockNoteTipTapOptions,
454
492
  ...newOptions._tiptapOptions,
455
493
  content: initialContent,
456
- extensions: [
457
- ...(newOptions._tiptapOptions?.extensions || []),
458
- ...extensions,
459
- ],
494
+ extensions: tiptapExtensions,
460
495
  editorProps: {
461
496
  ...newOptions._tiptapOptions?.editorProps,
462
497
  attributes: {