@blocknote/core 0.3.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/blocknote.js +12698 -1341
  2. package/dist/blocknote.js.map +1 -1
  3. package/dist/blocknote.umd.cjs +50 -1
  4. package/dist/blocknote.umd.cjs.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +16 -5
  7. package/src/BlockNoteEditor.test.ts +12 -0
  8. package/src/BlockNoteEditor.ts +39 -15
  9. package/src/BlockNoteExtensions.ts +36 -32
  10. package/src/api/Editor.ts +226 -0
  11. package/src/api/blockManipulation/__snapshots__/blockManipulation.test.ts.snap +616 -0
  12. package/src/api/blockManipulation/blockManipulation.test.ts +172 -0
  13. package/src/api/blockManipulation/blockManipulation.ts +125 -0
  14. package/src/api/formatConversions/__snapshots__/formatConversions.test.ts.snap +346 -0
  15. package/src/api/formatConversions/formatConversions.test.ts +766 -0
  16. package/src/api/formatConversions/formatConversions.ts +86 -0
  17. package/src/api/formatConversions/removeUnderlinesRehypePlugin.ts +39 -0
  18. package/src/api/formatConversions/simplifyBlocksRehypePlugin.ts +125 -0
  19. package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +268 -0
  20. package/src/api/nodeConversions/nodeConversions.test.ts +244 -0
  21. package/src/api/nodeConversions/nodeConversions.ts +279 -0
  22. package/src/api/nodeConversions/testUtil.ts +61 -0
  23. package/src/api/util/nodeUtil.ts +38 -0
  24. package/src/editor.module.css +8 -1
  25. package/src/extensions/Blocks/PreviousBlockTypePlugin.ts +7 -1
  26. package/src/extensions/Blocks/api/blockTypes.ts +90 -0
  27. package/src/extensions/Blocks/api/cursorPositionTypes.ts +5 -0
  28. package/src/extensions/Blocks/api/inlineContentTypes.ts +35 -0
  29. package/src/extensions/Blocks/helpers/getBlockInfoFromPos.ts +4 -4
  30. package/src/extensions/Blocks/nodes/Block.module.css +39 -36
  31. package/src/extensions/Blocks/nodes/BlockContainer.ts +74 -23
  32. package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +23 -5
  33. package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +28 -6
  34. package/src/extensions/DraggableBlocks/DraggableBlocksPlugin.ts +149 -87
  35. package/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.ts +2 -2
  36. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +3 -3
  37. package/src/extensions/SlashMenu/SlashMenuExtension.ts +7 -12
  38. package/src/extensions/SlashMenu/SlashMenuItem.ts +4 -1
  39. package/src/extensions/SlashMenu/{defaultCommands.tsx → defaultSlashCommands.tsx} +34 -17
  40. package/src/extensions/SlashMenu/index.ts +7 -4
  41. package/src/extensions/UniqueID/UniqueID.ts +1 -1
  42. package/src/index.ts +4 -2
  43. package/src/shared/utils.ts +6 -0
  44. package/types/src/BlockNoteEditor.d.ts +13 -4
  45. package/types/src/BlockNoteEditor.test.d.ts +1 -0
  46. package/types/src/BlockNoteExtensions.d.ts +7 -3
  47. package/types/src/api/Editor.d.ts +93 -0
  48. package/types/src/api/blockManipulation/blockManipulation.d.ts +6 -0
  49. package/types/src/api/blockManipulation/blockManipulation.test.d.ts +1 -0
  50. package/types/src/api/formatConversions/formatConversions.d.ts +6 -0
  51. package/types/src/api/formatConversions/formatConversions.test.d.ts +1 -0
  52. package/types/src/api/formatConversions/removeUnderlinesRehypePlugin.d.ts +6 -0
  53. package/types/src/api/formatConversions/simplifyBlocksRehypePlugin.d.ts +16 -0
  54. package/types/src/api/nodeConversions/nodeConversions.d.ts +15 -0
  55. package/types/src/api/nodeConversions/nodeConversions.test.d.ts +1 -0
  56. package/types/src/api/nodeConversions/testUtil.d.ts +2 -0
  57. package/types/src/api/util/nodeUtil.d.ts +8 -0
  58. package/types/src/extensions/Blocks/api/blockTypes.d.ts +37 -0
  59. package/types/src/extensions/Blocks/api/cursorPositionTypes.d.ts +4 -0
  60. package/types/src/extensions/Blocks/api/inlineContentTypes.d.ts +29 -0
  61. package/types/src/extensions/Blocks/nodes/BlockContainer.d.ts +3 -3
  62. package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +15 -0
  63. package/types/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.d.ts +2 -2
  64. package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +1 -3
  65. package/types/src/extensions/SlashMenu/SlashMenuItem.d.ts +4 -1
  66. package/types/src/extensions/SlashMenu/index.d.ts +3 -3
  67. package/types/src/index.d.ts +4 -2
  68. package/types/src/shared/utils.d.ts +3 -0
  69. package/src/extensions/Blocks/apiTypes.ts +0 -48
  70. package/types/src/EditorElement.d.ts +0 -7
  71. package/types/src/api/Document.d.ts +0 -5
  72. package/types/src/extensions/Blocks/BlockAttributes.d.ts +0 -2
  73. package/types/src/extensions/Blocks/MultipleNodeSelection.d.ts +0 -24
  74. package/types/src/extensions/Blocks/apiTypes.d.ts +0 -16
  75. package/types/src/extensions/Blocks/nodes/Block.d.ts +0 -24
  76. package/types/src/extensions/Blocks/nodes/BlockContent/BlockContentTypes.d.ts +0 -4
  77. package/types/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContentTypes.d.ts +0 -4
  78. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContentTypes.d.ts +0 -2
  79. package/types/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContentTypes.d.ts +0 -2
  80. package/types/src/extensions/Blocks/nodes/BlockContent/ParagraphBlockContent/ParagraphBlockContentTypes.d.ts +0 -2
  81. package/types/src/extensions/Blocks/nodes/BlockTypes/HeadingBlock/HeadingContent.d.ts +0 -8
  82. package/types/src/extensions/Blocks/nodes/BlockTypes/ListItemBlock/ListItemContent.d.ts +0 -8
  83. package/types/src/extensions/Blocks/nodes/BlockTypes/ListItemBlock/OrderedListItemIndexPlugin.d.ts +0 -2
  84. package/types/src/extensions/Blocks/nodes/BlockTypes/TextBlock/TextContent.d.ts +0 -6
  85. package/types/src/extensions/BubbleMenu/BubbleMenuExtension.d.ts +0 -8
  86. package/types/src/extensions/BubbleMenu/BubbleMenuFactoryTypes.d.ts +0 -27
  87. package/types/src/extensions/BubbleMenu/BubbleMenuPlugin.d.ts +0 -44
  88. package/types/src/extensions/DraggableBlocks/BlockMenuFactoryTypes.d.ts +0 -12
  89. package/types/src/extensions/DraggableBlocks/DragMenuFactoryTypes.d.ts +0 -18
  90. package/types/src/extensions/Hyperlinks/HyperlinkMark.d.ts +0 -8
  91. package/types/src/extensions/Hyperlinks/HyperlinkMenuFactoryTypes.d.ts +0 -11
  92. package/types/src/extensions/Hyperlinks/HyperlinkMenuPlugin.d.ts +0 -11
  93. package/types/src/extensions/Paragraph/FixedParagraph.d.ts +0 -1
  94. package/types/src/extensions/SlashMenu/defaultCommands.d.ts +0 -8
  95. package/types/src/utils.d.ts +0 -2
