@blocknote/core 0.25.2 → 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 (153) hide show
  1. package/dist/blocknote.cjs +11 -10
  2. package/dist/blocknote.cjs.map +1 -1
  3. package/dist/blocknote.js +3722 -9856
  4. package/dist/blocknote.js.map +1 -1
  5. package/dist/comments.cjs +1 -1
  6. package/dist/comments.cjs.map +1 -1
  7. package/dist/comments.js +45 -44
  8. package/dist/comments.js.map +1 -1
  9. package/dist/en-B7ycW7c8.js +360 -0
  10. package/dist/en-B7ycW7c8.js.map +1 -0
  11. package/dist/en-D4taoCs4.cjs +2 -0
  12. package/dist/en-D4taoCs4.cjs.map +1 -0
  13. package/dist/locales.cjs +2 -0
  14. package/dist/locales.cjs.map +1 -0
  15. package/dist/locales.js +6129 -0
  16. package/dist/locales.js.map +1 -0
  17. package/dist/style.css +1 -1
  18. package/dist/tsconfig.tsbuildinfo +1 -1
  19. package/dist/webpack-stats.json +1 -1
  20. package/package.json +36 -27
  21. package/src/api/clipboard/__snapshots__/internal/basicBlocks.html +1 -1
  22. package/src/api/clipboard/__snapshots__/internal/basicBlocksWithProps.html +1 -1
  23. package/src/api/clipboard/clipboardExternal.test.ts +1 -1
  24. package/src/api/clipboard/clipboardInternal.test.ts +1 -1
  25. package/src/api/clipboard/fromClipboard/acceptedMIMETypes.ts +1 -0
  26. package/src/api/clipboard/fromClipboard/pasteExtension.ts +96 -42
  27. package/src/api/exporters/html/__snapshots__/codeBlock/contains-newlines/external.html +1 -1
  28. package/src/api/exporters/html/__snapshots__/codeBlock/contains-newlines/internal.html +1 -1
  29. package/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/external.html +1 -1
  30. package/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/internal.html +1 -1
  31. package/src/api/exporters/html/__snapshots__/codeBlock/empty/external.html +1 -1
  32. package/src/api/exporters/html/__snapshots__/codeBlock/empty/internal.html +1 -1
  33. package/src/api/exporters/html/__snapshots__/codeBlock/python/external.html +1 -1
  34. package/src/api/exporters/html/__snapshots__/codeBlock/python/internal.html +1 -1
  35. package/src/api/exporters/html/htmlConversion.test.ts +2 -2
  36. package/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +4 -4
  37. package/src/api/exporters/markdown/__snapshots__/codeBlock/defaultLanguage/markdown.md +1 -1
  38. package/src/api/exporters/markdown/__snapshots__/codeBlock/empty/markdown.md +1 -1
  39. package/src/api/exporters/markdown/__snapshots__/complex/misc/markdown.md +1 -1
  40. package/src/api/exporters/markdown/__snapshots__/lists/basic/markdown.md +8 -6
  41. package/src/api/exporters/markdown/__snapshots__/lists/nested/markdown.md +6 -6
  42. package/src/api/exporters/markdown/markdownExporter.test.ts +2 -2
  43. package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +2 -2
  44. package/src/api/nodeConversions/nodeToBlock.ts +1 -0
  45. package/src/api/parsers/html/__snapshots__/parse-2-tables.json +129 -0
  46. package/src/api/parsers/html/__snapshots__/parse-codeblocks.json +1 -1
  47. package/src/api/parsers/html/parseHTML.test.ts +36 -1
  48. package/src/api/parsers/markdown/__snapshots__/pasted/whitespace bold.json +42 -0
  49. package/src/api/parsers/markdown/__snapshots__/whitespace bold.json +19 -0
  50. package/src/api/parsers/markdown/detectMarkdown.ts +60 -0
  51. package/src/api/parsers/markdown/parseMarkdown.test.ts +7 -2
  52. package/src/api/parsers/markdown/parseMarkdown.ts +19 -18
  53. package/src/api/testUtil/cases/defaultSchema.ts +13 -0
  54. package/src/blocks/CodeBlockContent/CodeBlockContent.ts +100 -69
  55. package/src/blocks/QuoteBlockContent/QuoteBlockContent.ts +98 -0
  56. package/src/blocks/TableBlockContent/TableExtension.ts +1 -1
  57. package/src/blocks/defaultBlocks.ts +2 -2
  58. package/src/comments/threadstore/yjs/YjsThreadStore.ts +1 -0
  59. package/src/comments/threadstore/yjs/yjsHelpers.ts +3 -1
  60. package/src/comments/types.ts +4 -0
  61. package/src/editor/Block.css +14 -1
  62. package/src/editor/BlockNoteEditor.ts +103 -11
  63. package/src/editor/BlockNoteExtensions.ts +18 -4
  64. package/src/editor/BlockNoteTipTapEditor.ts +18 -7
  65. package/src/extensions/Comments/CommentsPlugin.ts +77 -30
  66. package/src/extensions/HardBreak/HardBreak.ts +35 -0
  67. package/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +100 -3
  68. package/src/extensions/SideMenu/dragging.ts +13 -0
  69. package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +3 -1
  70. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +18 -2
  71. package/src/extensions/TableHandles/TableHandlesPlugin.ts +24 -23
  72. package/src/i18n/index.ts +2 -0
  73. package/src/i18n/locales/ar.ts +10 -0
  74. package/src/i18n/locales/de.ts +21 -1
  75. package/src/i18n/locales/en.ts +10 -0
  76. package/src/i18n/locales/es.ts +21 -1
  77. package/src/i18n/locales/fr.ts +10 -0
  78. package/src/i18n/locales/hr.ts +27 -4
  79. package/src/i18n/locales/is.ts +10 -0
  80. package/src/i18n/locales/it.ts +21 -1
  81. package/src/i18n/locales/ja.ts +10 -0
  82. package/src/i18n/locales/ko.ts +10 -0
  83. package/src/i18n/locales/nl.ts +10 -0
  84. package/src/i18n/locales/no.ts +10 -0
  85. package/src/i18n/locales/pl.ts +10 -0
  86. package/src/i18n/locales/pt.ts +10 -0
  87. package/src/i18n/locales/ru.ts +10 -0
  88. package/src/i18n/locales/uk.ts +10 -0
  89. package/src/i18n/locales/vi.ts +10 -0
  90. package/src/i18n/locales/zh.ts +10 -0
  91. package/src/index.ts +2 -3
  92. package/src/locales.ts +1 -0
  93. package/src/schema/blocks/types.ts +1 -0
  94. package/types/src/api/blockManipulation/commands/updateBlock/updateBlock.d.ts +1 -1
  95. package/types/src/api/blockManipulation/setupTestEnv.d.ts +34 -2
  96. package/types/src/api/clipboard/fromClipboard/acceptedMIMETypes.d.ts +1 -1
  97. package/types/src/api/clipboard/fromClipboard/fileDropExtension.d.ts +2 -2
  98. package/types/src/api/clipboard/fromClipboard/pasteExtension.d.ts +3 -3
  99. package/types/src/api/clipboard/testUtil.d.ts +66 -34
  100. package/types/src/api/clipboard/toClipboard/copyExtension.d.ts +1 -1
  101. package/types/src/api/exporters/html/externalHTMLExporter.d.ts +2 -2
  102. package/types/src/api/exporters/html/internalHTMLSerializer.d.ts +2 -2
  103. package/types/src/api/exporters/html/util/serializeBlocksExternalHTML.d.ts +1 -1
  104. package/types/src/api/exporters/html/util/serializeBlocksInternalHTML.d.ts +1 -1
  105. package/types/src/api/parsers/markdown/detectMarkdown.d.ts +6 -0
  106. package/types/src/api/parsers/markdown/parseMarkdown.d.ts +1 -0
  107. package/types/src/api/testUtil/cases/customBlocks.d.ts +72 -40
  108. package/types/src/api/testUtil/cases/customInlineContent.d.ts +34 -2
  109. package/types/src/api/testUtil/cases/customStyles.d.ts +34 -2
  110. package/types/src/blocks/AudioBlockContent/AudioBlockContent.d.ts +3 -3
  111. package/types/src/blocks/CodeBlockContent/CodeBlockContent.d.ts +46 -34
  112. package/types/src/blocks/FileBlockContent/FileBlockContent.d.ts +3 -3
  113. package/types/src/blocks/FileBlockContent/helpers/render/createFileBlockWrapper.d.ts +1 -1
  114. package/types/src/blocks/HeadingBlockContent/HeadingBlockContent.d.ts +2 -2
  115. package/types/src/blocks/ImageBlockContent/ImageBlockContent.d.ts +2 -2
  116. package/types/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.d.ts +2 -2
  117. package/types/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.d.ts +2 -2
  118. package/types/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.d.ts +2 -2
  119. package/types/src/blocks/PageBreakBlockContent/PageBreakBlockContent.d.ts +2 -2
  120. package/types/src/blocks/PageBreakBlockContent/schema.d.ts +13 -13
  121. package/types/src/blocks/ParagraphBlockContent/ParagraphBlockContent.d.ts +2 -2
  122. package/types/src/blocks/QuoteBlockContent/QuoteBlockContent.d.ts +52 -0
  123. package/types/src/blocks/TableBlockContent/TableBlockContent.d.ts +2 -2
  124. package/types/src/blocks/VideoBlockContent/VideoBlockContent.d.ts +2 -2
  125. package/types/src/blocks/defaultBlockHelpers.d.ts +2 -2
  126. package/types/src/blocks/defaultBlocks.d.ts +107 -44
  127. package/types/src/comments/threadstore/yjs/YjsThreadStore.d.ts +3 -3
  128. package/types/src/comments/types.d.ts +4 -0
  129. package/types/src/editor/BlockNoteEditor.d.ts +58 -0
  130. package/types/src/editor/BlockNoteExtensions.d.ts +3 -2
  131. package/types/src/editor/BlockNoteTipTapEditor.d.ts +2 -1
  132. package/types/src/exporter/mapping.d.ts +2 -2
  133. package/types/src/extensions/BackgroundColor/BackgroundColorMark.d.ts +1 -1
  134. package/types/src/extensions/Collaboration/createCollaborationExtensions.d.ts +1 -1
  135. package/types/src/extensions/Comments/CommentsPlugin.d.ts +16 -1
  136. package/types/src/extensions/HardBreak/HardBreak.d.ts +2 -0
  137. package/types/src/extensions/TableHandles/TableHandlesPlugin.d.ts +4 -4
  138. package/types/src/extensions/TextColor/TextColorMark.d.ts +1 -1
  139. package/types/src/i18n/index.d.ts +2 -0
  140. package/types/src/i18n/locales/de.d.ts +2 -304
  141. package/types/src/i18n/locales/en.d.ts +11 -1
  142. package/types/src/i18n/locales/es.d.ts +2 -269
  143. package/types/src/i18n/locales/hr.d.ts +2 -266
  144. package/types/src/i18n/locales/it.d.ts +2 -269
  145. package/types/src/index.d.ts +1 -2
  146. package/types/src/locales.d.ts +1 -0
  147. package/types/src/pm-nodes/BlockContainer.d.ts +2 -7
  148. package/types/src/pm-nodes/BlockGroup.d.ts +2 -7
  149. package/types/src/schema/blocks/types.d.ts +1 -0
  150. package/types/src/schema/inlineContent/internal.d.ts +1 -1
  151. package/README.md +0 -125
  152. package/src/blocks/CodeBlockContent/defaultSupportedLanguages.ts +0 -116
  153. package/types/src/blocks/CodeBlockContent/shiki.bundle.d.ts +0 -82
