@blocknote/core 0.7.0 → 0.8.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 (70) hide show
  1. package/dist/blocknote.js +1428 -1252
  2. package/dist/blocknote.js.map +1 -1
  3. package/dist/blocknote.umd.cjs +2 -2
  4. package/dist/blocknote.umd.cjs.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +4 -3
  7. package/src/BlockNoteEditor.ts +100 -53
  8. package/src/BlockNoteExtensions.ts +24 -14
  9. package/src/api/blockManipulation/blockManipulation.test.ts +6 -3
  10. package/src/api/blockManipulation/blockManipulation.ts +7 -6
  11. package/src/api/formatConversions/formatConversions.test.ts +13 -8
  12. package/src/api/formatConversions/formatConversions.ts +15 -12
  13. package/src/api/nodeConversions/nodeConversions.test.ts +29 -10
  14. package/src/api/nodeConversions/nodeConversions.ts +33 -12
  15. package/src/api/nodeConversions/testUtil.ts +8 -4
  16. package/src/editor.module.css +0 -1
  17. package/src/extensions/Blocks/api/block.ts +229 -0
  18. package/src/extensions/Blocks/api/blockTypes.ts +158 -71
  19. package/src/extensions/Blocks/api/cursorPositionTypes.ts +5 -5
  20. package/src/extensions/Blocks/api/defaultBlocks.ts +44 -0
  21. package/src/extensions/Blocks/api/selectionTypes.ts +3 -3
  22. package/src/extensions/Blocks/api/serialization.ts +29 -0
  23. package/src/extensions/Blocks/index.ts +0 -8
  24. package/src/extensions/Blocks/nodes/Block.module.css +24 -12
  25. package/src/extensions/Blocks/nodes/BlockContainer.ts +8 -4
  26. package/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.ts +4 -4
  27. package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +5 -5
  28. package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +100 -97
  29. package/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.ts +4 -4
  30. package/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.ts +11 -9
  31. package/src/extensions/DraggableBlocks/DraggableBlocksExtension.ts +6 -5
  32. package/src/extensions/DraggableBlocks/DraggableBlocksPlugin.ts +12 -11
  33. package/src/extensions/FormattingToolbar/FormattingToolbarExtension.ts +21 -16
  34. package/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.ts +9 -5
  35. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +30 -42
  36. package/src/extensions/Placeholder/PlaceholderExtension.ts +1 -0
  37. package/src/extensions/SlashMenu/BaseSlashMenuItem.ts +5 -2
  38. package/src/extensions/SlashMenu/SlashMenuExtension.ts +37 -33
  39. package/src/extensions/SlashMenu/defaultSlashMenuItems.tsx +14 -10
  40. package/src/extensions/SlashMenu/index.ts +2 -2
  41. package/src/index.ts +4 -0
  42. package/src/shared/plugins/suggestion/SuggestionPlugin.ts +29 -13
  43. package/types/src/BlockNoteEditor.d.ts +37 -23
  44. package/types/src/BlockNoteExtensions.d.ts +15 -8
  45. package/types/src/api/blockManipulation/blockManipulation.d.ts +4 -4
  46. package/types/src/api/formatConversions/formatConversions.d.ts +5 -5
  47. package/types/src/api/nodeConversions/nodeConversions.d.ts +3 -3
  48. package/types/src/api/nodeConversions/testUtil.d.ts +2 -2
  49. package/types/src/extensions/Blocks/api/block.d.ts +6 -5
  50. package/types/src/extensions/Blocks/api/blockTypes.d.ts +77 -33
  51. package/types/src/extensions/Blocks/api/cursorPositionTypes.d.ts +5 -5
  52. package/types/src/extensions/Blocks/api/selectionTypes.d.ts +3 -3
  53. package/types/src/extensions/Blocks/api/serialization.d.ts +2 -0
  54. package/types/src/extensions/Blocks/nodes/BlockContainer.d.ts +3 -3
  55. package/types/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.d.ts +1 -2
  56. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.d.ts +1 -2
  57. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.d.ts +1 -2
  58. package/types/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContent.d.ts +1 -2
  59. package/types/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.d.ts +7 -7
  60. package/types/src/extensions/DraggableBlocks/DraggableBlocksExtension.d.ts +5 -4
  61. package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +11 -10
  62. package/types/src/extensions/FormattingToolbar/FormattingToolbarExtension.d.ts +6 -5
  63. package/types/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.d.ts +4 -3
  64. package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +16 -19
  65. package/types/src/extensions/SlashMenu/BaseSlashMenuItem.d.ts +4 -3
  66. package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +5 -4
  67. package/types/src/extensions/SlashMenu/defaultSlashMenuItems.d.ts +66 -1
  68. package/types/src/extensions/SlashMenu/index.d.ts +2 -2
  69. package/types/src/index.d.ts +4 -0
  70. package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +5 -4
