@blocknote/core 0.11.1 → 0.12.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 (129) hide show
  1. package/README.md +13 -17
  2. package/dist/blocknote.js +1611 -1408
  3. package/dist/blocknote.js.map +1 -1
  4. package/dist/blocknote.umd.cjs +6 -6
  5. package/dist/blocknote.umd.cjs.map +1 -1
  6. package/dist/style.css +1 -1
  7. package/dist/webpack-stats.json +1 -1
  8. package/package.json +8 -4
  9. package/src/api/blockManipulation/blockManipulation.test.ts +19 -15
  10. package/src/api/blockManipulation/blockManipulation.ts +107 -17
  11. package/src/api/exporters/html/externalHTMLExporter.ts +3 -7
  12. package/src/api/exporters/html/htmlConversion.test.ts +6 -3
  13. package/src/api/exporters/html/internalHTMLSerializer.ts +3 -7
  14. package/src/api/exporters/html/util/sharedHTMLConversion.ts +3 -3
  15. package/src/api/exporters/markdown/markdownExporter.test.ts +7 -3
  16. package/src/api/exporters/markdown/markdownExporter.ts +2 -6
  17. package/src/api/nodeConversions/nodeConversions.test.ts +14 -7
  18. package/src/api/nodeConversions/nodeConversions.ts +1 -2
  19. package/src/api/parsers/html/parseHTML.test.ts +5 -1
  20. package/src/api/parsers/html/parseHTML.ts +2 -6
  21. package/src/api/parsers/html/util/nestedLists.ts +11 -1
  22. package/src/api/parsers/markdown/parseMarkdown.test.ts +3 -0
  23. package/src/api/parsers/markdown/parseMarkdown.ts +2 -6
  24. package/src/api/testUtil/cases/customBlocks.ts +18 -16
  25. package/src/api/testUtil/cases/customInlineContent.ts +12 -13
  26. package/src/api/testUtil/cases/customStyles.ts +12 -10
  27. package/src/api/testUtil/index.ts +4 -2
  28. package/src/api/testUtil/partialBlockTestUtil.ts +2 -6
  29. package/src/blocks/ImageBlockContent/ImageBlockContent.ts +1 -2
  30. package/src/blocks/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.ts +8 -1
  31. package/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts +13 -0
  32. package/src/blocks/defaultBlockHelpers.ts +3 -3
  33. package/src/blocks/defaultBlockTypeGuards.ts +84 -0
  34. package/src/blocks/defaultBlocks.ts +29 -3
  35. package/src/editor/Block.css +2 -31
  36. package/src/editor/BlockNoteEditor.ts +219 -263
  37. package/src/editor/BlockNoteExtensions.ts +5 -2
  38. package/src/editor/BlockNoteSchema.ts +98 -0
  39. package/src/editor/BlockNoteTipTapEditor.ts +162 -0
  40. package/src/editor/cursorPositionTypes.ts +2 -6
  41. package/src/editor/editor.css +0 -1
  42. package/src/editor/selectionTypes.ts +2 -6
  43. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +22 -29
  44. package/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.ts +26 -27
  45. package/src/extensions/ImageToolbar/ImageToolbarPlugin.ts +45 -51
  46. package/src/extensions/Placeholder/PlaceholderExtension.ts +81 -88
  47. package/src/extensions/SideMenu/SideMenuPlugin.ts +55 -56
  48. package/src/extensions/SuggestionMenu/DefaultSuggestionItem.ts +8 -0
  49. package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +353 -0
  50. package/src/extensions/{SlashMenu/defaultSlashMenuItems.ts → SuggestionMenu/getDefaultSlashMenuItems.ts} +119 -89
  51. package/src/extensions/TableHandles/TableHandlesPlugin.ts +62 -45
  52. package/src/extensions-shared/UiElementPosition.ts +4 -0
  53. package/src/index.ts +6 -6
  54. package/src/pm-nodes/BlockContainer.ts +5 -9
  55. package/src/schema/blocks/types.ts +15 -15
  56. package/src/schema/inlineContent/createSpec.ts +2 -2
  57. package/src/schema/inlineContent/types.ts +1 -1
  58. package/src/util/browser.ts +6 -4
  59. package/src/util/typescript.ts +7 -4
  60. package/types/src/api/blockManipulation/blockManipulation.d.ts +6 -1
  61. package/types/src/api/exporters/html/externalHTMLExporter.d.ts +2 -1
  62. package/types/src/api/exporters/html/internalHTMLSerializer.d.ts +2 -1
  63. package/types/src/api/exporters/markdown/markdownExporter.d.ts +2 -1
  64. package/types/src/api/nodeConversions/nodeConversions.d.ts +2 -1
  65. package/types/src/api/parsers/html/parseHTML.d.ts +2 -1
  66. package/types/src/api/parsers/markdown/parseMarkdown.d.ts +2 -1
  67. package/types/src/api/testUtil/cases/customBlocks.d.ts +72 -13
  68. package/types/src/api/testUtil/cases/customInlineContent.d.ts +281 -6
  69. package/types/src/api/testUtil/cases/customStyles.d.ts +247 -13
  70. package/types/src/api/testUtil/index.d.ts +4 -2
  71. package/types/src/api/testUtil/partialBlockTestUtil.d.ts +2 -1
  72. package/types/src/blocks/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.d.ts +6 -1
  73. package/types/src/blocks/defaultBlockHelpers.d.ts +2 -2
  74. package/types/src/blocks/defaultBlockTypeGuards.d.ts +24 -0
  75. package/types/src/blocks/defaultBlocks.d.ts +21 -15
  76. package/types/src/editor/BlockNoteEditor.d.ts +48 -53
  77. package/types/src/editor/BlockNoteExtensions.d.ts +1 -0
  78. package/types/src/editor/BlockNoteSchema.d.ts +34 -0
  79. package/types/src/editor/BlockNoteTipTapEditor.d.ts +28 -0
  80. package/types/src/editor/cursorPositionTypes.d.ts +2 -1
  81. package/types/src/editor/selectionTypes.d.ts +2 -1
  82. package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +5 -6
  83. package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.d.ts +2 -2
  84. package/types/src/extensions/ImageToolbar/ImageToolbarPlugin.d.ts +15 -14
  85. package/types/src/extensions/Placeholder/PlaceholderExtension.d.ts +2 -15
  86. package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +8 -7
  87. package/types/src/extensions/SuggestionMenu/DefaultSuggestionItem.d.ts +8 -0
  88. package/types/src/extensions/SuggestionMenu/SuggestionPlugin.d.ts +31 -0
  89. package/types/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.d.ts +10 -0
  90. package/types/src/extensions/TableHandles/TableHandlesPlugin.d.ts +7 -7
  91. package/types/src/extensions-shared/UiElementPosition.d.ts +4 -0
  92. package/types/src/index.d.ts +6 -6
  93. package/types/src/pm-nodes/BlockContainer.d.ts +3 -2
  94. package/types/src/pm-nodes/BlockGroup.d.ts +1 -1
  95. package/types/src/schema/blocks/types.d.ts +15 -15
  96. package/types/src/schema/inlineContent/types.d.ts +1 -1
  97. package/types/src/util/browser.d.ts +1 -0
  98. package/types/src/util/typescript.d.ts +1 -0
  99. package/src/extensions/SlashMenu/BaseSlashMenuItem.ts +0 -12
  100. package/src/extensions/SlashMenu/SlashMenuPlugin.ts +0 -53
  101. package/src/extensions-shared/BaseUiElementTypes.ts +0 -8
  102. package/src/extensions-shared/README.md +0 -3
  103. package/src/extensions-shared/suggestion/SuggestionItem.ts +0 -3
  104. package/src/extensions-shared/suggestion/SuggestionPlugin.ts +0 -448
  105. package/types/src/extensions/SlashMenu/BaseSlashMenuItem.d.ts +0 -7
  106. package/types/src/extensions/SlashMenu/SlashMenuPlugin.d.ts +0 -13
  107. package/types/src/extensions/SlashMenu/defaultSlashMenuItems.d.ts +0 -3
  108. package/types/src/extensions-shared/BaseUiElementTypes.d.ts +0 -7
  109. package/types/src/extensions-shared/suggestion/SuggestionItem.d.ts +0 -3
  110. package/types/src/extensions-shared/suggestion/SuggestionPlugin.d.ts +0 -36
  111. /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-100.woff +0 -0
  112. /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-100.woff2 +0 -0
  113. /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-200.woff +0 -0
  114. /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-200.woff2 +0 -0
  115. /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-300.woff +0 -0
  116. /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-300.woff2 +0 -0
  117. /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-500.woff +0 -0
  118. /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-500.woff2 +0 -0
  119. /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-600.woff +0 -0
  120. /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-600.woff2 +0 -0
  121. /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-700.woff +0 -0
  122. /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-700.woff2 +0 -0
  123. /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-800.woff +0 -0
  124. /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-800.woff2 +0 -0
  125. /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-900.woff +0 -0
  126. /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-900.woff2 +0 -0
  127. /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-regular.woff +0 -0
  128. /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-regular.woff2 +0 -0
  129. /package/src/{assets/fonts-inter.css → fonts/inter.css} +0 -0