@@ -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,
@@ -237,6 +237,7 @@ export class YjsThreadStore extends YjsThreadStoreBase {
237
237
 
238
238
  yThread.set("resolved", true);
239
239
  yThread.set("resolvedUpdatedAt", new Date().getTime());
240
+ yThread.set("resolvedBy", this.userId);
240
241
  });
241
242
 
242
243
  public unresolveThread = this.transact((options: { threadId: string }) => {
@@ -40,6 +40,7 @@ export function threadToYMap(thread: ThreadData) {
40
40
  yMap.set("comments", commentsArray);
41
41
  yMap.set("resolved", thread.resolved);
42
42
  yMap.set("resolvedUpdatedAt", thread.resolvedUpdatedAt?.getTime());
43
+ yMap.set("resolvedBy", thread.resolvedBy);
43
44
  yMap.set("metadata", thread.metadata);
44
45
  return yMap;
45
46
  }
@@ -115,7 +116,8 @@ export function yMapToThread(yMap: Y.Map<any>): ThreadData {
115
116
  (comment) => yMapToComment(comment)
116
117
  ),
117
118
  resolved: yMap.get("resolved"),
118
- resolvedUpdatedAt: yMap.get("resolvedUpdatedAt"),
119
+ resolvedUpdatedAt: new Date(yMap.get("resolvedUpdatedAt")),
120
+ resolvedBy: yMap.get("resolvedBy"),
119
121
  metadata: yMap.get("metadata"),
120
122
  };
121
123
  }
