@blocknote/core 0.14.5 → 0.15.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 (72) hide show
  1. package/dist/blocknote.js +1311 -1074
  2. package/dist/blocknote.js.map +1 -1
  3. package/dist/blocknote.umd.cjs +6 -6
  4. package/dist/blocknote.umd.cjs.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/dist/webpack-stats.json +1 -1
  7. package/package.json +28 -25
  8. package/src/api/blockManipulation/blockManipulation.ts +21 -21
  9. package/src/api/exporters/copyExtension.ts +14 -10
  10. package/src/api/exporters/html/externalHTMLExporter.ts +26 -8
  11. package/src/api/exporters/html/htmlConversion.test.ts +27 -25
  12. package/src/api/exporters/html/internalHTMLSerializer.ts +22 -7
  13. package/src/api/exporters/html/util/sharedHTMLConversion.ts +3 -2
  14. package/src/api/exporters/markdown/markdownExporter.ts +5 -4
  15. package/src/api/nodeConversions/nodeConversions.test.ts +9 -6
  16. package/src/api/parsers/html/parseHTML.test.ts +3 -4
  17. package/src/api/parsers/html/util/__snapshots__/nestedLists.test.ts.snap +7 -7
  18. package/src/blocks/FileBlockContent/fileBlockHelpers.ts +1 -1
  19. package/src/blocks/defaultBlockHelpers.ts +3 -6
  20. package/src/blocks/defaultBlockTypeGuards.ts +26 -1
  21. package/src/editor/BlockNoteEditor.test.ts +48 -1
  22. package/src/editor/BlockNoteEditor.ts +153 -36
  23. package/src/editor/BlockNoteExtensions.ts +0 -1
  24. package/src/editor/BlockNoteTipTapEditor.ts +14 -5
  25. package/src/extensions/BackgroundColor/BackgroundColorExtension.ts +10 -4
  26. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +37 -0
  27. package/src/extensions/LinkToolbar/LinkToolbarPlugin.ts +2 -2
  28. package/src/extensions/SideMenu/SideMenuPlugin.ts +20 -14
  29. package/src/extensions/SuggestionMenu/DefaultGridSuggestionItem.ts +4 -0
  30. package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +2 -2
  31. package/src/extensions/SuggestionMenu/getDefaultEmojiPickerItems.ts +45 -0
  32. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +13 -6
  33. package/src/extensions/TableHandles/TableHandlesPlugin.ts +5 -5
  34. package/src/extensions/TextAlignment/TextAlignmentExtension.ts +7 -3
  35. package/src/extensions/TextColor/TextColorExtension.ts +7 -3
  36. package/src/i18n/locales/ar.ts +6 -0
  37. package/src/i18n/locales/en.ts +19 -13
  38. package/src/i18n/locales/fr.ts +6 -0
  39. package/src/i18n/locales/is.ts +6 -0
  40. package/src/i18n/locales/ja.ts +6 -0
  41. package/src/i18n/locales/ko.ts +15 -0
  42. package/src/i18n/locales/nl.ts +11 -0
  43. package/src/i18n/locales/pl.ts +6 -0
  44. package/src/i18n/locales/pt.ts +6 -0
  45. package/src/i18n/locales/ru.ts +322 -309
  46. package/src/i18n/locales/vi.ts +13 -0
  47. package/src/i18n/locales/zh.ts +14 -0
  48. package/src/index.ts +14 -5
  49. package/src/style.css +2 -7
  50. package/types/src/api/blockManipulation/blockManipulation.d.ts +1 -1
  51. package/types/src/api/exporters/copyExtension.d.ts +2 -2
  52. package/types/src/api/exporters/html/externalHTMLExporter.d.ts +7 -3
  53. package/types/src/api/exporters/html/internalHTMLSerializer.d.ts +7 -3
  54. package/types/src/api/exporters/html/util/sharedHTMLConversion.d.ts +5 -3
  55. package/types/src/api/exporters/markdown/markdownExporter.d.ts +4 -2
  56. package/types/src/api/parsers/fileDropExtension.d.ts +2 -2
  57. package/types/src/api/parsers/pasteExtension.d.ts +2 -2
  58. package/types/src/blocks/defaultBlockHelpers.d.ts +2 -2
  59. package/types/src/blocks/defaultBlockTypeGuards.d.ts +4 -1
  60. package/types/src/editor/BlockNoteEditor.d.ts +71 -11
  61. package/types/src/editor/BlockNoteExtensions.d.ts +2 -3
  62. package/types/src/editor/BlockNoteTipTapEditor.d.ts +2 -2
  63. package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +1 -0
  64. package/types/src/extensions/SuggestionMenu/DefaultGridSuggestionItem.d.ts +4 -0
  65. package/types/src/extensions/SuggestionMenu/SuggestionPlugin.d.ts +1 -2
  66. package/types/src/extensions/SuggestionMenu/getDefaultEmojiPickerItems.d.ts +4 -0
  67. package/types/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.d.ts +1 -1
  68. package/types/src/i18n/locales/en.d.ts +6 -0
  69. package/types/src/index.d.ts +11 -5
  70. package/types/src/pm-nodes/BlockContainer.d.ts +2 -8
  71. package/types/src/pm-nodes/BlockGroup.d.ts +2 -7
  72. package/types/src/schema/inlineContent/internal.d.ts +1 -1