@@ -0,0 +1,86 @@
1
+ import { DOMParser, DOMSerializer, Schema } from "prosemirror-model";
2
+ import { unified } from "unified";
3
+ import rehypeParse from "rehype-parse";
4
+ import rehypeStringify from "rehype-stringify";
5
+ import rehypeRemark from "rehype-remark";
6
+ import remarkGfm from "remark-gfm";
7
+ import remarkStringify from "remark-stringify";
8
+ import remarkParse from "remark-parse";
9
+ import remarkRehype from "remark-rehype";
10
+ import { Block } from "../../extensions/Blocks/api/blockTypes";
11
+ import { blockToNode, nodeToBlock } from "../nodeConversions/nodeConversions";
12
+ import { simplifyBlocks } from "./simplifyBlocksRehypePlugin";
13
+ import { removeUnderlines } from "./removeUnderlinesRehypePlugin";
14
+
15
+ export async function blocksToHTML(
16
+ blocks: Block[],
17
+ schema: Schema
18
+ ): Promise<string> {
19
+ const htmlParentElement = document.createElement("div");
20
+ const serializer = DOMSerializer.fromSchema(schema);
21
+
22
+ for (const block of blocks) {
23
+ const node = blockToNode(block, schema);
24
+ const htmlNode = serializer.serializeNode(node);
25
+ htmlParentElement.appendChild(htmlNode);
26
+ }
27
+
28
+ const htmlString = await unified()
29
+ .use(rehypeParse, { fragment: true })
30
+ .use(simplifyBlocks, {
31
+ orderedListItemBlockTypes: new Set<string>(["numberedListItem"]),
32
+ unorderedListItemBlockTypes: new Set<string>(["bulletListItem"]),
33
+ })
34
+ .use(rehypeStringify)
35
+ .process(htmlParentElement.innerHTML);
36
+
37
+ return htmlString.value as string;
38
+ }
39
+
40
+ export async function HTMLToBlocks(
41
+ htmlString: string,
42
+ schema: Schema
43
+ ): Promise<Block[]> {
44
+ const htmlNode = document.createElement("div");
45
+ htmlNode.innerHTML = htmlString.trim();
46
+
47
+ const parser = DOMParser.fromSchema(schema);
48
+ const parentNode = parser.parse(htmlNode);
49
+
50
+ const blocks: Block[] = [];
51
+
52
+ for (let i = 0; i < parentNode.firstChild!.childCount; i++) {
53
+ blocks.push(nodeToBlock(parentNode.firstChild!.child(i)));
54
+ }
55
+
56
+ return blocks;
57
+ }
58
+
59
+ export async function blocksToMarkdown(
60
+ blocks: Block[],
61
+ schema: Schema
62
+ ): Promise<string> {
63
+ const markdownString = await unified()
64
+ .use(rehypeParse, { fragment: true })
65
+ .use(removeUnderlines)
66
+ .use(rehypeRemark)
67
+ .use(remarkGfm)
68
+ .use(remarkStringify)
69
+ .process(await blocksToHTML(blocks, schema));
70
+
71
+ return markdownString.value as string;
72
+ }
73
+
74
+ export async function markdownToBlocks(
75
+ markdownString: string,
76
+ schema: Schema
77
+ ): Promise<Block[]> {
78
+ const htmlString = await unified()
79
+ .use(remarkParse)
80
+ .use(remarkGfm)
81
+ .use(remarkRehype)
82
+ .use(rehypeStringify)
83
+ .process(markdownString);
84
+
85
+ return HTMLToBlocks(htmlString.value as string, schema);
86
+ }
@@ -0,0 +1,39 @@
1
+ import { Element as HASTElement, Parent as HASTParent } from "hast";
2
+
3
+ /**
4
+ * Rehype plugin which removes <u> tags. Used to remove underlines before converting HTML to markdown, as Markdown
5
+ * doesn't support underlines.
6
+ */
7
+ export function removeUnderlines() {
8
+ const removeUnderlinesHelper = (tree: HASTParent) => {
9
+ let numChildElements = tree.children.length;
10
+
11
+ for (let i = 0; i < numChildElements; i++) {
12
+ const node = tree.children[i];
13
+
14
+ if (node.type === "element") {
15
+ // Recursively removes underlines from child elements.
16
+ removeUnderlinesHelper(node);
17
+
18
+ if ((node as HASTElement).tagName === "u") {
19
+ // Lifts child nodes outside underline element, deletes the underline element, and updates current index &
20
+ // the number of child elements.
21
+ if (node.children.length > 0) {
22
+ tree.children.splice(i, 1, ...node.children);
23
+
24
+ const numElementsAdded = node.children.length - 1;
25
+ numChildElements += numElementsAdded;
26
+ i += numElementsAdded;
27
+ } else {
28
+ tree.children.splice(i, 1);
29
+
30
+ numChildElements--;
31
+ i--;
32
+ }
33
+ }
34
+ }
35
+ }
36
+ };
37
+
38
+ return removeUnderlinesHelper;
39
+ }
@@ -0,0 +1,125 @@
1
+ import { Element as HASTElement, Parent as HASTParent } from "hast";
2
+ import { fromDom } from "hast-util-from-dom";
3
+
4
+ type SimplifyBlocksOptions = {
5
+ orderedListItemBlockTypes: Set<string>;
6
+ unorderedListItemBlockTypes: Set<string>;
7
+ };
8
+
9
+ /**
10
+ * Rehype plugin which converts the HTML output string rendered by BlockNote into a simplified structure which better
11
+ * follows HTML standards. It does several things:
12
+ * - Removes all block related div elements, leaving only the actual content inside the block.
13
+ * - Lifts nested blocks to a higher level for all block types that don't represent list items.
14
+ * - Wraps blocks which represent list items in corresponding ul/ol HTML elements and restructures them to comply
15
+ * with HTML list structure.
16
+ * @param options Options for specifying which block types represent ordered and unordered list items.
17
+ */
18
+ export function simplifyBlocks(options: SimplifyBlocksOptions) {
19
+ const listItemBlockTypes = new Set<string>([
20
+ ...options.orderedListItemBlockTypes,
21
+ ...options.unorderedListItemBlockTypes,
22
+ ]);
23
+
24
+ const simplifyBlocksHelper = (tree: HASTParent) => {
25
+ let numChildElements = tree.children.length;
26
+ let activeList: HASTElement | undefined;
27
+
28
+ for (let i = 0; i < numChildElements; i++) {
29
+ const blockOuter = tree.children[i] as HASTElement;
30
+ const blockContainer = blockOuter.children[0] as HASTElement;
31
+ const blockContent = blockContainer.children[0] as HASTElement;
32
+ const blockGroup =
33
+ blockContainer.children.length === 2
34
+ ? (blockContainer.children[1] as HASTElement)
35
+ : null;
36
+
37
+ const isListItemBlock = listItemBlockTypes.has(
38
+ blockContent.properties!["dataContentType"] as string
39
+ );
40
+
41
+ const listItemBlockType = isListItemBlock
42
+ ? options.orderedListItemBlockTypes.has(
43
+ blockContent.properties!["dataContentType"] as string
44
+ )
45
+ ? "ol"
46
+ : "ul"
47
+ : null;
48
+
49
+ // Plugin runs recursively to process nested blocks.
50
+ if (blockGroup !== null) {
51
+ simplifyBlocksHelper(blockGroup);
52
+ }
53
+
54
+ // Checks that there is an active list, but the block can't be added to it as it's of a different type.
55
+ if (activeList && activeList.tagName !== listItemBlockType) {
56
+ // Blocks that were copied into the list are removed and the list is inserted in their place.
57
+ tree.children.splice(
58
+ i - activeList.children.length,
59
+ activeList.children.length,
60
+ activeList
61
+ );
62
+
63
+ // Updates the current index and number of child elements.
64
+ const numElementsRemoved = activeList.children.length - 1;
65
+ i -= numElementsRemoved;
66
+ numChildElements -= numElementsRemoved;
67
+
68
+ activeList = undefined;
69
+ }
70
+
71
+ // Checks if the block represents a list item.
72
+ if (isListItemBlock) {
73
+ // Checks if a list isn't already active. We don't have to check if the block and the list are of the same
74
+ // type as this was already done earlier.
75
+ if (!activeList) {
76
+ // Creates a new list element to represent an active list.
77
+ activeList = fromDom(
78
+ document.createElement(listItemBlockType!)
79
+ ) as HASTElement;
80
+ }
81
+
82
+ // Creates a new list item element to represent the block.
83
+ const listItemElement = fromDom(
84
+ document.createElement("li")
85
+ ) as HASTElement;
86
+
87
+ // Adds only the content inside the block to the active list.
88
+ listItemElement.children.push(blockContent.children[0]);
89
+ // Nested blocks have already been processed in the recursive function call, so the resulting elements are
90
+ // also added to the active list.
91
+ if (blockGroup !== null) {
92
+ listItemElement.children.push(...blockGroup.children);
93
+ }
94
+
95
+ // Adds the list item representing the block to the active list.
96
+ activeList.children.push(listItemElement);
97
+ } else if (blockGroup !== null) {
98
+ // Lifts all children out of the current block, as only list items should allow nesting.
99
+ tree.children.splice(i + 1, 0, ...blockGroup.children);
100
+ // Replaces the block with only the content inside it.
101
+ tree.children[i] = blockContent.children[0];
102
+
103
+ // Updates the current index and number of child elements.
104
+ const numElementsAdded = blockGroup.children.length;
105
+ i += numElementsAdded;
106
+ numChildElements += numElementsAdded;
107
+ } else {
108
+ // Replaces the block with only the content inside it.
109
+ tree.children[i] = blockContent.children[0];
110
+ }
111
+ }
112
+
113
+ // Since the active list is only inserted after encountering a block which can't be added to it, there are cases
114
+ // where it remains un-inserted after processing all blocks, which are handled here.
115
+ if (activeList) {
116
+ tree.children.splice(
117
+ numChildElements - activeList.children.length,
118
+ activeList.children.length,
119
+ activeList
120
+ );
121
+ }
122
+ };
123
+
124
+ return simplifyBlocksHelper;
125
+ }
@@ -0,0 +1,268 @@
1
+ // Vitest Snapshot v1
2
+
3
+ exports[`Complex ProseMirror Node Conversions > Convert complex block to node 1`] = `
4
+ {
5
+ "attrs": {
6
+ "backgroundColor": "blue",
7
+ "id": 4,
8
+ "textColor": "yellow",
9
+ },
10
+ "content": [
11
+ {
12
+ "attrs": {
13
+ "level": "2",
14
+ "textAlignment": "right",
15
+ },
16
+ "content": [
17
+ {
18
+ "marks": [
19
+ {
20
+ "type": "bold",
21
+ },
22
+ {
23
+ "type": "underline",
24
+ },
25
+ ],
26
+ "text": "Heading ",
27
+ "type": "text",
28
+ },
29
+ {
30
+ "marks": [
31
+ {
32
+ "type": "italic",
33
+ },
34
+ {
35
+ "type": "strike",
36
+ },
37
+ ],
38
+ "text": "2",
39
+ "type": "text",
40
+ },
41
+ ],
42
+ "type": "heading",
43
+ },
44
+ {
45
+ "content": [
46
+ {
47
+ "attrs": {
48
+ "backgroundColor": "red",
49
+ "id": 5,
50
+ "textColor": "default",
51
+ },
52
+ "content": [
53
+ {
54
+ "attrs": {
55
+ "textAlignment": "left",
56
+ },
57
+ "content": [
58
+ {
59
+ "text": "Paragraph",
60
+ "type": "text",
61
+ },
62
+ ],
63
+ "type": "paragraph",
64
+ },
65
+ ],
66
+ "type": "blockContainer",
67
+ },
68
+ {
69
+ "attrs": {
70
+ "backgroundColor": "default",
71
+ "id": 6,
72
+ "textColor": "default",
73
+ },
74
+ "content": [
75
+ {
76
+ "attrs": {
77
+ "textAlignment": "left",
78
+ },
79
+ "type": "bulletListItem",
80
+ },
81
+ ],
82
+ "type": "blockContainer",
83
+ },
84
+ ],
85
+ "type": "blockGroup",
86
+ },
87
+ ],
88
+ "type": "blockContainer",
89
+ }
90
+ `;
91
+
92
+ exports[`Complex ProseMirror Node Conversions > Convert complex node to block 1`] = `
93
+ {
94
+ "children": [
95
+ {
96
+ "children": [],
97
+ "content": [
98
+ {
99
+ "styles": {},
100
+ "text": "Paragraph",
101
+ "type": "text",
102
+ },
103
+ ],
104
+ "id": 2,
105
+ "props": {
106
+ "backgroundColor": "red",
107
+ "textAlignment": "left",
108
+ "textColor": "default",
109
+ },
110
+ "type": "paragraph",
111
+ },
112
+ {
113
+ "children": [],
114
+ "content": [],
115
+ "id": 3,
116
+ "props": {
117
+ "backgroundColor": "default",
118
+ "textAlignment": "left",
119
+ "textColor": "default",
120
+ },
121
+ "type": "bulletListItem",
122
+ },
123
+ ],
124
+ "content": [
125
+ {
126
+ "styles": {
127
+ "bold": true,
128
+ "underline": true,
129
+ },
130
+ "text": "Heading ",
131
+ "type": "text",
132
+ },
133
+ {
134
+ "styles": {
135
+ "italic": true,
136
+ "strike": true,
137
+ },
138
+ "text": "2",
139
+ "type": "text",
140
+ },
141
+ ],
142
+ "id": 1,
143
+ "props": {
144
+ "backgroundColor": "blue",
145
+ "level": "2",
146
+ "textAlignment": "right",
147
+ "textColor": "yellow",
148
+ },
149
+ "type": "heading",
150
+ }
151
+ `;
152
+
153
+ exports[`Simple ProseMirror Node Conversions > Convert simple block to node 1`] = `
154
+ {
155
+ "attrs": {
156
+ "backgroundColor": "default",
157
+ "id": 4,
158
+ "textColor": "default",
159
+ },
160
+ "content": [
161
+ {
162
+ "attrs": {
163
+ "textAlignment": "left",
164
+ },
165
+ "type": "paragraph",
166
+ },
167
+ ],
168
+ "type": "blockContainer",
169
+ }
170
+ `;
171
+
172
+ exports[`Simple ProseMirror Node Conversions > Convert simple node to block 1`] = `
173
+ {
174
+ "children": [],
175
+ "content": [],
176
+ "id": 0,
177
+ "props": {
178
+ "backgroundColor": "default",
179
+ "textAlignment": "left",
180
+ "textColor": "default",
181
+ },
182
+ "type": "paragraph",
183
+ }
184
+ `;
185
+
186
+ exports[`links > Convert a block with link 1`] = `
187
+ {
188
+ "attrs": {
189
+ "backgroundColor": "default",
190
+ "id": 4,
191
+ "textColor": "default",
192
+ },
193
+ "content": [
194
+ {
195
+ "attrs": {
196
+ "textAlignment": "left",
197
+ },
198
+ "content": [
199
+ {
200
+ "marks": [
201
+ {
202
+ "attrs": {
203
+ "class": null,
204
+ "href": "https://www.website.com",
205
+ "target": "_blank",
206
+ },
207
+ "type": "link",
208
+ },
209
+ ],
210
+ "text": "Website",
211
+ "type": "text",
212
+ },
213
+ ],
214
+ "type": "paragraph",
215
+ },
216
+ ],
217
+ "type": "blockContainer",
218
+ }
219
+ `;
220
+
221
+ exports[`links > Convert two adjacent links in a block 1`] = `
222
+ {
223
+ "attrs": {
224
+ "backgroundColor": "default",
225
+ "id": 4,
226
+ "textColor": "default",
227
+ },
228
+ "content": [
229
+ {
230
+ "attrs": {
231
+ "textAlignment": "left",
232
+ },
233
+ "content": [
234
+ {
235
+ "marks": [
236
+ {
237
+ "attrs": {
238
+ "class": null,
239
+ "href": "https://www.website.com",
240
+ "target": "_blank",
241
+ },
242
+ "type": "link",
243
+ },
244
+ ],
245
+ "text": "Website",
246
+ "type": "text",
247
+ },
248
+ {
249
+ "marks": [
250
+ {
251
+ "attrs": {
252
+ "class": null,
253
+ "href": "https://www.website2.com",
254
+ "target": "_blank",
255
+ },
256
+ "type": "link",
257
+ },
258
+ ],
259
+ "text": "Website2",
260
+ "type": "text",
261
+ },
262
+ ],
263
+ "type": "paragraph",
264
+ },
265
+ ],
266
+ "type": "blockContainer",
267
+ }
268
+ `;