@@ -1,10 +1,10 @@
1
- import { Editor, EditorOptions, Extension } from "@tiptap/core";
1
+ import { EditorOptions, Extension } from "@tiptap/core";
2
2
  import { Node } from "prosemirror-model";
3
3
  // import "./blocknote.css";
4
- import { Editor as TiptapEditor } from "@tiptap/core/dist/packages/core/src/Editor";
5
4
  import * as Y from "yjs";
6
5
  import {
7
6
  insertBlocks,
7
+ insertContentAt,
8
8
  removeBlocks,
9
9
  replaceBlocks,
10
10
  updateBlock,
@@ -13,52 +13,40 @@ import { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLEx
13
13
  import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter";
14
14
  import { getBlockInfoFromPos } from "../api/getBlockInfoFromPos";
15
15
  import {
16
- blockToNode,
16
+ inlineContentToNodes,
17
17
  nodeToBlock,
18
18
  } from "../api/nodeConversions/nodeConversions";
19
19
  import { getNodeById } from "../api/nodeUtil";
20
20
  import { HTMLToBlocks } from "../api/parsers/html/parseHTML";
21
21
  import { markdownToBlocks } from "../api/parsers/markdown/parseMarkdown";
22
22
  import {
23
+ Block,
23
24
  DefaultBlockSchema,
24
- defaultBlockSchema,
25
- defaultBlockSpecs,
26
25
  DefaultInlineContentSchema,
27
- defaultInlineContentSpecs,
28
26
  DefaultStyleSchema,
29
- defaultStyleSpecs,
27
+ PartialBlock,
30
28
  } from "../blocks/defaultBlocks";
31
29
  import { FormattingToolbarProsemirrorPlugin } from "../extensions/FormattingToolbar/FormattingToolbarPlugin";
32
30
  import { HyperlinkToolbarProsemirrorPlugin } from "../extensions/HyperlinkToolbar/HyperlinkToolbarPlugin";
33
31
  import { ImageToolbarProsemirrorPlugin } from "../extensions/ImageToolbar/ImageToolbarPlugin";
34
32
  import { SideMenuProsemirrorPlugin } from "../extensions/SideMenu/SideMenuPlugin";
35
- import { BaseSlashMenuItem } from "../extensions/SlashMenu/BaseSlashMenuItem";
36
- import { SlashMenuProsemirrorPlugin } from "../extensions/SlashMenu/SlashMenuPlugin";
37
- import { getDefaultSlashMenuItems } from "../extensions/SlashMenu/defaultSlashMenuItems";
33
+ import { SuggestionMenuProseMirrorPlugin } from "../extensions/SuggestionMenu/SuggestionPlugin";
38
34
  import { TableHandlesProsemirrorPlugin } from "../extensions/TableHandles/TableHandlesPlugin";
39
35
  import { UniqueID } from "../extensions/UniqueID/UniqueID";
40
36
  import {
41
- Block,
42
37
  BlockIdentifier,
43
38
  BlockNoteDOMAttributes,
44
39
  BlockSchema,
45
- BlockSchemaFromSpecs,
46
- BlockSchemaWithBlock,
47
40
  BlockSpecs,
48
- getBlockSchemaFromSpecs,
49
- getInlineContentSchemaFromSpecs,
50
- getStyleSchemaFromSpecs,
51
41
  InlineContentSchema,
52
- InlineContentSchemaFromSpecs,
53
42
  InlineContentSpecs,
54
- PartialBlock,
55
- Styles,
43
+ PartialInlineContent,
56
44
  StyleSchema,
57
- StyleSchemaFromSpecs,
58
45
  StyleSpecs,
46
+ Styles,
59
47
  } from "../schema";
60
48
  import { mergeCSSClasses } from "../util/browser";
61
- import { UnreachableCaseError } from "../util/typescript";
49
+ import { NoInfer, UnreachableCaseError } from "../util/typescript";
62
50
 
63
51
  import { getBlockNoteExtensions } from "./BlockNoteExtensions";
64
52
  import { TextCursorPosition } from "./cursorPositionTypes";
@@ -67,77 +55,39 @@ import { Selection } from "./selectionTypes";
67
55
  import { transformPasted } from "./transformPasted";
68
56
 
69
57
  // CSS
58
+ import { checkDefaultBlockTypeInSchema } from "../blocks/defaultBlockTypeGuards";
70
59
  import "./Block.css";
60
+ import { BlockNoteSchema } from "./BlockNoteSchema";
61
+ import {
62
+ BlockNoteTipTapEditor,
63
+ BlockNoteTipTapEditorOptions,
64
+ } from "./BlockNoteTipTapEditor";
71
65
  import "./editor.css";
72
66
 
73
67
  export type BlockNoteEditorOptions<
74
- BSpecs extends BlockSpecs,
75
- ISpecs extends InlineContentSpecs,
76
- SSpecs extends StyleSpecs
68
+ BSchema extends BlockSchema,
69
+ ISchema extends InlineContentSchema,
70
+ SSchema extends StyleSchema
77
71
  > = {
78
72
  // TODO: Figure out if enableBlockNoteExtensions/disableHistoryExtension are needed and document them.
79
73
  enableBlockNoteExtensions: boolean;
80
- /**
81
- *
82
- * (couldn't fix any type, see https://github.com/TypeCellOS/BlockNote/pull/191#discussion_r1210708771)
83
- *
84
- * @default defaultSlashMenuItems from `./extensions/SlashMenu`
85
- */
86
- slashMenuItems: BaseSlashMenuItem<any, any, any>[];
87
74
 
88
- /**
89
- * The HTML element that should be used as the parent element for the editor.
90
- *
91
- * @default: undefined, the editor is not attached to the DOM
92
- */
93
- parentElement: HTMLElement;
75
+ placeholders: Record<string | "default", string>;
76
+
94
77
  /**
95
78
  * An object containing attributes that should be added to HTML elements of the editor.
96
79
  *
97
80
  * @example { editor: { class: "my-editor-class" } }
98
81
  */
99
82
  domAttributes: Partial<BlockNoteDOMAttributes>;
100
- /**
101
- * A callback function that runs when the editor is ready to be used.
102
- */
103
- onEditorReady: (
104
- editor: BlockNoteEditor<
105
- BlockSchemaFromSpecs<BSpecs>,
106
- InlineContentSchemaFromSpecs<ISpecs>,
107
- StyleSchemaFromSpecs<SSpecs>
108
- >
109
- ) => void;
110
- /**
111
- * A callback function that runs whenever the editor's contents change.
112
- */
113
- onEditorContentChange: (
114
- editor: BlockNoteEditor<
115
- BlockSchemaFromSpecs<BSpecs>,
116
- InlineContentSchemaFromSpecs<ISpecs>,
117
- StyleSchemaFromSpecs<SSpecs>
118
- >
119
- ) => void;
120
- /**
121
- * A callback function that runs whenever the text cursor position changes.
122
- */
123
- onTextCursorPositionChange: (
124
- editor: BlockNoteEditor<
125
- BlockSchemaFromSpecs<BSpecs>,
126
- InlineContentSchemaFromSpecs<ISpecs>,
127
- StyleSchemaFromSpecs<SSpecs>
128
- >
129
- ) => void;
130
- /**
131
- * Locks the editor from being editable by the user if set to `false`.
132
- */
133
- editable: boolean;
83
+
134
84
  /**
135
85
  * The content that should be in the editor when it's created, represented as an array of partial block objects.
136
86
  */
137
87
  initialContent: PartialBlock<
138
- BlockSchemaFromSpecs<BSpecs>,
139
- InlineContentSchemaFromSpecs<ISpecs>,
140
- StyleSchemaFromSpecs<SSpecs>
88
+ NoInfer<BSchema>,
89
+ NoInfer<ISchema>,
90
+ NoInfer<SSchema>
141
91
  >[];
142
92
  /**
143
93
  * Use default BlockNote font and reset the styles of <p> <li> <h1> elements etc., that are used in BlockNote.
@@ -146,14 +96,7 @@ export type BlockNoteEditorOptions<
146
96
  */
147
97
  defaultStyles: boolean;
148
98
 
149
- /**
150
- * A list of block types that should be available in the editor.
151
- */
152
- blockSpecs: BSpecs;
153
-
154
- styleSpecs: SSpecs;
155
-
156
- inlineContentSpecs: ISpecs;
99
+ schema: BlockNoteSchema<BSchema, ISchema, SSchema>;
157
100
 
158
101
  /**
159
102
  * A custom function to handle file uploads.
@@ -202,110 +145,112 @@ export class BlockNoteEditor<
202
145
  ISchema extends InlineContentSchema = DefaultInlineContentSchema,
203
146
  SSchema extends StyleSchema = DefaultStyleSchema
204
147
  > {
205
- public readonly _tiptapEditor: TiptapEditor & { contentComponent: any };
148
+ public readonly _tiptapEditor: BlockNoteTipTapEditor & {
149
+ contentComponent: any;
150
+ };
206
151
  public blockCache = new WeakMap<Node, Block<any, any, any>>();
207
- public readonly blockSchema: BSchema;
208
- public readonly inlineContentSchema: ISchema;
209
- public readonly styleSchema: SSchema;
152
+ public readonly schema: BlockNoteSchema<BSchema, ISchema, SSchema>;
210
153
 
211
154
  public readonly blockImplementations: BlockSpecs;
212
155
  public readonly inlineContentImplementations: InlineContentSpecs;
213
156
  public readonly styleImplementations: StyleSpecs;
214
157
 
215
- public ready = false;
216
-
217
- public readonly sideMenu: SideMenuProsemirrorPlugin<
158
+ public readonly formattingToolbar: FormattingToolbarProsemirrorPlugin;
159
+ public readonly hyperlinkToolbar: HyperlinkToolbarProsemirrorPlugin<
218
160
  BSchema,
219
161
  ISchema,
220
162
  SSchema
221
163
  >;
222
- public readonly formattingToolbar: FormattingToolbarProsemirrorPlugin;
223
- public readonly slashMenu: SlashMenuProsemirrorPlugin<
164
+ public readonly sideMenu: SideMenuProsemirrorPlugin<
224
165
  BSchema,
225
166
  ISchema,
226
- SSchema,
227
- any
167
+ SSchema
228
168
  >;
229
- public readonly hyperlinkToolbar: HyperlinkToolbarProsemirrorPlugin<
169
+ public readonly suggestionMenus: SuggestionMenuProseMirrorPlugin<
230
170
  BSchema,
231
171
  ISchema,
232
172
  SSchema
233
173
  >;
234
- public readonly imageToolbar: ImageToolbarProsemirrorPlugin<
235
- BSchema,
174
+ public readonly imageToolbar?: ImageToolbarProsemirrorPlugin<
175
+ ISchema,
176
+ SSchema
177
+ >;
178
+ public readonly tableHandles?: TableHandlesProsemirrorPlugin<
236
179
  ISchema,
237
180
  SSchema
238
181
  >;
239
- public readonly tableHandles:
240
- | TableHandlesProsemirrorPlugin<
241
- BSchema extends BlockSchemaWithBlock<
242
- "table",
243
- DefaultBlockSchema["table"]
244
- >
245
- ? BSchema
246
- : any,
247
- ISchema,
248
- SSchema
249
- >
250
- | undefined;
251
182
 
252
183
  public readonly uploadFile: ((file: File) => Promise<string>) | undefined;
253
184
 
254
185
  public static create<
255
- BSpecs extends BlockSpecs = typeof defaultBlockSpecs,
256
- ISpecs extends InlineContentSpecs = typeof defaultInlineContentSpecs,
257
- SSpecs extends StyleSpecs = typeof defaultStyleSpecs
258
- >(options: Partial<BlockNoteEditorOptions<BSpecs, ISpecs, SSpecs>> = {}) {
259
- return new BlockNoteEditor(options) as BlockNoteEditor<
260
- BlockSchemaFromSpecs<BSpecs>,
261
- InlineContentSchemaFromSpecs<ISpecs>,
262
- StyleSchemaFromSpecs<SSpecs>
263
- >;
186
+ BSchema extends BlockSchema = DefaultBlockSchema,
187
+ ISchema extends InlineContentSchema = DefaultInlineContentSchema,
188
+ SSchema extends StyleSchema = DefaultStyleSchema
189
+ >(options: Partial<BlockNoteEditorOptions<BSchema, ISchema, SSchema>> = {}) {
190
+ return new BlockNoteEditor<BSchema, ISchema, SSchema>(options);
264
191
  }
265
192
 
266
193
  private constructor(
267
194
  private readonly options: Partial<BlockNoteEditorOptions<any, any, any>>
268
195
  ) {
196
+ const anyOpts = options as any;
197
+ if (anyOpts.onEditorContentChange) {
198
+ throw new Error(
199
+ "onEditorContentChange initialization option is deprecated, use <BlockNoteView onChange={...} />, the useEditorChange(...) hook, or editor.onChange(...)"
200
+ );
201
+ }
202
+
203
+ if (anyOpts.onTextCursorPositionChange) {
204
+ throw new Error(
205
+ "onTextCursorPositionChange initialization option is deprecated, use <BlockNoteView onSelectionChange={...} />, the useEditorSelectionChange(...) hook, or editor.onSelectionChange(...)"
206
+ );
207
+ }
208
+
209
+ if (anyOpts.onEditorReady) {
210
+ throw new Error(
211
+ "onEditorReady is deprecated. Editor is immediately ready for use after creation."
212
+ );
213
+ }
214
+
215
+ if (anyOpts.editable) {
216
+ throw new Error(
217
+ "editable initialization option is deprecated, use <BlockNoteView editable={true/false} />, or alternatively editor.isEditable = true/false"
218
+ );
219
+ }
220
+
269
221
  // apply defaults
270
222
  const newOptions = {
271
223
  defaultStyles: true,
272
- blockSpecs: options.blockSpecs || defaultBlockSpecs,
273
- styleSpecs: options.styleSpecs || defaultStyleSpecs,
274
- inlineContentSpecs:
275
- options.inlineContentSpecs || defaultInlineContentSpecs,
224
+ schema: options.schema || BlockNoteSchema.create(),
276
225
  ...options,
277
226
  };
278
227
 
279
- this.blockSchema = getBlockSchemaFromSpecs(newOptions.blockSpecs);
280
- this.inlineContentSchema = getInlineContentSchemaFromSpecs(
281
- newOptions.inlineContentSpecs
282
- );
283
- this.styleSchema = getStyleSchemaFromSpecs(newOptions.styleSpecs);
284
- this.blockImplementations = newOptions.blockSpecs;
285
- this.inlineContentImplementations = newOptions.inlineContentSpecs;
286
- this.styleImplementations = newOptions.styleSpecs;
228
+ // @ts-ignore
229
+ this.schema = newOptions.schema;
230
+ this.blockImplementations = newOptions.schema.blockSpecs;
231
+ this.inlineContentImplementations = newOptions.schema.inlineContentSpecs;
232
+ this.styleImplementations = newOptions.schema.styleSpecs;
287
233
 
288
- this.sideMenu = new SideMenuProsemirrorPlugin(this);
289
234
  this.formattingToolbar = new FormattingToolbarProsemirrorPlugin(this);
290
- this.slashMenu = new SlashMenuProsemirrorPlugin(
291
- this,
292
- newOptions.slashMenuItems ||
293
- (getDefaultSlashMenuItems(this.blockSchema) as any)
294
- );
295
235
  this.hyperlinkToolbar = new HyperlinkToolbarProsemirrorPlugin(this);
296
- this.imageToolbar = new ImageToolbarProsemirrorPlugin(this);
297
-
298
- if (this.blockSchema.table === defaultBlockSchema.table) {
236
+ this.sideMenu = new SideMenuProsemirrorPlugin(this);
237
+ this.suggestionMenus = new SuggestionMenuProseMirrorPlugin(this);
238
+ if (checkDefaultBlockTypeInSchema("image", this)) {
239
+ // Type guards only work on `const`s? Not working for `this`
240
+ this.imageToolbar = new ImageToolbarProsemirrorPlugin(this as any);
241
+ }
242
+ if (checkDefaultBlockTypeInSchema("table", this)) {
299
243
  this.tableHandles = new TableHandlesProsemirrorPlugin(this as any);
300
244
  }
301
245
 
302
246
  const extensions = getBlockNoteExtensions({
303
247
  editor: this,
248
+ placeholders: newOptions.placeholders,
304
249
  domAttributes: newOptions.domAttributes || {},
305
- blockSchema: this.blockSchema,
306
- blockSpecs: newOptions.blockSpecs,
307
- styleSpecs: newOptions.styleSpecs,
308
- inlineContentSpecs: newOptions.inlineContentSpecs,
250
+ blockSchema: this.schema.blockSchema,
251
+ blockSpecs: this.schema.blockSpecs,
252
+ styleSpecs: this.schema.styleSpecs,
253
+ inlineContentSpecs: this.schema.inlineContentSpecs,
309
254
  collaboration: newOptions.collaboration,
310
255
  });
311
256
 
@@ -314,11 +259,11 @@ export class BlockNoteEditor<
314
259
 
315
260
  addProseMirrorPlugins: () => {
316
261
  return [
317
- this.sideMenu.plugin,
318
262
  this.formattingToolbar.plugin,
319
- this.slashMenu.plugin,
320
263
  this.hyperlinkToolbar.plugin,
321
- this.imageToolbar.plugin,
264
+ this.sideMenu.plugin,
265
+ this.suggestionMenus.plugin,
266
+ ...(this.imageToolbar ? [this.imageToolbar.plugin] : []),
322
267
  ...(this.tableHandles ? [this.tableHandles.plugin] : []),
323
268
  ];
324
269
  },
@@ -336,95 +281,30 @@ export class BlockNoteEditor<
336
281
  const initialContent =
337
282
  newOptions.initialContent ||
338
283
  (options.collaboration
339
- ? undefined
284
+ ? [
285
+ {
286
+ type: "paragraph",
287
+ id: "initialBlockId",
288
+ },
289
+ ]
340
290
  : [
341
291
  {
342
292
  type: "paragraph",
343
293
  id: UniqueID.options.generateID(),
344
294
  },
345
295
  ]);
346
- const styleSchema = this.styleSchema;
347
296
 
348
- const tiptapOptions: Partial<EditorOptions> = {
297
+ if (!Array.isArray(initialContent) || initialContent.length === 0) {
298
+ throw new Error(
299
+ "initialContent must be a non-empty array of blocks, received: " +
300
+ initialContent
301
+ );
302
+ }
303
+
304
+ const tiptapOptions: BlockNoteTipTapEditorOptions = {
349
305
  ...blockNoteTipTapOptions,
350
306
  ...newOptions._tiptapOptions,
351
- onBeforeCreate(editor) {
352
- newOptions._tiptapOptions?.onBeforeCreate?.(editor);
353
- // We always set the initial content to a single paragraph block. This
354
- // allows us to easily replace it with the actual initial content once
355
- // the TipTap editor is initialized.
356
- const schema = editor.editor.schema;
357
-
358
- // This is a hack to make "initial content detection" by y-prosemirror (and also tiptap isEmpty)
359
- // properly detect whether or not the document has changed.
360
- // We change the doc.createAndFill function to make sure the initial block id is set, instead of null
361
- let cache: any;
362
- const oldCreateAndFill = schema.nodes.doc.createAndFill;
363
- (schema.nodes.doc as any).createAndFill = (...args: any) => {
364
- if (cache) {
365
- return cache;
366
- }
367
- const ret = oldCreateAndFill.apply(schema.nodes.doc, args);
368
-
369
- // create a copy that we can mutate (otherwise, assigning attrs is not safe and corrupts the pm state)
370
- const jsonNode = JSON.parse(JSON.stringify(ret!.toJSON()));
371
- jsonNode.content[0].content[0].attrs.id = "initialBlockId";
372
-
373
- cache = Node.fromJSON(schema, jsonNode);
374
- return ret;
375
- };
376
-
377
- const root = schema.node(
378
- "doc",
379
- undefined,
380
- schema.node("blockGroup", undefined, [
381
- blockToNode(
382
- { id: "initialBlockId", type: "paragraph" },
383
- schema,
384
- styleSchema
385
- ),
386
- ])
387
- );
388
- editor.editor.options.content = root.toJSON();
389
- },
390
- onCreate: (editor) => {
391
- newOptions._tiptapOptions?.onCreate?.(editor);
392
- // We need to wait for the TipTap editor to init before we can set the
393
- // initial content, as the schema may contain custom blocks which need
394
- // it to render.
395
- if (initialContent !== undefined) {
396
- this.replaceBlocks(this.topLevelBlocks, initialContent as any);
397
- }
398
-
399
- newOptions.onEditorReady?.(this);
400
- this.ready = true;
401
- },
402
- onUpdate: (editor) => {
403
- newOptions._tiptapOptions?.onUpdate?.(editor);
404
- // This seems to be necessary due to a bug in TipTap:
405
- // https://github.com/ueberdosis/tiptap/issues/2583
406
- if (!this.ready) {
407
- return;
408
- }
409
-
410
- newOptions.onEditorContentChange?.(this);
411
- },
412
- onSelectionUpdate: (editor) => {
413
- newOptions._tiptapOptions?.onSelectionUpdate?.(editor);
414
- // This seems to be necessary due to a bug in TipTap:
415
- // https://github.com/ueberdosis/tiptap/issues/2583
416
- if (!this.ready) {
417
- return;
418
- }
419
-
420
- newOptions.onTextCursorPositionChange?.(this);
421
- },
422
- editable:
423
- options.editable !== undefined
424
- ? options.editable
425
- : newOptions._tiptapOptions?.editable !== undefined
426
- ? newOptions._tiptapOptions?.editable
427
- : true,
307
+ content: initialContent,
428
308
  extensions:
429
309
  newOptions.enableBlockNoteExtensions === false
430
310
  ? newOptions._tiptapOptions?.extensions || []
@@ -435,7 +315,6 @@ export class BlockNoteEditor<
435
315
  ...newOptions._tiptapOptions?.editorProps?.attributes,
436
316
  ...newOptions.domAttributes?.editor,
437
317
  class: mergeCSSClasses(
438
- "bn-root",
439
318
  "bn-editor",
440
319
  newOptions.defaultStyles ? "bn-default-styles" : "",
441
320
  newOptions.domAttributes?.editor?.class || ""
@@ -445,15 +324,23 @@ export class BlockNoteEditor<
445
324
  },
446
325
  };
447
326
 
448
- if (newOptions.parentElement) {
449
- tiptapOptions.element = newOptions.parentElement;
450
- }
451
-
452
- this._tiptapEditor = new Editor(tiptapOptions) as Editor & {
327
+ this._tiptapEditor = new BlockNoteTipTapEditor(
328
+ tiptapOptions,
329
+ this.schema.styleSchema
330
+ ) as BlockNoteTipTapEditor & {
453
331
  contentComponent: any;
454
332
  };
455
333
  }
456
334
 
335
+ /**
336
+ * Mount the editor to a parent DOM element. Call mount(undefined) to clean up
337
+ *
338
+ * @warning Not needed for React, use BlockNoteView to take care of this
339
+ */
340
+ public mount(parentElement?: HTMLElement | null) {
341
+ this._tiptapEditor.mount(parentElement);
342
+ }
343
+
457
344
  public get prosemirrorView() {
458
345
  return this._tiptapEditor.view;
459
346
  }
@@ -470,20 +357,27 @@ export class BlockNoteEditor<
470
357
  this._tiptapEditor.view.focus();
471
358
  }
472
359
 
360
+ /**
361
+ * @deprecated, use `editor.document` instead
362
+ */
363
+ public get topLevelBlocks(): Block<BSchema, ISchema, SSchema>[] {
364
+ return this.topLevelBlocks;
365
+ }
366
+
473
367
  /**
474
368
  * Gets a snapshot of all top-level (non-nested) blocks in the editor.
475
369
  * @returns A snapshot of all top-level (non-nested) blocks in the editor.
476
370
  */
477
- public get topLevelBlocks(): Block<BSchema, ISchema, SSchema>[] {
371
+ public get document(): Block<BSchema, ISchema, SSchema>[] {
478
372
  const blocks: Block<BSchema, ISchema, SSchema>[] = [];
479
373
 
480
374
  this._tiptapEditor.state.doc.firstChild!.descendants((node) => {
481
375
  blocks.push(
482
376
  nodeToBlock(
483
377
  node,
484
- this.blockSchema,
485
- this.inlineContentSchema,
486
- this.styleSchema,
378
+ this.schema.blockSchema,
379
+ this.schema.inlineContentSchema,
380
+ this.schema.styleSchema,
487
381
  this.blockCache
488
382
  )
489
383
  );
@@ -519,9 +413,9 @@ export class BlockNoteEditor<
519
413
 
520
414
  newBlock = nodeToBlock(
521
415
  node,
522
- this.blockSchema,
523
- this.inlineContentSchema,
524
- this.styleSchema,
416
+ this.schema.blockSchema,
417
+ this.schema.inlineContentSchema,
418
+ this.schema.styleSchema,
525
419
  this.blockCache
526
420
  );
527
421
 
@@ -540,7 +434,7 @@ export class BlockNoteEditor<
540
434
  callback: (block: Block<BSchema, ISchema, SSchema>) => boolean,
541
435
  reverse = false
542
436
  ): void {
543
- const blocks = this.topLevelBlocks.slice();
437
+ const blocks = this.document.slice();
544
438
 
545
439
  if (reverse) {
546
440
  blocks.reverse();
@@ -623,9 +517,9 @@ export class BlockNoteEditor<
623
517
  return {
624
518
  block: nodeToBlock(
625
519
  node,
626
- this.blockSchema,
627
- this.inlineContentSchema,
628
- this.styleSchema,
520
+ this.schema.blockSchema,
521
+ this.schema.inlineContentSchema,
522
+ this.schema.styleSchema,
629
523
  this.blockCache
630
524
  ),
631
525
  prevBlock:
@@ -633,9 +527,9 @@ export class BlockNoteEditor<
633
527
  ? undefined
634
528
  : nodeToBlock(
635
529
  prevNode,
636
- this.blockSchema,
637
- this.inlineContentSchema,
638
- this.styleSchema,
530
+ this.schema.blockSchema,
531
+ this.schema.inlineContentSchema,
532
+ this.schema.styleSchema,
639
533
  this.blockCache
640
534
  ),
641
535
  nextBlock:
@@ -643,9 +537,9 @@ export class BlockNoteEditor<
643
537
  ? undefined
644
538
  : nodeToBlock(
645
539
  nextNode,
646
- this.blockSchema,
647
- this.inlineContentSchema,
648
- this.styleSchema,
540
+ this.schema.blockSchema,
541
+ this.schema.inlineContentSchema,
542
+ this.schema.styleSchema,
649
543
  this.blockCache
650
544
  ),
651
545
  };
@@ -670,7 +564,7 @@ export class BlockNoteEditor<
670
564
  )!;
671
565
 
672
566
  const contentType: "none" | "inline" | "table" =
673
- this.blockSchema[contentNode.type.name]!.content;
567
+ this.schema.blockSchema[contentNode.type.name]!.content;
674
568
 
675
569
  if (contentType === "none") {
676
570
  this._tiptapEditor.commands.setNodeSelection(startPos);
@@ -734,9 +628,9 @@ export class BlockNoteEditor<
734
628
  blocks.push(
735
629
  nodeToBlock(
736
630
  this._tiptapEditor.state.doc.resolve(pos).node(),
737
- this.blockSchema,
738
- this.inlineContentSchema,
739
- this.styleSchema,
631
+ this.schema.blockSchema,
632
+ this.schema.inlineContentSchema,
633
+ this.schema.styleSchema,
740
634
  this.blockCache
741
635
  )
742
636
  );
@@ -815,6 +709,28 @@ export class BlockNoteEditor<
815
709
  return replaceBlocks(blocksToRemove, blocksToInsert, this);
816
710
  }
817
711
 
712
+ /**
713
+ * Insert a piece of content at the current cursor position.
714
+ *
715
+ * @param content can be a string, or array of partial inline content elements
716
+ */
717
+ public insertInlineContent(content: PartialInlineContent<ISchema, SSchema>) {
718
+ const nodes = inlineContentToNodes(
719
+ content,
720
+ this._tiptapEditor.schema,
721
+ this.schema.styleSchema
722
+ );
723
+
724
+ insertContentAt(
725
+ {
726
+ from: this._tiptapEditor.state.selection.from,
727
+ to: this._tiptapEditor.state.selection.to,
728
+ },
729
+ nodes,
730
+ this
731
+ );
732
+ }
733
+
818
734
  /**
819
735
  * Gets the active text styles at the text cursor position or at the end of the current selection if it's active.
820
736
  */
@@ -823,7 +739,7 @@ export class BlockNoteEditor<
823
739
  const marks = this._tiptapEditor.state.selection.$to.marks();
824
740
 
825
741
  for (const mark of marks) {
826
- const config = this.styleSchema[mark.type.name];
742
+ const config = this.schema.styleSchema[mark.type.name];
827
743
  if (!config) {
828
744
  console.warn("mark not found in styleschema", mark.type.name);
829
745
  continue;
@@ -846,7 +762,7 @@ export class BlockNoteEditor<
846
762
  this._tiptapEditor.view.focus();
847
763
 
848
764
  for (const [style, value] of Object.entries(styles)) {
849
- const config = this.styleSchema[style];
765
+ const config = this.schema.styleSchema[style];
850
766
  if (!config) {
851
767
  throw new Error(`style ${style} not found in styleSchema`);
852
768
  }
@@ -880,7 +796,7 @@ export class BlockNoteEditor<
880
796
  this._tiptapEditor.view.focus();
881
797
 
882
798
  for (const [style, value] of Object.entries(styles)) {
883
- const config = this.styleSchema[style];
799
+ const config = this.schema.styleSchema[style];
884
800
  if (!config) {
885
801
  throw new Error(`style ${style} not found in styleSchema`);
886
802
  }
@@ -982,7 +898,7 @@ export class BlockNoteEditor<
982
898
  * @returns The blocks, serialized as an HTML string.
983
899
  */
984
900
  public async blocksToHTMLLossy(
985
- blocks = this.topLevelBlocks
901
+ blocks: Block<BSchema, ISchema, SSchema>[] = this.document
986
902
  ): Promise<string> {
987
903
  const exporter = createExternalHTMLExporter(
988
904
  this._tiptapEditor.schema,
@@ -1003,9 +919,9 @@ export class BlockNoteEditor<
1003
919
  ): Promise<Block<BSchema, ISchema, SSchema>[]> {
1004
920
  return HTMLToBlocks(
1005
921
  html,
1006
- this.blockSchema,
1007
- this.inlineContentSchema,
1008
- this.styleSchema,
922
+ this.schema.blockSchema,
923
+ this.schema.inlineContentSchema,
924
+ this.schema.styleSchema,
1009
925
  this._tiptapEditor.schema
1010
926
  );
1011
927
  }
@@ -1017,7 +933,7 @@ export class BlockNoteEditor<
1017
933
  * @returns The blocks, serialized as a Markdown string.
1018
934
  */
1019
935
  public async blocksToMarkdownLossy(
1020
- blocks: Block<BSchema, ISchema, SSchema>[] = this.topLevelBlocks
936
+ blocks: Block<BSchema, ISchema, SSchema>[] = this.document
1021
937
  ): Promise<string> {
1022
938
  return blocksToMarkdown(blocks, this._tiptapEditor.schema, this);
1023
939
  }
@@ -1034,9 +950,9 @@ export class BlockNoteEditor<
1034
950
  ): Promise<Block<BSchema, ISchema, SSchema>[]> {
1035
951
  return markdownToBlocks(
1036
952
  markdown,
1037
- this.blockSchema,
1038
- this.inlineContentSchema,
1039
- this.styleSchema,
953
+ this.schema.blockSchema,
954
+ this.schema.inlineContentSchema,
955
+ this.schema.styleSchema,
1040
956
  this._tiptapEditor.schema
1041
957
  );
1042
958
  }
@@ -1052,4 +968,44 @@ export class BlockNoteEditor<
1052
968
  }
1053
969
  this._tiptapEditor.commands.updateUser(user);
1054
970
  }
971
+
972
+ /**
973
+ * A callback function that runs whenever the editor's contents change.
974
+ *
975
+ * @param callback The callback to execute.
976
+ * @returns A function to remove the callback.
977
+ */
978
+ public onChange(
979
+ callback: (editor: BlockNoteEditor<BSchema, ISchema, SSchema>) => void
980
+ ) {
981
+ const cb = () => {
982
+ callback(this);
983
+ };
984
+
985
+ this._tiptapEditor.on("update", cb);
986
+
987
+ return () => {
988
+ this._tiptapEditor.off("update", cb);
989
+ };
990
+ }
991
+
992
+ /**
993
+ * A callback function that runs whenever the text cursor position or selection changes.
994
+ *
995
+ * @param callback The callback to execute.
996
+ * @returns A function to remove the callback.
997
+ */
998
+ public onSelectionChange(
999
+ callback: (editor: BlockNoteEditor<BSchema, ISchema, SSchema>) => void
1000
+ ) {
1001
+ const cb = () => {
1002
+ callback(this);
1003
+ };
1004
+
1005
+ this._tiptapEditor.on("selectionUpdate", cb);
1006
+
1007
+ return () => {
1008
+ this._tiptapEditor.off("selectionUpdate", cb);
1009
+ };
1010
+ }
1055
1011
  }