From 6f0d67e571f23ba2755600bdc964ab7b64cb23f4 Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Wed, 8 Oct 2025 20:19:26 +0200 Subject: [PATCH 1/5] Fixed partial block to block conversion in HTML export --- .../html/util/serializeBlocksExternalHTML.ts | 48 +-- .../html/util/serializeBlocksInternalHTML.ts | 43 ++- packages/core/src/api/partialBlockToBlock.ts | 324 ++++++++++++++++++ .../core/src/schema/inlineContent/types.ts | 2 +- .../blocknoteHTML/paragraph/styled.html | 4 +- .../blocknoteHTML/partial/empty.html | 9 + .../blocknoteHTML/partial/noChildren.html | 13 + .../blocknoteHTML/partial/noId.html | 18 + .../blocknoteHTML/partial/noProps.html | 26 ++ .../blocknoteHTML/partial/noType.html | 26 ++ .../__snapshots__/html/paragraph/styled.html | 4 +- .../__snapshots__/html/partial/empty.html | 1 + .../html/partial/noChildren.html | 1 + .../__snapshots__/html/partial/noId.html | 2 + .../__snapshots__/html/partial/noProps.html | 2 + .../__snapshots__/html/partial/noType.html | 2 + .../__snapshots__/markdown/partial/empty.md | 0 .../markdown/partial/noChildren.md | 0 .../__snapshots__/markdown/partial/noId.md | 0 .../__snapshots__/markdown/partial/noProps.md | 0 .../__snapshots__/markdown/partial/noType.md | 0 .../__snapshots__/nodes/partial/empty.json | 18 + .../nodes/partial/noChildren.json | 18 + .../__snapshots__/nodes/partial/noId.json | 39 +++ .../__snapshots__/nodes/partial/noProps.json | 39 +++ .../__snapshots__/nodes/partial/noType.json | 39 +++ .../export/exportTestInstances.ts | 106 ++++++ .../exportParseEqualityTestInstances.ts | 10 +- tests/src/unit/core/testSchema.ts | 92 +++++ .../blocknoteHTML/customParagraph/styled.html | 4 +- .../simpleCustomParagraph/styled.html | 4 +- .../html/customParagraph/styled.html | 4 +- .../html/simpleCustomParagraph/styled.html | 4 +- 33 files changed, 832 insertions(+), 70 deletions(-) create mode 100644 packages/core/src/api/partialBlockToBlock.ts create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/empty.html create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/noChildren.html create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/noId.html create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/noProps.html create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/noType.html create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/html/partial/empty.html create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/html/partial/noChildren.html create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/html/partial/noId.html create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/html/partial/noProps.html create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/html/partial/noType.html create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/markdown/partial/empty.md create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/markdown/partial/noChildren.md create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/markdown/partial/noId.md create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/markdown/partial/noProps.md create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/markdown/partial/noType.md create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/empty.json create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/noChildren.json create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/noId.json create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/noProps.json create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/noType.json diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts index 17ab49fe69..23d936bbf8 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts @@ -14,6 +14,7 @@ import { tableContentToNodes, } from "../../../nodeConversions/blockToNode.js"; import { nodeToCustomInlineContent } from "../../../nodeConversions/nodeToBlock.js"; +import { partialBlockToBlock } from "../../../partialBlockToBlock.js"; function addAttributesAndRemoveClasses(element: HTMLElement) { // Removes all BlockNote specific class names. @@ -175,20 +176,12 @@ function serializeBlock< const doc = options?.document ?? document; const BC_NODE = editor.pmSchema.nodes["blockContainer"]; - // set default props in case we were passed a partial block - const props = block.props || {}; - for (const [name, spec] of Object.entries( - editor.schema.blockSchema[block.type as any].propSchema, - )) { - if (!(name in props) && spec.default !== undefined) { - (props as any)[name] = spec.default; - } - } + const fullBlock = partialBlockToBlock(block, editor); const bc = BC_NODE.spec?.toDOM?.( BC_NODE.create({ - id: block.id, - ...props, + id: fullBlock.id, + ...fullBlock.props, }), ) as { dom: HTMLElement; @@ -199,19 +192,14 @@ function serializeBlock< // we should change toExternalHTML so that this is not necessary const attrs = Array.from(bc.dom.attributes); - const blockImplementation = editor.blockImplementations[block.type as any] + const blockImplementation = editor.blockImplementations[fullBlock.type] .implementation as BlockImplementation; const ret = blockImplementation.toExternalHTML?.call( {}, - { ...block, props } as any, + fullBlock as any, editor as any, - ) || - blockImplementation.render.call( - {}, - { ...block, props } as any, - editor as any, - ); + ) || blockImplementation.render.call({}, fullBlock as any, editor as any); const elementFragment = doc.createDocumentFragment(); @@ -241,10 +229,10 @@ function serializeBlock< elementFragment.append(ret.dom); } - if (ret.contentDOM && block.content) { + if (ret.contentDOM && fullBlock.content) { const ic = serializeInlineContentExternalHTML( editor, - block.content as any, // TODO + fullBlock.content as any, // TODO serializer, options, ); @@ -253,9 +241,9 @@ function serializeBlock< } let listType = undefined; - if (orderedListItemBlockTypes.has(block.type!)) { + if (orderedListItemBlockTypes.has(fullBlock.type!)) { listType = "OL"; - } else if (unorderedListItemBlockTypes.has(block.type!)) { + } else if (unorderedListItemBlockTypes.has(fullBlock.type!)) { listType = "UL"; } @@ -265,11 +253,11 @@ function serializeBlock< if ( listType === "OL" && - "start" in props && - props.start && - props?.start !== 1 + "start" in fullBlock.props && + fullBlock.props.start && + fullBlock.props?.start !== 1 ) { - list.setAttribute("start", props.start + ""); + list.setAttribute("start", fullBlock.props.start + ""); } fragment.append(list); } @@ -278,12 +266,12 @@ function serializeBlock< fragment.append(elementFragment); } - if (block.children && block.children.length > 0) { + if (fullBlock.children && fullBlock.children.length > 0) { const childFragment = doc.createDocumentFragment(); serializeBlocksToFragment( childFragment, editor, - block.children, + fullBlock.children, serializer, orderedListItemBlockTypes, unorderedListItemBlockTypes, @@ -302,7 +290,7 @@ function serializeBlock< } } - if (editor.pmSchema.nodes[block.type as any].isInGroup("blockContent")) { + if (editor.pmSchema.nodes[fullBlock.type].isInGroup("blockContent")) { // default "blockContainer" style blocks are flattened (no "nested block" support) for externalHTML, so append the child fragment to the outer fragment fragment.append(childFragment); } else { diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts index 0f890b77ab..627dbe7085 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts @@ -12,8 +12,9 @@ import { inlineContentToNodes, tableContentToNodes, } from "../../../nodeConversions/blockToNode.js"; - import { nodeToCustomInlineContent } from "../../../nodeConversions/nodeToBlock.js"; +import { partialBlockToBlock } from "../../../partialBlockToBlock.js"; + export function serializeInlineContentInternalHTML< BSchema extends BlockSchema, I extends InlineContentSchema, @@ -138,45 +139,36 @@ function serializeBlock< ) { const BC_NODE = editor.pmSchema.nodes["blockContainer"]; - // set default props in case we were passed a partial block - const props = block.props || {}; - for (const [name, spec] of Object.entries( - editor.schema.blockSchema[block.type as any].propSchema, - )) { - if (!(name in props) && spec.default !== undefined) { - (props as any)[name] = spec.default; - } - } - const children = block.children || []; + const fullBlock = partialBlockToBlock(block, editor); - const impl = editor.blockImplementations[block.type as any].implementation; + const impl = editor.blockImplementations[fullBlock.type].implementation; const ret = impl.render.call( { renderType: "dom", props: undefined, }, - { ...block, props, children } as any, + fullBlock, editor as any, ); - if (ret.contentDOM && block.content) { + if (ret.contentDOM && fullBlock.content) { const ic = serializeInlineContentInternalHTML( editor, - block.content as any, // TODO + fullBlock.content as any, // TODO serializer, - block.type, + fullBlock.type, options, ); ret.contentDOM.appendChild(ic); } - const pmType = editor.pmSchema.nodes[block.type as any]; + const pmType = editor.pmSchema.nodes[fullBlock.type as any]; if (pmType.isInGroup("bnBlock")) { - if (block.children && block.children.length > 0) { + if (fullBlock.children && fullBlock.children.length > 0) { const fragment = serializeBlocks( editor, - block.children, + fullBlock.children, serializer, options, ); @@ -189,8 +181,8 @@ function serializeBlock< // wrap the block in a blockContainer const bc = BC_NODE.spec?.toDOM?.( BC_NODE.create({ - id: block.id, - ...props, + id: fullBlock.id, + ...fullBlock.props, }), ) as { dom: HTMLElement; @@ -199,9 +191,14 @@ function serializeBlock< bc.contentDOM?.appendChild(ret.dom); - if (block.children && block.children.length > 0) { + if (fullBlock.children && fullBlock.children.length > 0) { bc.contentDOM?.appendChild( - serializeBlocksInternalHTML(editor, block.children, serializer, options), + serializeBlocksInternalHTML( + editor, + fullBlock.children, + serializer, + options, + ), ); } return bc.dom; diff --git a/packages/core/src/api/partialBlockToBlock.ts b/packages/core/src/api/partialBlockToBlock.ts new file mode 100644 index 0000000000..16fe4e6a03 --- /dev/null +++ b/packages/core/src/api/partialBlockToBlock.ts @@ -0,0 +1,324 @@ +import { Block, PartialBlock } from "../blocks/defaultBlocks.js"; +import { BlockNoteEditor } from "../editor/BlockNoteEditor.js"; +import { + BlockSchema, + InlineContent, + InlineContentConfig, + InlineContentSchema, + Link, + PartialInlineContent, + PartialInlineContentElement, + PartialLink, + PartialTableContent, + Props, + PropSchema, + StyledText, + StyleSchema, + TableCell, + TableContent, +} from "../schema/index.js"; + +const partialPropsToProps = ( + partialProps: Partial> | undefined, + propSchema: PropSchema, +): Record => + Object.fromEntries( + Object.entries(propSchema).map(([propName, propSpec]) => { + if ( + partialProps && + propName in partialProps && + partialProps[propName] !== undefined + ) { + const propTypeMatches = + typeof partialProps[propName] === + (propSpec.default !== undefined + ? typeof propSpec.default + : propSpec.type); + const propValueIsValid = + propSpec.values === undefined + ? true + : propSpec.values.includes(partialProps[propName]); + + if (propTypeMatches && propValueIsValid) { + return [propName, partialProps[propName]]; + } + } + + return [propName, propSpec.default]; + }), + ); + +const textStringToStyledText = (text: string): StyledText => ({ + type: "text", + styles: {}, + text: text, +}); + +const partialLinkToLink = ( + partialLink: PartialLink, +): Link => ({ + type: "link", + href: partialLink.href, + content: + typeof partialLink.content === "string" + ? [textStringToStyledText(partialLink.content)] + : partialLink.content, +}); + +const partialInlineContentElementIsStyledText = ( + partialInlineContentElement: PartialInlineContentElement< + InlineContentSchema, + StyleSchema + >, + inlineContentConfig: InlineContentConfig, +): partialInlineContentElement is StyledText => + typeof partialInlineContentElement !== "string" && + inlineContentConfig === "text"; + +const partialInlineContentElementIsPartialLink = ( + partialInlineContentElement: PartialInlineContentElement< + InlineContentSchema, + StyleSchema + >, + inlineContentConfig: InlineContentConfig, +): partialInlineContentElement is PartialLink => + typeof partialInlineContentElement !== "string" && + inlineContentConfig === "link"; + +const partialInlineContentToInlineContent = ( + partialInlineContent: + | PartialInlineContent + | undefined, + inlineContentSchema: InlineContentSchema, +): InlineContent[] => { + if (partialInlineContent === undefined) { + return []; + } + + if (typeof partialInlineContent === "string") { + return [textStringToStyledText(partialInlineContent)]; + } + + return partialInlineContent.map((partialInlineContentElement) => { + if (typeof partialInlineContentElement === "string") { + return textStringToStyledText(partialInlineContentElement); + } + + const inlineContentConfig = + inlineContentSchema[partialInlineContentElement.type]; + + if ( + partialInlineContentElementIsStyledText( + partialInlineContentElement, + inlineContentConfig, + ) + ) { + return partialInlineContentElement; + } + + if ( + partialInlineContentElementIsPartialLink( + partialInlineContentElement, + inlineContentConfig, + ) + ) { + return partialLinkToLink(partialInlineContentElement); + } + + // Couldn't get a proper type guard for these instead. + const content: StyledText[] | string | undefined = + partialInlineContentElement.content; + if (typeof inlineContentConfig === "string") { + throw new Error(""); + } + + return { + type: partialInlineContentElement.type, + props: partialPropsToProps( + partialInlineContentElement.props, + inlineContentConfig.propSchema, + ), + content: + typeof content === "undefined" + ? undefined + : typeof content === "string" + ? [textStringToStyledText(content)] + : content, + }; + }); +}; + +const partialTableContentToTableContent = ( + partialTableContent: PartialTableContent, + inlineContentSchema: InlineContentSchema, +): TableContent => { + const columnWidths: undefined[] = []; + const rows: { + cells: TableCell[]; + }[] = partialTableContent.rows.map((row, rowIndex) => { + return { + cells: row.cells.map((cell) => { + if (typeof cell === "object" && "type" in cell) { + if (rowIndex === 0) { + for (let i = 0; i < (cell.props?.colspan || 1); i++) { + columnWidths.push(undefined); + } + } + + return { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + ...cell.props, + }, + content: partialInlineContentToInlineContent( + cell.content, + inlineContentSchema, + ), + }; + } + + columnWidths.push(undefined); + + return { + type: "tableCell", + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + }, + content: partialInlineContentToInlineContent( + cell, + inlineContentSchema, + ), + }; + }), + }; + }); + + return { + type: "tableContent", + headerRows: partialTableContent.headerRows, + headerCols: partialTableContent.headerCols, + columnWidths: partialTableContent.columnWidths || columnWidths, + rows, + }; +}; + +const partialBlockContentToBlockContent = ( + partialBlockContent: + | PartialTableContent + | PartialInlineContent + | undefined, + content: "table" | "inline" | "none", + inlineContentSchema: InlineContentSchema, +): + | TableContent + | InlineContent[] + | undefined => { + if (content === "table") { + if ( + typeof partialBlockContent === "object" && + "type" in partialBlockContent + ) { + return partialTableContentToTableContent( + partialBlockContent, + inlineContentSchema, + ); + } + + if (partialBlockContent === undefined) { + return { + type: "tableContent", + columnWidths: [undefined], + rows: [ + { + cells: [[{ type: "text", styles: {}, text: "" }]], + }, + ], + }; + } + + return { + type: "tableContent", + columnWidths: [undefined], + rows: [ + { + cells: [ + partialInlineContentToInlineContent( + partialBlockContent, + inlineContentSchema, + ), + ], + }, + ], + }; + } + + if (content === "inline") { + if ( + typeof partialBlockContent === "object" && + "type" in partialBlockContent + ) { + const cell = partialTableContentToTableContent( + partialBlockContent, + inlineContentSchema, + ).rows[0].cells[0]; + + if (typeof cell === "object" && "type" in cell) { + return cell.content; + } + + return cell; + } + + if (partialBlockContent === undefined) { + return [{ type: "text", styles: {}, text: "" }]; + } + + return partialInlineContentToInlineContent( + partialBlockContent, + inlineContentSchema, + ); + } + + return undefined; +}; + +export const partialBlockToBlock = < + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema, +>( + partialBlock: PartialBlock, + editor: BlockNoteEditor, +): Block => { + const id = partialBlock.id || ""; + + const type: string = partialBlock.type || "paragraph"; + + const props = partialPropsToProps( + partialBlock.props, + editor.schema.blockSchema[type].propSchema, + ); + + const content = partialBlockContentToBlockContent( + partialBlock.content, + editor.schema.blockSchema[type].content, + editor.schema.inlineContentSchema, + ); + + const children = + partialBlock.children?.map((child) => partialBlockToBlock(child, editor)) || + []; + + return { + id, + type, + props, + content, + children, + } as Block; +}; diff --git a/packages/core/src/schema/inlineContent/types.ts b/packages/core/src/schema/inlineContent/types.ts index 88ebfd740c..4577fef659 100644 --- a/packages/core/src/schema/inlineContent/types.ts +++ b/packages/core/src/schema/inlineContent/types.ts @@ -145,7 +145,7 @@ export type InlineContent< T extends StyleSchema, > = InlineContentFromConfig; -type PartialInlineContentElement< +export type PartialInlineContentElement< I extends InlineContentSchema, T extends StyleSchema, > = PartialInlineContentFromConfig; diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/paragraph/styled.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/paragraph/styled.html index b0e77ba748..654083d01e 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/paragraph/styled.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/paragraph/styled.html @@ -4,9 +4,9 @@

Plain diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/empty.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/empty.html new file mode 100644 index 0000000000..8c0240ae7c --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/empty.html @@ -0,0 +1,9 @@ +

+
+
+
+

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/noChildren.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/noChildren.html new file mode 100644 index 0000000000..65ab940c29 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/noChildren.html @@ -0,0 +1,13 @@ +
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/noId.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/noId.html new file mode 100644 index 0000000000..d9b1909f1a --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/noId.html @@ -0,0 +1,18 @@ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/noProps.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/noProps.html new file mode 100644 index 0000000000..c5bf99d7b2 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/noProps.html @@ -0,0 +1,26 @@ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/noType.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/noType.html new file mode 100644 index 0000000000..c4185a74b4 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/partial/noType.html @@ -0,0 +1,26 @@ +
+
+
+
+

+
+
+
+
+
+

+
+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/styled.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/styled.html index 637d51b2e0..c9eec107c7 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/styled.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/styled.html @@ -1,8 +1,8 @@

Plain

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/partial/noChildren.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/partial/noChildren.html new file mode 100644 index 0000000000..281c6866c3 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/partial/noChildren.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/partial/noId.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/partial/noId.html new file mode 100644 index 0000000000..c6280d2338 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/partial/noId.html @@ -0,0 +1,2 @@ +
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/partial/noProps.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/partial/noProps.html new file mode 100644 index 0000000000..c6280d2338 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/partial/noProps.html @@ -0,0 +1,2 @@ +
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/partial/noType.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/partial/noType.html new file mode 100644 index 0000000000..0ba6caf1bc --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/partial/noType.html @@ -0,0 +1,2 @@ +

+

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/partial/empty.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/partial/empty.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/partial/noChildren.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/partial/noChildren.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/partial/noId.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/partial/noId.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/partial/noProps.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/partial/noProps.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/partial/noType.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/partial/noType.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/empty.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/empty.json new file mode 100644 index 0000000000..8fdcafd2e5 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/empty.json @@ -0,0 +1,18 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/noChildren.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/noChildren.json new file mode 100644 index 0000000000..d6bd0378f6 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/noChildren.json @@ -0,0 +1,18 @@ +[ + { + "attrs": { + "id": "exportTestBlock", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "exportTestBlock", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/noId.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/noId.json new file mode 100644 index 0000000000..10cb759fac --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/noId.json @@ -0,0 +1,39 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "exportTestBlock", + }, + { + "content": [ + { + "attrs": { + "id": "2", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "exportTestBlock", + }, + ], + "type": "blockContainer", + }, + ], + "type": "blockGroup", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/noProps.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/noProps.json new file mode 100644 index 0000000000..071417670f --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/noProps.json @@ -0,0 +1,39 @@ +[ + { + "attrs": { + "id": "exportTestBlock", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "exportTestBlock", + }, + { + "content": [ + { + "attrs": { + "id": "exportTestBlock", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "exportTestBlock", + }, + ], + "type": "blockContainer", + }, + ], + "type": "blockGroup", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/noType.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/noType.json new file mode 100644 index 0000000000..96ecb6b69f --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/partial/noType.json @@ -0,0 +1,39 @@ +[ + { + "attrs": { + "id": "exportTestBlock", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "content": [ + { + "attrs": { + "id": "exportTestBlock", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + ], + "type": "blockGroup", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/exportTestInstances.ts b/tests/src/unit/core/formatConversion/export/exportTestInstances.ts index 298e361884..e65619ae31 100644 --- a/tests/src/unit/core/formatConversion/export/exportTestInstances.ts +++ b/tests/src/unit/core/formatConversion/export/exportTestInstances.ts @@ -1678,6 +1678,112 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, executeTest: testExportBlockNoteHTML, }, + { + testCase: { + name: "partial/empty", + content: [{}], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "partial/noId", + content: [ + { + type: "exportTestBlock", + props: { + textAlignment: "left", + textColor: "default", + backgroundColor: "default", + }, + content: undefined, + children: [ + { + type: "exportTestBlock", + props: { + textAlignment: "left", + textColor: "default", + backgroundColor: "default", + }, + content: undefined, + children: [], + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "partial/noType", + content: [ + { + id: "exportTestBlock", + props: { + textAlignment: "left", + textColor: "default", + backgroundColor: "default", + invalidProp: true, + }, + content: undefined, + children: [ + { + id: "exportTestBlock", + props: { + textAlignment: "left", + textColor: "default", + backgroundColor: "default", + invalidProp: true, + }, + content: undefined, + children: [], + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "partial/noProps", + content: [ + { + id: "exportTestBlock", + type: "exportTestBlock", + content: undefined, + children: [ + { + id: "exportTestBlock", + type: "exportTestBlock", + content: undefined, + children: [], + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "partial/noChildren", + content: [ + { + id: "exportTestBlock", + type: "exportTestBlock", + props: { + textAlignment: "left", + textColor: "default", + backgroundColor: "default", + }, + content: undefined, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, { testCase: { name: "inlineContent/mentionWithToExternalHTML", diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts b/tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts index 33d9b09c15..84e355f026 100644 --- a/tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts +++ b/tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts @@ -20,10 +20,12 @@ export const exportParseEqualityTestInstancesBlockNoteHTML: TestInstance< TestBlockSchema, TestInlineContentSchema, TestStyleSchema ->[] = exportTestInstancesBlockNoteHTML.map(({ testCase }) => ({ - testCase, - executeTest: testExportParseEqualityBlockNoteHTML, -})); +>[] = exportTestInstancesBlockNoteHTML + .filter(({ testCase }) => !testCase.name.startsWith("partial/")) + .map(({ testCase }) => ({ + testCase, + executeTest: testExportParseEqualityBlockNoteHTML, + })); export const exportParseEqualityTestInstancesHTML: TestInstance< ExportParseEqualityTestCase< diff --git a/tests/src/unit/core/testSchema.ts b/tests/src/unit/core/testSchema.ts index 537350eb78..4d29e7bb81 100644 --- a/tests/src/unit/core/testSchema.ts +++ b/tests/src/unit/core/testSchema.ts @@ -1,6 +1,9 @@ import { + BlockFromConfig, + BlockNoteEditor, BlockNoteSchema, addNodeAndExtensionsToSpec, + createBlockSpec, createImageBlockConfig, createImageBlockSpec, createInlineContentSpec, @@ -78,6 +81,94 @@ const SimpleCustomParagraph = addNodeAndExtensionsToSpec( }, ); +// This block is used to test converting partial blocks into full blocks when +// exporting. If any of the fields are incorrectly defined, will throw an +// error. +const validateBlock = ( + block: BlockFromConfig, + editor: BlockNoteEditor, +) => { + if (typeof block.id !== "string") { + throw new Error("ID of block is not a string."); + } + + if (typeof block.type !== "string") { + throw new Error("Type of block is not a string."); + } + + if (block.type !== "exportTestBlock") { + throw new Error(`Block has wrong type ("${block.type}").`); + } + + if (block.props === undefined) { + throw new Error("Block has no props."); + } + + const propSchema = editor.schema.blockSchema.exportTestBlock.propSchema; + const propSchemaSize = Object.keys(propSchema).length; + const propsSize = Object.keys(block.props).length; + + if (propsSize !== propSchemaSize) { + throw new Error( + `Block has ${propsSize} props but its schema declares it should have ${propSchemaSize}.`, + ); + } + + for (const [propName, propValue] of Object.entries(block.props)) { + if (!(propName in propSchema)) { + throw new Error( + `Block has prop "${propName}" that is not declared in its schema.`, + ); + } + + if ( + typeof propValue !== + typeof propSchema[propName as keyof typeof propSchema].default + ) { + throw new Error( + `Block has prop "${propName} that is of incorrect type (${typeof propValue}).`, + ); + } + } + + if (block.content !== undefined) { + throw new Error( + "Block has content but it's schema declares it should be undefined.", + ); + } + + if (!Array.isArray(block.children)) { + throw new Error("Block is missing children array."); + } + + for (const child of block.children) { + validateBlock(child, editor); + } +}; +const createExportTestBlock = createBlockSpec( + { + type: "exportTestBlock", + propSchema: defaultProps, + content: "none", + }, + { + render: (block, editor) => { + validateBlock(block, editor); + + return { + dom: document.createElement("div"), + }; + }, + toExternalHTML: (block, editor) => { + validateBlock(block, editor); + + return { + dom: document.createElement("div"), + }; + }, + }, +); + // INLINE CONTENT -------------------------------------------------------------- const Mention = createInlineContentSpec( @@ -199,6 +290,7 @@ export const testSchema = BlockNoteSchema.create().extend({ customParagraph: CustomParagraph, simpleCustomParagraph: SimpleCustomParagraph, simpleImage: SimpleImage, + exportTestBlock: createExportTestBlock(), }, inlineContentSpecs: { mention: Mention, diff --git a/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/customParagraph/styled.html b/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/customParagraph/styled.html index df960b11f1..93f75f1865 100644 --- a/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/customParagraph/styled.html +++ b/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/customParagraph/styled.html @@ -4,9 +4,9 @@
diff --git a/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/simpleCustomParagraph/styled.html b/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/simpleCustomParagraph/styled.html index ef3d75ab28..9144580e5b 100644 --- a/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/simpleCustomParagraph/styled.html +++ b/tests/src/unit/react/formatConversion/export/__snapshots__/blocknoteHTML/simpleCustomParagraph/styled.html @@ -4,9 +4,9 @@
diff --git a/tests/src/unit/react/formatConversion/export/__snapshots__/html/customParagraph/styled.html b/tests/src/unit/react/formatConversion/export/__snapshots__/html/customParagraph/styled.html index 7e7e60707d..f8940fee7f 100644 --- a/tests/src/unit/react/formatConversion/export/__snapshots__/html/customParagraph/styled.html +++ b/tests/src/unit/react/formatConversion/export/__snapshots__/html/customParagraph/styled.html @@ -1,6 +1,6 @@

Hello World

\ No newline at end of file diff --git a/tests/src/unit/react/formatConversion/export/__snapshots__/html/simpleCustomParagraph/styled.html b/tests/src/unit/react/formatConversion/export/__snapshots__/html/simpleCustomParagraph/styled.html index 87efb71bf5..772e08fc9a 100644 --- a/tests/src/unit/react/formatConversion/export/__snapshots__/html/simpleCustomParagraph/styled.html +++ b/tests/src/unit/react/formatConversion/export/__snapshots__/html/simpleCustomParagraph/styled.html @@ -1,8 +1,8 @@

Plain Date: Fri, 10 Oct 2025 11:06:10 +0200 Subject: [PATCH 2/5] Implemented PR feedback --- .../html/util/serializeBlocksExternalHTML.ts | 4 ++-- .../html/util/serializeBlocksInternalHTML.ts | 4 ++-- ...ckToBlock.ts => partialBlockToFullBlock.ts} | 18 ++++++++---------- 3 files changed, 12 insertions(+), 14 deletions(-) rename packages/core/src/api/{partialBlockToBlock.ts => partialBlockToFullBlock.ts} (95%) diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts index 23d936bbf8..a29afa08fc 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts @@ -14,7 +14,7 @@ import { tableContentToNodes, } from "../../../nodeConversions/blockToNode.js"; import { nodeToCustomInlineContent } from "../../../nodeConversions/nodeToBlock.js"; -import { partialBlockToBlock } from "../../../partialBlockToBlock.js"; +import { partialBlockToFullBlock } from "../../../partialBlockToFullBlock.js"; function addAttributesAndRemoveClasses(element: HTMLElement) { // Removes all BlockNote specific class names. @@ -176,7 +176,7 @@ function serializeBlock< const doc = options?.document ?? document; const BC_NODE = editor.pmSchema.nodes["blockContainer"]; - const fullBlock = partialBlockToBlock(block, editor); + const fullBlock = partialBlockToFullBlock(block, editor); const bc = BC_NODE.spec?.toDOM?.( BC_NODE.create({ diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts index 627dbe7085..a3d95d2201 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts @@ -13,7 +13,7 @@ import { tableContentToNodes, } from "../../../nodeConversions/blockToNode.js"; import { nodeToCustomInlineContent } from "../../../nodeConversions/nodeToBlock.js"; -import { partialBlockToBlock } from "../../../partialBlockToBlock.js"; +import { partialBlockToFullBlock } from "../../../partialBlockToFullBlock.js"; export function serializeInlineContentInternalHTML< BSchema extends BlockSchema, @@ -139,7 +139,7 @@ function serializeBlock< ) { const BC_NODE = editor.pmSchema.nodes["blockContainer"]; - const fullBlock = partialBlockToBlock(block, editor); + const fullBlock = partialBlockToFullBlock(block, editor); const impl = editor.blockImplementations[fullBlock.type].implementation; const ret = impl.render.call( diff --git a/packages/core/src/api/partialBlockToBlock.ts b/packages/core/src/api/partialBlockToFullBlock.ts similarity index 95% rename from packages/core/src/api/partialBlockToBlock.ts rename to packages/core/src/api/partialBlockToFullBlock.ts index 16fe4e6a03..34a3e98493 100644 --- a/packages/core/src/api/partialBlockToBlock.ts +++ b/packages/core/src/api/partialBlockToFullBlock.ts @@ -31,13 +31,10 @@ const partialPropsToProps = ( ) { const propTypeMatches = typeof partialProps[propName] === - (propSpec.default !== undefined - ? typeof propSpec.default - : propSpec.type); - const propValueIsValid = - propSpec.values === undefined - ? true - : propSpec.values.includes(partialProps[propName]); + ("type" in propSpec ? propSpec.type : typeof propSpec.default); + const propValueIsValid = Array.isArray(propSpec.values) + ? propSpec.values.includes(partialProps[propName]) + : true; if (propTypeMatches && propValueIsValid) { return [propName, partialProps[propName]]; @@ -287,7 +284,7 @@ const partialBlockContentToBlockContent = ( return undefined; }; -export const partialBlockToBlock = < +export const partialBlockToFullBlock = < BSchema extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema, @@ -311,8 +308,9 @@ export const partialBlockToBlock = < ); const children = - partialBlock.children?.map((child) => partialBlockToBlock(child, editor)) || - []; + partialBlock.children?.map((child) => + partialBlockToFullBlock(child, editor), + ) || []; return { id, From 906549400f6352e5c01d68b04a640970056ed9bf Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Fri, 10 Oct 2025 12:03:56 +0200 Subject: [PATCH 3/5] Updated schema test snapshot --- .../core/schema/__snapshots__/blocks.json | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/src/unit/core/schema/__snapshots__/blocks.json b/tests/src/unit/core/schema/__snapshots__/blocks.json index b54e1b4d93..3c9946a695 100644 --- a/tests/src/unit/core/schema/__snapshots__/blocks.json +++ b/tests/src/unit/core/schema/__snapshots__/blocks.json @@ -510,6 +510,74 @@ "toExternalHTML": [Function], }, }, + "exportTestBlock": { + "config": { + "content": "none", + "propSchema": { + "backgroundColor": { + "default": "default", + }, + "textAlignment": { + "default": "left", + "values": [ + "left", + "center", + "right", + "justify", + ], + }, + "textColor": { + "default": "default", + }, + }, + "type": "exportTestBlock", + }, + "extensions": undefined, + "implementation": { + "node": _Node { + "child": _Node { + "child": null, + "config": { + "addAttributes": [Function], + "addNodeView": [Function], + "addOptions": [Function], + "code": false, + "content": "", + "defining": true, + "group": "blockContent", + "isolating": true, + "name": "exportTestBlock", + "parseHTML": [Function], + "priority": 101, + "renderHTML": [Function], + "selectable": true, + }, + "name": "exportTestBlock", + "parent": null, + "type": "node", + }, + "config": { + "addAttributes": [Function], + "addNodeView": [Function], + "code": false, + "content": "", + "defining": true, + "group": "blockContent", + "isolating": true, + "name": "exportTestBlock", + "parseHTML": [Function], + "priority": 101, + "renderHTML": [Function], + "selectable": true, + }, + "name": "exportTestBlock", + "parent": null, + "type": "node", + }, + "render": [Function], + "toExternalHTML": [Function], + }, + }, "file": { "config": { "content": "none", From 4ee7870f3b3f55a3b1d47eb7271587ab534623d6 Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Fri, 10 Oct 2025 15:01:52 +0200 Subject: [PATCH 4/5] Cleaned up `mapTableCell` and used it in `partialTableContentToTableContent` --- .../core/src/api/partialBlockToFullBlock.ts | 56 ++++++------------- packages/core/src/util/table.ts | 15 ++--- 2 files changed, 24 insertions(+), 47 deletions(-) diff --git a/packages/core/src/api/partialBlockToFullBlock.ts b/packages/core/src/api/partialBlockToFullBlock.ts index 34a3e98493..22c8b329cb 100644 --- a/packages/core/src/api/partialBlockToFullBlock.ts +++ b/packages/core/src/api/partialBlockToFullBlock.ts @@ -17,6 +17,7 @@ import { TableCell, TableContent, } from "../schema/index.js"; +import { mapTableCell } from "../util/table.js"; const partialPropsToProps = ( partialProps: Partial> | undefined, @@ -149,52 +150,31 @@ const partialTableContentToTableContent = ( partialTableContent: PartialTableContent, inlineContentSchema: InlineContentSchema, ): TableContent => { - const columnWidths: undefined[] = []; const rows: { cells: TableCell[]; - }[] = partialTableContent.rows.map((row, rowIndex) => { + }[] = partialTableContent.rows.map((row) => { return { cells: row.cells.map((cell) => { - if (typeof cell === "object" && "type" in cell) { - if (rowIndex === 0) { - for (let i = 0; i < (cell.props?.colspan || 1); i++) { - columnWidths.push(undefined); - } - } - - return { - type: "tableCell", - props: { - backgroundColor: "default", - textColor: "default", - textAlignment: "left", - ...cell.props, - }, - content: partialInlineContentToInlineContent( - cell.content, - inlineContentSchema, - ), - }; - } - - columnWidths.push(undefined); - - return { - type: "tableCell", - props: { - backgroundColor: "default", - textColor: "default", - textAlignment: "left", - }, - content: partialInlineContentToInlineContent( - cell, - inlineContentSchema, - ), - }; + const fullCell = mapTableCell(cell); + // `mapTableCell` doesn't actually convert `PartialInlineContent` to + // `InlineContent`, so this is done manually here. + fullCell.content = partialInlineContentToInlineContent( + fullCell.content, + inlineContentSchema, + ); + + return fullCell; }), }; }); + const columnWidths: undefined[] = []; + for (const cell of rows[0].cells) { + for (let i = 0; i < (cell.props?.colspan || 1); i++) { + columnWidths.push(undefined); + } + } + return { type: "tableContent", headerRows: partialTableContent.headerRows, diff --git a/packages/core/src/util/table.ts b/packages/core/src/util/table.ts index f7ecd84910..d4e30fecd4 100644 --- a/packages/core/src/util/table.ts +++ b/packages/core/src/util/table.ts @@ -25,24 +25,21 @@ export function mapTableCell< : isPartialTableCell(content) ? { type: "tableCell", - content: ([] as InlineContent[]).concat(content.content as any), + content: content.content as InlineContent[], props: { - backgroundColor: content.props?.backgroundColor ?? "default", - textColor: content.props?.textColor ?? "default", - textAlignment: content.props?.textAlignment ?? "left", - colspan: content.props?.colspan ?? 1, - rowspan: content.props?.rowspan ?? 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + ...content.props, }, } : { type: "tableCell", - content: ([] as InlineContent[]).concat(content as any), + content: content as InlineContent[], props: { backgroundColor: "default", textColor: "default", textAlignment: "left", - colspan: 1, - rowspan: 1, }, }; } From 03fd40e49ba2395ddb7cb868974f1e633661b3aa Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Mon, 13 Oct 2025 12:27:10 +0200 Subject: [PATCH 5/5] Small revert --- packages/core/src/util/table.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/util/table.ts b/packages/core/src/util/table.ts index d4e30fecd4..9f35eabea8 100644 --- a/packages/core/src/util/table.ts +++ b/packages/core/src/util/table.ts @@ -25,7 +25,7 @@ export function mapTableCell< : isPartialTableCell(content) ? { type: "tableCell", - content: content.content as InlineContent[], + content: ([] as InlineContent[]).concat(content.content as any), props: { backgroundColor: "default", textColor: "default", @@ -35,7 +35,7 @@ export function mapTableCell< } : { type: "tableCell", - content: content as InlineContent[], + content: ([] as InlineContent[]).concat(content as any), props: { backgroundColor: "default", textColor: "default",