@blocknote/core 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/dist/blocknote.cjs +11 -10
  2. package/dist/blocknote.cjs.map +1 -1
  3. package/dist/blocknote.js +3685 -9960
  4. package/dist/blocknote.js.map +1 -1
  5. package/dist/comments.cjs.map +1 -1
  6. package/dist/comments.js.map +1 -1
  7. package/dist/en-B7ycW7c8.js +360 -0
  8. package/dist/en-B7ycW7c8.js.map +1 -0
  9. package/dist/en-D4taoCs4.cjs +2 -0
  10. package/dist/en-D4taoCs4.cjs.map +1 -0
  11. package/dist/locales.cjs +2 -0
  12. package/dist/locales.cjs.map +1 -0
  13. package/dist/locales.js +6129 -0
  14. package/dist/locales.js.map +1 -0
  15. package/dist/style.css +1 -1
  16. package/dist/tsconfig.tsbuildinfo +1 -1
  17. package/dist/webpack-stats.json +1 -1
  18. package/package.json +36 -27
  19. package/src/api/clipboard/__snapshots__/internal/basicBlocks.html +1 -1
  20. package/src/api/clipboard/__snapshots__/internal/basicBlocksWithProps.html +1 -1
  21. package/src/api/clipboard/clipboardExternal.test.ts +1 -1
  22. package/src/api/clipboard/clipboardInternal.test.ts +1 -1
  23. package/src/api/clipboard/fromClipboard/acceptedMIMETypes.ts +1 -0
  24. package/src/api/clipboard/fromClipboard/pasteExtension.ts +96 -42
  25. package/src/api/exporters/html/__snapshots__/codeBlock/contains-newlines/external.html +1 -1
  26. package/src/api/exporters/html/__snapshots__/codeBlock/contains-newlines/internal.html +1 -1
  27. package/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/external.html +1 -1
  28. package/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/internal.html +1 -1
  29. package/src/api/exporters/html/__snapshots__/codeBlock/empty/external.html +1 -1
  30. package/src/api/exporters/html/__snapshots__/codeBlock/empty/internal.html +1 -1
  31. package/src/api/exporters/html/__snapshots__/codeBlock/python/external.html +1 -1
  32. package/src/api/exporters/html/__snapshots__/codeBlock/python/internal.html +1 -1
  33. package/src/api/exporters/html/htmlConversion.test.ts +2 -2
  34. package/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +4 -4
  35. package/src/api/exporters/markdown/__snapshots__/codeBlock/defaultLanguage/markdown.md +1 -1
  36. package/src/api/exporters/markdown/__snapshots__/codeBlock/empty/markdown.md +1 -1
  37. package/src/api/exporters/markdown/__snapshots__/complex/misc/markdown.md +1 -1
  38. package/src/api/exporters/markdown/__snapshots__/lists/basic/markdown.md +8 -6
  39. package/src/api/exporters/markdown/__snapshots__/lists/nested/markdown.md +6 -6
  40. package/src/api/exporters/markdown/markdownExporter.test.ts +2 -2
  41. package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +2 -2
  42. package/src/api/parsers/html/__snapshots__/parse-codeblocks.json +1 -1
  43. package/src/api/parsers/html/parseHTML.test.ts +1 -1
  44. package/src/api/parsers/markdown/__snapshots__/pasted/whitespace bold.json +42 -0
  45. package/src/api/parsers/markdown/__snapshots__/whitespace bold.json +19 -0
  46. package/src/api/parsers/markdown/detectMarkdown.ts +60 -0
  47. package/src/api/parsers/markdown/parseMarkdown.test.ts +7 -2
  48. package/src/api/parsers/markdown/parseMarkdown.ts +19 -18
  49. package/src/api/testUtil/cases/defaultSchema.ts +13 -0
  50. package/src/blocks/CodeBlockContent/CodeBlockContent.ts +100 -69
  51. package/src/blocks/QuoteBlockContent/QuoteBlockContent.ts +98 -0
  52. package/src/blocks/TableBlockContent/TableExtension.ts +1 -1
  53. package/src/blocks/defaultBlocks.ts +2 -2
  54. package/src/editor/Block.css +13 -0
  55. package/src/editor/BlockNoteEditor.ts +102 -10
  56. package/src/editor/BlockNoteExtensions.ts +18 -4
  57. package/src/extensions/Comments/CommentsPlugin.ts +1 -1
  58. package/src/extensions/HardBreak/HardBreak.ts +35 -0
  59. package/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +100 -3
  60. package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +3 -1
  61. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +12 -0
  62. package/src/i18n/index.ts +2 -0
  63. package/src/i18n/locales/ar.ts +6 -0
  64. package/src/i18n/locales/de.ts +6 -0
  65. package/src/i18n/locales/en.ts +6 -0
  66. package/src/i18n/locales/es.ts +6 -0
  67. package/src/i18n/locales/fr.ts +6 -0
  68. package/src/i18n/locales/hr.ts +6 -0
  69. package/src/i18n/locales/is.ts +6 -0
  70. package/src/i18n/locales/it.ts +6 -0
  71. package/src/i18n/locales/ja.ts +6 -0
  72. package/src/i18n/locales/ko.ts +6 -0
  73. package/src/i18n/locales/nl.ts +6 -0
  74. package/src/i18n/locales/no.ts +6 -0
  75. package/src/i18n/locales/pl.ts +6 -0
  76. package/src/i18n/locales/pt.ts +6 -0
  77. package/src/i18n/locales/ru.ts +6 -0
  78. package/src/i18n/locales/uk.ts +6 -0
  79. package/src/i18n/locales/vi.ts +6 -0
  80. package/src/i18n/locales/zh.ts +6 -0
  81. package/src/index.ts +2 -3
  82. package/src/locales.ts +1 -0
  83. package/src/schema/blocks/types.ts +1 -0
  84. package/types/src/api/blockManipulation/commands/updateBlock/updateBlock.d.ts +1 -1
  85. package/types/src/api/blockManipulation/setupTestEnv.d.ts +34 -2
  86. package/types/src/api/clipboard/fromClipboard/acceptedMIMETypes.d.ts +1 -1
  87. package/types/src/api/clipboard/fromClipboard/fileDropExtension.d.ts +2 -2
  88. package/types/src/api/clipboard/fromClipboard/pasteExtension.d.ts +3 -3
  89. package/types/src/api/clipboard/testUtil.d.ts +66 -34
  90. package/types/src/api/clipboard/toClipboard/copyExtension.d.ts +1 -1
  91. package/types/src/api/exporters/html/externalHTMLExporter.d.ts +2 -2
  92. package/types/src/api/exporters/html/internalHTMLSerializer.d.ts +2 -2
  93. package/types/src/api/exporters/html/util/serializeBlocksExternalHTML.d.ts +1 -1
  94. package/types/src/api/exporters/html/util/serializeBlocksInternalHTML.d.ts +1 -1
  95. package/types/src/api/parsers/markdown/detectMarkdown.d.ts +6 -0
  96. package/types/src/api/parsers/markdown/parseMarkdown.d.ts +1 -0
  97. package/types/src/api/testUtil/cases/customBlocks.d.ts +72 -40
  98. package/types/src/api/testUtil/cases/customInlineContent.d.ts +34 -2
  99. package/types/src/api/testUtil/cases/customStyles.d.ts +34 -2
  100. package/types/src/blocks/AudioBlockContent/AudioBlockContent.d.ts +3 -3
  101. package/types/src/blocks/CodeBlockContent/CodeBlockContent.d.ts +46 -34
  102. package/types/src/blocks/FileBlockContent/FileBlockContent.d.ts +3 -3
  103. package/types/src/blocks/FileBlockContent/helpers/render/createFileBlockWrapper.d.ts +1 -1
  104. package/types/src/blocks/HeadingBlockContent/HeadingBlockContent.d.ts +2 -2
  105. package/types/src/blocks/ImageBlockContent/ImageBlockContent.d.ts +2 -2
  106. package/types/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.d.ts +2 -2
  107. package/types/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.d.ts +2 -2
  108. package/types/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.d.ts +2 -2
  109. package/types/src/blocks/PageBreakBlockContent/PageBreakBlockContent.d.ts +2 -2
  110. package/types/src/blocks/PageBreakBlockContent/schema.d.ts +13 -13
  111. package/types/src/blocks/ParagraphBlockContent/ParagraphBlockContent.d.ts +2 -2
  112. package/types/src/blocks/QuoteBlockContent/QuoteBlockContent.d.ts +52 -0
  113. package/types/src/blocks/TableBlockContent/TableBlockContent.d.ts +2 -2
  114. package/types/src/blocks/VideoBlockContent/VideoBlockContent.d.ts +2 -2
  115. package/types/src/blocks/defaultBlockHelpers.d.ts +2 -2
  116. package/types/src/blocks/defaultBlocks.d.ts +107 -44
  117. package/types/src/comments/threadstore/yjs/YjsThreadStore.d.ts +3 -3
  118. package/types/src/editor/BlockNoteEditor.d.ts +58 -0
  119. package/types/src/editor/BlockNoteExtensions.d.ts +3 -2
  120. package/types/src/exporter/mapping.d.ts +2 -2
  121. package/types/src/extensions/BackgroundColor/BackgroundColorMark.d.ts +1 -1
  122. package/types/src/extensions/Collaboration/createCollaborationExtensions.d.ts +1 -1
  123. package/types/src/extensions/HardBreak/HardBreak.d.ts +2 -0
  124. package/types/src/extensions/TableHandles/TableHandlesPlugin.d.ts +4 -4
  125. package/types/src/extensions/TextColor/TextColorMark.d.ts +1 -1
  126. package/types/src/i18n/index.d.ts +2 -0
  127. package/types/src/i18n/locales/en.d.ts +7 -1
  128. package/types/src/index.d.ts +1 -2
  129. package/types/src/locales.d.ts +1 -0
  130. package/types/src/pm-nodes/BlockContainer.d.ts +2 -7
  131. package/types/src/pm-nodes/BlockGroup.d.ts +2 -7
  132. package/types/src/schema/blocks/types.d.ts +1 -0
  133. package/types/src/schema/inlineContent/internal.d.ts +1 -1
  134. package/README.md +0 -125
  135. package/src/blocks/CodeBlockContent/defaultSupportedLanguages.ts +0 -116
  136. package/types/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.d.ts +0 -47
  137. package/types/src/extensions/Comments/threadstore/ThreadStore.d.ts +0 -121
  138. package/types/src/extensions/Comments/threadstore/ThreadStoreAuth.d.ts +0 -12
  139. package/types/src/extensions/Comments/threadstore/TipTapThreadStore.d.ts +0 -97
  140. package/types/src/extensions/Comments/threadstore/yjs/RESTYjsThreadStore.d.ts +0 -83
  141. package/types/src/extensions/Comments/threadstore/yjs/YjsThreadStore.d.ts +0 -79
  142. package/types/src/extensions/Comments/threadstore/yjs/YjsThreadStore.test.d.ts +0 -1
  143. package/types/src/extensions/Comments/threadstore/yjs/YjsThreadStoreBase.d.ts +0 -15
  144. package/types/src/extensions/Comments/threadstore/yjs/yjsHelpers.d.ts +0 -13
  145. package/types/src/extensions/Comments/types.d.ts +0 -109
  146. package/types/src/models/User.d.ts +0 -5
