Paragraph
Paragraph
Hello World
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/customParagraph/basic/internal.html b/packages/core/src/api/exporters/html/__snapshots__/customParagraph/basic/internal.html new file mode 100644 index 0000000000..2d4cb0a6df --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/customParagraph/basic/internal.html @@ -0,0 +1 @@ +Custom Paragraph
Hello World
Hello World
Hello World
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/customParagraph/nested/internal.html b/packages/core/src/api/exporters/html/__snapshots__/customParagraph/nested/internal.html new file mode 100644 index 0000000000..ddb4899ad2 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/customParagraph/nested/internal.html @@ -0,0 +1 @@ +Custom Paragraph
Nested Custom Paragraph 1
Nested Custom Paragraph 2
Hello World
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/customParagraph/styled/internal.html b/packages/core/src/api/exporters/html/__snapshots__/customParagraph/styled/internal.html new file mode 100644 index 0000000000..e637925cb4 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/customParagraph/styled/internal.html @@ -0,0 +1 @@ +Plain Red Text Blue Background Mixed Colors
This is text with a custom fontSize
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/fontSize/basic/internal.html b/packages/core/src/api/exporters/html/__snapshots__/fontSize/basic/internal.html new file mode 100644 index 0000000000..3fe864246c --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/fontSize/basic/internal.html @@ -0,0 +1 @@ +This is text with a custom fontSize
Text1
Text2
Text1
Text2
Text1
Text1
Text1
Text2
Text3
Text1
Text2
Text3
Text1
Text1
Text1
Text2
Text1
Text2
Add Image
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/image/button/internal.html b/packages/core/src/api/exporters/html/__snapshots__/image/button/internal.html new file mode 100644 index 0000000000..39de1869c4 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/image/button/internal.html @@ -0,0 +1 @@ +I enjoy working with@Matthew
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/mention/basic/internal.html b/packages/core/src/api/exporters/html/__snapshots__/mention/basic/internal.html new file mode 100644 index 0000000000..6ca7d81c2c --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/mention/basic/internal.html @@ -0,0 +1 @@ +I enjoy working with@Matthew
Paragraph
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/paragraph/basic/internal.html b/packages/core/src/api/exporters/html/__snapshots__/paragraph/basic/internal.html new file mode 100644 index 0000000000..7a7fe019c2 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/paragraph/basic/internal.html @@ -0,0 +1 @@ +Paragraph
Paragraph
Nested Paragraph 1
Nested Paragraph 2
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/paragraph/nested/internal.html b/packages/core/src/api/exporters/html/__snapshots__/paragraph/nested/internal.html new file mode 100644 index 0000000000..79557fb3a3 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/paragraph/nested/internal.html @@ -0,0 +1 @@ +Paragraph
Nested Paragraph 1
Nested Paragraph 2
Plain Red Text Blue Background Mixed Colors
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/paragraph/styled/internal.html b/packages/core/src/api/exporters/html/__snapshots__/paragraph/styled/internal.html new file mode 100644 index 0000000000..fa01c74894 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/paragraph/styled/internal.html @@ -0,0 +1 @@ +Plain Red Text Blue Background Mixed Colors
Custom Paragraph
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleCustomParagraph/basic/internal.html b/packages/core/src/api/exporters/html/__snapshots__/simpleCustomParagraph/basic/internal.html new file mode 100644 index 0000000000..b36014c69c --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleCustomParagraph/basic/internal.html @@ -0,0 +1 @@ +Custom Paragraph
Custom Paragraph
Nested Custom Paragraph 1
Nested Custom Paragraph 2
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleCustomParagraph/nested/internal.html b/packages/core/src/api/exporters/html/__snapshots__/simpleCustomParagraph/nested/internal.html new file mode 100644 index 0000000000..a8a00fc7e6 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleCustomParagraph/nested/internal.html @@ -0,0 +1 @@ +Custom Paragraph
Nested Custom Paragraph 1
Nested Custom Paragraph 2
Plain Red Text Blue Background Mixed Colors
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/simpleCustomParagraph/styled/internal.html b/packages/core/src/api/exporters/html/__snapshots__/simpleCustomParagraph/styled/internal.html new file mode 100644 index 0000000000..f870679466 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/simpleCustomParagraph/styled/internal.html @@ -0,0 +1 @@ +Plain Red Text Blue Background Mixed Colors
This is a small text
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/small/basic/internal.html b/packages/core/src/api/exporters/html/__snapshots__/small/basic/internal.html new file mode 100644 index 0000000000..73836f647d --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/small/basic/internal.html @@ -0,0 +1 @@ +This is a small text
I love #BlockNote
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/tag/basic/internal.html b/packages/core/src/api/exporters/html/__snapshots__/tag/basic/internal.html new file mode 100644 index 0000000000..bac28633b0 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/tag/basic/internal.html @@ -0,0 +1 @@ +I love #BlockNote
Paragraph
Bullet List Item
Numbered List Item
Paragraph
Bullet List Item
Numbered List Item
BoldItalicUnderlineStrikethroughTextColorBackgroundColorMultiple
Paragraph
Paragraph
Paragraph
Bullet List Item
Bullet List Item
Bullet List Item
Bullet List Item
Paragraph
Numbered List Item
Numbered List Item
Numbered List Item
Numbered List Item
Bullet List Item
Bullet List Item
Bullet List Item
`.
- /** @type {Element} */
- let result: any = {
- type: "element",
- tagName: "code",
- properties,
- children: [{ type: "text", value }],
- };
-
- if (node.meta) {
- result.data = { meta: node.meta };
- }
-
- state.patch(node, result);
- result = state.applyData(node, result);
-
- // Create ``.
- result = {
- type: "element",
- tagName: "pre",
- properties: {},
- children: [result],
- };
- state.patch(node, result);
- return result;
-}
-
-export async function markdownToBlocks(
- markdown: string,
- blockSchema: BSchema,
- schema: Schema
-): Promise[]> {
- const htmlString = await unified()
- .use(remarkParse)
- .use(remarkGfm)
- .use(remarkRehype, {
- handlers: {
- ...(defaultHandlers as any),
- code,
- },
- })
- .use(rehypeStringify)
- .process(markdown);
-
- return HTMLToBlocks(htmlString.value as string, blockSchema, schema);
-}
diff --git a/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap b/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap
index 92be1196e5..41b67fb5ca 100644
--- a/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap
+++ b/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap
@@ -1,6 +1,134 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`Complex ProseMirror Node Conversions > Convert complex block to node 1`] = `
+exports[`Test BlockNote-Prosemirror conversion > Case: custom inline content schema > Convert mention/basic to/from prosemirror 1`] = `
+{
+ "attrs": {
+ "backgroundColor": "default",
+ "id": "1",
+ "textColor": "default",
+ },
+ "content": [
+ {
+ "attrs": {
+ "textAlignment": "left",
+ },
+ "content": [
+ {
+ "text": "I enjoy working with",
+ "type": "text",
+ },
+ {
+ "attrs": {
+ "user": "Matthew",
+ },
+ "type": "mention",
+ },
+ ],
+ "type": "paragraph",
+ },
+ ],
+ "type": "blockContainer",
+}
+`;
+
+exports[`Test BlockNote-Prosemirror conversion > Case: custom inline content schema > Convert tag/basic to/from prosemirror 1`] = `
+{
+ "attrs": {
+ "backgroundColor": "default",
+ "id": "1",
+ "textColor": "default",
+ },
+ "content": [
+ {
+ "attrs": {
+ "textAlignment": "left",
+ },
+ "content": [
+ {
+ "text": "I love ",
+ "type": "text",
+ },
+ {
+ "content": [
+ {
+ "text": "BlockNote",
+ "type": "text",
+ },
+ ],
+ "type": "tag",
+ },
+ ],
+ "type": "paragraph",
+ },
+ ],
+ "type": "blockContainer",
+}
+`;
+
+exports[`Test BlockNote-Prosemirror conversion > Case: custom style schema > Convert fontSize/basic to/from prosemirror 1`] = `
+{
+ "attrs": {
+ "backgroundColor": "default",
+ "id": "1",
+ "textColor": "default",
+ },
+ "content": [
+ {
+ "attrs": {
+ "textAlignment": "left",
+ },
+ "content": [
+ {
+ "marks": [
+ {
+ "attrs": {
+ "stringValue": "18px",
+ },
+ "type": "fontSize",
+ },
+ ],
+ "text": "This is text with a custom fontSize",
+ "type": "text",
+ },
+ ],
+ "type": "paragraph",
+ },
+ ],
+ "type": "blockContainer",
+}
+`;
+
+exports[`Test BlockNote-Prosemirror conversion > Case: custom style schema > Convert small/basic to/from prosemirror 1`] = `
+{
+ "attrs": {
+ "backgroundColor": "default",
+ "id": "1",
+ "textColor": "default",
+ },
+ "content": [
+ {
+ "attrs": {
+ "textAlignment": "left",
+ },
+ "content": [
+ {
+ "marks": [
+ {
+ "type": "small",
+ },
+ ],
+ "text": "This is a small text",
+ "type": "text",
+ },
+ ],
+ "type": "paragraph",
+ },
+ ],
+ "type": "blockContainer",
+}
+`;
+
+exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert complex/misc to/from prosemirror 1`] = `
{
"attrs": {
"backgroundColor": "blue",
@@ -89,68 +217,91 @@ exports[`Complex ProseMirror Node Conversions > Convert complex block to node 1`
}
`;
-exports[`Complex ProseMirror Node Conversions > Convert complex node to block 1`] = `
+exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert hardbreak/basic to/from prosemirror 1`] = `
{
- "children": [
+ "attrs": {
+ "backgroundColor": "default",
+ "id": "1",
+ "textColor": "default",
+ },
+ "content": [
{
- "children": [],
+ "attrs": {
+ "textAlignment": "left",
+ },
"content": [
{
- "styles": {},
- "text": "Paragraph",
+ "text": "Text1",
+ "type": "text",
+ },
+ {
+ "type": "hardBreak",
+ },
+ {
+ "text": "Text2",
"type": "text",
},
],
- "id": "2",
- "props": {
- "backgroundColor": "red",
- "textAlignment": "left",
- "textColor": "default",
- },
"type": "paragraph",
},
- {
- "children": [],
- "content": [],
- "id": "3",
- "props": {
- "backgroundColor": "default",
- "textAlignment": "left",
- "textColor": "default",
- },
- "type": "bulletListItem",
- },
],
+ "type": "blockContainer",
+}
+`;
+
+exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert hardbreak/between-links to/from prosemirror 1`] = `
+{
+ "attrs": {
+ "backgroundColor": "default",
+ "id": "1",
+ "textColor": "default",
+ },
"content": [
{
- "styles": {
- "bold": true,
- "underline": true,
- },
- "text": "Heading ",
- "type": "text",
- },
- {
- "styles": {
- "italic": true,
- "strike": true,
+ "attrs": {
+ "textAlignment": "left",
},
- "text": "2",
- "type": "text",
+ "content": [
+ {
+ "marks": [
+ {
+ "attrs": {
+ "class": null,
+ "href": "https://www.website.com",
+ "target": "_blank",
+ },
+ "type": "link",
+ },
+ ],
+ "text": "Link1",
+ "type": "text",
+ },
+ {
+ "type": "hardBreak",
+ },
+ {
+ "marks": [
+ {
+ "attrs": {
+ "class": null,
+ "href": "https://www.website2.com",
+ "target": "_blank",
+ },
+ "type": "link",
+ },
+ ],
+ "text": "Link2",
+ "type": "text",
+ },
+ ],
+ "type": "paragraph",
},
],
- "id": "1",
- "props": {
- "backgroundColor": "blue",
- "level": 2,
- "textAlignment": "right",
- "textColor": "yellow",
- },
- "type": "heading",
+ "type": "blockContainer",
}
`;
-exports[`Simple ProseMirror Node Conversions > Convert simple block to node 1`] = `
+exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert hardbreak/end to/from prosemirror 1`] = `
{
"attrs": {
"backgroundColor": "default",
@@ -162,6 +313,15 @@ exports[`Simple ProseMirror Node Conversions > Convert simple block to node 1`]
"attrs": {
"textAlignment": "left",
},
+ "content": [
+ {
+ "text": "Text1",
+ "type": "text",
+ },
+ {
+ "type": "hardBreak",
+ },
+ ],
"type": "paragraph",
},
],
@@ -169,21 +329,59 @@ exports[`Simple ProseMirror Node Conversions > Convert simple block to node 1`]
}
`;
-exports[`Simple ProseMirror Node Conversions > Convert simple node to block 1`] = `
+exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert hardbreak/link to/from prosemirror 1`] = `
{
- "children": [],
- "content": [],
- "id": "1",
- "props": {
+ "attrs": {
"backgroundColor": "default",
- "textAlignment": "left",
+ "id": "1",
"textColor": "default",
},
- "type": "paragraph",
+ "content": [
+ {
+ "attrs": {
+ "textAlignment": "left",
+ },
+ "content": [
+ {
+ "marks": [
+ {
+ "attrs": {
+ "class": null,
+ "href": "https://www.website.com",
+ "target": "_blank",
+ },
+ "type": "link",
+ },
+ ],
+ "text": "Link1",
+ "type": "text",
+ },
+ {
+ "type": "hardBreak",
+ },
+ {
+ "marks": [
+ {
+ "attrs": {
+ "class": null,
+ "href": "https://www.website.com",
+ "target": "_blank",
+ },
+ "type": "link",
+ },
+ ],
+ "text": "Link1",
+ "type": "text",
+ },
+ ],
+ "type": "paragraph",
+ },
+ ],
+ "type": "blockContainer",
}
`;
-exports[`hard breaks > Convert a block with a hard break 1`] = `
+exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert hardbreak/multiple to/from prosemirror 1`] = `
{
"attrs": {
"backgroundColor": "default",
@@ -207,6 +405,13 @@ exports[`hard breaks > Convert a block with a hard break 1`] = `
"text": "Text2",
"type": "text",
},
+ {
+ "type": "hardBreak",
+ },
+ {
+ "text": "Text3",
+ "type": "text",
+ },
],
"type": "paragraph",
},
@@ -215,7 +420,7 @@ exports[`hard breaks > Convert a block with a hard break 1`] = `
}
`;
-exports[`hard breaks > Convert a block with a hard break and different styles 1`] = `
+exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert hardbreak/only to/from prosemirror 1`] = `
{
"attrs": {
"backgroundColor": "default",
@@ -229,19 +434,34 @@ exports[`hard breaks > Convert a block with a hard break and different styles 1`
},
"content": [
{
- "text": "Text1",
- "type": "text",
+ "type": "hardBreak",
},
+ ],
+ "type": "paragraph",
+ },
+ ],
+ "type": "blockContainer",
+}
+`;
+
+exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert hardbreak/start to/from prosemirror 1`] = `
+{
+ "attrs": {
+ "backgroundColor": "default",
+ "id": "1",
+ "textColor": "default",
+ },
+ "content": [
+ {
+ "attrs": {
+ "textAlignment": "left",
+ },
+ "content": [
{
"type": "hardBreak",
},
{
- "marks": [
- {
- "type": "bold",
- },
- ],
- "text": "Text2",
+ "text": "Text1",
"type": "text",
},
],
@@ -252,7 +472,7 @@ exports[`hard breaks > Convert a block with a hard break and different styles 1`
}
`;
-exports[`hard breaks > Convert a block with a hard break at the end 1`] = `
+exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert hardbreak/styles to/from prosemirror 1`] = `
{
"attrs": {
"backgroundColor": "default",
@@ -272,6 +492,15 @@ exports[`hard breaks > Convert a block with a hard break at the end 1`] = `
{
"type": "hardBreak",
},
+ {
+ "marks": [
+ {
+ "type": "bold",
+ },
+ ],
+ "text": "Text2",
+ "type": "text",
+ },
],
"type": "paragraph",
},
@@ -280,7 +509,7 @@ exports[`hard breaks > Convert a block with a hard break at the end 1`] = `
}
`;
-exports[`hard breaks > Convert a block with a hard break at the start 1`] = `
+exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert image/basic to/from prosemirror 1`] = `
{
"attrs": {
"backgroundColor": "default",
@@ -290,25 +519,87 @@ exports[`hard breaks > Convert a block with a hard break at the start 1`] = `
"content": [
{
"attrs": {
+ "caption": "Caption",
"textAlignment": "left",
+ "url": "exampleURL",
+ "width": 256,
},
+ "type": "image",
+ },
+ ],
+ "type": "blockContainer",
+}
+`;
+
+exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert image/button to/from prosemirror 1`] = `
+{
+ "attrs": {
+ "backgroundColor": "default",
+ "id": "1",
+ "textColor": "default",
+ },
+ "content": [
+ {
+ "attrs": {
+ "caption": "",
+ "textAlignment": "left",
+ "url": "",
+ "width": 512,
+ },
+ "type": "image",
+ },
+ ],
+ "type": "blockContainer",
+}
+`;
+
+exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert image/nested to/from prosemirror 1`] = `
+{
+ "attrs": {
+ "backgroundColor": "default",
+ "id": "1",
+ "textColor": "default",
+ },
+ "content": [
+ {
+ "attrs": {
+ "caption": "Caption",
+ "textAlignment": "left",
+ "url": "exampleURL",
+ "width": 256,
+ },
+ "type": "image",
+ },
+ {
"content": [
{
- "type": "hardBreak",
- },
- {
- "text": "Text1",
- "type": "text",
+ "attrs": {
+ "backgroundColor": "default",
+ "id": "2",
+ "textColor": "default",
+ },
+ "content": [
+ {
+ "attrs": {
+ "caption": "Caption",
+ "textAlignment": "left",
+ "url": "exampleURL",
+ "width": 256,
+ },
+ "type": "image",
+ },
+ ],
+ "type": "blockContainer",
},
],
- "type": "paragraph",
+ "type": "blockGroup",
},
],
"type": "blockContainer",
}
`;
-exports[`hard breaks > Convert a block with a hard break between links 1`] = `
+exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert link/adjacent to/from prosemirror 1`] = `
{
"attrs": {
"backgroundColor": "default",
@@ -332,12 +623,9 @@ exports[`hard breaks > Convert a block with a hard break between links 1`] = `
"type": "link",
},
],
- "text": "Link1",
+ "text": "Website",
"type": "text",
},
- {
- "type": "hardBreak",
- },
{
"marks": [
{
@@ -349,7 +637,7 @@ exports[`hard breaks > Convert a block with a hard break between links 1`] = `
"type": "link",
},
],
- "text": "Link2",
+ "text": "Website2",
"type": "text",
},
],
@@ -360,7 +648,7 @@ exports[`hard breaks > Convert a block with a hard break between links 1`] = `
}
`;
-exports[`hard breaks > Convert a block with a hard break in a link 1`] = `
+exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert link/basic to/from prosemirror 1`] = `
{
"attrs": {
"backgroundColor": "default",
@@ -384,11 +672,46 @@ exports[`hard breaks > Convert a block with a hard break in a link 1`] = `
"type": "link",
},
],
- "text": "Link1",
+ "text": "Website",
"type": "text",
},
+ ],
+ "type": "paragraph",
+ },
+ ],
+ "type": "blockContainer",
+}
+`;
+
+exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert link/styled to/from prosemirror 1`] = `
+{
+ "attrs": {
+ "backgroundColor": "default",
+ "id": "1",
+ "textColor": "default",
+ },
+ "content": [
+ {
+ "attrs": {
+ "textAlignment": "left",
+ },
+ "content": [
{
- "type": "hardBreak",
+ "marks": [
+ {
+ "type": "bold",
+ },
+ {
+ "attrs": {
+ "class": null,
+ "href": "https://www.website.com",
+ "target": "_blank",
+ },
+ "type": "link",
+ },
+ ],
+ "text": "Web",
+ "type": "text",
},
{
"marks": [
@@ -401,7 +724,7 @@ exports[`hard breaks > Convert a block with a hard break in a link 1`] = `
"type": "link",
},
],
- "text": "Link1",
+ "text": "site",
"type": "text",
},
],
@@ -412,7 +735,7 @@ exports[`hard breaks > Convert a block with a hard break in a link 1`] = `
}
`;
-exports[`hard breaks > Convert a block with multiple hard breaks 1`] = `
+exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert paragraph/basic to/from prosemirror 1`] = `
{
"attrs": {
"backgroundColor": "default",
@@ -426,21 +749,7 @@ exports[`hard breaks > Convert a block with multiple hard breaks 1`] = `
},
"content": [
{
- "text": "Text1",
- "type": "text",
- },
- {
- "type": "hardBreak",
- },
- {
- "text": "Text2",
- "type": "text",
- },
- {
- "type": "hardBreak",
- },
- {
- "text": "Text3",
+ "text": "Paragraph",
"type": "text",
},
],
@@ -451,7 +760,7 @@ exports[`hard breaks > Convert a block with multiple hard breaks 1`] = `
}
`;
-exports[`hard breaks > Convert a block with only a hard break 1`] = `
+exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert paragraph/empty to/from prosemirror 1`] = `
{
"attrs": {
"backgroundColor": "default",
@@ -463,11 +772,6 @@ exports[`hard breaks > Convert a block with only a hard break 1`] = `
"attrs": {
"textAlignment": "left",
},
- "content": [
- {
- "type": "hardBreak",
- },
- ],
"type": "paragraph",
},
],
@@ -475,7 +779,7 @@ exports[`hard breaks > Convert a block with only a hard break 1`] = `
}
`;
-exports[`links > Convert a block with link 1`] = `
+exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert paragraph/nested to/from prosemirror 1`] = `
{
"attrs": {
"backgroundColor": "default",
@@ -489,66 +793,123 @@ exports[`links > Convert a block with link 1`] = `
},
"content": [
{
- "marks": [
+ "text": "Paragraph",
+ "type": "text",
+ },
+ ],
+ "type": "paragraph",
+ },
+ {
+ "content": [
+ {
+ "attrs": {
+ "backgroundColor": "default",
+ "id": "2",
+ "textColor": "default",
+ },
+ "content": [
{
"attrs": {
- "class": null,
- "href": "https://www.website.com",
- "target": "_blank",
+ "textAlignment": "left",
},
- "type": "link",
+ "content": [
+ {
+ "text": "Nested Paragraph 1",
+ "type": "text",
+ },
+ ],
+ "type": "paragraph",
},
],
- "text": "Website",
- "type": "text",
+ "type": "blockContainer",
+ },
+ {
+ "attrs": {
+ "backgroundColor": "default",
+ "id": "3",
+ "textColor": "default",
+ },
+ "content": [
+ {
+ "attrs": {
+ "textAlignment": "left",
+ },
+ "content": [
+ {
+ "text": "Nested Paragraph 2",
+ "type": "text",
+ },
+ ],
+ "type": "paragraph",
+ },
+ ],
+ "type": "blockContainer",
},
],
- "type": "paragraph",
+ "type": "blockGroup",
},
],
"type": "blockContainer",
}
`;
-exports[`links > Convert two adjacent links in a block 1`] = `
+exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert paragraph/styled to/from prosemirror 1`] = `
{
"attrs": {
- "backgroundColor": "default",
+ "backgroundColor": "pink",
"id": "1",
- "textColor": "default",
+ "textColor": "orange",
},
"content": [
{
"attrs": {
- "textAlignment": "left",
+ "textAlignment": "center",
},
"content": [
+ {
+ "text": "Plain ",
+ "type": "text",
+ },
{
"marks": [
{
"attrs": {
- "class": null,
- "href": "https://www.website.com",
- "target": "_blank",
+ "stringValue": "red",
},
- "type": "link",
+ "type": "textColor",
},
],
- "text": "Website",
+ "text": "Red Text ",
"type": "text",
},
{
"marks": [
{
"attrs": {
- "class": null,
- "href": "https://www.website2.com",
- "target": "_blank",
+ "stringValue": "blue",
},
- "type": "link",
+ "type": "backgroundColor",
},
],
- "text": "Website2",
+ "text": "Blue Background ",
+ "type": "text",
+ },
+ {
+ "marks": [
+ {
+ "attrs": {
+ "stringValue": "red",
+ },
+ "type": "textColor",
+ },
+ {
+ "attrs": {
+ "stringValue": "blue",
+ },
+ "type": "backgroundColor",
+ },
+ ],
+ "text": "Mixed Colors",
"type": "text",
},
],
diff --git a/packages/core/src/api/nodeConversions/nodeConversions.test.ts b/packages/core/src/api/nodeConversions/nodeConversions.test.ts
index 37bdadfdb9..be1d1cfaf2 100644
--- a/packages/core/src/api/nodeConversions/nodeConversions.test.ts
+++ b/packages/core/src/api/nodeConversions/nodeConversions.test.ts
@@ -1,501 +1,70 @@
-import { Editor } from "@tiptap/core";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
-import { BlockNoteEditor, PartialBlock } from "../..";
-import {
- DefaultBlockSchema,
- defaultBlockSchema,
-} from "../../extensions/Blocks/api/defaultBlocks";
-import UniqueID from "../../extensions/UniqueID/UniqueID";
-import { blockToNode, nodeToBlock } from "./nodeConversions";
-import { partialBlockToBlockForTesting } from "./testUtil";
-
-let editor: BlockNoteEditor;
-let tt: Editor;
-
-beforeEach(() => {
- (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS = {};
-
- editor = new BlockNoteEditor();
- tt = editor._tiptapEditor;
-});
-
-afterEach(() => {
- tt.destroy();
- editor = undefined as any;
- tt = undefined as any;
-
- delete (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS;
-});
-
-describe("Simple ProseMirror Node Conversions", () => {
- it("Convert simple block to node", async () => {
- const block: PartialBlock = {
- type: "paragraph",
- };
- const firstNodeConversion = blockToNode(block, tt.schema);
-
- expect(firstNodeConversion).toMatchSnapshot();
- });
-
- it("Convert simple node to block", async () => {
- const node = tt.schema.nodes["blockContainer"].create(
- { id: UniqueID.options.generateID() },
- tt.schema.nodes["paragraph"].create()
- );
- const firstBlockConversion = nodeToBlock(
- node,
- defaultBlockSchema
- );
-
- expect(firstBlockConversion).toMatchSnapshot();
-
- const firstNodeConversion = blockToNode(
- firstBlockConversion,
- tt.schema
- );
-
- expect(firstNodeConversion).toStrictEqual(node);
- });
-});
-
-describe("Complex ProseMirror Node Conversions", () => {
- it("Convert complex block to node", async () => {
- const block: PartialBlock = {
- type: "heading",
- props: {
- backgroundColor: "blue",
- textColor: "yellow",
- textAlignment: "right",
- level: 2,
- },
- content: [
- {
- type: "text",
- text: "Heading ",
- styles: {
- bold: true,
- underline: true,
- },
- },
- {
- type: "text",
- text: "2",
- styles: {
- italic: true,
- strike: true,
- },
- },
- ],
- children: [
- {
- type: "paragraph",
- props: {
- backgroundColor: "red",
- },
- content: "Paragraph",
- children: [],
- },
- {
- type: "bulletListItem",
- },
- ],
- };
- const firstNodeConversion = blockToNode(block, tt.schema);
-
- expect(firstNodeConversion).toMatchSnapshot();
- });
-
- it("Convert complex node to block", async () => {
- const node = tt.schema.nodes["blockContainer"].create(
- {
- id: UniqueID.options.generateID(),
- backgroundColor: "blue",
- textColor: "yellow",
- },
- [
- tt.schema.nodes["heading"].create(
- { textAlignment: "right", level: 2 },
- [
- tt.schema.text("Heading ", [
- tt.schema.mark("bold"),
- tt.schema.mark("underline"),
- ]),
- tt.schema.text("2", [
- tt.schema.mark("italic"),
- tt.schema.mark("strike"),
- ]),
- ]
- ),
- tt.schema.nodes["blockGroup"].create({}, [
- tt.schema.nodes["blockContainer"].create(
- { id: UniqueID.options.generateID(), backgroundColor: "red" },
- [
- tt.schema.nodes["paragraph"].create(
- {},
- tt.schema.text("Paragraph")
- ),
- ]
- ),
- tt.schema.nodes["blockContainer"].create(
- { id: UniqueID.options.generateID() },
- [tt.schema.nodes["bulletListItem"].create()]
- ),
- ]),
- ]
- );
- const firstBlockConversion = nodeToBlock(
- node,
- defaultBlockSchema
- );
-
- expect(firstBlockConversion).toMatchSnapshot();
-
- const firstNodeConversion = blockToNode(
- firstBlockConversion,
- tt.schema
- );
-
- expect(firstNodeConversion).toStrictEqual(node);
- });
-});
-
-describe("links", () => {
- it("Convert a block with link", async () => {
- const block: PartialBlock = {
- id: UniqueID.options.generateID(),
- type: "paragraph",
- content: [
- {
- type: "link",
- href: "https://www.website.com",
- content: "Website",
- },
- ],
- };
- const node = blockToNode(block, tt.schema);
- expect(node).toMatchSnapshot();
- const outputBlock = nodeToBlock(
- node,
- defaultBlockSchema
- );
-
- // Temporary fix to set props to {}, because at this point
- // we don't have an easy way to access default props at runtime,
- // so partialBlockToBlockForTesting will not set them.
- (outputBlock as any).props = {};
- const fullOriginalBlock = partialBlockToBlockForTesting(block);
-
- expect(outputBlock).toStrictEqual(fullOriginalBlock);
- });
-
- it("Convert link block with marks", async () => {
- const block: PartialBlock = {
- id: UniqueID.options.generateID(),
- type: "paragraph",
- content: [
- {
- type: "link",
- href: "https://www.website.com",
- content: [
- {
- type: "text",
- text: "Web",
- styles: {
- bold: true,
- },
- },
- {
- type: "text",
- text: "site",
- styles: {},
- },
- ],
- },
- ],
- };
- const node = blockToNode(block, tt.schema);
- // expect(node).toMatchSnapshot();
- const outputBlock = nodeToBlock(
- node,
- defaultBlockSchema
- );
-
- // Temporary fix to set props to {}, because at this point
- // we don't have an easy way to access default props at runtime,
- // so partialBlockToBlockForTesting will not set them.
- (outputBlock as any).props = {};
- const fullOriginalBlock = partialBlockToBlockForTesting(block);
-
- expect(outputBlock).toStrictEqual(fullOriginalBlock);
- });
-
- it("Convert two adjacent links in a block", async () => {
- const block: PartialBlock = {
- id: UniqueID.options.generateID(),
- type: "paragraph",
- content: [
- {
- type: "link",
- href: "https://www.website.com",
- content: "Website",
- },
- {
- type: "link",
- href: "https://www.website2.com",
- content: "Website2",
- },
- ],
- };
- const node = blockToNode(block, tt.schema);
- expect(node).toMatchSnapshot();
- const outputBlock = nodeToBlock(
- node,
- defaultBlockSchema
- );
-
- // Temporary fix to set props to {}, because at this point
- // we don't have an easy way to access default props at runtime,
- // so partialBlockToBlockForTesting will not set them.
- (outputBlock as any).props = {};
- const fullOriginalBlock = partialBlockToBlockForTesting(block);
-
- expect(outputBlock).toStrictEqual(fullOriginalBlock);
- });
-});
-
-describe("hard breaks", () => {
- it("Convert a block with a hard break", async () => {
- const block: PartialBlock = {
- id: UniqueID.options.generateID(),
- type: "paragraph",
- content: [
- {
- type: "text",
- text: "Text1\nText2",
- styles: {},
- },
- ],
- };
- const node = blockToNode(block, tt.schema);
- expect(node).toMatchSnapshot();
- const outputBlock = nodeToBlock(
- node,
- defaultBlockSchema
- );
-
- // Temporary fix to set props to {}, because at this point
- // we don't have an easy way to access default props at runtime,
- // so partialBlockToBlockForTesting will not set them.
- (outputBlock as any).props = {};
- const fullOriginalBlock = partialBlockToBlockForTesting(block);
-
- expect(outputBlock).toStrictEqual(fullOriginalBlock);
- });
-
- it("Convert a block with multiple hard breaks", async () => {
- const block: PartialBlock = {
- id: UniqueID.options.generateID(),
- type: "paragraph",
- content: [
- {
- type: "text",
- text: "Text1\nText2\nText3",
- styles: {},
- },
- ],
- };
- const node = blockToNode(block, tt.schema);
- expect(node).toMatchSnapshot();
- const outputBlock = nodeToBlock(
- node,
- defaultBlockSchema
- );
-
- // Temporary fix to set props to {}, because at this point
- // we don't have an easy way to access default props at runtime,
- // so partialBlockToBlockForTesting will not set them.
- (outputBlock as any).props = {};
- const fullOriginalBlock = partialBlockToBlockForTesting(block);
-
- expect(outputBlock).toStrictEqual(fullOriginalBlock);
- });
-
- it("Convert a block with a hard break at the start", async () => {
- const block: PartialBlock = {
- id: UniqueID.options.generateID(),
- type: "paragraph",
- content: [
- {
- type: "text",
- text: "\nText1",
- styles: {},
- },
- ],
- };
- const node = blockToNode(block, tt.schema);
- expect(node).toMatchSnapshot();
- const outputBlock = nodeToBlock(
- node,
- defaultBlockSchema
- );
-
- // Temporary fix to set props to {}, because at this point
- // we don't have an easy way to access default props at runtime,
- // so partialBlockToBlockForTesting will not set them.
- (outputBlock as any).props = {};
- const fullOriginalBlock = partialBlockToBlockForTesting(block);
-
- expect(outputBlock).toStrictEqual(fullOriginalBlock);
- });
-
- it("Convert a block with a hard break at the end", async () => {
- const block: PartialBlock = {
- id: UniqueID.options.generateID(),
- type: "paragraph",
- content: [
- {
- type: "text",
- text: "Text1\n",
- styles: {},
- },
- ],
- };
- const node = blockToNode(block, tt.schema);
- expect(node).toMatchSnapshot();
- const outputBlock = nodeToBlock(
- node,
- defaultBlockSchema
- );
-
- // Temporary fix to set props to {}, because at this point
- // we don't have an easy way to access default props at runtime,
- // so partialBlockToBlockForTesting will not set them.
- (outputBlock as any).props = {};
- const fullOriginalBlock = partialBlockToBlockForTesting(block);
-
- expect(outputBlock).toStrictEqual(fullOriginalBlock);
- });
-
- it("Convert a block with only a hard break", async () => {
- const block: PartialBlock = {
- id: UniqueID.options.generateID(),
- type: "paragraph",
- content: [
- {
- type: "text",
- text: "\n",
- styles: {},
- },
- ],
- };
- const node = blockToNode(block, tt.schema);
- expect(node).toMatchSnapshot();
- const outputBlock = nodeToBlock(
- node,
- defaultBlockSchema
- );
-
- // Temporary fix to set props to {}, because at this point
- // we don't have an easy way to access default props at runtime,
- // so partialBlockToBlockForTesting will not set them.
- (outputBlock as any).props = {};
- const fullOriginalBlock = partialBlockToBlockForTesting(block);
-
- expect(outputBlock).toStrictEqual(fullOriginalBlock);
- });
-
- it("Convert a block with a hard break and different styles", async () => {
- const block: PartialBlock = {
- id: UniqueID.options.generateID(),
- type: "paragraph",
- content: [
- {
- type: "text",
- text: "Text1\n",
- styles: {},
- },
- {
- type: "text",
- text: "Text2",
- styles: { bold: true },
- },
- ],
- };
- const node = blockToNode(block, tt.schema);
- expect(node).toMatchSnapshot();
- const outputBlock = nodeToBlock(
- node,
- defaultBlockSchema
- );
-
- // Temporary fix to set props to {}, because at this point
- // we don't have an easy way to access default props at runtime,
- // so partialBlockToBlockForTesting will not set them.
- (outputBlock as any).props = {};
- const fullOriginalBlock = partialBlockToBlockForTesting(block);
-
- expect(outputBlock).toStrictEqual(fullOriginalBlock);
- });
-
- it("Convert a block with a hard break in a link", async () => {
- const block: PartialBlock = {
- id: UniqueID.options.generateID(),
- type: "paragraph",
- content: [
- {
- type: "link",
- href: "https://www.website.com",
- content: "Link1\nLink1",
- },
- ],
- };
- const node = blockToNode(block, tt.schema);
- expect(node).toMatchSnapshot();
- const outputBlock = nodeToBlock(
- node,
- defaultBlockSchema
- );
-
- // Temporary fix to set props to {}, because at this point
- // we don't have an easy way to access default props at runtime,
- // so partialBlockToBlockForTesting will not set them.
- (outputBlock as any).props = {};
- const fullOriginalBlock = partialBlockToBlockForTesting(block);
-
- expect(outputBlock).toStrictEqual(fullOriginalBlock);
- });
-
- it("Convert a block with a hard break between links", async () => {
- const block: PartialBlock = {
- id: UniqueID.options.generateID(),
- type: "paragraph",
- content: [
- {
- type: "link",
- href: "https://www.website.com",
- content: "Link1\n",
- },
- {
- type: "link",
- href: "https://www.website2.com",
- content: "Link2",
- },
- ],
- };
- const node = blockToNode(block, tt.schema);
- expect(node).toMatchSnapshot();
- const outputBlock = nodeToBlock(
- node,
- defaultBlockSchema
- );
-
- // Temporary fix to set props to {}, because at this point
- // we don't have an easy way to access default props at runtime,
- // so partialBlockToBlockForTesting will not set them.
- (outputBlock as any).props = {};
- const fullOriginalBlock = partialBlockToBlockForTesting(block);
-
- expect(outputBlock).toStrictEqual(fullOriginalBlock);
- });
+import { BlockNoteEditor } from "../../BlockNoteEditor";
+import { PartialBlock } from "../../extensions/Blocks/api/blocks/types";
+import { customInlineContentTestCases } from "../testCases/cases/customInlineContent";
+import { customStylesTestCases } from "../testCases/cases/customStyles";
+import { defaultSchemaTestCases } from "../testCases/cases/defaultSchema";
+import { blockToNode, nodeToBlock } from "./nodeConversions";
+import { addIdsToBlock, partialBlockToBlockForTesting } from "./testUtil";
+
+function validateConversion(
+ block: PartialBlock,
+ editor: BlockNoteEditor
+) {
+ addIdsToBlock(block);
+ const node = blockToNode(
+ block,
+ editor._tiptapEditor.schema,
+ editor.styleSchema
+ );
+
+ expect(node).toMatchSnapshot();
+
+ const outputBlock = nodeToBlock(
+ node,
+ editor.blockSchema,
+ editor.inlineContentSchema,
+ editor.styleSchema
+ );
+
+ const fullOriginalBlock = partialBlockToBlockForTesting(
+ editor.blockSchema,
+ block
+ );
+
+ expect(outputBlock).toStrictEqual(fullOriginalBlock);
+}
+
+const testCases = [
+ defaultSchemaTestCases,
+ customStylesTestCases,
+ customInlineContentTestCases,
+];
+
+describe("Test BlockNote-Prosemirror conversion", () => {
+ for (const testCase of testCases) {
+ describe("Case: " + testCase.name, () => {
+ let editor: BlockNoteEditor;
+
+ beforeEach(() => {
+ editor = testCase.createEditor();
+ });
+
+ afterEach(() => {
+ editor._tiptapEditor.destroy();
+ editor = undefined as any;
+
+ delete (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS;
+ });
+
+ for (const document of testCase.documents) {
+ // eslint-disable-next-line no-loop-func
+ it("Convert " + document.name + " to/from prosemirror", () => {
+ // NOTE: only converts first block
+ validateConversion(document.blocks[0], editor);
+ });
+ }
+ });
+ }
});
diff --git a/packages/core/src/api/nodeConversions/nodeConversions.ts b/packages/core/src/api/nodeConversions/nodeConversions.ts
index e20ff08315..391fbe1e5d 100644
--- a/packages/core/src/api/nodeConversions/nodeConversions.ts
+++ b/packages/core/src/api/nodeConversions/nodeConversions.ts
@@ -4,41 +4,51 @@ import {
Block,
BlockSchema,
PartialBlock,
-} from "../../extensions/Blocks/api/blockTypes";
+ PartialTableContent,
+ TableContent,
+} from "../../extensions/Blocks/api/blocks/types";
import {
- ColorStyle,
+ CustomInlineContentConfig,
+ CustomInlineContentFromConfig,
InlineContent,
+ InlineContentFromConfig,
+ InlineContentSchema,
+ PartialCustomInlineContentFromConfig,
PartialInlineContent,
PartialLink,
StyledText,
- Styles,
- ToggledStyle,
-} from "../../extensions/Blocks/api/inlineContentTypes";
+ isLinkInlineContent,
+ isPartialLinkInlineContent,
+ isStyledTextInlineContent,
+} from "../../extensions/Blocks/api/inlineContent/types";
+import { StyleSchema, Styles } from "../../extensions/Blocks/api/styles/types";
import { getBlockInfo } from "../../extensions/Blocks/helpers/getBlockInfoFromPos";
import UniqueID from "../../extensions/UniqueID/UniqueID";
import { UnreachableCaseError } from "../../shared/utils";
-const toggleStyles = new Set([
- "bold",
- "italic",
- "underline",
- "strike",
- "code",
-]);
-const colorStyles = new Set(["textColor", "backgroundColor"]);
-
/**
* Convert a StyledText inline element to a
* prosemirror text node with the appropriate marks
*/
-function styledTextToNodes(styledText: StyledText, schema: Schema): Node[] {
+function styledTextToNodes(
+ styledText: StyledText,
+ schema: Schema,
+ styleSchema: T
+): Node[] {
const marks: Mark[] = [];
for (const [style, value] of Object.entries(styledText.styles)) {
- if (toggleStyles.has(style as ToggledStyle)) {
+ const config = styleSchema[style];
+ if (!config) {
+ throw new Error(`style ${style} not found in styleSchema`);
+ }
+
+ if (config.propSchema === "boolean") {
marks.push(schema.mark(style));
- } else if (colorStyles.has(style as ColorStyle)) {
- marks.push(schema.mark(style, { color: value }));
+ } else if (config.propSchema === "string") {
+ marks.push(schema.mark(style, { stringValue: value }));
+ } else {
+ throw new UnreachableCaseError(config.propSchema);
}
}
@@ -64,42 +74,53 @@ function styledTextToNodes(styledText: StyledText, schema: Schema): Node[] {
* Converts a Link inline content element to
* prosemirror text nodes with the appropriate marks
*/
-function linkToNodes(link: PartialLink, schema: Schema): Node[] {
+function linkToNodes(
+ link: PartialLink,
+ schema: Schema,
+ styleSchema: StyleSchema
+): Node[] {
const linkMark = schema.marks.link.create({
href: link.href,
});
- return styledTextArrayToNodes(link.content, schema).map((node) => {
- if (node.type.name === "text") {
- return node.mark([...node.marks, linkMark]);
- }
+ return styledTextArrayToNodes(link.content, schema, styleSchema).map(
+ (node) => {
+ if (node.type.name === "text") {
+ return node.mark([...node.marks, linkMark]);
+ }
- if (node.type.name === "hardBreak") {
- return node;
+ if (node.type.name === "hardBreak") {
+ return node;
+ }
+ throw new Error("unexpected node type");
}
- throw new Error("unexpected node type");
- });
+ );
}
/**
* Converts an array of StyledText inline content elements to
* prosemirror text nodes with the appropriate marks
*/
-function styledTextArrayToNodes(
- content: string | StyledText[],
- schema: Schema
+function styledTextArrayToNodes(
+ content: string | StyledText[],
+ schema: Schema,
+ styleSchema: S
): Node[] {
const nodes: Node[] = [];
if (typeof content === "string") {
nodes.push(
- ...styledTextToNodes({ type: "text", text: content, styles: {} }, schema)
+ ...styledTextToNodes(
+ { type: "text", text: content, styles: {} },
+ schema,
+ styleSchema
+ )
);
return nodes;
}
for (const styledText of content) {
- nodes.push(...styledTextToNodes(styledText, schema));
+ nodes.push(...styledTextToNodes(styledText, schema, styleSchema));
}
return nodes;
}
@@ -107,44 +128,85 @@ function styledTextArrayToNodes(
/**
* converts an array of inline content elements to prosemirror nodes
*/
-export function inlineContentToNodes(
- blockContent: PartialInlineContent[],
- schema: Schema
+export function inlineContentToNodes<
+ I extends InlineContentSchema,
+ S extends StyleSchema
+>(
+ blockContent: PartialInlineContent,
+ schema: Schema,
+ styleSchema: S
): Node[] {
const nodes: Node[] = [];
for (const content of blockContent) {
- if (content.type === "link") {
- nodes.push(...linkToNodes(content, schema));
- } else if (content.type === "text") {
- nodes.push(...styledTextArrayToNodes([content], schema));
+ if (typeof content === "string") {
+ nodes.push(...styledTextArrayToNodes(content, schema, styleSchema));
+ } else if (isPartialLinkInlineContent(content)) {
+ nodes.push(...linkToNodes(content, schema, styleSchema));
+ } else if (isStyledTextInlineContent(content)) {
+ nodes.push(...styledTextArrayToNodes([content], schema, styleSchema));
} else {
- throw new UnreachableCaseError(content);
+ nodes.push(
+ blockOrInlineContentToContentNode(content, schema, styleSchema)
+ );
}
}
return nodes;
}
/**
- * Converts a BlockNote block to a TipTap node.
+ * converts an array of inline content elements to prosemirror nodes
*/
-export function blockToNode(
- block: PartialBlock,
- schema: Schema
-) {
- let id = block.id;
+export function tableContentToNodes<
+ I extends InlineContentSchema,
+ S extends StyleSchema
+>(
+ tableContent: PartialTableContent,
+ schema: Schema,
+ styleSchema: StyleSchema
+): Node[] {
+ const rowNodes: Node[] = [];
+
+ for (const row of tableContent.rows) {
+ const columnNodes: Node[] = [];
+ for (const cell of row.cells) {
+ let pNode: Node;
+ if (!cell) {
+ pNode = schema.nodes["tableParagraph"].create({});
+ } else if (typeof cell === "string") {
+ pNode = schema.nodes["tableParagraph"].create({}, schema.text(cell));
+ } else {
+ const textNodes = inlineContentToNodes(cell, schema, styleSchema);
+ pNode = schema.nodes["tableParagraph"].create({}, textNodes);
+ }
- if (id === undefined) {
- id = UniqueID.options.generateID();
+ const cellNode = schema.nodes["tableCell"].create({}, pNode);
+ columnNodes.push(cellNode);
+ }
+ const rowNode = schema.nodes["tableRow"].create({}, columnNodes);
+ rowNodes.push(rowNode);
}
+ return rowNodes;
+}
+function blockOrInlineContentToContentNode(
+ block:
+ | PartialBlock
+ | PartialCustomInlineContentFromConfig,
+ schema: Schema,
+ styleSchema: StyleSchema
+) {
+ let contentNode: Node;
let type = block.type;
+ // TODO: needed? came from previous code
if (type === undefined) {
type = "paragraph";
}
- let contentNode: Node;
+ if (!schema.nodes[type]) {
+ throw new Error(`node type ${type} not found in schema`);
+ }
if (!block.content) {
contentNode = schema.nodes[type].create(block.props);
@@ -153,16 +215,42 @@ export function blockToNode(
block.props,
schema.text(block.content)
);
- } else {
- const nodes = inlineContentToNodes(block.content, schema);
+ } else if (Array.isArray(block.content)) {
+ const nodes = inlineContentToNodes(block.content, schema, styleSchema);
+ contentNode = schema.nodes[type].create(block.props, nodes);
+ } else if (block.content.type === "tableContent") {
+ const nodes = tableContentToNodes(block.content, schema, styleSchema);
contentNode = schema.nodes[type].create(block.props, nodes);
+ } else {
+ throw new UnreachableCaseError(block.content.type);
+ }
+ return contentNode;
+}
+/**
+ * Converts a BlockNote block to a TipTap node.
+ */
+export function blockToNode(
+ block: PartialBlock,
+ schema: Schema,
+ styleSchema: StyleSchema
+) {
+ let id = block.id;
+
+ if (id === undefined) {
+ id = UniqueID.options.generateID();
}
+ const contentNode = blockOrInlineContentToContentNode(
+ block,
+ schema,
+ styleSchema
+ );
+
const children: Node[] = [];
if (block.children) {
for (const child of block.children) {
- children.push(blockToNode(child, schema));
+ children.push(blockToNode(child, schema, styleSchema));
}
}
@@ -177,12 +265,48 @@ export function blockToNode(
);
}
+/**
+ * Converts an internal (prosemirror) table node contentto a BlockNote Tablecontent
+ */
+function contentNodeToTableContent<
+ I extends InlineContentSchema,
+ S extends StyleSchema
+>(contentNode: Node, inlineContentSchema: I, styleSchema: S) {
+ const ret: TableContent = {
+ type: "tableContent",
+ rows: [],
+ };
+
+ contentNode.content.forEach((rowNode) => {
+ const row: TableContent["rows"][0] = {
+ cells: [],
+ };
+
+ rowNode.content.forEach((cellNode) => {
+ row.cells.push(
+ contentNodeToInlineContent(
+ cellNode.firstChild!,
+ inlineContentSchema,
+ styleSchema
+ )
+ );
+ });
+
+ ret.rows.push(row);
+ });
+
+ return ret;
+}
+
/**
* Converts an internal (prosemirror) content node to a BlockNote InlineContent array.
*/
-function contentNodeToInlineContent(contentNode: Node) {
- const content: InlineContent[] = [];
- let currentContent: InlineContent | undefined = undefined;
+export function contentNodeToInlineContent<
+ I extends InlineContentSchema,
+ S extends StyleSchema
+>(contentNode: Node, inlineContentSchema: I, styleSchema: S) {
+ const content: InlineContent[] = [];
+ let currentContent: InlineContent | undefined = undefined;
// Most of the logic below is for handling links because in ProseMirror links are marks
// while in BlockNote links are a type of inline content
@@ -192,13 +316,15 @@ function contentNodeToInlineContent(contentNode: Node) {
if (node.type.name === "hardBreak") {
if (currentContent) {
// Current content exists.
- if (currentContent.type === "text") {
+ if (isStyledTextInlineContent(currentContent)) {
// Current content is text.
currentContent.text += "\n";
- } else if (currentContent.type === "link") {
+ } else if (isLinkInlineContent(currentContent)) {
// Current content is a link.
currentContent.content[currentContent.content.length - 1].text +=
"\n";
+ } else {
+ throw new Error("unexpected");
}
} else {
// Current content does not exist.
@@ -212,18 +338,41 @@ function contentNodeToInlineContent(contentNode: Node) {
return;
}
- const styles: Styles = {};
+ if (
+ node.type.name !== "link" &&
+ node.type.name !== "text" &&
+ inlineContentSchema[node.type.name]
+ ) {
+ if (currentContent) {
+ content.push(currentContent);
+ currentContent = undefined;
+ }
+
+ content.push(
+ nodeToCustomInlineContent(node, inlineContentSchema, styleSchema)
+ );
+
+ return;
+ }
+
+ const styles: Styles = {};
let linkMark: Mark | undefined;
for (const mark of node.marks) {
if (mark.type.name === "link") {
linkMark = mark;
- } else if (toggleStyles.has(mark.type.name as ToggledStyle)) {
- styles[mark.type.name as ToggledStyle] = true;
- } else if (colorStyles.has(mark.type.name as ColorStyle)) {
- styles[mark.type.name as ColorStyle] = mark.attrs.color;
} else {
- throw Error("Mark is of an unrecognized type: " + mark.type.name);
+ const config = styleSchema[mark.type.name];
+ if (!config) {
+ throw new Error(`style ${mark.type.name} not found in styleSchema`);
+ }
+ if (config.propSchema === "boolean") {
+ (styles as any)[config.type] = true;
+ } else if (config.propSchema === "string") {
+ (styles as any)[config.type] = mark.attrs.stringValue;
+ } else {
+ throw new UnreachableCaseError(config.propSchema);
+ }
}
}
@@ -231,7 +380,7 @@ function contentNodeToInlineContent(contentNode: Node) {
// Current content exists.
if (currentContent) {
// Current content is text.
- if (currentContent.type === "text") {
+ if (isStyledTextInlineContent(currentContent)) {
if (!linkMark) {
// Node is text (same type as current content).
if (
@@ -263,7 +412,7 @@ function contentNodeToInlineContent(contentNode: Node) {
],
};
}
- } else if (currentContent.type === "link") {
+ } else if (isLinkInlineContent(currentContent)) {
// Current content is a link.
if (linkMark) {
// Node is a link (same type as current content).
@@ -309,6 +458,8 @@ function contentNodeToInlineContent(contentNode: Node) {
styles,
};
}
+ } else {
+ // TODO
}
}
// Current content does not exist.
@@ -342,17 +493,66 @@ function contentNodeToInlineContent(contentNode: Node) {
content.push(currentContent);
}
- return content;
+ return content as InlineContent[];
+}
+
+export function nodeToCustomInlineContent<
+ I extends InlineContentSchema,
+ S extends StyleSchema
+>(node: Node, inlineContentSchema: I, styleSchema: S): InlineContent {
+ if (node.type.name === "text" || node.type.name === "link") {
+ throw new Error("unexpected");
+ }
+ const props: any = {};
+ const icConfig = inlineContentSchema[
+ node.type.name
+ ] as CustomInlineContentConfig;
+ for (const [attr, value] of Object.entries(node.attrs)) {
+ if (!icConfig) {
+ throw Error("ic node is of an unrecognized type: " + node.type.name);
+ }
+
+ const propSchema = icConfig.propSchema;
+
+ if (attr in propSchema) {
+ props[attr] = value;
+ }
+ }
+
+ let content: CustomInlineContentFromConfig["content"];
+
+ if (icConfig.content === "styled") {
+ content = contentNodeToInlineContent(
+ node,
+ inlineContentSchema,
+ styleSchema
+ ) as any; // TODO: is this safe? could we have Links here that are undesired?
+ } else {
+ content = undefined;
+ }
+
+ const ic = {
+ type: node.type.name,
+ props,
+ content,
+ } as InlineContentFromConfig;
+ return ic;
}
/**
* Convert a TipTap node to a BlockNote block.
*/
-export function nodeToBlock(
+export function nodeToBlock<
+ BSchema extends BlockSchema,
+ I extends InlineContentSchema,
+ S extends StyleSchema
+>(
node: Node,
blockSchema: BSchema,
- blockCache?: WeakMap>
-): Block {
+ inlineContentSchema: I,
+ styleSchema: S,
+ blockCache?: WeakMap>
+): Block {
if (node.type.name !== "blockContainer") {
throw Error(
"Node must be of type blockContainer, but is of type" +
@@ -382,6 +582,7 @@ export function nodeToBlock(
...blockInfo.contentNode.attrs,
})) {
const blockSpec = blockSchema[blockInfo.contentType.name];
+
if (!blockSpec) {
throw Error(
"Block is of an unrecognized type: " + blockInfo.contentType.name
@@ -395,25 +596,48 @@ export function nodeToBlock(
}
}
- const blockSpec = blockSchema[blockInfo.contentType.name];
+ const blockConfig = blockSchema[blockInfo.contentType.name];
- const children: Block[] = [];
+ const children: Block[] = [];
for (let i = 0; i < blockInfo.numChildBlocks; i++) {
children.push(
- nodeToBlock(node.lastChild!.child(i), blockSchema, blockCache)
+ nodeToBlock(
+ node.lastChild!.child(i),
+ blockSchema,
+ inlineContentSchema,
+ styleSchema,
+ blockCache
+ )
);
}
- const block: Block = {
+ let content: Block["content"];
+
+ if (blockConfig.content === "inline") {
+ content = contentNodeToInlineContent(
+ blockInfo.contentNode,
+ inlineContentSchema,
+ styleSchema
+ );
+ } else if (blockConfig.content === "table") {
+ content = contentNodeToTableContent(
+ blockInfo.contentNode,
+ inlineContentSchema,
+ styleSchema
+ );
+ } else if (blockConfig.content === "none") {
+ content = undefined;
+ } else {
+ throw new UnreachableCaseError(blockConfig.content);
+ }
+
+ const block = {
id,
- type: blockSpec.node.name,
+ type: blockConfig.type,
props,
- content:
- blockSpec.node.config.content === "inline*"
- ? contentNodeToInlineContent(blockInfo.contentNode)
- : undefined,
+ content,
children,
- } as Block;
+ } as Block;
blockCache?.set(node, block);
diff --git a/packages/core/src/api/nodeConversions/testUtil.ts b/packages/core/src/api/nodeConversions/testUtil.ts
index c1740b0120..3398e19d2d 100644
--- a/packages/core/src/api/nodeConversions/testUtil.ts
+++ b/packages/core/src/api/nodeConversions/testUtil.ts
@@ -2,16 +2,22 @@ import {
Block,
BlockSchema,
PartialBlock,
-} from "../../extensions/Blocks/api/blockTypes";
+ TableContent,
+} from "../../extensions/Blocks/api/blocks/types";
import {
InlineContent,
+ InlineContentSchema,
PartialInlineContent,
StyledText,
-} from "../../extensions/Blocks/api/inlineContentTypes";
+ isPartialLinkInlineContent,
+ isStyledTextInlineContent,
+} from "../../extensions/Blocks/api/inlineContent/types";
+import { StyleSchema } from "../../extensions/Blocks/api/styles/types";
+import UniqueID from "../../extensions/UniqueID/UniqueID";
function textShorthandToStyledText(
- content: string | StyledText[] = ""
-): StyledText[] {
+ content: string | StyledText[] = ""
+): StyledText[] {
if (typeof content === "string") {
return [
{
@@ -25,41 +31,97 @@ function textShorthandToStyledText(
}
function partialContentToInlineContent(
- content: string | PartialInlineContent[] = ""
-): InlineContent[] {
+ content: PartialInlineContent | TableContent | undefined
+): InlineContent[] | TableContent | undefined {
if (typeof content === "string") {
return textShorthandToStyledText(content);
}
- return content.map((partialContent) => {
- if (partialContent.type === "link") {
- return {
- ...partialContent,
- content: textShorthandToStyledText(partialContent.content),
- };
- } else {
- return partialContent;
- }
- });
+ if (Array.isArray(content)) {
+ return content.flatMap((partialContent) => {
+ if (typeof partialContent === "string") {
+ return textShorthandToStyledText(partialContent);
+ } else if (isPartialLinkInlineContent(partialContent)) {
+ return {
+ ...partialContent,
+ content: textShorthandToStyledText(partialContent.content),
+ };
+ } else if (isStyledTextInlineContent(partialContent)) {
+ return partialContent;
+ } else {
+ // custom inline content
+
+ return {
+ props: {},
+ ...partialContent,
+ content: partialContentToInlineContent(partialContent.content),
+ } as any;
+ }
+ });
+ }
+
+ return content;
}
-export function partialBlockToBlockForTesting(
- partialBlock: PartialBlock
-): Block {
- const withDefaults = {
+export function partialBlocksToBlocksForTesting<
+ BSchema extends BlockSchema,
+ I extends InlineContentSchema,
+ S extends StyleSchema
+>(
+ schema: BSchema,
+ partialBlocks: Array>
+): Array> {
+ return partialBlocks.map((partialBlock) =>
+ partialBlockToBlockForTesting(schema, partialBlock)
+ );
+}
+
+export function partialBlockToBlockForTesting<
+ BSchema extends BlockSchema,
+ I extends InlineContentSchema,
+ S extends StyleSchema
+>(
+ schema: BSchema,
+ partialBlock: PartialBlock
+): Block {
+ const withDefaults: Block