@@ -7,13 +7,14 @@ import remarkParse from "remark-parse";
7
7
  import remarkRehype from "remark-rehype";
8
8
  import remarkStringify from "remark-stringify";
9
9
  import { unified } from "unified";
10
- import { Block } from "../../extensions/Blocks/api/blockTypes";
10
+ import { Block, BlockSchema } from "../../extensions/Blocks/api/blockTypes";
11
+
11
12
  import { blockToNode, nodeToBlock } from "../nodeConversions/nodeConversions";
12
13
  import { removeUnderlines } from "./removeUnderlinesRehypePlugin";
13
14
  import { simplifyBlocks } from "./simplifyBlocksRehypePlugin";
14
15
 
15
- export async function blocksToHTML(
16
- blocks: Block[],
16
+ export async function blocksToHTML<BSchema extends BlockSchema>(
17
+ blocks: Block<BSchema>[],
17
18
  schema: Schema
18
19
  ): Promise<string> {
19
20
  const htmlParentElement = document.createElement("div");
@@ -37,27 +38,28 @@ export async function blocksToHTML(
37
38
  return htmlString.value as string;
38
39
  }
39
40
 
40
- export async function HTMLToBlocks(
41
+ export async function HTMLToBlocks<BSchema extends BlockSchema>(
41
42
  html: string,
43
+ blockSchema: BSchema,
42
44
  schema: Schema
43
- ): Promise<Block[]> {
45
+ ): Promise<Block<BSchema>[]> {
44
46
  const htmlNode = document.createElement("div");
45
47
  htmlNode.innerHTML = html.trim();
46
48
 
47
49
  const parser = DOMParser.fromSchema(schema);
48
50
  const parentNode = parser.parse(htmlNode);
49
51
 
50
- const blocks: Block[] = [];
52
+ const blocks: Block<BSchema>[] = [];
51
53
 
52
54
  for (let i = 0; i < parentNode.firstChild!.childCount; i++) {
53
- blocks.push(nodeToBlock(parentNode.firstChild!.child(i)));
55
+ blocks.push(nodeToBlock(parentNode.firstChild!.child(i), blockSchema));
54
56
  }
55
57
 
56
58
  return blocks;
57
59
  }
58
60
 
59
- export async function blocksToMarkdown(
60
- blocks: Block[],
61
+ export async function blocksToMarkdown<BSchema extends BlockSchema>(
62
+ blocks: Block<BSchema>[],
61
63
  schema: Schema
62
64
  ): Promise<string> {
63
65
  const markdownString = await unified()
@@ -71,10 +73,11 @@ export async function blocksToMarkdown(
71
73
  return markdownString.value as string;
72
74
  }
73
75
 
74
- export async function markdownToBlocks(
76
+ export async function markdownToBlocks<BSchema extends BlockSchema>(
75
77
  markdown: string,
78
+ blockSchema: BSchema,
76
79
  schema: Schema
77
- ): Promise<Block[]> {
80
+ ): Promise<Block<BSchema>[]> {
78
81
  const htmlString = await unified()
79
82
  .use(remarkParse)
80
83
  .use(remarkGfm)
@@ -82,5 +85,5 @@ export async function markdownToBlocks(
82
85
  .use(rehypeStringify)
83
86
  .process(markdown);
84
87
 
85
- return HTMLToBlocks(htmlString.value as string, schema);
88
+ return HTMLToBlocks(htmlString.value as string, blockSchema, schema);
86
89
  }
@@ -5,14 +5,18 @@ import { BlockNoteEditor, PartialBlock } from "../..";
5
5
  import UniqueID from "../../extensions/UniqueID/UniqueID";
6
6
  import { blockToNode, nodeToBlock } from "./nodeConversions";
7
7
  import { partialBlockToBlockForTesting } from "./testUtil";
8
+ import {
9
+ defaultBlockSchema,
10
+ DefaultBlockSchema,
11
+ } from "../../extensions/Blocks/api/defaultBlocks";
8
12
 
9
13
  let editor: BlockNoteEditor;
10
14
  let tt: Editor;
11
15
 
12
- let simpleBlock: PartialBlock;
16
+ let simpleBlock: PartialBlock<DefaultBlockSchema>;
13
17
  let simpleNode: Node;
14
18
 
15
- let complexBlock: PartialBlock;
19
+ let complexBlock: PartialBlock<DefaultBlockSchema>;
16
20
  let complexNode: Node;
17
21
 
18
22
  beforeEach(() => {
@@ -119,7 +123,10 @@ describe("Simple ProseMirror Node Conversions", () => {
119
123
  });
120
124
 
121
125
  it("Convert simple node to block", async () => {
122
- const firstBlockConversion = nodeToBlock(simpleNode);
126
+ const firstBlockConversion = nodeToBlock<DefaultBlockSchema>(
127
+ simpleNode,
128
+ defaultBlockSchema
129
+ );
123
130
 
124
131
  expect(firstBlockConversion).toMatchSnapshot();
125
132
 
@@ -137,7 +144,10 @@ describe("Complex ProseMirror Node Conversions", () => {
137
144
  });
138
145
 
139
146
  it("Convert complex node to block", async () => {
140
- const firstBlockConversion = nodeToBlock(complexNode);
147
+ const firstBlockConversion = nodeToBlock<DefaultBlockSchema>(
148
+ complexNode,
149
+ defaultBlockSchema
150
+ );
141
151
 
142
152
  expect(firstBlockConversion).toMatchSnapshot();
143
153
 
@@ -149,7 +159,7 @@ describe("Complex ProseMirror Node Conversions", () => {
149
159
 
150
160
  describe("links", () => {
151
161
  it("Convert a block with link", async () => {
152
- const block: PartialBlock = {
162
+ const block: PartialBlock<DefaultBlockSchema> = {
153
163
  id: UniqueID.options.generateID(),
154
164
  type: "paragraph",
155
165
  content: [
@@ -162,7 +172,10 @@ describe("links", () => {
162
172
  };
163
173
  const node = blockToNode(block, tt.schema);
164
174
  expect(node).toMatchSnapshot();
165
- const outputBlock = nodeToBlock(node);
175
+ const outputBlock = nodeToBlock<DefaultBlockSchema>(
176
+ node,
177
+ defaultBlockSchema
178
+ );
166
179
 
167
180
  // Temporary fix to set props to {}, because at this point
168
181
  // we don't have an easy way to access default props at runtime,
@@ -174,7 +187,7 @@ describe("links", () => {
174
187
  });
175
188
 
176
189
  it("Convert link block with marks", async () => {
177
- const block: PartialBlock = {
190
+ const block: PartialBlock<DefaultBlockSchema> = {
178
191
  id: UniqueID.options.generateID(),
179
192
  type: "paragraph",
180
193
  content: [
@@ -200,7 +213,10 @@ describe("links", () => {
200
213
  };
201
214
  const node = blockToNode(block, tt.schema);
202
215
  // expect(node).toMatchSnapshot();
203
- const outputBlock = nodeToBlock(node);
216
+ const outputBlock = nodeToBlock<DefaultBlockSchema>(
217
+ node,
218
+ defaultBlockSchema
219
+ );
204
220
 
205
221
  // Temporary fix to set props to {}, because at this point
206
222
  // we don't have an easy way to access default props at runtime,
@@ -212,7 +228,7 @@ describe("links", () => {
212
228
  });
213
229
 
214
230
  it("Convert two adjacent links in a block", async () => {
215
- const block: PartialBlock = {
231
+ const block: PartialBlock<DefaultBlockSchema> = {
216
232
  id: UniqueID.options.generateID(),
217
233
  type: "paragraph",
218
234
  content: [
@@ -231,7 +247,10 @@ describe("links", () => {
231
247
 
232
248
  const node = blockToNode(block, tt.schema);
233
249
  expect(node).toMatchSnapshot();
234
- const outputBlock = nodeToBlock(node);
250
+ const outputBlock = nodeToBlock<DefaultBlockSchema>(
251
+ node,
252
+ defaultBlockSchema
253
+ );
235
254
 
236
255
  // Temporary fix to set props to {}, because at this point
237
256
  // we don't have an easy way to access default props at runtime,
@@ -2,9 +2,11 @@ import { Mark } from "@tiptap/pm/model";
2
2
  import { Node, Schema } from "prosemirror-model";
3
3
  import {
4
4
  Block,
5
- blockProps,
5
+ BlockSchema,
6
6
  PartialBlock,
7
7
  } from "../../extensions/Blocks/api/blockTypes";
8
+
9
+ import { defaultProps } from "../../extensions/Blocks/api/defaultBlocks";
8
10
  import {
9
11
  ColorStyle,
10
12
  InlineContent,
@@ -105,7 +107,10 @@ export function inlineContentToNodes(
105
107
  /**
106
108
  * Converts a BlockNote block to a TipTap node.
107
109
  */
108
- export function blockToNode(block: PartialBlock, schema: Schema) {
110
+ export function blockToNode<BSchema extends BlockSchema>(
111
+ block: PartialBlock<BSchema>,
112
+ schema: Schema
113
+ ) {
109
114
  let id = block.id;
110
115
 
111
116
  if (id === undefined) {
@@ -214,10 +219,11 @@ function contentNodeToInlineContent(contentNode: Node) {
214
219
  /**
215
220
  * Convert a TipTap node to a BlockNote block.
216
221
  */
217
- export function nodeToBlock(
222
+ export function nodeToBlock<BSchema extends BlockSchema>(
218
223
  node: Node,
219
- blockCache?: WeakMap<Node, Block>
220
- ): Block {
224
+ blockSchema: BSchema,
225
+ blockCache?: WeakMap<Node, Block<BSchema>>
226
+ ): Block<BSchema> {
221
227
  if (node.type.name !== "blockContainer") {
222
228
  throw Error(
223
229
  "Node must be of type blockContainer, but is of type" +
@@ -246,29 +252,44 @@ export function nodeToBlock(
246
252
  ...blockInfo.node.attrs,
247
253
  ...blockInfo.contentNode.attrs,
248
254
  })) {
249
- if (!(blockInfo.contentType.name in blockProps)) {
255
+ const blockSpec = blockSchema[blockInfo.contentType.name];
256
+ if (!blockSpec) {
250
257
  throw Error(
251
258
  "Block is of an unrecognized type: " + blockInfo.contentType.name
252
259
  );
253
260
  }
254
261
 
255
- const validAttrs = blockProps[blockInfo.contentType.name as Block["type"]];
262
+ const propSchema = blockSpec.propSchema;
256
263
 
257
- if (validAttrs.has(attr)) {
264
+ if (attr in propSchema) {
258
265
  props[attr] = value;
259
266
  }
267
+ // Block ids are stored as node attributes the same way props are, so we
268
+ // need to ensure we don't attempt to read block ids as props.
269
+
270
+ // the second check is for the backgroundColor & textColor props.
271
+ // Since we want them to be inherited by child blocks, we can't put them on the blockContent node,
272
+ // and instead have to put them on the blockContainer node.
273
+ // The blockContainer node is the same for all block types, but some custom blocks might not use backgroundColor & textColor,
274
+ // so these 2 props are technically unexpected but we shouldn't log a warning.
275
+ // (this is a bit hacky)
276
+ else if (attr !== "id" && !(attr in defaultProps)) {
277
+ console.warn("Block has an unrecognized attribute: " + attr);
278
+ }
260
279
  }
261
280
 
262
281
  const content = contentNodeToInlineContent(blockInfo.contentNode);
263
282
 
264
- const children: Block[] = [];
283
+ const children: Block<BSchema>[] = [];
265
284
  for (let i = 0; i < blockInfo.numChildBlocks; i++) {
266
- children.push(nodeToBlock(blockInfo.node.lastChild!.child(i)));
285
+ children.push(
286
+ nodeToBlock(blockInfo.node.lastChild!.child(i), blockSchema, blockCache)
287
+ );
267
288
  }
268
289
 
269
- const block: Block = {
290
+ const block: Block<BSchema> = {
270
291
  id,
271
- type: blockInfo.contentType.name as Block["type"],
292
+ type: blockInfo.contentType.name,
272
293
  props,
273
294
  content,
274
295
  children,
@@ -1,4 +1,8 @@
1
- import { Block, PartialBlock } from "../../extensions/Blocks/api/blockTypes";
1
+ import {
2
+ Block,
3
+ BlockSchema,
4
+ PartialBlock,
5
+ } from "../../extensions/Blocks/api/blockTypes";
2
6
  import {
3
7
  InlineContent,
4
8
  PartialInlineContent,
@@ -39,9 +43,9 @@ function partialContentToInlineContent(
39
43
  });
40
44
  }
41
45
 
42
- export function partialBlockToBlockForTesting(
43
- partialBlock: PartialBlock
44
- ): Block {
46
+ export function partialBlockToBlockForTesting<BSchema extends BlockSchema>(
47
+ partialBlock: PartialBlock<BSchema>
48
+ ): Block<BSchema> {
45
49
  const withDefaults = {
46
50
  id: "",
47
51
  type: "paragraph" as any,
@@ -38,7 +38,6 @@ Tippy popups that are appended to document.body directly
38
38
  .defaultStyles h3,
39
39
  .defaultStyles li {
40
40
  all: unset !important;
41
- flex-grow: 1 !important;
42
41
  margin: 0;
43
42
  padding: 0;
44
43
  font-size: inherit;
@@ -0,0 +1,229 @@
1
+ import { Attribute, Node } from "@tiptap/core";
2
+ import { BlockNoteEditor } from "../../..";
3
+ import styles from "../nodes/Block.module.css";
4
+ import {
5
+ BlockConfig,
6
+ BlockSchema,
7
+ BlockSpec,
8
+ PropSchema,
9
+ TipTapNode,
10
+ TipTapNodeConfig,
11
+ } from "./blockTypes";
12
+
13
+ export function camelToDataKebab(str: string): string {
14
+ return "data-" + str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
15
+ }
16
+
17
+ // Function that uses the 'propSchema' of a blockConfig to create a TipTap
18
+ // node's `addAttributes` property.
19
+ export function propsToAttributes<
20
+ BType extends string,
21
+ PSchema extends PropSchema,
22
+ ContainsInlineContent extends boolean,
23
+ BSchema extends BlockSchema
24
+ >(
25
+ blockConfig: Omit<
26
+ BlockConfig<BType, PSchema, ContainsInlineContent, BSchema>,
27
+ "render"
28
+ >
29
+ ) {
30
+ const tiptapAttributes: Record<string, Attribute> = {};
31
+
32
+ Object.entries(blockConfig.propSchema).forEach(([name, spec]) => {
33
+ tiptapAttributes[name] = {
34
+ default: spec.default,
35
+ keepOnSplit: true,
36
+ // Props are displayed in kebab-case as HTML attributes. If a prop's
37
+ // value is the same as its default, we don't display an HTML
38
+ // attribute for it.
39
+ parseHTML: (element) => element.getAttribute(camelToDataKebab(name)),
40
+ renderHTML: (attributes) =>
41
+ attributes[name] !== spec.default
42
+ ? {
43
+ [camelToDataKebab(name)]: attributes[name],
44
+ }
45
+ : {},
46
+ };
47
+ });
48
+
49
+ return tiptapAttributes;
50
+ }
51
+
52
+ // Function that uses the 'parse' function of a blockConfig to create a
53
+ // TipTap node's `parseHTML` property. This is only used for parsing content
54
+ // from the clipboard.
55
+ export function parse<
56
+ BType extends string,
57
+ PSchema extends PropSchema,
58
+ ContainsInlineContent extends boolean,
59
+ BSchema extends BlockSchema
60
+ >(
61
+ blockConfig: Omit<
62
+ BlockConfig<BType, PSchema, ContainsInlineContent, BSchema>,
63
+ "render"
64
+ >
65
+ ) {
66
+ return [
67
+ {
68
+ tag: "div[data-content-type=" + blockConfig.type + "]",
69
+ },
70
+ ];
71
+ }
72
+
73
+ // Function that uses the 'render' function of a blockConfig to create a
74
+ // TipTap node's `renderHTML` property. Since custom blocks use node views,
75
+ // this is only used for serializing content to the clipboard.
76
+ export function render<
77
+ BType extends string,
78
+ PSchema extends PropSchema,
79
+ ContainsInlineContent extends boolean,
80
+ BSchema extends BlockSchema
81
+ >(
82
+ blockConfig: Omit<
83
+ BlockConfig<BType, PSchema, ContainsInlineContent, BSchema>,
84
+ "render"
85
+ >,
86
+ HTMLAttributes: Record<string, any>
87
+ ) {
88
+ // Create blockContent element
89
+ const blockContent = document.createElement("div");
90
+ // Add blockContent HTML attribute
91
+ blockContent.setAttribute("data-content-type", blockConfig.type);
92
+ // Add props as HTML attributes in kebab-case with "data-" prefix
93
+ for (const [attribute, value] of Object.entries(HTMLAttributes)) {
94
+ blockContent.setAttribute(attribute, value);
95
+ }
96
+
97
+ // TODO: This only works for content copied within BlockNote.
98
+ // Creates contentDOM element to serialize inline content into.
99
+ let contentDOM: HTMLDivElement | undefined;
100
+ if (blockConfig.containsInlineContent) {
101
+ contentDOM = document.createElement("div");
102
+ blockContent.appendChild(contentDOM);
103
+ } else {
104
+ contentDOM = undefined;
105
+ }
106
+
107
+ return contentDOM !== undefined
108
+ ? {
109
+ dom: blockContent,
110
+ contentDOM: contentDOM,
111
+ }
112
+ : {
113
+ dom: blockContent,
114
+ };
115
+ }
116
+
117
+ // A function to create custom block for API consumers
118
+ // we want to hide the tiptap node from API consumers and provide a simpler API surface instead
119
+ export function createBlockSpec<
120
+ BType extends string,
121
+ PSchema extends PropSchema,
122
+ ContainsInlineContent extends boolean,
123
+ BSchema extends BlockSchema
124
+ >(
125
+ blockConfig: BlockConfig<BType, PSchema, ContainsInlineContent, BSchema>
126
+ ): BlockSpec<BType, PSchema> {
127
+ const node = createTipTapBlock<BType>({
128
+ name: blockConfig.type,
129
+ content: blockConfig.containsInlineContent ? "inline*" : "",
130
+ selectable: blockConfig.containsInlineContent,
131
+
132
+ addOptions() {
133
+ return {
134
+ editor: undefined,
135
+ };
136
+ },
137
+
138
+ addAttributes() {
139
+ return propsToAttributes(blockConfig);
140
+ },
141
+
142
+ parseHTML() {
143
+ return parse(blockConfig);
144
+ },
145
+
146
+ renderHTML({ HTMLAttributes }) {
147
+ return render(blockConfig, HTMLAttributes);
148
+ },
149
+
150
+ addNodeView() {
151
+ return ({ HTMLAttributes, getPos }) => {
152
+ // Create blockContent element
153
+ const blockContent = document.createElement("div");
154
+ // Sets blockContent class
155
+ blockContent.className = styles.blockContent;
156
+ // Add blockContent HTML attribute
157
+ blockContent.setAttribute("data-content-type", blockConfig.type);
158
+ // Add props as HTML attributes in kebab-case with "data-" prefix
159
+ for (const [attribute, value] of Object.entries(HTMLAttributes)) {
160
+ blockContent.setAttribute(attribute, value);
161
+ }
162
+
163
+ // Gets BlockNote editor instance
164
+ const editor = this.options.editor! as BlockNoteEditor<
165
+ BSchema & { [k in BType]: BlockSpec<BType, PSchema> }
166
+ >;
167
+ // Gets position of the node
168
+ if (typeof getPos === "boolean") {
169
+ throw new Error(
170
+ "Cannot find node position as getPos is a boolean, not a function."
171
+ );
172
+ }
173
+ const pos = getPos();
174
+ // Gets TipTap editor instance
175
+ const tipTapEditor = editor._tiptapEditor;
176
+ // Gets parent blockContainer node
177
+ const blockContainer = tipTapEditor.state.doc.resolve(pos!).node();
178
+ // Gets block identifier
179
+ const blockIdentifier = blockContainer.attrs.id;
180
+
181
+ // Get the block
182
+ const block = editor.getBlock(blockIdentifier)!;
183
+ if (block.type !== blockConfig.type) {
184
+ throw new Error("Block type does not match");
185
+ }
186
+
187
+ // Render elements
188
+ const rendered = blockConfig.render(block as any, editor);
189
+ // Add inlineContent class to inline content
190
+ if ("contentDOM" in rendered) {
191
+ rendered.contentDOM.className = `${
192
+ rendered.contentDOM.className
193
+ ? rendered.contentDOM.className + " "
194
+ : ""
195
+ }${styles.inlineContent}`;
196
+ }
197
+ // Add elements to blockContent
198
+ blockContent.appendChild(rendered.dom);
199
+
200
+ return "contentDOM" in rendered
201
+ ? {
202
+ dom: blockContent,
203
+ contentDOM: rendered.contentDOM,
204
+ }
205
+ : {
206
+ dom: blockContent,
207
+ };
208
+ };
209
+ },
210
+ });
211
+
212
+ return {
213
+ node: node,
214
+ propSchema: blockConfig.propSchema,
215
+ };
216
+ }
217
+
218
+ export function createTipTapBlock<Type extends string>(
219
+ config: TipTapNodeConfig<Type>
220
+ ): TipTapNode<Type> {
221
+ // Type cast is needed as Node.name is mutable, though there is basically no
222
+ // reason to change it after creation. Alternative is to wrap Node in a new
223
+ // class, which I don't think is worth it since we'd only be changing 1
224
+ // attribute to be read only.
225
+ return Node.create({
226
+ ...config,
227
+ group: "blockContent",
228
+ }) as TipTapNode<Type>;
229
+ }