@@ -4,7 +4,7 @@ import rehypeRemark from "rehype-remark";
4
4
  import remarkGfm from "remark-gfm";
5
5
  import remarkStringify from "remark-stringify";
6
6
  import { unified } from "unified";
7
- import { Block } from "../../../blocks/defaultBlocks";
7
+ import { PartialBlock } from "../../../blocks/defaultBlocks";
8
8
  import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor";
9
9
  import { BlockSchema, InlineContentSchema, StyleSchema } from "../../../schema";
10
10
  import { createExternalHTMLExporter } from "../html/externalHTMLExporter";
@@ -29,12 +29,13 @@ export function blocksToMarkdown<
29
29
  I extends InlineContentSchema,
30
30
  S extends StyleSchema
31
31
  >(
32
- blocks: Block<BSchema, I, S>[],
32
+ blocks: PartialBlock<BSchema, I, S>[],
33
33
  schema: Schema,
34
- editor: BlockNoteEditor<BSchema, I, S>
34
+ editor: BlockNoteEditor<BSchema, I, S>,
35
+ options: { document?: Document }
35
36
  ): string {
36
37
  const exporter = createExternalHTMLExporter(schema, editor);
37
- const externalHTML = exporter.exportBlocks(blocks);
38
+ const externalHTML = exporter.exportBlocks(blocks, options);
38
39
 
39
40
  return cleanHTMLToMarkdown(externalHTML);
40
41
  }
@@ -3,10 +3,10 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
3
3
  import { BlockNoteEditor } from "../../editor/BlockNoteEditor";
4
4
 
5
5
  import { PartialBlock } from "../../blocks/defaultBlocks";
6
+ import { customBlocksTestCases } from "../testUtil/cases/customBlocks";
6
7
  import { customInlineContentTestCases } from "../testUtil/cases/customInlineContent";
7
8
  import { customStylesTestCases } from "../testUtil/cases/customStyles";
8
9
  import { defaultSchemaTestCases } from "../testUtil/cases/defaultSchema";