@@ -699,7 +699,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert
699
699
  "content": [
700
700
  {
701
701
  "attrs": {
702
- "language": "javascript",
702
+ "language": "text",
703
703
  },
704
704
  "content": [
705
705
  {
@@ -724,7 +724,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert
724
724
  "content": [
725
725
  {
726
726
  "attrs": {
727
- "language": "javascript",
727
+ "language": "text",
728
728
  },
729
729
  "type": "codeBlock",
730
730
  },
@@ -3,7 +3,7 @@
3
3
  "id": "1",
4
4
  "type": "codeBlock",
5
5
  "props": {
6
- "language": "javascript"
6
+ "language": "text"
7
7
  },
8
8
  "content": [
9
9
  {
@@ -13,7 +13,7 @@ async function parseHTMLAndCompareSnapshots(
13
13
  const blocks = await editor.tryParseHTMLToBlocks(html);
14
14
 
15
15
  const snapshotPath = "./__snapshots__/" + snapshotName + ".json";
16
- expect(JSON.stringify(blocks, undefined, 2)).toMatchFileSnapshot(
16
+ await expect(JSON.stringify(blocks, undefined, 2)).toMatchFileSnapshot(
17
17
  snapshotPath
18
18
  );
19
19
 
@@ -0,0 +1,42 @@
1
+ [
2
+ {
3
+ "id": "0",
4
+ "type": "paragraph",
5
+ "props": {
6
+ "textColor": "default",
7
+ "backgroundColor": "default",
8
+ "textAlignment": "left"
9
+ },
10
+ "content": [
11
+ {
12
+ "type": "text",
13
+ "text": "hello ",
14
+ "styles": {}
15
+ },
16
+ {
17
+ "type": "text",
18
+ "text": "beautiful ",
19
+ "styles": {
20
+ "bold": true
21
+ }
22
+ },
23
+ {
24
+ "type": "text",
25
+ "text": " world",
26
+ "styles": {}
27
+ }
28
+ ],
29
+ "children": []
30
+ },
31
+ {
32
+ "id": "2",
33
+ "type": "paragraph",
34
+ "props": {
35
+ "textColor": "default",
36
+ "backgroundColor": "default",
37
+ "textAlignment": "left"
38
+ },
39
+ "content": [],
40
+ "children": []
41
+ }
42
+ ]
@@ -0,0 +1,19 @@
1
+ [
2
+ {
3
+ "id": "1",
4
+ "type": "paragraph",
5
+ "props": {
6
+ "textColor": "default",
7
+ "backgroundColor": "default",
8
+ "textAlignment": "left"
9
+ },
10
+ "content": [
11
+ {
12
+ "type": "text",
13
+ "text": "hello **beautiful ** world",
14
+ "styles": {}
15
+ }
16
+ ],
17
+ "children": []
18
+ }
19
+ ]
@@ -0,0 +1,60 @@
1
+ // Headings H1-H6.
2
+ const h1 = /(^|\n) {0,3}#{1,6} {1,8}[^\n]{1,64}\r?\n\r?\n\s{0,32}\S/;
3
+
4
+ // Bold, italic, underline, strikethrough, highlight.
5
+ const bold = /(?:\s|^)(_|__|\*|\*\*|~~|==|\+\+)(?!\s).{1,64}(?<!\s)(?=\1)/;
6
+
7
+ // Basic inline link (also captures images).
8
+ const link = /\[[^\]]{1,128}\]\(https?:\/\/\S{1,999}\)/;
9
+
10
+ // Inline code.
11
+ const code = /(?:\s|^)`(?!\s)[^`]{1,48}(?<!\s)`([^\w]|$)/;
12
+
13
+ // Unordered list.
14
+ const ul = /(?:^|\n)\s{0,5}-\s{1}[^\n]+\n\s{0,15}-\s/;
15
+
16
+ // Ordered list.
17
+ const ol = /(?:^|\n)\s{0,5}\d+\.\s{1}[^\n]+\n\s{0,15}\d+\.\s/;
18
+
19
+ // Horizontal rule.
20
+ const hr = /\n{2} {0,3}-{2,48}\n{2}/;
21
+
22
+ // Fenced code block.
23
+ const fences =
24
+ /(?:\n|^)(```|~~~|\$\$)(?!`|~)[^\s]{0,64} {0,64}[^\n]{0,64}\n[\s\S]{0,9999}?\s*\1 {0,64}(?:\n+|$)/;
25
+
26
+ // Classical underlined H1 and H2 headings.
27
+ const title = /(?:\n|^)(?!\s)\w[^\n]{0,64}\r?\n(-|=)\1{0,64}\n\n\s{0,64}(\w|$)/;
28
+
29
+ // Blockquote.
30
+ const blockquote =
31
+ /(?:^|(\r?\n\r?\n))( {0,3}>[^\n]{1,333}\n){1,999}($|(\r?\n))/;
32
+
33
+ // Table Header
34
+ const tableHeader = /^\s*\|(.+\|)+\s*$/m;
35
+
36
+ // Table Divider
37
+ const tableDivider = /^\s*\|(\s*[-:]+[-:]\s*\|)+\s*$/m;
38
+
39
+ // Table Row
40
+ const tableRow = /^\s*\|(.+\|)+\s*$/m;
41
+
42
+ /**
43
+ * Returns `true` if the source text might be a markdown document.
44
+ *
45
+ * @param src Source text to analyze.
46
+ */
47
+ export const isMarkdown = (src: string): boolean =>
48
+ h1.test(src) ||
49
+ bold.test(src) ||
50
+ link.test(src) ||
51
+ code.test(src) ||
52
+ ul.test(src) ||
53
+ ol.test(src) ||
54
+ hr.test(src) ||
55
+ fences.test(src) ||
56
+ title.test(src) ||
57
+ blockquote.test(src) ||
58
+ tableHeader.test(src) ||
59
+ tableDivider.test(src) ||
60
+ tableRow.test(src);
@@ -12,7 +12,7 @@ async function parseMarkdownAndCompareSnapshots(
12
12
  const blocks = await editor.tryParseMarkdownToBlocks(md);
13
13
 
14
14
  const snapshotPath = "./__snapshots__/" + snapshotName + ".json";
15
- expect(JSON.stringify(blocks, undefined, 2)).toMatchFileSnapshot(
15
+ await expect(JSON.stringify(blocks, undefined, 2)).toMatchFileSnapshot(
16
16
  snapshotPath
17
17
  );
18
18
 
@@ -23,7 +23,7 @@ async function parseMarkdownAndCompareSnapshots(
23
23
  doPaste(editor.prosemirrorView, md, null, true, new ClipboardEvent("paste"));
24
24
 
25
25
  const pastedSnapshotPath = "./__snapshots__/pasted/" + snapshotName + ".json";
26
- expect(JSON.stringify(editor.document, undefined, 2)).toMatchFileSnapshot(
26
+ await expect(JSON.stringify(editor.document, undefined, 2)).toMatchFileSnapshot(
27
27
  pastedSnapshotPath
28
28
  );
29
29
 
@@ -99,6 +99,11 @@ P*ara*~~grap~~h
99
99
  * Bullet List Item`;
100
100
  await parseMarkdownAndCompareSnapshots(markdown, "complex");
101
101
  });
102
+
103
+ it("whitespace bold", async () => {
104
+ const markdown = `hello **beautiful ** world`;
105
+ await parseMarkdownAndCompareSnapshots(markdown, "whitespace bold");
106
+ });
102
107
  });
103
108
 
104
109
  describe("Issue 226", () => {
@@ -48,18 +48,9 @@ function code(state: any, node: any) {
48
48
  return result;
49
49
  }
50
50
 
51
- export async function markdownToBlocks<
52
- BSchema extends BlockSchema,
53
- I extends InlineContentSchema,
54
- S extends StyleSchema
55
- >(
56
- markdown: string,
57
- blockSchema: BSchema,
58
- icSchema: I,
59
- styleSchema: S,
60
- pmSchema: Schema
61
- ): Promise<Block<BSchema, I, S>[]> {
51
+ export async function markdownToHTML(markdown: string): Promise<string> {
62
52
  const deps = await initializeESMDependencies();
53
+
63
54
  const htmlString = deps.unified
64
55
  .unified()
65
56
  .use(deps.remarkParse.default)
@@ -73,11 +64,21 @@ export async function markdownToBlocks<
73
64
  .use(deps.rehypeStringify.default)
74
65
  .processSync(markdown);
75
66
 
76
- return HTMLToBlocks(
77
- htmlString.value as string,
78
- blockSchema,
79
- icSchema,
80
- styleSchema,
81
- pmSchema
82
- );
67
+ return htmlString.value as string;
68
+ }
69
+
70
+ export async function markdownToBlocks<
71
+ BSchema extends BlockSchema,
72
+ I extends InlineContentSchema,
73
+ S extends StyleSchema
74
+ >(
75
+ markdown: string,
76
+ blockSchema: BSchema,
77
+ icSchema: I,
78
+ styleSchema: S,
79
+ pmSchema: Schema
80
+ ): Promise<Block<BSchema, I, S>[]> {
81
+ const htmlString = await markdownToHTML(markdown);
82
+
83
+ return HTMLToBlocks(htmlString, blockSchema, icSchema, styleSchema, pmSchema);
83
84
  }
@@ -23,6 +23,18 @@ export const defaultSchemaTestCases: EditorTestCases<
23
23
  return BlockNoteEditor.create({
24
24
  schema: withPageBreak(BlockNoteSchema.create()),
25
25
  uploadFile: uploadToTmpFilesDotOrg_DEV_ONLY,
26
+ codeBlock: {
27
+ supportedLanguages: {
28
+ javascript: {
29
+ name: "JavaScript",
30
+ aliases: ["js"],
31
+ },
32
+ python: {
33
+ name: "Python",
34
+ aliases: ["py"],
35
+ },
36
+ },
37
+ },
26
38
  });
27
39
  },
28
40
  documents: [
@@ -213,6 +225,7 @@ export const defaultSchemaTestCases: EditorTestCases<
213
225
  blocks: [
214
226
  {
215
227
  type: "codeBlock",
228
+ props: { language: "javascript" },
216
229
  content: "const hello = 'world';\nconsole.log(hello);\n",
217
230
  },
218
231
  ],
@@ -2,28 +2,65 @@ import { InputRule, isTextSelection } from "@tiptap/core";
2
2
  import { TextSelection } from "@tiptap/pm/state";
3
3
  import { createHighlightPlugin, Parser } from "prosemirror-highlight";
4
4
  import { createParser } from "prosemirror-highlight/shiki";
5
- import {
6
- BundledLanguage,
7
- bundledLanguagesInfo,
8
- createHighlighter,
9
- Highlighter,
10
- } from "shiki";
11
5
  import {
12
6
  createBlockSpecFromStronglyTypedTiptapNode,
13
7
  createStronglyTypedTiptapNode,
14
8
  PropSchema,
15
9
  } from "../../schema/index.js";
16
10
  import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js";
17
- import {
18
- defaultSupportedLanguages,
19
- SupportedLanguageConfig,
20
- } from "./defaultSupportedLanguages.js";
21
-
22
- interface CodeBlockOptions {
23
- defaultLanguage: string;
24
- indentLineWithTab: boolean;
25
- supportedLanguages: SupportedLanguageConfig[];
26
- }
11
+ import type { HighlighterGeneric } from "@shikijs/types";
12
+ import { BlockNoteEditor } from "../../index.js";
13
+
14
+ export type CodeBlockOptions = {
15
+ /**
16
+ * Whether to indent lines with a tab when the user presses `Tab` in a code block.
17
+ *
18
+ * @default true
19
+ */
20
+ indentLineWithTab?: boolean;
21
+ /**
22
+ * The default language to use for code blocks.
23
+ *
24
+ * @default "text"
25
+ */
26
+ defaultLanguage?: string;
27
+ /**
28
+ * The languages that are supported in the editor.
29
+ *
30
+ * @example
31
+ * {
32
+ * javascript: {
33
+ * name: "JavaScript",
34
+ * aliases: ["js"],
35
+ * },
36
+ * typescript: {
37
+ * name: "TypeScript",
38
+ * aliases: ["ts"],
39
+ * },
40
+ * }
41
+ */
42
+ supportedLanguages: Record<
43
+ string,
44
+ {
45
+ /**
46
+ * The display name of the language.
47
+ */
48
+ name: string;
49
+ /**
50
+ * Aliases for this language.
51
+ */
52
+ aliases?: string[];
53
+ }
54
+ >;
55
+ /**
56
+ * The highlighter to use for code blocks.
57
+ */
58
+ createHighlighter?: () => Promise<HighlighterGeneric<any, any>>;
59
+ };
60
+
61
+ type CodeBlockConfigOptions = {
62
+ editor: BlockNoteEditor<any, any, any>;
63
+ };
27
64
 
28
65
  export const shikiParserSymbol = Symbol.for("blocknote.shikiParser");
29
66
  export const shikiHighlighterPromiseSymbol = Symbol.for(
@@ -31,8 +68,7 @@ export const shikiHighlighterPromiseSymbol = Symbol.for(
31
68
  );
32
69
  export const defaultCodeBlockPropSchema = {
33
70
  language: {
34
- default: "javascript",
35
- values: [...defaultSupportedLanguages.map((lang) => lang.id)],
71
+ default: "text",
36
72
  },
37
73
  } satisfies PropSchema;
38
74
 
@@ -45,18 +81,17 @@ const CodeBlockContent = createStronglyTypedTiptapNode({
45
81
  defining: true,
46
82
  addOptions() {
47
83
  return {
48
- defaultLanguage: "javascript",
84
+ defaultLanguage: "text",
49
85
  indentLineWithTab: true,
50
- supportedLanguages: defaultSupportedLanguages,
86
+ supportedLanguages: {},
51
87
  };
52
88
  },
53
89
  addAttributes() {
54
- const supportedLanguages = this.options
55
- .supportedLanguages as SupportedLanguageConfig[];
90
+ const options = this.options as CodeBlockConfigOptions;
56
91
 
57
92
  return {
58
93
  language: {
59
- default: this.options.defaultLanguage,
94
+ default: options.editor.settings.codeBlock.defaultLanguage,
60
95
  parseHTML: (inputElement) => {
61
96
  let element = inputElement as HTMLElement | null;
62
97
  let language: string | null = null;
@@ -91,17 +126,13 @@ const CodeBlockContent = createStronglyTypedTiptapNode({
91
126
  return null;
92
127
  }
93
128
 
94
- return (
95
- supportedLanguages.find(({ match }) => {
96
- return match.includes(language);
97
- })?.id || this.options.defaultLanguage
98
- );
129
+ return getLanguageId(options.editor.settings.codeBlock, language);
99
130
  },
100
131
  renderHTML: (attributes) => {
101
- // TODO: Use `data-language="..."` instead for easier parsing
102
- return attributes.language && attributes.language !== "text"
132
+ return attributes.language
103
133
  ? {
104
134
  class: `language-${attributes.language}`,
135
+ "data-language": attributes.language,
105
136
  }
106
137
  : {};
107
138
  },
@@ -143,8 +174,7 @@ const CodeBlockContent = createStronglyTypedTiptapNode({
143
174
  };
144
175
  },
145
176
  addNodeView() {
146
- const supportedLanguages = this.options
147
- .supportedLanguages as SupportedLanguageConfig[];
177
+ const options = this.options as CodeBlockConfigOptions;
148
178
 
149
179
  return ({ editor, node, getPos, HTMLAttributes }) => {
150
180
  const pre = document.createElement("pre");
@@ -169,7 +199,9 @@ const CodeBlockContent = createStronglyTypedTiptapNode({
169
199
  });
170
200
  };
171
201
 
172
- supportedLanguages.forEach(({ id, name }) => {
202
+ Object.entries(
203
+ options.editor.settings.codeBlock.supportedLanguages
204
+ ).forEach(([id, { name }]) => {
173
205
  const option = document.createElement("option");
174
206
 
175
207
  option.value = id;
@@ -178,7 +210,9 @@ const CodeBlockContent = createStronglyTypedTiptapNode({
178
210
  });
179
211
 
180
212
  selectWrapper.contentEditable = "false";
181
- select.value = node.attrs.language || this.options.defaultLanguage;
213
+ select.value =
214
+ node.attrs.language ||
215
+ options.editor.settings.codeBlock.defaultLanguage;
182
216
  dom.removeChild(contentDOM);
183
217
  dom.appendChild(selectWrapper);
184
218
  dom.appendChild(pre);
@@ -203,24 +237,30 @@ const CodeBlockContent = createStronglyTypedTiptapNode({
203
237
  };
204
238
  },
205
239
  addProseMirrorPlugins() {
206
- const supportedLanguages = this.options
207
- .supportedLanguages as SupportedLanguageConfig[];
240
+ const options = this.options as CodeBlockConfigOptions;
208
241
  const globalThisForShiki = globalThis as {
209
- [shikiHighlighterPromiseSymbol]?: Promise<Highlighter>;
242
+ [shikiHighlighterPromiseSymbol]?: Promise<HighlighterGeneric<any, any>>;
210
243
  [shikiParserSymbol]?: Parser;
211
244
  };
212
245
 
213
- let highlighter: Highlighter | undefined;
246
+ let highlighter: HighlighterGeneric<any, any> | undefined;
214
247
  let parser: Parser | undefined;
215
-
216
- const lazyParser: Parser = (options) => {
248
+ let hasWarned = false;
249
+ const lazyParser: Parser = (parserOptions) => {
250
+ if (!options.editor.settings.codeBlock.createHighlighter) {
251
+ if (process.env.NODE_ENV === "development" && !hasWarned) {
252
+ // eslint-disable-next-line no-console
253
+ console.log(
254
+ "For syntax highlighting of code blocks, you must provide a highlighter function"
255
+ );
256
+ hasWarned = true;
257
+ }
258
+ return [];
259
+ }
217
260
  if (!highlighter) {
218
261
  globalThisForShiki[shikiHighlighterPromiseSymbol] =
219
262
  globalThisForShiki[shikiHighlighterPromiseSymbol] ||
220
- createHighlighter({
221
- themes: ["github-dark"],
222
- langs: [],
223
- });
263
+ options.editor.settings.codeBlock.createHighlighter();
224
264
 
225
265
  return globalThisForShiki[shikiHighlighterPromiseSymbol].then(
226
266
  (createdHighlighter) => {
@@ -229,25 +269,25 @@ const CodeBlockContent = createStronglyTypedTiptapNode({
229
269
  );
230
270
  }
231
271
 
232
- const language = options.language;
272
+ const language = parserOptions.language;
233
273
 
234
274
  if (
235
275
  language &&
236
276
  language !== "text" &&
237
277
  !highlighter.getLoadedLanguages().includes(language) &&
238
- supportedLanguages.find(({ id }) => id === language) &&
239
- bundledLanguagesInfo.find(({ id }) => id === language)
278
+ language in options.editor.settings.codeBlock.supportedLanguages
240
279
  ) {
241
- return highlighter.loadLanguage(language as BundledLanguage);
280
+ return highlighter.loadLanguage(language);
242
281
  }
243
282
 
244
283
  if (!parser) {
245
284
  parser =
246
- globalThisForShiki[shikiParserSymbol] || createParser(highlighter);
285
+ globalThisForShiki[shikiParserSymbol] ||
286
+ createParser(highlighter as any);
247
287
  globalThisForShiki[shikiParserSymbol] = parser;
248
288
  }
249
289
 
250
- return parser(options);
290
+ return parser(parserOptions);
251
291
  };
252
292
 
253
293
  const shikiLazyPlugin = createHighlightPlugin({
@@ -259,8 +299,7 @@ const CodeBlockContent = createStronglyTypedTiptapNode({
259
299
  return [shikiLazyPlugin];
260
300
  },
261
301
  addInputRules() {
262
- const supportedLanguages = this.options
263
- .supportedLanguages as SupportedLanguageConfig[];
302
+ const options = this.options as CodeBlockConfigOptions;
264
303
 
265
304
  return [
266
305
  new InputRule({
@@ -269,10 +308,10 @@ const CodeBlockContent = createStronglyTypedTiptapNode({
269
308
  const $start = state.doc.resolve(range.from);
270
309
  const languageName = match[1].trim();
271
310
  const attributes = {
272
- language:
273
- supportedLanguages.find(({ match }) => {
274
- return match.includes(languageName);
275
- })?.id || this.options.defaultLanguage,
311
+ language: getLanguageId(
312
+ options.editor.settings.codeBlock,
313
+ languageName
314
+ ),
276
315
  };
277
316
 
278
317
  if (
@@ -383,18 +422,10 @@ export const CodeBlock = createBlockSpecFromStronglyTypedTiptapNode(
383
422
  defaultCodeBlockPropSchema
384
423
  );
385
424
 
386
- export function customizeCodeBlock(options: Partial<CodeBlockOptions>) {
387
- return createBlockSpecFromStronglyTypedTiptapNode(
388
- CodeBlockContent.configure(options),
389
- {
390
- language: {
391
- default:
392
- options.defaultLanguage ||
393
- defaultCodeBlockPropSchema.language.default,
394
- values:
395
- options.supportedLanguages?.map((lang) => lang.id) ||
396
- defaultCodeBlockPropSchema.language.values,
397
- },
398
- }
425
+ function getLanguageId(options: CodeBlockOptions, languageName: string) {
426
+ return (
427
+ Object.entries(options.supportedLanguages).find(([id, { aliases }]) => {
428
+ return aliases?.includes(languageName) || id === languageName;
429
+ })?.[0] || languageName
399
430
  );
400
431
  }
@@ -0,0 +1,98 @@
1
+ import {
2
+ createBlockSpecFromStronglyTypedTiptapNode,
3
+ createStronglyTypedTiptapNode,
4
+ } from "../../schema/index.js";
5
+ import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js";
6
+ import { defaultProps } from "../defaultProps.js";
7
+ import { getBlockInfoFromSelection } from "../../api/getBlockInfoFromPos.js";
8
+ import { updateBlockCommand } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js";
9
+ import { InputRule } from "@tiptap/core";
10
+
11
+ export const quotePropSchema = {
12
+ ...defaultProps,
13
+ };
14
+
15
+ export const QuoteBlockContent = createStronglyTypedTiptapNode({
16
+ name: "quote",
17
+ content: "inline*",
18
+ group: "blockContent",
19
+
20
+ addInputRules() {
21
+ return [
22
+ // Creates a block quote when starting with ">".
23
+ new InputRule({
24
+ find: new RegExp(`^>\\s$`),
25
+ handler: ({ state, chain, range }) => {
26
+ const blockInfo = getBlockInfoFromSelection(state);
27
+ if (
28
+ !blockInfo.isBlockContainer ||
29
+ blockInfo.blockContent.node.type.spec.content !== "inline*"
30
+ ) {
31
+ return;
32
+ }
33
+
34
+ chain()
35
+ .command(
36
+ updateBlockCommand(
37
+ this.options.editor,
38
+ blockInfo.bnBlock.beforePos,
39
+ {
40
+ type: "quote",
41
+ props: {},
42
+ }
43
+ )
44
+ )
45
+ // Removes the ">" character used to set the list.
46
+ .deleteRange({ from: range.from, to: range.to });
47
+ },
48
+ }),
49
+ ];
50
+ },
51
+
52
+ addKeyboardShortcuts() {
53
+ return {
54
+ "Mod-Alt-q": () => {
55
+ const blockInfo = getBlockInfoFromSelection(this.editor.state);
56
+ if (
57
+ !blockInfo.isBlockContainer ||
58
+ blockInfo.blockContent.node.type.spec.content !== "inline*"
59
+ ) {
60
+ return true;
61
+ }
62
+
63
+ return this.editor.commands.command(
64
+ updateBlockCommand(this.options.editor, blockInfo.bnBlock.beforePos, {
65
+ type: "quote",
66
+ })
67
+ );
68
+ },
69
+ };
70
+ },
71
+
72
+ parseHTML() {
73
+ return [
74
+ { tag: "div[data-content-type=" + this.name + "]" },
75
+ {
76
+ tag: "blockquote",
77
+ node: "quote",
78
+ },
79
+ ];
80
+ },
81
+
82
+ renderHTML({ HTMLAttributes }) {
83
+ return createDefaultBlockDOMOutputSpec(
84
+ this.name,
85
+ "blockquote",
86
+ {
87
+ ...(this.options.domAttributes?.blockContent || {}),
88
+ ...HTMLAttributes,
89
+ },
90
+ this.options.domAttributes?.inlineContent || {}
91
+ );
92
+ },
93
+ });
94
+
95
+ export const Quote = createBlockSpecFromStronglyTypedTiptapNode(
96
+ QuoteBlockContent,
97
+ quotePropSchema
98
+ );
@@ -31,7 +31,7 @@ export const TableExtension = Extension.create({
31
31
  this.editor.state.selection.$head.parent.type.name ===
32
32
  "tableParagraph"
33
33
  ) {
34
- this.editor.commands.setHardBreak();
34
+ this.editor.commands.insertContent({ type: "hardBreak" });
35
35
 
36
36
  return true;
37
37
  }
@@ -29,14 +29,14 @@ import { BulletListItem } from "./ListItemBlockContent/BulletListItemBlockConten
29
29
  import { CheckListItem } from "./ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.js";
30
30
  import { NumberedListItem } from "./ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.js";
31
31
  import { Paragraph } from "./ParagraphBlockContent/ParagraphBlockContent.js";
32
+ import { Quote } from "./QuoteBlockContent/QuoteBlockContent.js";
32
33
  import { Table } from "./TableBlockContent/TableBlockContent.js";
33
34
  import { VideoBlock } from "./VideoBlockContent/VideoBlockContent.js";
34
35
 
35
- export { customizeCodeBlock } from "./CodeBlockContent/CodeBlockContent.js";
36
-
37
36
  export const defaultBlockSpecs = {
38
37
  paragraph: Paragraph,
39
38
  heading: Heading,
39
+ quote: Quote,
40
40
  codeBlock: CodeBlock,
41
41
  bulletListItem: BulletListItem,
42
42
  numberedListItem: NumberedListItem,