@@ -105,6 +105,10 @@ export type ThreadData = {
105
105
  * The date when the thread was marked as resolved.
106
106
  */
107
107
  resolvedUpdatedAt?: Date;
108
+ /**
109
+ * The id of the user that marked the thread as resolved.
110
+ */
111
+ resolvedBy?: string;
108
112
  /**
109
113
  * You can use this store any additional information about the thread.
110
114
  */
@@ -37,6 +37,10 @@ BASIC STYLES
37
37
  outline: 4px solid rgb(100, 160, 255);
38
38
  }
39
39
 
40
+ .bn-inline-content {
41
+ width: 100%;
42
+ }
43
+
40
44
  /*
41
45
  NESTED BLOCKS
42
46
  */
@@ -146,6 +150,14 @@ NESTED BLOCKS
146
150
  font-weight: bold;
147
151
  }
148
152
 
153
+ /* QUOTES */
154
+ [data-content-type="quote"] blockquote {
155
+ border-left: 2px solid rgb(125, 121, 122);
156
+ color: rgb(125, 121, 122);
157
+ margin: 0;
158
+ padding-left: 1em;
159
+ }
160
+
149
161
  /* LISTS */
150
162
 
151
163
  .bn-block-content::before {
@@ -188,6 +200,7 @@ NESTED BLOCKS
188
200
  /* Checked */
189
201
  .bn-block-content[data-content-type="checkListItem"] > div {
190
202
  display: flex;
203
+ width: 100%;
191
204
  }
192
205
 
193
206
  .bn-block-content[data-content-type="checkListItem"] > div > div > input {
@@ -542,6 +555,6 @@ NESTED BLOCKS
542
555
  background: rgba(255, 200, 0, 0.15);
543
556
  }
544
557
 
545
- .bn-thread-mark:not([data-orphan="true"]) .bn-thread-mark-selected {
558
+ .bn-thread-mark .bn-thread-mark-selected {
546
559
  background: rgba(255, 200, 0, 0.25);
547
560
  }
@@ -43,7 +43,10 @@ import {
43
43
  import { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLExporter.js";
44
44
  import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter.js";
45
45
  import { HTMLToBlocks } from "../api/parsers/html/parseHTML.js";
46
- import { markdownToBlocks } from "../api/parsers/markdown/parseMarkdown.js";
46
+ import {
47
+ markdownToBlocks,
48
+ markdownToHTML,
49
+ } from "../api/parsers/markdown/parseMarkdown.js";
47
50
  import {
48
51
  Block,
49
52
  DefaultBlockSchema,
@@ -91,7 +94,7 @@ import {
91
94
  import { Dictionary } from "../i18n/dictionary.js";
92
95
  import { en } from "../i18n/locales/index.js";
93
96
 
94
- import { Plugin, Transaction } from "@tiptap/pm/state";
97
+ import { Plugin, TextSelection, Transaction } from "@tiptap/pm/state";
95
98
  import { dropCursor } from "prosemirror-dropcursor";
96
99
  import { EditorView } from "prosemirror-view";
97
100
  import { ySyncPluginKey } from "y-prosemirror";
@@ -101,6 +104,8 @@ import { nodeToBlock } from "../api/nodeConversions/nodeToBlock.js";
101
104
  import type { ThreadStore, User } from "../comments/index.js";
102
105
  import "../style.css";
103
106
  import { EventEmitter } from "../util/EventEmitter.js";
107
+ import { CodeBlockOptions } from "../blocks/CodeBlockContent/CodeBlockContent.js";
108
+ import { nestedListsToBlockNoteStructure } from "../api/parsers/html/util/nestedLists.js";
104
109
 
105
110
  export type BlockNoteExtensionFactory = (
106
111
  editor: BlockNoteEditor<any, any, any>
@@ -156,6 +161,11 @@ export type BlockNoteEditorOptions<
156
161
  showCursorLabels?: "always" | "activity";
157
162
  };
158
163
 
164
+ /**
165
+ * Options for code blocks.
166
+ */
167
+ codeBlock?: CodeBlockOptions;
168
+
159
169
  comments: {
160
170
  threadStore: ThreadStore;
161
171
  };
@@ -212,6 +222,39 @@ export type BlockNoteEditorOptions<
212
222
  string | undefined
213
223
  >;
214
224
 
225
+ /**
226
+ * Custom paste handler that can be used to override the default paste behavior.
227
+ * @returns The function should return `true` if the paste event was handled, otherwise it should return `false` if it should be canceled or `undefined` if it should be handled by another handler.
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * pasteHandler: ({ defaultPasteHandler }) => {
232
+ * return defaultPasteHandler({ pasteBehavior: "prefer-html" });
233
+ * }
234
+ * ```
235
+ */
236
+ pasteHandler?: (context: {
237
+ event: ClipboardEvent;
238
+ editor: BlockNoteEditor<BSchema, ISchema, SSchema>;
239
+ /**
240
+ * The default paste handler
241
+ * @param context The context object
242
+ * @returns Whether the paste event was handled or not
243
+ */
244
+ defaultPasteHandler: (context?: {
245
+ /**
246
+ * Whether to prioritize Markdown content in `text/plain` over `text/html` when pasting from the clipboard.
247
+ * @default true
248
+ */
249
+ prioritizeMarkdownOverHTML?: boolean;
250
+ /**
251
+ * Whether to parse `text/plain` content from the clipboard as Markdown content.
252
+ * @default true
253
+ */
254
+ plainTextAsMarkdown?: boolean;
255
+ }) => boolean | undefined;
256
+ }) => boolean | undefined;
257
+
215
258
  /**
216
259
  * Resolve a URL of a file block to one that can be displayed or downloaded. This can be used for creating authenticated URL or
217
260
  * implementing custom protocols / schemes
@@ -442,6 +485,7 @@ export class BlockNoteEditor<
442
485
  cellTextColor: boolean;
443
486
  headers: boolean;
444
487
  };
488
+ codeBlock: CodeBlockOptions;
445
489
  };
446
490
 
447
491
  public static create<
@@ -489,6 +533,12 @@ export class BlockNoteEditor<
489
533
  cellTextColor: options?.tables?.cellTextColor ?? false,
490
534
  headers: options?.tables?.headers ?? false,
491
535
  },
536
+ codeBlock: {
537
+ indentLineWithTab: options?.codeBlock?.indentLineWithTab ?? true,
538
+ defaultLanguage: options?.codeBlock?.defaultLanguage ?? "text",
539
+ supportedLanguages: options?.codeBlock?.supportedLanguages ?? {},
540
+ createHighlighter: options?.codeBlock?.createHighlighter ?? undefined,
541
+ },
492
542
  };
493
543
 
494
544
  // apply defaults
@@ -532,6 +582,7 @@ export class BlockNoteEditor<
532
582
  tabBehavior: newOptions.tabBehavior,
533
583
  sideMenuDetection: newOptions.sideMenuDetection || "viewport",
534
584
  comments: newOptions.comments,
585
+ pasteHandler: newOptions.pasteHandler,
535
586
  });
536
587
 
537
588
  // add extensions from _tiptapOptions
@@ -688,7 +739,7 @@ export class BlockNoteEditor<
688
739
  parentElement?: HTMLElement | null,
689
740
  contentComponent?: any
690
741
  ) => {
691
- this._tiptapEditor.mount(parentElement, contentComponent);
742
+ this._tiptapEditor.mount(this, parentElement, contentComponent);
692
743
  };
693
744
 
694
745
  /**
@@ -1136,17 +1187,18 @@ export class BlockNoteEditor<
1136
1187
  }
1137
1188
 
1138
1189
  const { from, to } = this._tiptapEditor.state.selection;
1139
-
1140
- if (!text) {
1141
- text = this._tiptapEditor.state.doc.textBetween(from, to);
1142
- }
1143
-
1144
1190
  const mark = this.pmSchema.mark("link", { href: url });
1145
1191
 
1146
1192
  this.dispatch(
1147
- this._tiptapEditor.state.tr
1148
- .insertText(text, from, to)
1149
- .addMark(from, from + text.length, mark)
1193
+ text
1194
+ ? this._tiptapEditor.state.tr
1195
+ .insertText(text, from, to)
1196
+ .addMark(from, from + text.length, mark)
1197
+ : this._tiptapEditor.state.tr
1198
+ .setSelection(
1199
+ TextSelection.create(this._tiptapEditor.state.tr.doc, to)
1200
+ )
1201
+ .addMark(from, to, mark)
1150
1202
  );
1151
1203
  }
1152
1204
 
@@ -1431,4 +1483,44 @@ export class BlockNoteEditor<
1431
1483
  public setForceSelectionVisible(forceSelectionVisible: boolean) {
1432
1484
  this.showSelectionPlugin.setEnabled(forceSelectionVisible);
1433
1485
  }
1486
+
1487
+ /**
1488
+ * This will convert HTML into a format that is compatible with BlockNote.
1489
+ */
1490
+ private convertHtmlToBlockNoteHtml(html: string) {
1491
+ const htmlNode = nestedListsToBlockNoteStructure(html.trim());
1492
+ return htmlNode.innerHTML;
1493
+ }
1494
+
1495
+ /**
1496
+ * Paste HTML into the editor. Defaults to converting HTML to BlockNote HTML.
1497
+ * @param html The HTML to paste.
1498
+ * @param raw Whether to paste the HTML as is, or to convert it to BlockNote HTML.
1499
+ */
1500
+ public pasteHTML(html: string, raw = false) {
1501
+ let htmlToPaste = html;
1502
+ if (!raw) {
1503
+ htmlToPaste = this.convertHtmlToBlockNoteHtml(html);
1504
+ }
1505
+ if (!htmlToPaste) {
1506
+ return;
1507
+ }
1508
+ this.prosemirrorView?.pasteHTML(htmlToPaste);
1509
+ }
1510
+
1511
+ /**
1512
+ * Paste text into the editor. Defaults to interpreting text as markdown.
1513
+ * @param text The text to paste.
1514
+ */
1515
+ public pasteText(text: string) {
1516
+ return this.prosemirrorView?.pasteText(text);
1517
+ }
1518
+
1519
+ /**
1520
+ * Paste markdown into the editor.
1521
+ * @param markdown The markdown to paste.
1522
+ */
1523
+ public async pasteMarkdown(markdown: string) {
1524
+ return this.pasteHTML(await markdownToHTML(markdown));
1525
+ }
1434
1526
  }