9
- import { customBlocksTestCases } from "../testUtil/cases/customBlocks";
10
10
  import {
11
11
  addIdsToBlock,
12
12
  partialBlockToBlockForTesting,
@@ -18,11 +18,7 @@ function validateConversion(
18
18
  editor: BlockNoteEditor<any, any, any>
19
19
  ) {
20
20
  addIdsToBlock(block);
21
- const node = blockToNode(
22
- block,
23
- editor._tiptapEditor.schema,
24
- editor.schema.styleSchema
25
- );
21
+ const node = blockToNode(block, editor.pmSchema, editor.schema.styleSchema);
26
22
 
27
23
  expect(node).toMatchSnapshot();
28
24
 
@@ -56,6 +52,13 @@ describe("Test BlockNote-Prosemirror conversion", () => {
56
52
 
57
53
  beforeEach(() => {
58
54
  editor = testCase.createEditor();
55
+ // Note that we don't necessarily need to mount a root
56
+ // Currently, we do mount to a root so that it reflects the "production" use-case more closely.
57
+
58
+ // However, it would be nice to increased converage and share the same set of tests for these cases:
59
+ // - does render to a root
60
+ // - does not render to a root
61
+ // - runs in server (jsdom) environment using server-util
59
62
  editor.mount(div);
60
63
  });
61
64
 
@@ -33,16 +33,15 @@ async function parseHTMLAndCompareSnapshots(
33
33
 
34
34
  (window as any).__TEST_OPTIONS.mockID = 0; // reset id counter
35
35
  const htmlNode = nestedListsToBlockNoteStructure(html);
36
- const tt = editor._tiptapEditor;
37
36
 
38
37
  const slice = view.__parseFromClipboard(
39
- tt.view,
38
+ editor.prosemirrorView,
40
39
  "",
41
40
  htmlNode.innerHTML,
42
41
  false,
43
- tt.view.state.selection.$from
42
+ editor._tiptapEditor.state.selection.$from
44
43
  );
45
- tt.view.dispatch(tt.view.state.tr.replaceSelection(slice));
44
+ editor.dispatch(editor._tiptapEditor.state.tr.replaceSelection(slice));
46
45
 
47
46
  // alternative paste simulation doesn't work in a non-browser vitest env
48
47
  // editor._tiptapEditor.view.pasteHTML(html, {
@@ -5,7 +5,7 @@ exports[`Lift nested lists > Lifts multiple bullet lists 1`] = `
5
5
  <ul>
6
6
  <div>
7
7
  <li>Bullet List Item 1</li>
8
- <div data-node-type=\\"blockGroup\\">
8
+ <div data-node-type="blockGroup">
9
9
  <ul>
10
10
  <li>Nested Bullet List Item 1</li>
11
11
  <li>Nested Bullet List Item 2</li>
@@ -26,7 +26,7 @@ exports[`Lift nested lists > Lifts multiple bullet lists with content in between
26
26
  <ul>
27
27
  <div>
28
28
  <li>Bullet List Item 1</li>
29
- <div data-node-type=\\"blockGroup\\">
29
+ <div data-node-type="blockGroup">
30
30
  <ul>
31
31
  <li>Nested Bullet List Item 1</li>
32
32
  <li>Nested Bullet List Item 2</li>
@@ -35,7 +35,7 @@ exports[`Lift nested lists > Lifts multiple bullet lists with content in between
35
35
  </div>
36
36
  <div>
37
37
  <li>In between content</li>
38
- <div data-node-type=\\"blockGroup\\">
38
+ <div data-node-type="blockGroup">
39
39
  <ul>
40
40
  <li>Nested Bullet List Item 3</li>
41
41
  <li>Nested Bullet List Item 4</li>
@@ -52,7 +52,7 @@ exports[`Lift nested lists > Lifts nested bullet lists 1`] = `
52
52
  <ul>
53
53
  <div>
54
54
  <li>Bullet List Item 1</li>
55
- <div data-node-type=\\"blockGroup\\">
55
+ <div data-node-type="blockGroup">
56
56
  <ul>
57
57
  <li>Nested Bullet List Item 1</li>
58
58
  <li>Nested Bullet List Item 2</li>
@@ -69,7 +69,7 @@ exports[`Lift nested lists > Lifts nested bullet lists with content after nested
69
69
  <ul>
70
70
  <div>
71
71
  <li>Bullet List Item 1</li>
72
- <div data-node-type=\\"blockGroup\\">
72
+ <div data-node-type="blockGroup">
73
73
  <ul>
74
74
  <li>Nested Bullet List Item 1</li>
75
75
  <li>Nested Bullet List Item 2</li>
@@ -99,7 +99,7 @@ exports[`Lift nested lists > Lifts nested mixed lists 1`] = `
99
99
  <ol>
100
100
  <div>
101
101
  <li>Numbered List Item 1</li>
102
- <div data-node-type=\\"blockGroup\\">
102
+ <div data-node-type="blockGroup">
103
103
  <ul>
104
104
  <li>Bullet List Item 1</li>
105
105
  <li>Bullet List Item 2</li>
@@ -116,7 +116,7 @@ exports[`Lift nested lists > Lifts nested numbered lists 1`] = `
116
116
  <ol>
117
117
  <div>
118
118
  <li>Numbered List Item 1</li>
119
- <div data-node-type=\\"blockGroup\\">
119
+ <div data-node-type="blockGroup">
120
120
  <ol>
121
121
  <li>Nested Numbered List Item 1</li>
122
122
  <li>Nested Numbered List Item 2</li>
@@ -75,7 +75,7 @@ export const createAddFileButton = (
75
75
  };
76
76
  // Opens the file toolbar.
77
77
  const addFileButtonClickHandler = () => {
78
- editor._tiptapEditor.view.dispatch(
78
+ editor.dispatch(
79
79
  editor._tiptapEditor.state.tr.setMeta(editor.filePanel!.plugin, {
80
80
  block: block,
81
81
  })
@@ -67,12 +67,9 @@ export const defaultBlockToHTML = <
67
67
  dom: HTMLElement;
68
68
  contentDOM?: HTMLElement;
69
69
  } => {
70
- const node = blockToNode(
71
- block,
72
- editor._tiptapEditor.schema,
73
- editor.schema.styleSchema
74
- ).firstChild!;
75
- const toDOM = editor._tiptapEditor.schema.nodes[node.type.name].spec.toDOM;
70
+ const node = blockToNode(block, editor.pmSchema, editor.schema.styleSchema)
71
+ .firstChild!;
72
+ const toDOM = editor.pmSchema.nodes[node.type.name].spec.toDOM;
76
73
 
77
74
  if (toDOM === undefined) {
78
75
  throw new Error(
@@ -6,7 +6,13 @@ import {
6
6
  InlineContentSchema,
7
7
  StyleSchema,
8
8
  } from "../schema";
9
- import { Block, DefaultBlockSchema, defaultBlockSchema } from "./defaultBlocks";
9
+ import {
10
+ Block,
11
+ DefaultBlockSchema,
12
+ defaultBlockSchema,
13
+ defaultInlineContentSchema,
14
+ DefaultInlineContentSchema,
15
+ } from "./defaultBlocks";
10
16
  import { defaultProps } from "./defaultProps";
11
17
 
12
18
  export function checkDefaultBlockTypeInSchema<
@@ -23,6 +29,25 @@ export function checkDefaultBlockTypeInSchema<
23
29
  );
24
30
  }
25
31
 
32
+ export function checkDefaultInlineContentTypeInSchema<
33
+ InlineContentType extends keyof DefaultInlineContentSchema,
34
+ B extends BlockSchema,
35
+ S extends StyleSchema
36
+ >(
37
+ inlineContentType: InlineContentType,
38
+ editor: BlockNoteEditor<B, any, S>
39
+ ): editor is BlockNoteEditor<
40
+ B,
41
+ { Type: DefaultInlineContentSchema[InlineContentType] },
42
+ S
43
+ > {
44
+ return (
45
+ inlineContentType in editor.schema.inlineContentSchema &&
46
+ editor.schema.inlineContentSchema[inlineContentType] ===
47
+ defaultInlineContentSchema[inlineContentType]
48
+ );
49
+ }
50
+
26
51
  export function checkBlockIsDefaultType<
27
52
  BlockType extends keyof DefaultBlockSchema,
28
53
  I extends InlineContentSchema,
@@ -1,6 +1,6 @@
1
1
  import { expect, it } from "vitest";
2
- import { BlockNoteEditor } from "./BlockNoteEditor";
3
2
  import { getBlockInfoFromPos } from "../api/getBlockInfoFromPos";
3
+ import { BlockNoteEditor } from "./BlockNoteEditor";
4
4
 
5
5
  /**
6
6
  * @vitest-environment jsdom
@@ -10,3 +10,50 @@ it("creates an editor", () => {
10
10
  const blockInfo = getBlockInfoFromPos(editor._tiptapEditor.state.doc, 2);
11
11
  expect(blockInfo?.contentNode.type.name).toEqual("paragraph");
12
12
  });
13
+
14
+ it("immediately replaces doc", async () => {
15
+ const editor = BlockNoteEditor.create();
16
+ const blocks = await editor.tryParseMarkdownToBlocks(
17
+ "This is a normal text\n\n# And this is a large heading"
18
+ );
19
+ editor.replaceBlocks(editor.document, blocks);
20
+ expect(editor.document).toMatchInlineSnapshot(`
21
+ [
22
+ {
23
+ "children": [],
24
+ "content": [
25
+ {
26
+ "styles": {},
27
+ "text": "This is a normal text",
28
+ "type": "text",
29
+ },
30
+ ],
31
+ "id": "1",
32
+ "props": {
33
+ "backgroundColor": "default",
34
+ "textAlignment": "left",
35
+ "textColor": "default",
36
+ },
37
+ "type": "paragraph",
38
+ },
39
+ {
40
+ "children": [],
41
+ "content": [
42
+ {
43
+ "styles": {},
44
+ "text": "And this is a large heading",
45
+ "type": "text",
46
+ },
47
+ ],
48
+ "id": "2",
49
+ "props": {
50
+ "backgroundColor": "default",
51
+ "level": 1,
52
+ "textAlignment": "left",
53
+ "textColor": "default",
54
+ },
55
+ "type": "heading",
56
+ },
57
+ ]
58
+ `);
59
+ });
@@ -1,5 +1,5 @@
1
- import { EditorOptions, Extension } from "@tiptap/core";
2
- import { Node } from "prosemirror-model";
1
+ import { EditorOptions, Extension, getSchema } from "@tiptap/core";
2
+ import { Node, Schema } from "prosemirror-model";
3
3
  // import "./blocknote.css";
4
4
  import * as Y from "yjs";
5
5
  import {
@@ -61,12 +61,13 @@ import {
61
61
  BlockNoteTipTapEditorOptions,
62
62
  } from "./BlockNoteTipTapEditor";
63
63
 
64
- // CSS
65
64
  import { PlaceholderPlugin } from "../extensions/Placeholder/PlaceholderPlugin";
66
65
  import { Dictionary } from "../i18n/dictionary";
67
66
  import { en } from "../i18n/locales";
68
- import "./Block.css";
69
- import "./editor.css";
67
+
68
+ import { Transaction } from "@tiptap/pm/state";
69
+ import { createInternalHTMLSerializer } from "../api/exporters/html/internalHTMLSerializer";
70
+ import "../style.css";
70
71
 
71
72
  export type BlockNoteEditorOptions<
72
73
  BSchema extends BlockSchema,
@@ -109,7 +110,11 @@ export type BlockNoteEditorOptions<
109
110
  schema: BlockNoteSchema<BSchema, ISchema, SSchema>;
110
111
 
111
112
  /**
112
- * A custom function to handle file uploads.
113
+ * The `uploadFile` method is what the editor uses when files need to be uploaded (for example when selecting an image to upload).
114
+ * This method should set when creating the editor as this is application-specific.
115
+ *
116
+ * `undefined` means the application doesn't support file uploads.
117
+ *
113
118
  * @param file The file that should be uploaded.
114
119
  * @returns The URL of the uploaded file OR an object containing props that should be set on the file block (such as an id)
115
120
  */
@@ -151,6 +156,15 @@ export type BlockNoteEditorOptions<
151
156
  _tiptapOptions: Partial<EditorOptions>;
152
157
 
153
158
  trailingBlock?: boolean;
159
+
160
+ /**
161
+ * Boolean indicating whether the editor is in headless mode.
162
+ * Headless mode means we can use features like importing / exporting blocks,
163
+ * but there's no underlying editor (UI) instantiated.
164
+ *
165
+ * You probably don't need to set this manually, but use the `server-util` package instead that uses this option internally
166
+ */
167
+ _headless: boolean;
154
168
  };
155
169
 
156
170
  const blockNoteTipTapOptions = {
@@ -164,12 +178,44 @@ export class BlockNoteEditor<
164
178
  ISchema extends InlineContentSchema = DefaultInlineContentSchema,
165
179
  SSchema extends StyleSchema = DefaultStyleSchema
166
180
  > {
167
- public readonly _tiptapEditor: BlockNoteTipTapEditor & {
168
- contentComponent: any;
169
- };
181
+ private readonly _pmSchema: Schema;
182
+
183
+ /**
184
+ * Boolean indicating whether the editor is in headless mode.
185
+ * Headless mode means we can use features like importing / exporting blocks,
186
+ * but there's no underlying editor (UI) instantiated.
187
+ *
188
+ * You probably don't need to set this manually, but use the `server-util` package instead that uses this option internally
189
+ */
190
+ public readonly headless: boolean = false;
191
+
192
+ public readonly _tiptapEditor:
193
+ | BlockNoteTipTapEditor & {
194
+ contentComponent: any;
195
+ } = undefined as any; // TODO: Type should actually reflect that it can be `undefined` in headless mode
196
+
197
+ /**
198
+ * Used by React to store a reference to an `ElementRenderer` helper utility to make sure we can render React elements
199
+ * in the correct context (used by `ReactRenderUtil`)
200
+ */
201
+ public elementRenderer: ((node: any, container: HTMLElement) => void) | null =
202
+ null;
203
+
204
+ /**
205
+ * Cache of all blocks. This makes sure we don't have to "recompute" blocks if underlying Prosemirror Nodes haven't changed.
206
+ * This is especially useful when we want to keep track of the same block across multiple operations,
207
+ * with this cache, blocks stay the same object reference (referential equality with ===).
208
+ */
170
209
  public blockCache = new WeakMap<Node, Block<any, any, any>>();
210
+
211
+ /**
212
+ * The dictionary contains translations for the editor.
213
+ */
171
214
  public readonly dictionary: Dictionary;
172
215
 
216
+ /**
217
+ * The schema of the editor. The schema defines which Blocks, InlineContent, and Styles are available in the editor.
218
+ */
173
219
  public readonly schema: BlockNoteSchema<BSchema, ISchema, SSchema>;
174
220
 
175
221
  public readonly blockImplementations: BlockSpecs;
@@ -198,12 +244,25 @@ export class BlockNoteEditor<
198
244
  SSchema
199
245
  >;
200
246
 
247
+ /**
248
+ * The `uploadFile` method is what the editor uses when files need to be uploaded (for example when selecting an image to upload).
249
+ * This method should set when creating the editor as this is application-specific.
250
+ *
251
+ * `undefined` means the application doesn't support file uploads.
252
+ *
253
+ * @param file The file that should be uploaded.
254
+ * @returns The URL of the uploaded file OR an object containing props that should be set on the file block (such as an id)
255
+ */
201
256
  public readonly uploadFile:
202
257
  | ((file: File) => Promise<string | Record<string, any>>)
203
258
  | undefined;
204
259
 
205
260
  public readonly resolveFileUrl: (url: string) => Promise<string>;
206
261
 
262
+ public get pmSchema() {
263
+ return this._pmSchema;
264
+ }
265
+
207
266
  public static create<
208
267
  BSchema extends BlockSchema = DefaultBlockSchema,
209
268
  ISchema extends InlineContentSchema = DefaultInlineContentSchema,
@@ -246,6 +305,7 @@ export class BlockNoteEditor<
246
305
  const newOptions = {
247
306
  defaultStyles: true,
248
307
  schema: options.schema || BlockNoteSchema.create(),
308
+ _headless: false,
249
309
  ...options,
250
310
  placeholders: {
251
311
  ...this.dictionary.placeholders,
@@ -272,7 +332,6 @@ export class BlockNoteEditor<
272
332
  const extensions = getBlockNoteExtensions({
273
333
  editor: this,
274
334
  domAttributes: newOptions.domAttributes || {},
275
- blockSchema: this.schema.blockSchema,
276
335
  blockSpecs: this.schema.blockSpecs,
277
336
  styleSpecs: this.schema.styleSpecs,
278
337
  inlineContentSpecs: this.schema.inlineContentSpecs,
@@ -300,6 +359,7 @@ export class BlockNoteEditor<
300
359
 
301
360
  this.uploadFile = newOptions.uploadFile;
302
361
  this.resolveFileUrl = newOptions.resolveFileUrl || (async (url) => url);
362
+ this.headless = newOptions._headless;
303
363
 
304
364
  if (newOptions.collaboration && newOptions.initialContent) {
305
365
  // eslint-disable-next-line no-console
@@ -354,22 +414,33 @@ export class BlockNoteEditor<
354
414
  },
355
415
  };
356
416
 
357
- this._tiptapEditor = new BlockNoteTipTapEditor(
358
- tiptapOptions,
359
- this.schema.styleSchema
360
- ) as BlockNoteTipTapEditor & {
361
- contentComponent: any;
362
- };
417
+ if (!this.headless) {
418
+ this._tiptapEditor = new BlockNoteTipTapEditor(
419
+ tiptapOptions,
420
+ this.schema.styleSchema
421
+ ) as BlockNoteTipTapEditor & {
422
+ contentComponent: any;
423
+ };
424
+ this._pmSchema = this._tiptapEditor.schema;
425
+ } else {
426
+ // In headless mode, we don't instantiate an underlying TipTap editor,
427
+ // but we still need the schema
428
+ this._pmSchema = getSchema(tiptapOptions.extensions!);
429
+ }
430
+ }
431
+
432
+ dispatch(tr: Transaction) {
433
+ this._tiptapEditor.dispatch(tr);
363
434
  }
364
435
 
365
436
  /**
366
437
  * Mount the editor to a parent DOM element. Call mount(undefined) to clean up
367
438
  *
368
- * @warning Not needed for React, use BlockNoteView to take care of this
439
+ * @warning Not needed to call manually when using React, use BlockNoteView to take care of mounting
369
440
  */
370
- public mount(parentElement?: HTMLElement | null) {
441
+ public mount = (parentElement?: HTMLElement | null) => {
371
442
  this._tiptapEditor.mount(parentElement);
372
- }
443
+ };
373
444
 
374
445
  public get prosemirrorView() {
375
446
  return this._tiptapEditor.view;
@@ -391,7 +462,7 @@ export class BlockNoteEditor<
391
462
  * @deprecated, use `editor.document` instead
392
463
  */
393
464
  public get topLevelBlocks(): Block<BSchema, ISchema, SSchema>[] {
394
- return this.topLevelBlocks;
465
+ return this.document;
395
466
  }
396
467
 
397
468
  /**
@@ -676,6 +747,12 @@ export class BlockNoteEditor<
676
747
  * @returns True if the editor is editable, false otherwise.
677
748
  */
678
749
  public get isEditable(): boolean {
750
+ if (!this._tiptapEditor) {
751
+ if (!this.headless) {
752
+ throw new Error("no editor, but also not headless?");
753
+ }
754
+ return false;
755
+ }
679
756
  return this._tiptapEditor.isEditable;
680
757
  }
681
758
 
@@ -684,6 +761,13 @@ export class BlockNoteEditor<
684
761
  * @param editable True to make the editor editable, or false to lock it.
685
762
  */
686
763
  public set isEditable(editable: boolean) {
764
+ if (!this._tiptapEditor) {
765
+ if (!this.headless) {
766
+ throw new Error("no editor, but also not headless?");
767
+ }
768
+ // not relevant on headless
769
+ return;
770
+ }
687
771
  if (this._tiptapEditor.options.editable !== editable) {
688
772
  this._tiptapEditor.setEditable(editable);
689
773
  }
@@ -749,7 +833,7 @@ export class BlockNoteEditor<
749
833
  public insertInlineContent(content: PartialInlineContent<ISchema, SSchema>) {
750
834
  const nodes = inlineContentToNodes(
751
835
  content,
752
- this._tiptapEditor.schema,
836
+ this.pmSchema,
753
837
  this.schema.styleSchema
754
838
  );
755
839
 
@@ -873,10 +957,10 @@ export class BlockNoteEditor<
873
957
  text = this._tiptapEditor.state.doc.textBetween(from, to);
874
958
  }
875
959
 
876
- const mark = this._tiptapEditor.schema.mark("link", { href: url });
960
+ const mark = this.pmSchema.mark("link", { href: url });
877
961
 
878
- this._tiptapEditor.view.dispatch(
879
- this._tiptapEditor.view.state.tr
962
+ this.dispatch(
963
+ this._tiptapEditor.state.tr
880
964
  .insertText(text, from, to)
881
965
  .addMark(from, from + text.length, mark)
882
966
  );
@@ -920,23 +1004,35 @@ export class BlockNoteEditor<
920
1004
  this._tiptapEditor.commands.liftListItem("blockContainer");
921
1005
  }
922
1006
 
923
- // TODO: Fix when implementing HTML/Markdown import & export
924
1007
  /**
925
- * Serializes blocks into an HTML string. To better conform to HTML standards, children of blocks which aren't list
1008
+ * Exports blocks into a simplified HTML string. To better conform to HTML standards, children of blocks which aren't list
926
1009
  * items are un-nested in the output HTML.
1010
+ *
927
1011
  * @param blocks An array of blocks that should be serialized into HTML.
928
1012
  * @returns The blocks, serialized as an HTML string.
929
1013
  */
930
1014
  public async blocksToHTMLLossy(
931
- blocks: Block<BSchema, ISchema, SSchema>[] = this.document
1015
+ blocks: PartialBlock<BSchema, ISchema, SSchema>[] = this.document
932
1016
  ): Promise<string> {
933
- const exporter = createExternalHTMLExporter(
934
- this._tiptapEditor.schema,
935
- this
936
- );
937
- return exporter.exportBlocks(blocks);
1017
+ const exporter = createExternalHTMLExporter(this.pmSchema, this);
1018
+ return exporter.exportBlocks(blocks, {});
938
1019
  }
939
1020
 
1021
+ /**
1022
+ * Serializes blocks into an HTML string in the format that would normally be rendered by the editor.
1023
+ *
1024
+ * Use this method if you want to server-side render HTML (for example, a blog post that has been edited in BlockNote)
1025
+ * and serve it to users without loading the editor on the client (i.e.: displaying the blog post)
1026
+ *
1027
+ * @param blocks An array of blocks that should be serialized into HTML.
1028
+ * @returns The blocks, serialized as an HTML string.
1029
+ */
1030
+ public async blocksToFullHTML(
1031
+ blocks: PartialBlock<BSchema, ISchema, SSchema>[]
1032
+ ): Promise<string> {
1033
+ const exporter = createInternalHTMLSerializer(this.pmSchema, this);
1034
+ return exporter.serializeBlocks(blocks, {});
1035
+ }
940
1036
  /**
941
1037
  * Parses blocks from an HTML string. Tries to create `Block` objects out of any HTML block-level elements, and
942
1038
  * `InlineNode` objects from any HTML inline elements, though not all element types are recognized. If BlockNote
@@ -952,7 +1048,7 @@ export class BlockNoteEditor<
952
1048
  this.schema.blockSchema,
953
1049
  this.schema.inlineContentSchema,
954
1050
  this.schema.styleSchema,
955
- this._tiptapEditor.schema
1051
+ this.pmSchema
956
1052
  );
957
1053
  }
958
1054
 
@@ -963,9 +1059,9 @@ export class BlockNoteEditor<
963
1059
  * @returns The blocks, serialized as a Markdown string.
964
1060
  */
965
1061
  public async blocksToMarkdownLossy(
966
- blocks: Block<BSchema, ISchema, SSchema>[] = this.document
1062
+ blocks: PartialBlock<BSchema, ISchema, SSchema>[] = this.document
967
1063
  ): Promise<string> {
968
- return blocksToMarkdown(blocks, this._tiptapEditor.schema, this);
1064
+ return blocksToMarkdown(blocks, this.pmSchema, this, {});
969
1065
  }
970
1066
 
971
1067
  /**
@@ -983,7 +1079,7 @@ export class BlockNoteEditor<
983
1079
  this.schema.blockSchema,
984
1080
  this.schema.inlineContentSchema,
985
1081
  this.schema.styleSchema,
986
- this._tiptapEditor.schema
1082
+ this.pmSchema
987
1083
  );
988
1084
  }
989
1085
 
@@ -1008,6 +1104,11 @@ export class BlockNoteEditor<
1008
1104
  public onChange(
1009
1105
  callback: (editor: BlockNoteEditor<BSchema, ISchema, SSchema>) => void
1010
1106
  ) {
1107
+ if (this.headless) {
1108
+ // Note: would be nice if this is possible in headless mode as well
1109
+ return;
1110
+ }
1111
+
1011
1112
  const cb = () => {
1012
1113
  callback(this);
1013
1114
  };
@@ -1028,6 +1129,10 @@ export class BlockNoteEditor<
1028
1129
  public onSelectionChange(
1029
1130
  callback: (editor: BlockNoteEditor<BSchema, ISchema, SSchema>) => void
1030
1131
  ) {
1132
+ if (this.headless) {
1133
+ return;
1134
+ }
1135
+
1031
1136
  const cb = () => {
1032
1137
  callback(this);
1033
1138
  };
@@ -1038,4 +1143,16 @@ export class BlockNoteEditor<
1038
1143
  this._tiptapEditor.off("selectionUpdate", cb);
1039
1144
  };
1040
1145
  }
1146
+
1147
+ public openSelectionMenu(triggerCharacter: string) {
1148
+ this.prosemirrorView.focus();
1149
+ this.prosemirrorView.dispatch(
1150
+ this.prosemirrorView.state.tr
1151
+ .scrollIntoView()
1152
+ .setMeta(this.suggestionMenus.plugin, {
1153
+ triggerCharacter: triggerCharacter,
1154
+ fromUserInput: false,
1155
+ })
1156
+ );
1157
+ }
1041
1158
  }
@@ -40,7 +40,6 @@ export const getBlockNoteExtensions = <
40
40
  >(opts: {
41
41
  editor: BlockNoteEditor<BSchema, I, S>;
42
42
  domAttributes: Partial<BlockNoteDOMAttributes>;
43
- blockSchema: BSchema;
44
43
  blockSpecs: BlockSpecs;
45
44
  inlineContentSpecs: InlineContentSpecs;
46
45
  styleSpecs: StyleSpecs;
@@ -3,8 +3,8 @@ import { EditorOptions, createDocument } from "@tiptap/core";
3
3
  import { Editor as TiptapEditor } from "@tiptap/core";
4
4
  import { Node } from "@tiptap/pm/model";
5
5
  import { EditorView } from "@tiptap/pm/view";
6
- import { EditorState } from "prosemirror-state";
7
6
 
7
+ import { EditorState, Transaction } from "@tiptap/pm/state";
8
8
  import { blockToNode } from "../api/nodeConversions/nodeConversions";
9
9
  import { PartialBlock } from "../blocks/defaultBlocks";
10
10
  import { StyleSchema } from "../schema";
@@ -115,10 +115,13 @@ export class BlockNoteTipTapEditor extends TiptapEditor {
115
115
  return this._state;
116
116
  }
117
117
 
118
- createView() {
119
- // no-op
120
- // Disable default call to `createView` in the Editor constructor.
121
- // We should call `createView` manually only when a DOM element is available
118
+ dispatch(tr: Transaction) {
119
+ if (this.view) {
120
+ this.view.dispatch(tr);
121
+ } else {
122
+ // before view has been initialized
123
+ this._state = this.state.apply(tr);
124
+ }
122
125
  }
123
126
 
124
127
  /**
@@ -164,3 +167,9 @@ export class BlockNoteTipTapEditor extends TiptapEditor {
164
167
  }
165
168
  };
166
169
  }
170
+
171
+ (BlockNoteTipTapEditor.prototype as any).createView = () => {
172
+ // no-op
173
+ // Disable default call to `createView` in the Editor constructor.
174
+ // We should call `createView` manually only when a DOM element is available
175
+ };