From bd154ed44674f47ed4663828dbf2d48816befec4 Mon Sep 17 00:00:00 2001 From: yousefed Date: Thu, 16 Oct 2025 06:35:16 +0200 Subject: [PATCH 01/21] refactor types and partial / full --- .vscode/settings.json | 3 + .../commands/insertBlocks/insertBlocks.ts | 9 +- .../commands/moveBlocks/moveBlocks.ts | 1 - .../commands/replaceBlocks/replaceBlocks.ts | 19 +- .../commands/updateBlock/updateBlock.ts | 48 +++- .../api/blockManipulation/tables/tables.ts | 39 ++- .../exporters/html/externalHTMLExporter.ts | 6 +- .../exporters/html/internalHTMLSerializer.ts | 5 +- .../html/util/serializeBlocksExternalHTML.ts | 60 ++-- .../html/util/serializeBlocksInternalHTML.ts | 12 +- .../exporters/markdown/markdownExporter.ts | 6 +- .../src/api/nodeConversions/blockToNode.ts | 44 ++- .../api/nodeConversions/fragmentToBlocks.ts | 4 +- packages/core/src/blocks/Audio/block.ts | 4 +- .../helpers/render/createAddFileButton.ts | 7 +- .../helpers/render/createFileBlockWrapper.ts | 7 +- .../helpers/render/createFileNameWithIcon.ts | 7 +- .../render/createResizableFileBlockWrapper.ts | 7 +- .../core/src/blocks/defaultBlockHelpers.ts | 4 +- packages/core/src/blocks/defaultBlocks.ts | 30 +- packages/core/src/editor/BlockNoteEditor.ts | 41 +-- .../core/src/editor/BlockNoteExtension.ts | 4 +- .../editor/managers/CollaborationManager.ts | 12 +- .../core/src/editor/managers/ExportManager.ts | 9 +- .../core/src/editor/managers/StyleManager.ts | 19 +- packages/core/src/exporter/mapping.ts | 4 +- .../src/extensions/Comments/CommentsPlugin.ts | 2 +- .../TableHandles/TableHandlesPlugin.ts | 52 ++-- .../{schema.ts => CustomBlockNoteSchema.ts} | 38 ++- packages/core/src/schema/blocks/types.ts | 87 +----- .../src/schema/blocks/types/tableContent.ts | 65 +++++ packages/core/src/schema/index.ts | 4 +- .../src/schema/inlineContent/createSpec.ts | 7 +- .../core/src/schema/inlineContent/types.ts | 4 +- .../src/schema/partialBlockToFullBlock.ts | 258 ++++++++++++++++++ packages/core/src/schema/styles/createSpec.ts | 4 +- packages/core/src/util/table.ts | 8 +- .../DefaultButtons/TableCellMergeButton.tsx | 3 +- .../TableHandleMenu/TableHandleMenuProps.ts | 6 +- packages/react/src/schema/ReactBlockSpec.tsx | 10 +- .../src/schema/ReactInlineContentSpec.tsx | 9 +- .../src/context/ServerBlockNoteEditor.ts | 11 +- .../src/context/react/ReactServer.test.tsx | 30 +- .../src/docx/docxExporter.test.ts | 114 ++++---- .../xl-docx-exporter/src/docx/docxExporter.ts | 4 +- .../test/conversions/htmlConversion.test.ts | 12 +- .../test/conversions/nodeConversion.test.ts | 23 +- .../src/odt/odtExporter.test.ts | 4 +- .../src/pdf/pdfExporter.test.tsx | 4 +- shared/formatConversionTestUtil.ts | 213 --------------- shared/testDocument.ts | 4 +- .../export/exportTestExecutors.ts | 43 +-- .../exportParseEqualityTestExecutors.ts | 30 +- 53 files changed, 759 insertions(+), 701 deletions(-) rename packages/core/src/schema/{schema.ts => CustomBlockNoteSchema.ts} (89%) create mode 100644 packages/core/src/schema/blocks/types/tableContent.ts create mode 100644 packages/core/src/schema/partialBlockToFullBlock.ts delete mode 100644 shared/formatConversionTestUtil.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 7c24081186..79a37e7587 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,9 @@ { "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.addMissingImports": "explicit" + }, "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" diff --git a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts index 81390947bf..d5601458a9 100644 --- a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts @@ -6,12 +6,13 @@ import { BlockIdentifier, BlockSchema, InlineContentSchema, + partialBlockToFullBlock, StyleSchema, } from "../../../../schema/index.js"; import { blockToNode } from "../../../nodeConversions/blockToNode.js"; import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js"; import { getNodeById } from "../../../nodeUtil.js"; -import { getPmSchema } from "../../../pmUtil.js"; +import { getBlockNoteSchema, getPmSchema } from "../../../pmUtil.js"; export function insertBlocks< BSchema extends BlockSchema, @@ -19,6 +20,7 @@ export function insertBlocks< S extends StyleSchema, >( tr: Transaction, + // TBD: allow PartialBlock here? blocksToInsert: PartialBlock[], referenceBlock: BlockIdentifier, placement: "before" | "after" = "before", @@ -27,7 +29,10 @@ export function insertBlocks< typeof referenceBlock === "string" ? referenceBlock : referenceBlock.id; const pmSchema = getPmSchema(tr); const nodesToInsert = blocksToInsert.map((block) => - blockToNode(block, pmSchema), + blockToNode( + partialBlockToFullBlock(getBlockNoteSchema(pmSchema), block), + pmSchema, + ), ); const posInfo = getNodeById(id, tr.doc); diff --git a/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts b/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts index 8d4591123e..87bf59030a 100644 --- a/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts @@ -5,7 +5,6 @@ import { Transaction, } from "prosemirror-state"; import { CellSelection } from "prosemirror-tables"; - import { Block } from "../../../../blocks/defaultBlocks.js"; import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor"; import { BlockIdentifier } from "../../../../schema/index.js"; diff --git a/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts index 4ba8107636..e2f8150792 100644 --- a/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts @@ -1,15 +1,16 @@ import type { Node } from "prosemirror-model"; import type { Transaction } from "prosemirror-state"; import type { Block, PartialBlock } from "../../../../blocks/defaultBlocks.js"; -import type { - BlockIdentifier, - BlockSchema, - InlineContentSchema, - StyleSchema, +import { + partialBlockToFullBlock, + type BlockIdentifier, + type BlockSchema, + type InlineContentSchema, + type StyleSchema, } from "../../../../schema/index.js"; import { blockToNode } from "../../../nodeConversions/blockToNode.js"; import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js"; -import { getPmSchema } from "../../../pmUtil.js"; +import { getBlockNoteSchema, getPmSchema } from "../../../pmUtil.js"; export function removeAndInsertBlocks< BSchema extends BlockSchema, @@ -18,6 +19,7 @@ export function removeAndInsertBlocks< >( tr: Transaction, blocksToRemove: BlockIdentifier[], + // TBD: allow PartialBlock here? blocksToInsert: PartialBlock[], ): { insertedBlocks: Block[]; @@ -27,7 +29,10 @@ export function removeAndInsertBlocks< // Converts the `PartialBlock`s to ProseMirror nodes to insert them into the // document. const nodesToInsert: Node[] = blocksToInsert.map((block) => - blockToNode(block, pmSchema), + blockToNode( + partialBlockToFullBlock(getBlockNoteSchema(pmSchema), block), + pmSchema, + ), ); const idsOfBlocksToRemove = new Set( diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts index 4318b19ca7..5d3b94d0fe 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts @@ -14,6 +14,11 @@ import type { BlockSchema, } from "../../../../schema/blocks/types.js"; import type { InlineContentSchema } from "../../../../schema/inlineContent/types.js"; +import { + partialBlockToFullBlock, + partialInlineContentToInlineContent, + partialTableContentToTableContent, +} from "../../../../schema/partialBlockToFullBlock.js"; import type { StyleSchema } from "../../../../schema/styles/types.js"; import { UnreachableCaseError } from "../../../../util/typescript.js"; import { @@ -27,7 +32,7 @@ import { } from "../../../nodeConversions/blockToNode.js"; import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js"; import { getNodeById } from "../../../nodeUtil.js"; -import { getPmSchema } from "../../../pmUtil.js"; +import { getBlockNoteSchema, getPmSchema } from "../../../pmUtil.js"; // for compatibility with tiptap. TODO: remove as we want to remove dependency on tiptap command interface export const updateBlockCommand = < @@ -71,7 +76,7 @@ export function updateBlockTr< } const pmSchema = getPmSchema(tr); - + const schema = getBlockNoteSchema(pmSchema); if ( replaceFromPos !== undefined && replaceToPos !== undefined && @@ -127,17 +132,19 @@ export function updateBlockTr< // currently, we calculate the new node and replace the entire node with the desired new node. // for this, we do a nodeToBlock on the existing block to get the children. // it would be cleaner to use a ReplaceAroundStep, but this is a bit simpler and it's quite an edge case - const existingBlock = nodeToBlock(blockInfo.bnBlock.node, pmSchema); + const newFullBlock = partialBlockToFullBlock(schema, block); + if (block.children === undefined) { + // if no children are passed in, use existing children + const existingBlock = nodeToBlock( + blockInfo.bnBlock.node, + pmSchema, + ); + newFullBlock.children = existingBlock.children; + } tr.replaceWith( blockInfo.bnBlock.beforePos, blockInfo.bnBlock.afterPos, - blockToNode( - { - children: existingBlock.children, // if no children are passed in, use existing children - ...block, - }, - pmSchema, - ), + blockToNode(newFullBlock, pmSchema), ); return; @@ -174,6 +181,7 @@ function updateBlockContentNode< replaceToOffset?: number, ) { const pmSchema = getPmSchema(tr); + const schema = getBlockNoteSchema(pmSchema); let content: PMNode[] | "keep" = "keep"; // Has there been any custom content provided? @@ -188,9 +196,22 @@ function updateBlockContentNode< } else if (Array.isArray(block.content)) { // Adds a text node with the provided styles converted into marks to the content, // for each InlineContent object. - content = inlineContentToNodes(block.content, pmSchema, newNodeType.name); + content = inlineContentToNodes( + partialInlineContentToInlineContent( + block.content, + schema.inlineContentSchema, + ), + pmSchema, + newNodeType.name, + ); } else if (block.content.type === "tableContent") { - content = tableContentToNodes(block.content, pmSchema); + content = tableContentToNodes( + partialTableContentToTableContent( + block.content, + schema.inlineContentSchema, + ), + pmSchema, + ); } else { throw new UnreachableCaseError(block.content.type); } @@ -276,9 +297,10 @@ function updateChildren< S extends StyleSchema, >(block: PartialBlock, tr: Transform, blockInfo: BlockInfo) { const pmSchema = getPmSchema(tr); + const schema = getBlockNoteSchema(pmSchema); if (block.children !== undefined && block.children.length > 0) { const childNodes = block.children.map((child) => { - return blockToNode(child, pmSchema); + return blockToNode(partialBlockToFullBlock(schema, child), pmSchema); }); // Checks if a blockGroup node already exists. diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index f770a34afd..1a9e62d410 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -1,11 +1,9 @@ import { DefaultBlockSchema } from "../../../blocks/defaultBlocks.js"; import { - BlockFromConfigNoChildren, - PartialTableContent, - TableCell, - TableContent, -} from "../../../schema/blocks/types.js"; -import { + type BlockFromConfig, + type PartialTableContent, + type TableCell, + type TableContent, isPartialLinkInlineContent, isStyledTextInlineContent, } from "../../../schema/index.js"; @@ -185,7 +183,7 @@ type OccupancyGrid = (RelativeCellIndices & { * @returns an {@link OccupancyGrid} */ export function getTableCellOccupancyGrid( - block: BlockFromConfigNoChildren, + block: BlockFromConfig, ): OccupancyGrid { const { height, width } = getDimensionsOfTable(block); @@ -295,7 +293,7 @@ export function getAbsoluteTableCells( /** * The table block containing the cell. */ - block: BlockFromConfigNoChildren, + block: BlockFromConfig, /** * The occupancy grid of the table. */ @@ -327,7 +325,7 @@ export function getAbsoluteTableCells( * @returns The height and width of the table. */ export function getDimensionsOfTable( - block: BlockFromConfigNoChildren, + block: BlockFromConfig, ): { /** * The number of rows in the table. @@ -370,7 +368,7 @@ export function getRelativeTableCells( /** * The table block containing the cell. */ - block: BlockFromConfigNoChildren, + block: BlockFromConfig, /** * The occupancy grid of the table. */ @@ -428,7 +426,7 @@ export function getRelativeTableCells( * @returns All of the cells associated with the relative row of the table. (All cells that have the same relative row index) */ export function getCellsAtRowHandle( - block: BlockFromConfigNoChildren, + block: BlockFromConfig, relativeRowIndex: RelativeCellIndices["row"], ) { const occupancyGrid = getTableCellOccupancyGrid(block); @@ -507,7 +505,7 @@ export function getCellsAtRowHandle( * @returns All of the cells associated with the relative column of the table. (All cells that have the same relative column index) */ export function getCellsAtColumnHandle( - block: BlockFromConfigNoChildren, + block: BlockFromConfig, relativeColumnIndex: RelativeCellIndices["col"], ) { const occupancyGrid = getTableCellOccupancyGrid(block); @@ -563,7 +561,7 @@ export function getCellsAtColumnHandle( * @note This is a destructive operation, it will modify the provided {@link OccupancyGrid} in place. */ export function moveColumn( - block: BlockFromConfigNoChildren, + block: BlockFromConfig, fromColIndex: RelativeCellIndices["col"], toColIndex: RelativeCellIndices["col"], occupancyGrid: OccupancyGrid = getTableCellOccupancyGrid(block), @@ -607,7 +605,7 @@ export function moveColumn( * @note This is a destructive operation, it will modify the {@link OccupancyGrid} in place. */ export function moveRow( - block: BlockFromConfigNoChildren, + block: BlockFromConfig, fromRowIndex: RelativeCellIndices["row"], toRowIndex: RelativeCellIndices["row"], occupancyGrid: OccupancyGrid = getTableCellOccupancyGrid(block), @@ -656,7 +654,8 @@ function isCellEmpty( return true; } if (isPartialTableCell(cell)) { - return isCellEmpty(cell.content); + // TODO: what happened here? + return isCellEmpty(cell); } else if (typeof cell === "string") { return cell.length === 0; } else if (Array.isArray(cell)) { @@ -682,7 +681,7 @@ function isCellEmpty( * @note This is a destructive operation, it will modify the {@link OccupancyGrid} in place. */ export function cropEmptyRowsOrColumns( - block: BlockFromConfigNoChildren, + block: BlockFromConfig, removeEmpty: "columns" | "rows", occupancyGrid: OccupancyGrid = getTableCellOccupancyGrid(block), ): TableContent["rows"] { @@ -744,7 +743,7 @@ export function cropEmptyRowsOrColumns( * @note This is a destructive operation, it will modify the {@link OccupancyGrid} in place. */ export function addRowsOrColumns( - block: BlockFromConfigNoChildren, + block: BlockFromConfig, addType: "columns" | "rows", /** * The number of rows or columns to add. @@ -800,7 +799,7 @@ export function addRowsOrColumns( * Checks if a row can be safely dropped at the target row index without splitting merged cells. */ export function canRowBeDraggedInto( - block: BlockFromConfigNoChildren, + block: BlockFromConfig, draggingIndex: RelativeCellIndices["row"], targetRowIndex: RelativeCellIndices["row"], ) { @@ -835,7 +834,7 @@ export function canRowBeDraggedInto( * Checks if a column can be safely dropped at the target column index without splitting merged cells. */ export function canColumnBeDraggedInto( - block: BlockFromConfigNoChildren, + block: BlockFromConfig, draggingIndex: RelativeCellIndices["col"], targetColumnIndex: RelativeCellIndices["col"], ) { @@ -874,7 +873,7 @@ export function canColumnBeDraggedInto( export function areInSameColumn( from: RelativeCellIndices, to: RelativeCellIndices, - block: BlockFromConfigNoChildren, + block: BlockFromConfig, ) { // Table indices are relative to the table, so we need to resolve the absolute cell indices const anchorAbsoluteCellIndices = getAbsoluteTableCells(from, block); diff --git a/packages/core/src/api/exporters/html/externalHTMLExporter.ts b/packages/core/src/api/exporters/html/externalHTMLExporter.ts index 12f486d563..27c77b5efa 100644 --- a/packages/core/src/api/exporters/html/externalHTMLExporter.ts +++ b/packages/core/src/api/exporters/html/externalHTMLExporter.ts @@ -1,6 +1,6 @@ import { DOMSerializer, Schema } from "prosemirror-model"; -import { PartialBlock } from "../../../blocks/defaultBlocks.js"; +import { Block } from "../../../blocks/defaultBlocks.js"; import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; import { BlockSchema, @@ -40,7 +40,7 @@ export const createExternalHTMLExporter = < return { exportBlocks: ( - blocks: PartialBlock[], + blocks: Block[], options: { document?: Document }, ) => { const html = serializeBlocksExternalHTML( @@ -62,7 +62,7 @@ export const createExternalHTMLExporter = < ) => { const domFragment = serializeInlineContentExternalHTML( editor, - inlineContent as any, + inlineContent as unknown as never, serializer, options, ); diff --git a/packages/core/src/api/exporters/html/internalHTMLSerializer.ts b/packages/core/src/api/exporters/html/internalHTMLSerializer.ts index 5b3003cf55..3f1da10613 100644 --- a/packages/core/src/api/exporters/html/internalHTMLSerializer.ts +++ b/packages/core/src/api/exporters/html/internalHTMLSerializer.ts @@ -1,5 +1,6 @@ import { DOMSerializer, Schema } from "prosemirror-model"; -import { PartialBlock } from "../../../blocks/defaultBlocks.js"; + +import { Block } from "../../../blocks/index.js"; import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; import { BlockSchema, @@ -28,7 +29,7 @@ export const createInternalHTMLSerializer = < return { serializeBlocks: ( - blocks: PartialBlock[], + blocks: Block[], options: { document?: Document }, ) => { return serializeBlocksInternalHTML(editor, blocks, serializer, options) diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts index 3992ef4696..79da4d5df0 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts @@ -1,6 +1,5 @@ import { DOMSerializer, Fragment, Node } from "prosemirror-model"; -import * as z from "zod/v4/core"; -import { PartialBlock } from "../../../../blocks/defaultBlocks.js"; +import { Block } from "../../../../blocks/index.js"; import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; import { BlockImplementation, @@ -35,7 +34,7 @@ export function serializeInlineContentExternalHTML< S extends StyleSchema, >( editor: BlockNoteEditor, - blockContent: PartialBlock["content"], + blockContent: Block["content"], serializer: DOMSerializer, options?: { document?: Document }, ) { @@ -166,7 +165,7 @@ function serializeBlock< >( fragment: DocumentFragment, editor: BlockNoteEditor, - block: PartialBlock, + block: Block, serializer: DOMSerializer, orderedListItemBlockTypes: Set, unorderedListItemBlockTypes: Set, @@ -177,25 +176,25 @@ function serializeBlock< // set default props in case we were passed a partial block // TODO: should be a nicer way for this / or move to caller - const props = block.props || {}; - for (const [name, spec] of Object.entries( - editor.schema.blockSchema[ - block.type as keyof typeof editor.schema.blockSchema - ].propSchema._zod.def.shape, - )) { - if ( - !(name in props) && - spec instanceof z.$ZodDefault && - spec._zod.def.defaultValue !== undefined - ) { - (props as any)[name] = spec._zod.def.defaultValue; - } - } + // const props = block.props || {}; + // for (const [name, spec] of Object.entries( + // editor.schema.blockSchema[ + // block.type as keyof typeof editor.schema.blockSchema + // ].propSchema._zod.def.shape, + // )) { + // if ( + // !(name in props) && + // spec instanceof z.$ZodDefault && + // spec._zod.def.defaultValue !== undefined + // ) { + // (props as any)[name] = spec._zod.def.defaultValue; + // } + // } const bc = BC_NODE.spec?.toDOM?.( BC_NODE.create({ id: block.id, - ...props, + ...block.props, }), ) as { dom: HTMLElement; @@ -211,14 +210,10 @@ function serializeBlock< const ret = blockImplementation.toExternalHTML?.call( {}, - { ...block, props } as any, + { ...block } as any, editor as any, ) || - blockImplementation.render.call( - {}, - { ...block, props } as any, - editor as any, - ); + blockImplementation.render.call({}, { ...block } as any, editor as any); const elementFragment = doc.createDocumentFragment(); @@ -247,11 +242,10 @@ function serializeBlock< } else { elementFragment.append(ret.dom); } - if (ret.contentDOM && block.content) { const ic = serializeInlineContentExternalHTML( editor, - block.content as any, // TODO + block.content as unknown as never, // TODO serializer, options, ); @@ -272,11 +266,11 @@ function serializeBlock< if ( listType === "OL" && - "start" in props && - props.start && - props?.start !== 1 + "start" in block.props && + block.props.start && + block.props?.start !== 1 ) { - list.setAttribute("start", props.start + ""); + list.setAttribute("start", block.props.start + ""); } fragment.append(list); } @@ -326,7 +320,7 @@ const serializeBlocksToFragment = < >( fragment: DocumentFragment, editor: BlockNoteEditor, - blocks: PartialBlock[], + blocks: Block[], serializer: DOMSerializer, orderedListItemBlockTypes: Set, unorderedListItemBlockTypes: Set, @@ -351,7 +345,7 @@ export const serializeBlocksExternalHTML = < S extends StyleSchema, >( editor: BlockNoteEditor, - blocks: PartialBlock[], + blocks: Block[], serializer: DOMSerializer, orderedListItemBlockTypes: Set, unorderedListItemBlockTypes: Set, diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts index d2934620d0..d582e4922d 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts @@ -1,6 +1,6 @@ import { DOMSerializer, Fragment, Node } from "prosemirror-model"; -import { PartialBlock } from "../../../../blocks/defaultBlocks.js"; +import { Block } from "../../../../blocks/defaultBlocks.js"; import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; import { BlockSchema, @@ -21,7 +21,7 @@ export function serializeInlineContentInternalHTML< S extends StyleSchema, >( editor: BlockNoteEditor, - blockContent: PartialBlock["content"], + blockContent: Block["content"], serializer: DOMSerializer, blockType?: string, options?: { document?: Document }, @@ -133,7 +133,7 @@ function serializeBlock< S extends StyleSchema, >( editor: BlockNoteEditor, - block: PartialBlock, + block: Block, serializer: DOMSerializer, options?: { document?: Document }, ) { @@ -169,7 +169,7 @@ function serializeBlock< if (ret.contentDOM && block.content) { const ic = serializeInlineContentInternalHTML( editor, - block.content as any, // TODO + block.content as never, // TODO serializer, block.type, options, @@ -220,7 +220,7 @@ function serializeBlocks< S extends StyleSchema, >( editor: BlockNoteEditor, - blocks: PartialBlock[], + blocks: Block[], serializer: DOMSerializer, options?: { document?: Document }, ) { @@ -241,7 +241,7 @@ export const serializeBlocksInternalHTML = < S extends StyleSchema, >( editor: BlockNoteEditor, - blocks: PartialBlock[], + blocks: Block[], serializer: DOMSerializer, options?: { document?: Document }, ) => { diff --git a/packages/core/src/api/exporters/markdown/markdownExporter.ts b/packages/core/src/api/exporters/markdown/markdownExporter.ts index 23aad8db7c..6950f17254 100644 --- a/packages/core/src/api/exporters/markdown/markdownExporter.ts +++ b/packages/core/src/api/exporters/markdown/markdownExporter.ts @@ -5,7 +5,7 @@ import remarkGfm from "remark-gfm"; import remarkStringify from "remark-stringify"; import { unified } from "unified"; -import { PartialBlock } from "../../../blocks/defaultBlocks.js"; +import { Block } from "../../../blocks/defaultBlocks.js"; import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; import { BlockSchema, @@ -13,9 +13,9 @@ import { StyleSchema, } from "../../../schema/index.js"; import { createExternalHTMLExporter } from "../html/externalHTMLExporter.js"; -import { removeUnderlines } from "./util/removeUnderlinesRehypePlugin.js"; import { addSpacesToCheckboxes } from "./util/addSpacesToCheckboxesRehypePlugin.js"; import { convertVideoToMarkdown } from "./util/convertVideoToMarkdownRehypePlugin.js"; +import { removeUnderlines } from "./util/removeUnderlinesRehypePlugin.js"; // Needs to be sync because it's used in drag handler event (SideMenuPlugin) export function cleanHTMLToMarkdown(cleanHTMLString: string) { @@ -39,7 +39,7 @@ export function blocksToMarkdown< I extends InlineContentSchema, S extends StyleSchema, >( - blocks: PartialBlock[], + blocks: Block[], schema: Schema, editor: BlockNoteEditor, options: { document?: Document }, diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index 660d97406e..58f97b2c60 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -1,22 +1,21 @@ import { Attrs, Fragment, Mark, Node, Schema } from "@tiptap/pm/model"; -import UniqueID from "../../extensions/UniqueID/UniqueID.js"; import type { + CustomInlineContentFromConfig, + InlineContent, InlineContentSchema, - PartialCustomInlineContentFromConfig, - PartialInlineContent, - PartialLink, - PartialTableContent, + Link, StyleSchema, StyledText, + TableContent, } from "../../schema"; -import type { PartialBlock } from "../../blocks/defaultBlocks"; +import type { Block } from "../../blocks/index.js"; import { - isPartialLinkInlineContent, + isLinkInlineContent, isStyledTextInlineContent, } from "../../schema/inlineContent/types.js"; -import { getColspan, isPartialTableCell } from "../../util/table.js"; +import { getColspan, isTableCell } from "../../util/table.js"; import { UnreachableCaseError } from "../../util/typescript.js"; import { getAbsoluteTableCells } from "../blockManipulation/tables/tables.js"; import { getStyleSchema } from "../pmUtil.js"; @@ -83,7 +82,7 @@ function styledTextToNodes( * prosemirror text nodes with the appropriate marks */ function linkToNodes( - link: PartialLink, + link: Link, schema: Schema, styleSchema: StyleSchema, ): Node[] { @@ -144,7 +143,7 @@ export function inlineContentToNodes< I extends InlineContentSchema, S extends StyleSchema, >( - blockContent: PartialInlineContent, + blockContent: string[] | InlineContent[], schema: Schema, blockType?: string, styleSchema: S = getStyleSchema(schema), @@ -153,10 +152,11 @@ export function inlineContentToNodes< for (const content of blockContent) { if (typeof content === "string") { + // TODO: remove? nodes.push( ...styledTextArrayToNodes(content, schema, styleSchema, blockType), ); - } else if (isPartialLinkInlineContent(content)) { + } else if (isLinkInlineContent(content)) { nodes.push(...linkToNodes(content, schema, styleSchema)); } else if (isStyledTextInlineContent(content)) { nodes.push( @@ -178,7 +178,7 @@ export function tableContentToNodes< I extends InlineContentSchema, S extends StyleSchema, >( - tableContent: PartialTableContent, + tableContent: TableContent, schema: Schema, styleSchema: StyleSchema = getStyleSchema(schema), ): Node[] { @@ -227,7 +227,7 @@ export function tableContentToNodes< // No-op } else if (typeof cell === "string") { content = schema.text(cell); - } else if (isPartialTableCell(cell)) { + } else if (isTableCell(cell)) { if (cell.content) { content = inlineContentToNodes( cell.content, @@ -258,7 +258,7 @@ export function tableContentToNodes< isHeaderCol || isHeaderRow ? "tableHeader" : "tableCell" ].createChecked( { - ...(isPartialTableCell(cell) ? cell.props : {}), + ...(isTableCell(cell) ? cell.props : {}), colwidth, }, schema.nodes["tableParagraph"].createChecked(attrs, content), @@ -273,9 +273,7 @@ export function tableContentToNodes< } function blockOrInlineContentToContentNode( - block: - | PartialBlock - | PartialCustomInlineContentFromConfig, + block: Block | CustomInlineContentFromConfig, schema: Schema, styleSchema: StyleSchema, ) { @@ -322,16 +320,10 @@ function blockOrInlineContentToContentNode( * Converts a BlockNote block to a Prosemirror node. */ export function blockToNode( - block: PartialBlock, + block: Block, schema: Schema, styleSchema: StyleSchema = getStyleSchema(schema), ) { - let id = block.id; - - if (id === undefined) { - id = UniqueID.options.generateID(); - } - const children: Node[] = []; if (block.children) { @@ -360,7 +352,7 @@ export function blockToNode( return schema.nodes["blockContainer"].createChecked( { - id: id, + id: block.id, ...block.props, }, groupNode ? [contentNode, groupNode] : contentNode, @@ -369,7 +361,7 @@ export function blockToNode( // this is a bnBlock node like Column or ColumnList that directly translates to a prosemirror node return schema.nodes[block.type].createChecked( { - id: id, + id: block.id, ...block.props, }, children, diff --git a/packages/core/src/api/nodeConversions/fragmentToBlocks.ts b/packages/core/src/api/nodeConversions/fragmentToBlocks.ts index 724b552bda..878c2eb032 100644 --- a/packages/core/src/api/nodeConversions/fragmentToBlocks.ts +++ b/packages/core/src/api/nodeConversions/fragmentToBlocks.ts @@ -1,6 +1,6 @@ import { Fragment } from "@tiptap/pm/model"; +import type { Block } from "../../blocks/index.js"; import { - BlockNoDefaults, BlockSchema, InlineContentSchema, StyleSchema, @@ -18,7 +18,7 @@ export function fragmentToBlocks< >(fragment: Fragment) { // first convert selection to blocknote-style blocks, and then // pass these to the exporter - const blocks: BlockNoDefaults[] = []; + const blocks: Block[] = []; fragment.descendants((node) => { const pmSchema = getPmSchema(node); if (node.type.name === "blockContainer") { diff --git a/packages/core/src/blocks/Audio/block.ts b/packages/core/src/blocks/Audio/block.ts index 6dcf4e5139..d9effb12b0 100644 --- a/packages/core/src/blocks/Audio/block.ts +++ b/packages/core/src/blocks/Audio/block.ts @@ -1,6 +1,6 @@ -import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; +import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { - BlockFromConfig, + type BlockFromConfig, createBlockConfig, createBlockSpec, } from "../../schema/index.js"; diff --git a/packages/core/src/blocks/File/helpers/render/createAddFileButton.ts b/packages/core/src/blocks/File/helpers/render/createAddFileButton.ts index 227856b4ac..f322f2614d 100644 --- a/packages/core/src/blocks/File/helpers/render/createAddFileButton.ts +++ b/packages/core/src/blocks/File/helpers/render/createAddFileButton.ts @@ -1,11 +1,8 @@ import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; -import { - BlockConfig, - BlockFromConfigNoChildren, -} from "../../../../schema/index.js"; +import type { Block } from "../../../index.js"; export const createAddFileButton = ( - block: BlockFromConfigNoChildren, any, any>, + block: Block, editor: BlockNoteEditor, buttonIcon?: HTMLElement, ) => { diff --git a/packages/core/src/blocks/File/helpers/render/createFileBlockWrapper.ts b/packages/core/src/blocks/File/helpers/render/createFileBlockWrapper.ts index a55ddbbad7..d921b2dae7 100644 --- a/packages/core/src/blocks/File/helpers/render/createFileBlockWrapper.ts +++ b/packages/core/src/blocks/File/helpers/render/createFileBlockWrapper.ts @@ -1,8 +1,5 @@ import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; -import { - BlockConfig, - BlockFromConfigNoChildren, -} from "../../../../schema/index.js"; +import type { BlockConfig, BlockFromConfig } from "../../../../schema/index.js"; import { baseFilePropSchema, optionalFileProps, @@ -15,7 +12,7 @@ const requiredPropSchema = baseFilePropSchema.extend({ }); export const createFileBlockWrapper = ( - block: BlockFromConfigNoChildren< + block: BlockFromConfig< BlockConfig, any, any diff --git a/packages/core/src/blocks/File/helpers/render/createFileNameWithIcon.ts b/packages/core/src/blocks/File/helpers/render/createFileNameWithIcon.ts index f9e97476a4..23d3b44eb1 100644 --- a/packages/core/src/blocks/File/helpers/render/createFileNameWithIcon.ts +++ b/packages/core/src/blocks/File/helpers/render/createFileNameWithIcon.ts @@ -1,13 +1,10 @@ -import { - BlockConfig, - BlockFromConfigNoChildren, -} from "../../../../schema/index.js"; +import { BlockConfig, BlockFromConfig } from "../../../../schema/index.js"; import { baseFilePropSchema } from "../../../defaultFileProps.js"; export const FILE_ICON_SVG = ``; export const createFileNameWithIcon = ( - block: BlockFromConfigNoChildren< + block: BlockFromConfig< BlockConfig, any, any diff --git a/packages/core/src/blocks/File/helpers/render/createResizableFileBlockWrapper.ts b/packages/core/src/blocks/File/helpers/render/createResizableFileBlockWrapper.ts index 375ddb36e2..88f7275dbe 100644 --- a/packages/core/src/blocks/File/helpers/render/createResizableFileBlockWrapper.ts +++ b/packages/core/src/blocks/File/helpers/render/createResizableFileBlockWrapper.ts @@ -1,8 +1,5 @@ import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; -import { - BlockConfig, - BlockFromConfigNoChildren, -} from "../../../../schema/index.js"; +import { BlockConfig, BlockFromConfig } from "../../../../schema/index.js"; import { baseFilePropSchema, optionalFileProps, @@ -18,7 +15,7 @@ const requiredPropSchema = baseFilePropSchema.extend({ }); export const createResizableFileBlockWrapper = ( - block: BlockFromConfigNoChildren< + block: BlockFromConfig< BlockConfig, any, any diff --git a/packages/core/src/blocks/defaultBlockHelpers.ts b/packages/core/src/blocks/defaultBlockHelpers.ts index ccadf93e11..d4c5b0590f 100644 --- a/packages/core/src/blocks/defaultBlockHelpers.ts +++ b/packages/core/src/blocks/defaultBlockHelpers.ts @@ -1,12 +1,12 @@ import { blockToNode } from "../api/nodeConversions/blockToNode.js"; import type { BlockNoteEditor } from "../editor/BlockNoteEditor.js"; import type { - BlockNoDefaults, BlockSchema, InlineContentSchema, StyleSchema, } from "../schema/index.js"; import { mergeCSSClasses } from "../util/browser.js"; +import type { Block } from "./index.js"; // Function that creates a ProseMirror `DOMOutputSpec` for a default block. // Since all default blocks have the same structure (`blockContent` div with a @@ -61,7 +61,7 @@ export const defaultBlockToHTML = < I extends InlineContentSchema, S extends StyleSchema, >( - block: BlockNoDefaults, + block: Block, editor: BlockNoteEditor, ): { dom: HTMLElement; diff --git a/packages/core/src/blocks/defaultBlocks.ts b/packages/core/src/blocks/defaultBlocks.ts index 24fc32b07e..b58d155222 100644 --- a/packages/core/src/blocks/defaultBlocks.ts +++ b/packages/core/src/blocks/defaultBlocks.ts @@ -17,23 +17,21 @@ import { getInlineContentSchemaFromSpecs, getStyleSchemaFromSpecs, } from "../schema/index.js"; -import { - createAudioBlockSpec, - createBulletListItemBlockSpec, - createCheckListItemBlockSpec, - createCodeBlockSpec, - createDividerBlockSpec, - createFileBlockSpec, - createHeadingBlockSpec, - createImageBlockSpec, - createNumberedListItemBlockSpec, - createParagraphBlockSpec, - createQuoteBlockSpec, - createToggleListItemBlockSpec, - createVideoBlockSpec, - defaultProps, -} from "./index.js"; +import { createAudioBlockSpec } from "./Audio/block.js"; +import { createCodeBlockSpec } from "./Code/block.js"; +import { defaultProps } from "./defaultProps.js"; +import { createDividerBlockSpec } from "./Divider/block.js"; +import { createFileBlockSpec } from "./File/block.js"; +import { createHeadingBlockSpec } from "./Heading/block.js"; +import { createImageBlockSpec } from "./Image/block.js"; +import { createBulletListItemBlockSpec } from "./ListItem/BulletListItem/block.js"; +import { createCheckListItemBlockSpec } from "./ListItem/CheckListItem/block.js"; +import { createNumberedListItemBlockSpec } from "./ListItem/NumberedListItem/block.js"; +import { createToggleListItemBlockSpec } from "./ListItem/ToggleListItem/block.js"; +import { createParagraphBlockSpec } from "./Paragraph/block.js"; +import { createQuoteBlockSpec } from "./Quote/block.js"; import { createTableBlockSpec } from "./Table/block.js"; +import { createVideoBlockSpec } from "./Video/block.js"; export const defaultBlockSpecs = { audio: createAudioBlockSpec(), diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 692348f5e6..af3f7621aa 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -37,18 +37,19 @@ import type { TableHandlesProsemirrorPlugin } from "../extensions/TableHandles/T import { UniqueID } from "../extensions/UniqueID/UniqueID.js"; import type { Dictionary } from "../i18n/dictionary.js"; import { en } from "../i18n/locales/index.js"; -import type { - BlockIdentifier, - BlockNoteDOMAttributes, - BlockSchema, - BlockSpecs, - CustomBlockNoteSchema, - InlineContentSchema, - InlineContentSpecs, - PartialInlineContent, - Styles, - StyleSchema, - StyleSpecs, +import { + partialBlockToFullBlock, + type BlockIdentifier, + type BlockNoteDOMAttributes, + type BlockSchema, + type BlockSpecs, + type CustomBlockNoteSchema, + type InlineContentSchema, + type InlineContentSpecs, + type PartialInlineContent, + type Styles, + type StyleSchema, + type StyleSpecs, } from "../schema/index.js"; import { mergeCSSClasses } from "../util/browser.js"; import { EventEmitter } from "../util/EventEmitter.js"; @@ -59,13 +60,13 @@ import type { TextCursorPosition } from "./cursorPositionTypes.js"; import { BlockManager, CollaborationManager, - type CollaborationOptions, EventManager, ExportManager, ExtensionManager, SelectionManager, StateManager, StyleManager, + type CollaborationOptions, } from "./managers/index.js"; import type { Selection } from "./selectionTypes.js"; import { transformPasted } from "./transformPasted.js"; @@ -889,7 +890,11 @@ export class BlockNoteEditor< } const schema = getSchema(tiptapOptions.extensions!); const pmNodes = initialContent.map((b) => - blockToNode(b, schema, this.schema.styleSchema).toJSON(), + blockToNode( + partialBlockToFullBlock(this.schema, b), + schema, + this.schema.styleSchema, + ).toJSON(), ); const doc = createDocument( { @@ -1467,7 +1472,7 @@ export class BlockNoteEditor< * @returns The blocks, serialized as an HTML string. */ public blocksToHTMLLossy( - blocks: PartialBlock[] = this.document, + blocks: Block[] = this.document, ): string { return this._exportManager.blocksToHTMLLossy(blocks); } @@ -1481,9 +1486,7 @@ export class BlockNoteEditor< * @param blocks An array of blocks that should be serialized into HTML. * @returns The blocks, serialized as an HTML string. */ - public blocksToFullHTML( - blocks: PartialBlock[], - ): string { + public blocksToFullHTML(blocks: Block[]): string { return this._exportManager.blocksToFullHTML(blocks); } /** @@ -1506,7 +1509,7 @@ export class BlockNoteEditor< * @returns The blocks, serialized as a Markdown string. */ public blocksToMarkdownLossy( - blocks: PartialBlock[] = this.document, + blocks: Block[] = this.document, ): string { return this._exportManager.blocksToMarkdownLossy(blocks); } diff --git a/packages/core/src/editor/BlockNoteExtension.ts b/packages/core/src/editor/BlockNoteExtension.ts index f56d6c736a..ecc84c38be 100644 --- a/packages/core/src/editor/BlockNoteExtension.ts +++ b/packages/core/src/editor/BlockNoteExtension.ts @@ -2,10 +2,10 @@ import { Plugin } from "prosemirror-state"; import { EventEmitter } from "../util/EventEmitter.js"; import { AnyExtension } from "@tiptap/core"; +import { PartialBlock } from "../blocks/index.js"; import { BlockSchema, InlineContentSchema, - PartialBlockNoDefaults, StyleSchema, } from "../schema/index.js"; import { BlockNoteEditor } from "./BlockNoteEditor.js"; @@ -93,7 +93,7 @@ export type InputRule = { * The editor instance */ editor: BlockNoteEditor; - }) => undefined | PartialBlockNoDefaults; + }) => undefined | PartialBlock; }; /** diff --git a/packages/core/src/editor/managers/CollaborationManager.ts b/packages/core/src/editor/managers/CollaborationManager.ts index 8273fb5cb4..952701c82c 100644 --- a/packages/core/src/editor/managers/CollaborationManager.ts +++ b/packages/core/src/editor/managers/CollaborationManager.ts @@ -1,14 +1,14 @@ -import * as Y from "yjs"; import { redoCommand, undoCommand } from "y-prosemirror"; -import { CommentsPlugin } from "../../extensions/Comments/CommentsPlugin.js"; -import { CommentMark } from "../../extensions/Comments/CommentMark.js"; +import * as Y from "yjs"; +import type { ThreadStore, User } from "../../comments/index.js"; +import { CursorPlugin } from "../../extensions/Collaboration/CursorPlugin.js"; import { ForkYDocPlugin } from "../../extensions/Collaboration/ForkYDocPlugin.js"; import { SyncPlugin } from "../../extensions/Collaboration/SyncPlugin.js"; import { UndoPlugin } from "../../extensions/Collaboration/UndoPlugin.js"; -import { CursorPlugin } from "../../extensions/Collaboration/CursorPlugin.js"; -import type { ThreadStore, User } from "../../comments/index.js"; +import { CommentMark } from "../../extensions/Comments/CommentMark.js"; +import { CommentsPlugin } from "../../extensions/Comments/CommentsPlugin.js"; +import { CustomBlockNoteSchema } from "../../schema/CustomBlockNoteSchema.js"; import type { BlockNoteEditor } from "../BlockNoteEditor.js"; -import { CustomBlockNoteSchema } from "../../schema/schema.js"; export interface CollaborationOptions { /** diff --git a/packages/core/src/editor/managers/ExportManager.ts b/packages/core/src/editor/managers/ExportManager.ts index aba001ce65..2bce921f6f 100644 --- a/packages/core/src/editor/managers/ExportManager.ts +++ b/packages/core/src/editor/managers/ExportManager.ts @@ -11,7 +11,6 @@ import { DefaultBlockSchema, DefaultInlineContentSchema, DefaultStyleSchema, - PartialBlock, } from "../../blocks/defaultBlocks.js"; import { BlockSchema, @@ -35,7 +34,7 @@ export class ExportManager< * @returns The blocks, serialized as an HTML string. */ public blocksToHTMLLossy( - blocks: PartialBlock[] = this.editor.document, + blocks: Block[] = this.editor.document, ): string { const exporter = createExternalHTMLExporter( this.editor.pmSchema, @@ -53,9 +52,7 @@ export class ExportManager< * @param blocks An array of blocks that should be serialized into HTML. * @returns The blocks, serialized as an HTML string. */ - public blocksToFullHTML( - blocks: PartialBlock[], - ): string { + public blocksToFullHTML(blocks: Block[]): string { const exporter = createInternalHTMLSerializer( this.editor.pmSchema, this.editor, @@ -83,7 +80,7 @@ export class ExportManager< * @returns The blocks, serialized as a Markdown string. */ public blocksToMarkdownLossy( - blocks: PartialBlock[] = this.editor.document, + blocks: Block[] = this.editor.document, ): string { return blocksToMarkdown(blocks, this.editor.pmSchema, this.editor, {}); } diff --git a/packages/core/src/editor/managers/StyleManager.ts b/packages/core/src/editor/managers/StyleManager.ts index e03c46a6d1..bb487d0744 100644 --- a/packages/core/src/editor/managers/StyleManager.ts +++ b/packages/core/src/editor/managers/StyleManager.ts @@ -1,18 +1,19 @@ +import { TextSelection } from "@tiptap/pm/state"; import { insertContentAt } from "../../api/blockManipulation/insertContentAt.js"; import { inlineContentToNodes } from "../../api/nodeConversions/blockToNode.js"; +import { + DefaultBlockSchema, + DefaultInlineContentSchema, + DefaultStyleSchema, +} from "../../blocks/defaultBlocks.js"; import { BlockSchema, InlineContentSchema, PartialInlineContent, StyleSchema, Styles, + partialInlineContentToInlineContent, } from "../../schema/index.js"; -import { - DefaultBlockSchema, - DefaultInlineContentSchema, - DefaultStyleSchema, -} from "../../blocks/defaultBlocks.js"; -import { TextSelection } from "@tiptap/pm/state"; import { UnreachableCaseError } from "../../util/typescript.js"; import { BlockNoteEditor } from "../BlockNoteEditor.js"; @@ -32,7 +33,11 @@ export class StyleManager< content: PartialInlineContent, { updateSelection = false }: { updateSelection?: boolean } = {}, ) { - const nodes = inlineContentToNodes(content, this.editor.pmSchema); + const fullContent = partialInlineContentToInlineContent( + content, + this.editor.schema.inlineContentSchema, + ); + const nodes = inlineContentToNodes(fullContent, this.editor.pmSchema); this.editor.transact((tr) => { insertContentAt( diff --git a/packages/core/src/exporter/mapping.ts b/packages/core/src/exporter/mapping.ts index 0dca63ebc3..d75303438e 100644 --- a/packages/core/src/exporter/mapping.ts +++ b/packages/core/src/exporter/mapping.ts @@ -1,6 +1,6 @@ import { BlockNoteSchema } from "../blocks/BlockNoteSchema.js"; import { - BlockFromConfigNoChildren, + BlockFromConfig, BlockSchema, InlineContentFromConfig, InlineContentSchema, @@ -20,7 +20,7 @@ export type BlockMapping< RI, > = { [K in keyof B]: ( - block: BlockFromConfigNoChildren, + block: BlockFromConfig, // we don't know the exact types that are supported by the exporter at this point, // because the mapping only knows about converting certain types (which might be a subset of the supported types) // this is why there are many `any` types here (same for types below) diff --git a/packages/core/src/extensions/Comments/CommentsPlugin.ts b/packages/core/src/extensions/Comments/CommentsPlugin.ts index a5d1e24b6d..635bfacd2d 100644 --- a/packages/core/src/extensions/Comments/CommentsPlugin.ts +++ b/packages/core/src/extensions/Comments/CommentsPlugin.ts @@ -10,7 +10,7 @@ import type { } from "../../comments/index.js"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js"; -import { CustomBlockNoteSchema } from "../../schema/schema.js"; +import { CustomBlockNoteSchema } from "../../schema/CustomBlockNoteSchema.js"; import { UserStore } from "./userstore/UserStore.js"; const PLUGIN_KEY = new PluginKey(`blocknote-comments`); diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index f90fe9e95b..0d32681869 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -27,14 +27,18 @@ import { import { nodeToBlock } from "../../api/nodeConversions/nodeToBlock.js"; import { getNodeById } from "../../api/nodeUtil.js"; import { - editorHasBlockWithType, + blockHasType, isTableCellSelection, } from "../../blocks/defaultBlockTypeGuards.js"; -import { DefaultBlockSchema } from "../../blocks/defaultBlocks.js"; +import { + DefaultBlockSchema, + defaultBlockSpecs, +} from "../../blocks/defaultBlocks.js"; +import { TableBlockConfig } from "../../blocks/index.js"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js"; import { - BlockFromConfigNoChildren, + BlockFromConfig, BlockSchemaWithBlock, InlineContentSchema, StyleSchema, @@ -54,7 +58,7 @@ export type TableHandlesState< referencePosCell: DOMRect | undefined; referencePosTable: DOMRect; - block: BlockFromConfigNoChildren; + block: BlockFromConfig; colIndex: number | undefined; rowIndex: number | undefined; @@ -164,7 +168,7 @@ export class TableHandlesView< constructor( private readonly editor: BlockNoteEditor< - BlockSchemaWithBlock<"table", DefaultBlockSchema["table"]>, + BlockSchemaWithBlock<"table", TableBlockConfig>, I, S >, @@ -260,7 +264,7 @@ export class TableHandlesView< this.tableElement = blockEl.node; let tableBlock: - | BlockFromConfigNoChildren + | BlockFromConfig | undefined; const pmNodeInfo = this.editor.transact((tr) => @@ -278,7 +282,14 @@ export class TableHandlesView< this.editor.schema.styleSchema, ); - if (editorHasBlockWithType(this.editor, "table")) { + if ( + blockHasType( + block, + this.editor, + "table", + defaultBlockSpecs.table.config.propSchema, + ) + ) { this.tablePos = pmNodeInfo.posBeforeNode + 1; tableBlock = block; } @@ -535,10 +546,16 @@ export class TableHandlesView< } // Hide handles if the table block has been removed. - this.state.block = this.editor.getBlock(this.state.block.id)!; + const block = this.editor.getBlock(this.state.block.id)!; + if ( - !this.state.block || - this.state.block.type !== "table" || + !block || + !blockHasType( + block, + this.editor, + "table", + defaultBlockSpecs.table.config.propSchema, + ) || // when collaborating, the table element might be replaced and out of date // because yjs replaces the element when for example you change the color via the side menu !this.tableElement?.isConnected @@ -551,6 +568,7 @@ export class TableHandlesView< return; } + this.state.block = block; const { height: rowCount, width: colCount } = getDimensionsOfTable( this.state.block, ); @@ -626,7 +644,7 @@ export class TableHandlesProsemirrorPlugin< constructor( private readonly editor: BlockNoteEditor< - BlockSchemaWithBlock<"table", DefaultBlockSchema["table"]>, + BlockSchemaWithBlock<"table", TableBlockConfig>, I, S >, @@ -921,7 +939,7 @@ export class TableHandlesProsemirrorPlugin< }; getCellsAtRowHandle = ( - block: BlockFromConfigNoChildren, + block: BlockFromConfig, relativeRowIndex: RelativeCellIndices["row"], ) => { return getCellsAtRowHandle(block, relativeRowIndex); @@ -931,7 +949,7 @@ export class TableHandlesProsemirrorPlugin< * Get all the cells in a column of the table block. */ getCellsAtColumnHandle = ( - block: BlockFromConfigNoChildren, + block: BlockFromConfig, relativeColumnIndex: RelativeCellIndices["col"], ) => { return getCellsAtColumnHandle(block, relativeColumnIndex); @@ -1161,9 +1179,7 @@ export class TableHandlesProsemirrorPlugin< * Returns undefined when there is no cell selection, or the selection is not within a table. */ getMergeDirection = ( - block: - | BlockFromConfigNoChildren - | undefined, + block: BlockFromConfig | undefined, ) => { return this.editor.transact((tr) => { const isSelectingTableCells = isTableCellSelection(tr.selection) @@ -1194,14 +1210,14 @@ export class TableHandlesProsemirrorPlugin< }; cropEmptyRowsOrColumns = ( - block: BlockFromConfigNoChildren, + block: BlockFromConfig, removeEmpty: "columns" | "rows", ) => { return cropEmptyRowsOrColumns(block, removeEmpty); }; addRowsOrColumns = ( - block: BlockFromConfigNoChildren, + block: BlockFromConfig, addType: "columns" | "rows", numToAdd: number, ) => { diff --git a/packages/core/src/schema/schema.ts b/packages/core/src/schema/CustomBlockNoteSchema.ts similarity index 89% rename from packages/core/src/schema/schema.ts rename to packages/core/src/schema/CustomBlockNoteSchema.ts index 7accfbfc23..effa9f9e81 100644 --- a/packages/core/src/schema/schema.ts +++ b/packages/core/src/schema/CustomBlockNoteSchema.ts @@ -1,21 +1,20 @@ -import { BlockNoteEditor } from "../editor/BlockNoteEditor.js"; +import type { Block, PartialBlock } from "../blocks/defaultBlocks.js"; +import type { BlockNoteEditor } from "../editor/BlockNoteEditor.js"; import { createDependencyGraph, toposortReverse } from "../util/topo-sort.js"; +import { addNodeAndExtensionsToSpec } from "./blocks/createSpec.js"; import { - BlockNoDefaults, - BlockSchema, - BlockSpecs, - InlineContentConfig, - InlineContentSchema, - InlineContentSpec, - InlineContentSpecs, - LooseBlockSpec, - PartialBlockNoDefaults, - StyleSchema, - StyleSpecs, - addNodeAndExtensionsToSpec, - getInlineContentSchemaFromSpecs, - getStyleSchemaFromSpecs, + type BlockSchema, + type BlockSpecs, + type InlineContentConfig, + type InlineContentSchema, + type InlineContentSpec, + type InlineContentSpecs, + type LooseBlockSpec, + type StyleSchema, + type StyleSpecs, } from "./index.js"; +import { getInlineContentSchemaFromSpecs } from "./inlineContent/internal.js"; +import { getStyleSchemaFromSpecs } from "./styles/internal.js"; function removeUndefined | undefined>(obj: T): T { if (!obj) { @@ -35,14 +34,11 @@ export class CustomBlockNoteSchema< public readonly BlockNoteEditor: BlockNoteEditor = "only for types" as any; - public readonly Block: BlockNoDefaults = + public readonly Block: Block = "only for types" as any; - public readonly PartialBlock: PartialBlockNoDefaults< - BSchema, - ISchema, - SSchema - > = "only for types" as any; + public readonly PartialBlock: PartialBlock = + "only for types" as any; public inlineContentSpecs: InlineContentSpecs; public styleSpecs: StyleSpecs; diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts index b4c3500a17..b22babe091 100644 --- a/packages/core/src/schema/blocks/types.ts +++ b/packages/core/src/schema/blocks/types.ts @@ -12,6 +12,7 @@ import type { } from "../inlineContent/types.js"; import type { PropSchema, Props } from "../propTypes.js"; import type { StyleSchema } from "../styles/types.js"; +import { PartialTableContent, TableContent } from "./types/tableContent.js"; export type BlockNoteDOMElement = | "editor" @@ -222,44 +223,17 @@ export type BlockSpecsFromSchema = { }; }; -export type BlockSchemaWithBlock = { +export type BlockSchemaWithBlock< + T extends string, + C extends BlockConfig, +> = NamesMatch<{ [k in T]: C; -}; - -export type TableCellProps = { - backgroundColor: string; - textColor: string; - textAlignment: "left" | "center" | "right" | "justify"; - colspan?: number; - rowspan?: number; -}; - -export type TableCell< - I extends InlineContentSchema, - S extends StyleSchema = StyleSchema, -> = { - type: "tableCell"; - props: TableCellProps; - content: InlineContent[]; -}; - -export type TableContent< - I extends InlineContentSchema, - S extends StyleSchema = StyleSchema, -> = { - type: "tableContent"; - columnWidths: (number | undefined)[]; - headerRows?: number; - headerCols?: number; - rows: { - cells: InlineContent[][] | TableCell[]; - }[]; -}; +}>; // A BlockConfig has all the information to get the type of a Block (which is a specific instance of the BlockConfig. // i.e.: paragraphConfig: BlockConfig defines what a "paragraph" is / supports, and BlockFromConfigNoChildren is the shape of a specific paragraph block. // (for internal use) -export type BlockFromConfigNoChildren< +type BlockFromConfigNoChildren< B extends BlockConfig, I extends InlineContentSchema, S extends StyleSchema, @@ -297,6 +271,7 @@ type BlocksWithoutChildren< // Converts each block spec into a Block object without children, merges them // into a union type, and adds a children property +// TODO: should only be exposed internally export type BlockNoDefaults< BSchema extends BlockSchema, I extends InlineContentSchema, @@ -314,35 +289,6 @@ export type SpecificBlock< children: BlockNoDefaults[]; }; -/** CODE FOR PARTIAL BLOCKS, analogous to above - * - * Partial blocks are convenience-wrappers to make it easier to - *create/update blocks in the editor. - * - */ - -export type PartialTableCell< - I extends InlineContentSchema, - S extends StyleSchema = StyleSchema, -> = { - type: "tableCell"; - props?: Partial; - content?: PartialInlineContent; -}; - -export type PartialTableContent< - I extends InlineContentSchema, - S extends StyleSchema = StyleSchema, -> = { - type: "tableContent"; - columnWidths?: (number | undefined)[]; - headerRows?: number; - headerCols?: number; - rows: { - cells: PartialInlineContent[] | PartialTableCell[]; - }[]; -}; - type PartialBlockFromConfigNoChildren< B extends BlockConfig, I extends InlineContentSchema, @@ -385,23 +331,6 @@ export type PartialBlockNoDefaults< children: PartialBlockNoDefaults[]; }>; -export type SpecificPartialBlock< - BSchema extends BlockSchema, - I extends InlineContentSchema, - BType extends keyof BSchema, - S extends StyleSchema, -> = PartialBlocksWithoutChildren[BType] & { - children?: BlockNoDefaults[]; -}; - -export type PartialBlockFromConfig< - B extends BlockConfig, - I extends InlineContentSchema, - S extends StyleSchema, -> = PartialBlockFromConfigNoChildren & { - children?: BlockNoDefaults[]; -}; - export type BlockIdentifier = { id: string } | string; export type BlockImplementation< diff --git a/packages/core/src/schema/blocks/types/tableContent.ts b/packages/core/src/schema/blocks/types/tableContent.ts new file mode 100644 index 0000000000..ad64e8bb8d --- /dev/null +++ b/packages/core/src/schema/blocks/types/tableContent.ts @@ -0,0 +1,65 @@ +import { + InlineContent, + InlineContentSchema, + PartialInlineContent, +} from "../../inlineContent/types.js"; +import { StyleSchema } from "../../styles/types.js"; + +export type TableCellProps = { + backgroundColor: string; + textColor: string; + textAlignment: "left" | "center" | "right" | "justify"; + colspan?: number; + rowspan?: number; +}; + +export type TableCell< + I extends InlineContentSchema, + S extends StyleSchema = StyleSchema, +> = { + type: "tableCell"; + props: TableCellProps; + content: InlineContent[]; +}; + +export type TableContent< + I extends InlineContentSchema, + S extends StyleSchema = StyleSchema, +> = { + type: "tableContent"; + columnWidths: (number | undefined)[]; + headerRows?: number; + headerCols?: number; + rows: { + cells: InlineContent[][] | TableCell[]; + }[]; +}; + +/** CODE FOR PARTIAL BLOCKS, analogous to above + * + * Partial blocks are convenience-wrappers to make it easier to + *create/update blocks in the editor. + * + */ + +export type PartialTableCell< + I extends InlineContentSchema, + S extends StyleSchema = StyleSchema, +> = { + type: "tableCell"; + props?: Partial; + content?: PartialInlineContent; +}; + +export type PartialTableContent< + I extends InlineContentSchema, + S extends StyleSchema = StyleSchema, +> = { + type: "tableContent"; + columnWidths?: (number | undefined)[]; + headerRows?: number; + headerCols?: number; + rows: { + cells: PartialInlineContent[] | PartialTableCell[]; + }[]; +}; diff --git a/packages/core/src/schema/index.ts b/packages/core/src/schema/index.ts index 05585ab28b..8b2e9b7ad3 100644 --- a/packages/core/src/schema/index.ts +++ b/packages/core/src/schema/index.ts @@ -1,11 +1,13 @@ export * from "./blocks/createSpec.js"; export * from "./blocks/internal.js"; export * from "./blocks/types.js"; +export * from "./blocks/types/tableContent.js"; +export * from "./CustomBlockNoteSchema.js"; export * from "./inlineContent/createSpec.js"; export * from "./inlineContent/internal.js"; export * from "./inlineContent/types.js"; +export * from "./partialBlockToFullBlock.js"; export * from "./propTypes.js"; export * from "./styles/createSpec.js"; export * from "./styles/internal.js"; export * from "./styles/types.js"; -export * from "./schema.js"; diff --git a/packages/core/src/schema/inlineContent/createSpec.ts b/packages/core/src/schema/inlineContent/createSpec.ts index b48fd503b3..04251a8752 100644 --- a/packages/core/src/schema/inlineContent/createSpec.ts +++ b/packages/core/src/schema/inlineContent/createSpec.ts @@ -5,6 +5,7 @@ import { inlineContentToNodes } from "../../api/nodeConversions/blockToNode.js"; import { nodeToCustomInlineContent } from "../../api/nodeConversions/nodeToBlock.js"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { propsToAttributes } from "../blocks/internal.js"; +import { partialInlineContentToInlineContent } from "../partialBlockToFullBlock.js"; import { Props } from "../propTypes.js"; import { StyleSchema } from "../styles/types.js"; import { @@ -191,7 +192,11 @@ export function createInlineContentSpec< editor.schema.styleSchema, ) as any as InlineContentFromConfig, // TODO: fix cast (update) => { - const content = inlineContentToNodes([update], editor.pmSchema); + const fullUpdate = partialInlineContentToInlineContent( + [update], + editor.schema.inlineContentSchema, + ); + const content = inlineContentToNodes(fullUpdate, editor.pmSchema); const pos = getPos(); diff --git a/packages/core/src/schema/inlineContent/types.ts b/packages/core/src/schema/inlineContent/types.ts index 88ebfd740c..c6207e17c1 100644 --- a/packages/core/src/schema/inlineContent/types.ts +++ b/packages/core/src/schema/inlineContent/types.ts @@ -1,8 +1,8 @@ import { Node } from "@tiptap/core"; +import { ViewMutationRecord } from "prosemirror-view"; +import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { PropSchema, Props } from "../propTypes.js"; import { StyleSchema, Styles } from "../styles/types.js"; -import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; -import { ViewMutationRecord } from "prosemirror-view"; export type CustomInlineContentConfig = { type: string; diff --git a/packages/core/src/schema/partialBlockToFullBlock.ts b/packages/core/src/schema/partialBlockToFullBlock.ts new file mode 100644 index 0000000000..66903c67dd --- /dev/null +++ b/packages/core/src/schema/partialBlockToFullBlock.ts @@ -0,0 +1,258 @@ +import * as z from "zod/v4/core"; +import type { Block, BlockNoteSchema, PartialBlock } from "../blocks/index.js"; +import { UniqueID } from "../extensions/UniqueID/UniqueID.js"; +import { mapTableCell } from "../util/table.js"; +import { UnreachableCaseError } from "../util/typescript.js"; +import type { BlockSchema } from "./blocks/types.js"; +import type { + PartialTableContent, + TableCell, + TableContent, +} from "./blocks/types/tableContent.js"; +import type { CustomBlockNoteSchema } from "./CustomBlockNoteSchema.js"; +import type { + InlineContent, + InlineContentSchema, + Link, + PartialInlineContent, + PartialLink, + StyledText, +} from "./inlineContent/types.js"; +import { + isPartialLinkInlineContent, + isStyledTextInlineContent, +} from "./inlineContent/types.js"; +import type { Props, PropSchema } from "./propTypes.js"; +import type { StyleSchema } from "./styles/types.js"; + +function partialPropsToProps( + partialProps: Partial> | undefined, + propSchema: PropSchema, +): Props { + const props: Props = partialProps || {}; + + Object.entries(propSchema._zod.def.shape).forEach(([propKey, propValue]) => { + if (props[propKey] === undefined) { + if (propValue instanceof z.$ZodDefault) { + props[propKey] = propValue._zod.def.defaultValue; + } + if (propValue instanceof z.$ZodOptional) { + props[propKey] = undefined; + } + } + }); + return props; +} + +function textStringToStyledText(text: string): StyledText { + return { + type: "text", + styles: {}, + text, + }; +} + +function partialLinkToLink(partialLink: PartialLink): Link { + return { + type: "link", + href: partialLink.href, + content: + typeof partialLink.content === "string" + ? [textStringToStyledText(partialLink.content)] + : partialLink.content, + }; +} + +export function 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); + } + + if (isPartialLinkInlineContent(partialInlineContentElement)) { + return partialLinkToLink(partialInlineContentElement); + } + + if (isStyledTextInlineContent(partialInlineContentElement)) { + return partialInlineContentElement; + } + + const content = partialInlineContentElement.content; + const inlineContentConfig = + inlineContentSchema[partialInlineContentElement.type]; + + if (typeof inlineContentConfig === "string") { + throw new Error( + "unexpected, should be custom inline content (not 'text' or 'link'", + ); + } + + return { + type: partialInlineContentElement.type, + props: partialPropsToProps( + partialInlineContentElement.props, + inlineContentConfig.propSchema, + ), + content: + typeof content === "undefined" + ? undefined + : typeof content === "string" + ? [textStringToStyledText(content)] + : content, + }; + }); +} + +export function partialTableContentToTableContent( + partialTableContent: PartialTableContent, + inlineContentSchema: InlineContentSchema, +): TableContent { + const rows: { + cells: TableCell[]; + }[] = partialTableContent.rows.map((row) => { + return { + cells: row.cells.map((cell) => { + 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 = partialTableContent.columnWidths || []; + if (!partialTableContent.columnWidths) { + 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, + headerCols: partialTableContent.headerCols, + columnWidths: columnWidths, + rows, + }; +} + +function partialBlockContentToBlockContent( + partialBlockContent: + | PartialTableContent + | PartialInlineContent + | undefined, + content: "table" | "inline" | "none", + inlineContentSchema: InlineContentSchema, +): + | TableContent + | InlineContent[] + | undefined { + if (content === "table") { + partialBlockContent = partialBlockContent || { + type: "tableContent", + rows: [], + }; + + if ( + typeof partialBlockContent !== "object" || + !("type" in partialBlockContent) || + partialBlockContent.type !== "tableContent" + ) { + throw new Error("Invalid partial block content"); + } + + return partialTableContentToTableContent( + partialBlockContent, + inlineContentSchema, + ); + } else if (content === "inline") { + partialBlockContent = partialBlockContent || ""; + + if ( + typeof partialBlockContent === "object" && + "type" in partialBlockContent + ) { + throw new Error("Invalid partial block content. Table content passed!?"); + } + + return partialInlineContentToInlineContent( + partialBlockContent, + inlineContentSchema, + ); + } else if (content === "none") { + return undefined; + } else { + throw new UnreachableCaseError(content); + } +} + +export function partialBlockToFullBlock< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema, +>( + schema: CustomBlockNoteSchema, + partialBlock: PartialBlock, +): Block { + const id = partialBlock.id || UniqueID.options.generateID(); + + // TODO + const type: string = partialBlock.type || "paragraph"; + + const props = partialPropsToProps( + partialBlock.props, + schema.blockSchema[type].propSchema, + ); + + const content = partialBlockContentToBlockContent( + partialBlock.content, + schema.blockSchema[type].content, + schema.inlineContentSchema, + ); + + const children = + partialBlock.children?.map((child) => + partialBlockToFullBlock(schema, child), + ) || []; + + return { + id, + type, + props, + content, + children, + } as Block; +} + +export function partialBlocksToFullBlocks< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema, +>( + // TODO: I think this should be a CustomBlockNoteSchema, + // but that breaks docxExporter etc + schema: BlockNoteSchema, + partialBlocks: PartialBlock[], +): Block[] { + return partialBlocks.map((partialBlock) => + partialBlockToFullBlock(schema, partialBlock), + ); +} diff --git a/packages/core/src/schema/styles/createSpec.ts b/packages/core/src/schema/styles/createSpec.ts index 4bd83b3ae0..547793a14a 100644 --- a/packages/core/src/schema/styles/createSpec.ts +++ b/packages/core/src/schema/styles/createSpec.ts @@ -1,13 +1,11 @@ import { Mark } from "@tiptap/core"; - import { ParseRule, TagParseRule } from "@tiptap/pm/model"; import { addStyleAttributes, createInternalStyleSpec, stylePropsToAttributes, } from "./internal.js"; -import { StyleConfig, StyleSpec } from "./types.js"; - +import type { StyleConfig, StyleSpec } from "./types.js"; export type CustomStyleImplementation = { render: (value: T["propSchema"] extends "boolean" ? undefined : string) => { dom: HTMLElement; diff --git a/packages/core/src/util/table.ts b/packages/core/src/util/table.ts index f7ecd84910..7d9f47a350 100644 --- a/packages/core/src/util/table.ts +++ b/packages/core/src/util/table.ts @@ -1,10 +1,11 @@ import type { + InlineContent, InlineContentSchema, - StyleSchema, PartialInlineContent, - InlineContent, + PartialTableCell, + StyleSchema, + TableCell, } from "../schema"; -import { PartialTableCell, TableCell } from "../schema/blocks/types.js"; /** * This will map a table cell to a TableCell object. @@ -36,6 +37,7 @@ export function mapTableCell< } : { type: "tableCell", + // FIXME: content can actually be Partial, we should probably handle this as well content: ([] as InlineContent[]).concat(content as any), props: { backgroundColor: "default", diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx index bb11326a08..7ca557efb9 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/TableCellMergeButton.tsx @@ -1,4 +1,5 @@ import { + blockHasType, DefaultBlockSchema, InlineContentSchema, StyleSchema, @@ -32,7 +33,7 @@ export const TableCellMergeButton = () => { const block = selectedBlocks[0]; - if (block.type === "table") { + if (blockHasType(block, editor, "table")) { return editor.tableHandles?.getMergeDirection(block); } diff --git a/packages/react/src/components/TableHandles/TableHandleMenu/TableHandleMenuProps.ts b/packages/react/src/components/TableHandles/TableHandleMenu/TableHandleMenuProps.ts index 74477f8286..315a2b2d0d 100644 --- a/packages/react/src/components/TableHandles/TableHandleMenu/TableHandleMenuProps.ts +++ b/packages/react/src/components/TableHandles/TableHandleMenu/TableHandleMenuProps.ts @@ -1,9 +1,9 @@ -import { +import type { + BlockFromConfig, DefaultBlockSchema, DefaultInlineContentSchema, DefaultStyleSchema, InlineContentSchema, - SpecificBlock, StyleSchema, } from "@blocknote/core"; @@ -12,6 +12,6 @@ export type TableHandleMenuProps< S extends StyleSchema = DefaultStyleSchema, > = { orientation: "row" | "column"; - block: SpecificBlock<{ table: DefaultBlockSchema["table"] }, "table", I, S>; + block: BlockFromConfig; index: number; }; diff --git a/packages/react/src/schema/ReactBlockSpec.tsx b/packages/react/src/schema/ReactBlockSpec.tsx index cf194a60c1..06b281d231 100644 --- a/packages/react/src/schema/ReactBlockSpec.tsx +++ b/packages/react/src/schema/ReactBlockSpec.tsx @@ -1,7 +1,7 @@ import { + Block, BlockConfig, BlockImplementation, - BlockNoDefaults, BlockNoteEditor, BlockNoteExtension, BlockSpec, @@ -29,11 +29,7 @@ export type ReactCustomBlockRenderProps< TProps extends PropSchema = PropSchema, TContent extends "inline" | "none" = "inline" | "none", > = { - block: BlockNoDefaults< - Record>, - any, - any - >; + block: Block>, any, any>; editor: BlockNoteEditor; contentRef: (node: HTMLElement | null) => void; }; @@ -265,7 +261,7 @@ export function createReactBlockSpec< // `ReactNodeViewRenderer` instead. const block = getBlockFromPos( props.getPos, - editor, + editor as any, props.editor, blockConfig.type, ); diff --git a/packages/react/src/schema/ReactInlineContentSpec.tsx b/packages/react/src/schema/ReactInlineContentSpec.tsx index 7795eb4c22..e58c8fd02d 100644 --- a/packages/react/src/schema/ReactInlineContentSpec.tsx +++ b/packages/react/src/schema/ReactInlineContentSpec.tsx @@ -13,12 +13,12 @@ import { inlineContentToNodes, nodeToCustomInlineContent, PartialCustomInlineContentFromConfig, + partialInlineContentToInlineContent, Props, PropSchema, propsToAttributes, StyleSchema, } from "@blocknote/core"; -import * as z from "zod/v4/core"; import { Node } from "@tiptap/core"; import { NodeViewProps, @@ -26,6 +26,7 @@ import { ReactNodeViewRenderer, useReactNodeView, } from "@tiptap/react"; +import * as z from "zod/v4/core"; // import { useReactNodeView } from "@tiptap/react/dist/packages/react/src/useReactNodeView"; import { FC, JSX } from "react"; import { renderToDOMSpec } from "./@util/ReactRenderUtil.js"; @@ -207,8 +208,12 @@ export function createReactInlineContentSpec< ) as any as InlineContentFromConfig // TODO: fix cast } updateInlineContent={(update) => { - const content = inlineContentToNodes( + const fullUpdate = partialInlineContentToInlineContent( [update], + editor.schema.inlineContentSchema, + ); + const content = inlineContentToNodes( + fullUpdate, editor.pmSchema, ); diff --git a/packages/server-util/src/context/ServerBlockNoteEditor.ts b/packages/server-util/src/context/ServerBlockNoteEditor.ts index 1f5914162a..f80fb37f8c 100644 --- a/packages/server-util/src/context/ServerBlockNoteEditor.ts +++ b/packages/server-util/src/context/ServerBlockNoteEditor.ts @@ -15,6 +15,7 @@ import { createExternalHTMLExporter, createInternalHTMLSerializer, nodeToBlock, + partialBlockToFullBlock, } from "@blocknote/core"; import { BlockNoteViewRaw } from "@blocknote/react"; @@ -138,7 +139,9 @@ export class ServerBlockNoteEditor< blocks: PartialBlock[], ) { const pmSchema = this.editor.pmSchema; - const pmNodes = blocks.map((b) => blockToNode(b, pmSchema)); + const pmNodes = blocks.map((b) => + blockToNode(partialBlockToFullBlock(this.editor.schema, b), pmSchema), + ); const doc = pmSchema.topNodeType.create( null, @@ -216,7 +219,7 @@ export class ServerBlockNoteEditor< * @returns The blocks, serialized as an HTML string. */ public async blocksToHTMLLossy( - blocks: PartialBlock[], + blocks: Block[], ): Promise { return this._withJSDOM(async () => { const exporter = createExternalHTMLExporter( @@ -240,7 +243,7 @@ export class ServerBlockNoteEditor< * @returns The blocks, serialized as an HTML string. */ public async blocksToFullHTML( - blocks: PartialBlock[], + blocks: Block[], ): Promise { return this._withJSDOM(async () => { const exporter = createInternalHTMLSerializer( @@ -278,7 +281,7 @@ export class ServerBlockNoteEditor< * @returns The blocks, serialized as a Markdown string. */ public async blocksToMarkdownLossy( - blocks: PartialBlock[], + blocks: Block[], ): Promise { return this._withJSDOM(async () => { return blocksToMarkdown(blocks, this.editor.pmSchema, this.editor, { diff --git a/packages/server-util/src/context/react/ReactServer.test.tsx b/packages/server-util/src/context/react/ReactServer.test.tsx index 57c66709e1..d68400311b 100644 --- a/packages/server-util/src/context/react/ReactServer.test.tsx +++ b/packages/server-util/src/context/react/ReactServer.test.tsx @@ -2,6 +2,7 @@ import { BlockNoteSchema, defaultBlockSpecs, defaultProps, + partialBlockToFullBlock, } from "@blocknote/core"; import { createReactBlockSpec } from "@blocknote/react"; import { createContext, useContext } from "react"; @@ -56,13 +57,12 @@ describe("Test ServerBlockNoteEditor with React blocks", () => { const editor = ServerBlockNoteEditor.create({ schema, }); - const html = await editor.blocksToFullHTML([ - { - id: "1", - type: "simpleReactCustomParagraph", - content: "React Custom Paragraph", - }, - ]); + const fullBlock = partialBlockToFullBlock(editor.editor.schema, { + id: "1", + type: "simpleReactCustomParagraph", + content: "React Custom Paragraph", + }); + const html = await editor.blocksToFullHTML([fullBlock]); expect(html).toMatchSnapshot(); }); @@ -75,14 +75,14 @@ describe("Test ServerBlockNoteEditor with React blocks", () => { ({ children }) => ( {children} ), - async () => - editor.blocksToFullHTML([ - { - id: "1", - type: "reactContextParagraph", - content: "React Context Paragraph", - }, - ]), + async () => { + const fullBlock = partialBlockToFullBlock(editor.editor.schema, { + id: "1", + type: "reactContextParagraph", + content: "React Context Paragraph", + }); + return editor.blocksToFullHTML([fullBlock]); + }, ); expect(html).toMatchSnapshot(); diff --git a/packages/xl-docx-exporter/src/docx/docxExporter.test.ts b/packages/xl-docx-exporter/src/docx/docxExporter.test.ts index 96589d3c8e..2f2deb63ed 100644 --- a/packages/xl-docx-exporter/src/docx/docxExporter.test.ts +++ b/packages/xl-docx-exporter/src/docx/docxExporter.test.ts @@ -1,8 +1,10 @@ import { BlockNoteSchema, - defaultBlockSpecs, createPageBreakBlockSpec, + defaultBlockSpecs, + partialBlocksToFullBlocks, } from "@blocknote/core"; +import { ColumnBlock, ColumnListBlock } from "@blocknote/xl-multi-column"; import { testDocument } from "@shared/testDocument.js"; import { BlobReader, Entry, TextWriter, ZipReader } from "@zip.js/zip.js"; import { Packer, Paragraph, TextRun } from "docx"; @@ -10,8 +12,6 @@ import { describe, expect, it } from "vitest"; import xmlFormat from "xml-formatter"; import { docxDefaultSchemaMappings } from "./defaultSchema/index.js"; import { DOCXExporter } from "./docxExporter.js"; -import { ColumnBlock, ColumnListBlock } from "@blocknote/xl-multi-column"; -import { partialBlocksToBlocksForTesting } from "@shared/formatConversionTestUtil.js"; const getZIPEntryContent = (entries: Entry[], fileName: string) => { const entry = entries.find((entry) => { @@ -140,64 +140,66 @@ describe("exporter", () => { }, }); const exporter = new DOCXExporter(schema, docxDefaultSchemaMappings); - const doc = await exporter.toDocxJsDocument( - partialBlocksToBlocksForTesting(schema, [ - { - type: "columnList", - children: [ - { - type: "column", - props: { - width: 0.8, - }, - children: [ - { - type: "paragraph", - content: "This paragraph is in a column!", - }, - ], + const x = partialBlocksToFullBlocks(schema, [ + { + type: "columnList", + children: [ + { + type: "column", + props: { + width: 0.8, }, - { - type: "column", - props: { - width: 1.4, + children: [ + { + type: "paragraph", + content: "This paragraph is in a column!", }, - children: [ - { - type: "heading", - content: "So is this heading!", - }, - ], + ], + }, + { + type: "column", + props: { + width: 1.4, }, - { - type: "column", - props: { - width: 0.8, + children: [ + { + type: "heading", + content: "So is this heading!", }, - children: [ - { - type: "paragraph", - content: "You can have multiple blocks in a column too", - }, - { - type: "bulletListItem", - content: "Block 1", - }, - { - type: "bulletListItem", - content: "Block 2", - }, - { - type: "bulletListItem", - content: "Block 3", - }, - ], + ], + }, + { + type: "column", + props: { + width: 0.8, }, - ], - }, - ]), - { sectionOptions: {}, documentOptions: {}, locale: "en-US" }, - ); + children: [ + { + type: "paragraph", + content: "You can have multiple blocks in a column too", + }, + { + type: "bulletListItem", + content: "Block 1", + }, + { + type: "bulletListItem", + content: "Block 2", + }, + { + type: "bulletListItem", + content: "Block 3", + }, + ], + }, + ], + }, + ]); + const doc = await exporter.toDocxJsDocument(x, { + sectionOptions: {}, + documentOptions: {}, + locale: "en-US", + }); const blob = await Packer.toBlob(doc); const zip = new ZipReader(new BlobReader(blob)); diff --git a/packages/xl-docx-exporter/src/docx/docxExporter.ts b/packages/xl-docx-exporter/src/docx/docxExporter.ts index 6a79d5c53d..850906d671 100644 --- a/packages/xl-docx-exporter/src/docx/docxExporter.ts +++ b/packages/xl-docx-exporter/src/docx/docxExporter.ts @@ -1,8 +1,8 @@ import { Block, - BlockNoteSchema, BlockSchema, COLORS_DEFAULT, + CustomBlockNoteSchema, InlineContentSchema, StyleSchema, StyledText, @@ -54,7 +54,7 @@ export class DOCXExporter< /** * The schema of your editor. The mappings are automatically typed checked against this schema. */ - protected readonly schema: BlockNoteSchema, + protected readonly schema: CustomBlockNoteSchema, /** * The mappings that map the BlockNote schema to the docxjs content. * Pass {@link docxDefaultSchemaMappings} for the default schema. diff --git a/packages/xl-multi-column/src/test/conversions/htmlConversion.test.ts b/packages/xl-multi-column/src/test/conversions/htmlConversion.test.ts index 78d6462fde..58b0844d7a 100644 --- a/packages/xl-multi-column/src/test/conversions/htmlConversion.test.ts +++ b/packages/xl-multi-column/src/test/conversions/htmlConversion.test.ts @@ -8,11 +8,8 @@ import { StyleSchema, createExternalHTMLExporter, createInternalHTMLSerializer, + partialBlocksToFullBlocks, } from "@blocknote/core"; -import { - addIdsToBlocks, - partialBlocksToBlocksForTesting, -} from "@shared/formatConversionTestUtil.js"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { multiColumnSchemaTestCases } from "./testCases.js"; @@ -28,9 +25,9 @@ async function convertToHTMLAndCompareSnapshots< snapshotDirectory: string, snapshotName: string, ) { - addIdsToBlocks(blocks); + const fullBlocks = partialBlocksToFullBlocks(editor.schema, blocks); const serializer = createInternalHTMLSerializer(editor.pmSchema, editor); - const internalHTML = serializer.serializeBlocks(blocks, {}); + const internalHTML = serializer.serializeBlocks(fullBlocks, {}); const internalHTMLSnapshotPath = "./__snapshots__/" + snapshotDirectory + @@ -40,14 +37,13 @@ async function convertToHTMLAndCompareSnapshots< await expect(internalHTML).toMatchFileSnapshot(internalHTMLSnapshotPath); // turn the internalHTML back into blocks, and make sure no data was lost - const fullBlocks = partialBlocksToBlocksForTesting(editor.schema, blocks); const parsed = await editor.tryParseHTMLToBlocks(internalHTML); expect(parsed).toStrictEqual(fullBlocks); // Create the "external" HTML, which is a cleaned up HTML representation, but lossy const exporter = createExternalHTMLExporter(editor.pmSchema, editor); - const externalHTML = exporter.exportBlocks(blocks, {}); + const externalHTML = exporter.exportBlocks(fullBlocks, {}); const externalHTMLSnapshotPath = "./__snapshots__/" + snapshotDirectory + diff --git a/packages/xl-multi-column/src/test/conversions/nodeConversion.test.ts b/packages/xl-multi-column/src/test/conversions/nodeConversion.test.ts index 5cad44fe9f..139fb880b0 100644 --- a/packages/xl-multi-column/src/test/conversions/nodeConversion.test.ts +++ b/packages/xl-multi-column/src/test/conversions/nodeConversion.test.ts @@ -3,40 +3,25 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { BlockNoteEditor, PartialBlock, - UniqueID, blockToNode, nodeToBlock, + partialBlockToFullBlock, } from "@blocknote/core"; -import { partialBlockToBlockForTesting } from "@shared/formatConversionTestUtil.js"; import { multiColumnSchemaTestCases } from "./testCases.js"; -function addIdsToBlock(block: PartialBlock) { - if (!block.id) { - block.id = UniqueID.options.generateID(); - } - for (const child of block.children || []) { - addIdsToBlock(child); - } -} - function validateConversion( block: PartialBlock, editor: BlockNoteEditor, ) { - addIdsToBlock(block); - const node = blockToNode(block, editor.pmSchema); + const fullBlock = partialBlockToFullBlock(editor.schema, block); + const node = blockToNode(fullBlock, editor.pmSchema); expect(node).toMatchSnapshot(); const outputBlock = nodeToBlock(node, editor.pmSchema); - const fullOriginalBlock = partialBlockToBlockForTesting( - editor.schema.blockSchema, - block, - ); - - expect(outputBlock).toStrictEqual(fullOriginalBlock); + expect(outputBlock).toStrictEqual(fullBlock); } const testCases = [multiColumnSchemaTestCases]; diff --git a/packages/xl-odt-exporter/src/odt/odtExporter.test.ts b/packages/xl-odt-exporter/src/odt/odtExporter.test.ts index 6e8b26c6b4..98c6b67466 100644 --- a/packages/xl-odt-exporter/src/odt/odtExporter.test.ts +++ b/packages/xl-odt-exporter/src/odt/odtExporter.test.ts @@ -3,8 +3,8 @@ import { createPageBreakBlockSpec, defaultBlockSpecs, } from "@blocknote/core"; +import { partialBlocksToFullBlocks } from "@blocknote/core/src/schema/partialBlockToFullBlock.js"; import { ColumnBlock, ColumnListBlock } from "@blocknote/xl-multi-column"; -import { partialBlocksToBlocksForTesting } from "@shared/formatConversionTestUtil.js"; import { testDocument } from "@shared/testDocument.js"; import { BlobReader, TextWriter, ZipReader } from "@zip.js/zip.js"; import { beforeAll, describe, expect, it } from "vitest"; @@ -78,7 +78,7 @@ describe("exporter", () => { }); const exporter = new ODTExporter(schema, odtDefaultSchemaMappings); const odt = await exporter.toODTDocument( - partialBlocksToBlocksForTesting(schema, [ + partialBlocksToFullBlocks(schema, [ { type: "columnList", children: [ diff --git a/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx b/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx index f8f2ca9cc3..1f3d375ad0 100644 --- a/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx +++ b/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx @@ -7,10 +7,10 @@ import { defaultBlockSpecs, defaultInlineContentSpecs, defaultStyleSpecs, + partialBlocksToFullBlocks, } from "@blocknote/core"; import { ColumnBlock, ColumnListBlock } from "@blocknote/xl-multi-column"; import { Text } from "@react-pdf/renderer"; -import { partialBlocksToBlocksForTesting } from "@shared/formatConversionTestUtil.js"; import { testDocument } from "@shared/testDocument.js"; import reactElementToJSXString from "react-element-to-jsx-string"; import { describe, expect, it } from "vitest"; @@ -236,7 +236,7 @@ describe("exporter", () => { }); const exporter = new PDFExporter(schema, pdfDefaultSchemaMappings); const transformed = await exporter.toReactPDFDocument( - partialBlocksToBlocksForTesting(schema, [ + partialBlocksToFullBlocks(schema, [ { type: "columnList", children: [ diff --git a/shared/formatConversionTestUtil.ts b/shared/formatConversionTestUtil.ts deleted file mode 100644 index 6a723c6de0..0000000000 --- a/shared/formatConversionTestUtil.ts +++ /dev/null @@ -1,213 +0,0 @@ -// TODO: remove duplicate file - -import { - Block, - BlockNoteSchema, - BlockSchema, - InlineContent, - InlineContentSchema, - isPartialLinkInlineContent, - isStyledTextInlineContent, - PartialBlock, - PartialInlineContent, - PartialTableCell, - StyledText, - StyleSchema, - TableCell, - TableContent, - UniqueID, -} from "@blocknote/core"; -import * as z from "zod/v4/core"; - -function textShorthandToStyledText( - content: string | StyledText[] = "", -): StyledText[] { - if (typeof content === "string") { - return [ - { - type: "text", - text: content, - styles: {}, - }, - ]; - } - return content; -} - -function partialContentToInlineContent( - content: - | PartialInlineContent - | PartialTableCell - | TableContent - | undefined, -): - | InlineContent[] - | TableContent - | TableCell - | undefined { - if (typeof content === "string") { - return textShorthandToStyledText(content); - } - - 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; - } - }); - } else if (content?.type === "tableContent") { - return { - type: "tableContent", - columnWidths: content.columnWidths, - headerRows: content.headerRows, - headerCols: content.headerCols, - rows: content.rows.map((row) => { - const cells: any[] = row.cells.map((cell) => { - if (!("type" in cell) || cell.type !== "tableCell") { - return partialContentToInlineContent({ - type: "tableCell", - content: cell as any, - }); - } - return partialContentToInlineContent(cell); - }); - - return { - ...row, - cells, - }; - }), - }; - } else if (content?.type === "tableCell") { - return { - type: "tableCell", - content: partialContentToInlineContent(content.content) as any[], - 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, - }, - } satisfies TableCell; - } - - return content; -} - -export function partialBlocksToBlocksForTesting< - BSchema extends BlockSchema, - I extends InlineContentSchema, - S extends StyleSchema, ->( - schema: BlockNoteSchema, - partialBlocks: Array>, -): Array> { - return partialBlocks.map((partialBlock) => - partialBlockToBlockForTesting(schema.blockSchema, partialBlock), - ); -} - -export function partialBlockToBlockForTesting< - BSchema extends BlockSchema, - I extends InlineContentSchema, - S extends StyleSchema, ->( - schema: BSchema, - partialBlock: PartialBlock, -): Block { - const contentType: "inline" | "table" | "none" = - schema[partialBlock.type!].content; - - const withDefaults: Block = { - id: "", - type: partialBlock.type!, - props: {} as any, - content: - contentType === "inline" - ? [] - : contentType === "table" - ? { - type: "tableContent", - columnWidths: undefined, - headerRows: undefined, - headerCols: undefined, - rows: [], - } - : (undefined as any), - children: [] as any, - ...partialBlock, - }; - - Object.entries(schema[partialBlock.type!].propSchema._zod.def.shape).forEach( - ([propKey, propValue]) => { - if (withDefaults.props[propKey] === undefined) { - if (propValue instanceof z.$ZodDefault) { - (withDefaults.props as any)[propKey] = - propValue._zod.def.defaultValue; - } - if (propValue instanceof z.$ZodOptional) { - (withDefaults.props as any)[propKey] = undefined; - } - } - }, - ); - - if (contentType === "inline") { - const content = withDefaults.content as InlineContent[] | undefined; - withDefaults.content = partialContentToInlineContent(content) as any; - } else if (contentType === "table") { - const content = withDefaults.content as TableContent | undefined; - withDefaults.content = { - type: "tableContent", - columnWidths: - content?.columnWidths || - content?.rows[0]?.cells.map(() => undefined) || - [], - headerRows: content?.headerRows || undefined, - headerCols: content?.headerCols || undefined, - rows: - content?.rows.map((row) => ({ - cells: row.cells.map((cell) => partialContentToInlineContent(cell)), - })) || [], - } as any; - } - - return { - ...withDefaults, - content: partialContentToInlineContent(withDefaults.content), - children: withDefaults.children.map((c) => { - return partialBlockToBlockForTesting(schema, c); - }), - } as any; -} - -export function addIdsToBlock(block: PartialBlock) { - if (!block.id) { - block.id = UniqueID.options.generateID(); - } - if (block.children) { - addIdsToBlocks(block.children); - } -} - -export function addIdsToBlocks(blocks: PartialBlock[]) { - for (const block of blocks) { - addIdsToBlock(block); - } -} diff --git a/shared/testDocument.ts b/shared/testDocument.ts index 340e7d8779..f8dfe924d3 100644 --- a/shared/testDocument.ts +++ b/shared/testDocument.ts @@ -2,15 +2,15 @@ import { BlockNoteSchema, createPageBreakBlockSpec, defaultBlockSpecs, + partialBlocksToFullBlocks, } from "@blocknote/core"; import * as z from "zod/v4"; -import { partialBlocksToBlocksForTesting } from "./formatConversionTestUtil.js"; // @ts-ignore const y = z; // needed to fix build // TODO: Update tests that use this to the new format and remove -export const testDocument = partialBlocksToBlocksForTesting( +export const testDocument = partialBlocksToFullBlocks( BlockNoteSchema.create({ blockSpecs: { ...defaultBlockSpecs, pageBreak: createPageBreakBlockSpec() }, }), diff --git a/tests/src/unit/shared/formatConversion/export/exportTestExecutors.ts b/tests/src/unit/shared/formatConversion/export/exportTestExecutors.ts index e7ccef6e5b..f590bfb198 100644 --- a/tests/src/unit/shared/formatConversion/export/exportTestExecutors.ts +++ b/tests/src/unit/shared/formatConversion/export/exportTestExecutors.ts @@ -3,9 +3,10 @@ import { BlockSchema, blockToNode, InlineContentSchema, + partialBlocksToFullBlocks, + partialBlockToFullBlock, StyleSchema, } from "@blocknote/core"; -import { addIdsToBlocks } from "@shared/formatConversionTestUtil.js"; import { prettify } from "htmlfy"; import { expect } from "vitest"; @@ -21,12 +22,15 @@ export const testExportBlockNoteHTML = async < ) => { (window as any).__TEST_OPTIONS.mockID = 0; - addIdsToBlocks(testCase.content); - await expect( - prettify(await editor.blocksToFullHTML(testCase.content), { - tag_wrap: true, - }), + prettify( + await editor.blocksToFullHTML( + partialBlocksToFullBlocks(editor.schema, testCase.content), + ), + { + tag_wrap: true, + }, + ), ).toMatchFileSnapshot(`./__snapshots__/blocknoteHTML/${testCase.name}.html`); }; @@ -40,12 +44,15 @@ export const testExportHTML = async < ) => { (window as any).__TEST_OPTIONS.mockID = 0; - addIdsToBlocks(testCase.content); - await expect( - prettify(await editor.blocksToHTMLLossy(testCase.content), { - tag_wrap: true, - }), + prettify( + await editor.blocksToHTMLLossy( + partialBlocksToFullBlocks(editor.schema, testCase.content), + ), + { + tag_wrap: true, + }, + ), ).toMatchFileSnapshot(`./__snapshots__/html/${testCase.name}.html`); }; @@ -59,10 +66,10 @@ export const testExportMarkdown = async < ) => { (window as any).__TEST_OPTIONS.mockID = 0; - addIdsToBlocks(testCase.content); - await expect( - await editor.blocksToMarkdownLossy(testCase.content), + await editor.blocksToMarkdownLossy( + partialBlocksToFullBlocks(editor.schema, testCase.content), + ), ).toMatchFileSnapshot(`./__snapshots__/markdown/${testCase.name}.md`); }; @@ -76,11 +83,13 @@ export const testExportNodes = async < ) => { (window as any).__TEST_OPTIONS.mockID = 0; - addIdsToBlocks(testCase.content); - await expect( testCase.content.map((block) => - blockToNode(block, editor.pmSchema, editor.schema.styleSchema), + blockToNode( + partialBlockToFullBlock(editor.schema, block), + editor.pmSchema, + editor.schema.styleSchema, + ), ), ).toMatchFileSnapshot(`./__snapshots__/nodes/${testCase.name}.json`); }; diff --git a/tests/src/unit/shared/formatConversion/exportParseEquality/exportParseEqualityTestExecutors.ts b/tests/src/unit/shared/formatConversion/exportParseEquality/exportParseEqualityTestExecutors.ts index a42f7c7c4b..52cf6376bc 100644 --- a/tests/src/unit/shared/formatConversion/exportParseEquality/exportParseEqualityTestExecutors.ts +++ b/tests/src/unit/shared/formatConversion/exportParseEquality/exportParseEqualityTestExecutors.ts @@ -4,12 +4,9 @@ import { blockToNode, InlineContentSchema, nodeToBlock, + partialBlocksToFullBlocks, StyleSchema, } from "@blocknote/core"; -import { - addIdsToBlocks, - partialBlocksToBlocksForTesting, -} from "@shared/formatConversionTestUtil.js"; import { expect } from "vitest"; import { ExportParseEqualityTestCase } from "./exportParseEqualityTestCase.js"; @@ -23,19 +20,18 @@ export const testExportParseEqualityBlockNoteHTML = async < testCase: ExportParseEqualityTestCase, ) => { (window as any).__TEST_OPTIONS.mockID = 0; + const fullBlocks = partialBlocksToFullBlocks(editor.schema, testCase.content); - addIdsToBlocks(testCase.content); - - const exported = await editor.blocksToFullHTML(testCase.content); + const exported = await editor.blocksToFullHTML(fullBlocks); if (testCase.name.startsWith("malformed/")) { // We purposefully are okay with malformed response, we know they won't match expect(await editor.tryParseHTMLToBlocks(exported)).not.toStrictEqual( - partialBlocksToBlocksForTesting(editor.schema, testCase.content), + fullBlocks, ); } else { expect(await editor.tryParseHTMLToBlocks(exported)).toStrictEqual( - partialBlocksToBlocksForTesting(editor.schema, testCase.content), + fullBlocks, ); } }; @@ -50,17 +46,15 @@ export const testExportParseEqualityHTML = async < ) => { (window as any).__TEST_OPTIONS.mockID = 0; - addIdsToBlocks(testCase.content); + const fullBlocks = partialBlocksToFullBlocks(editor.schema, testCase.content); - const exported = await editor.blocksToHTMLLossy(testCase.content); + const exported = await editor.blocksToHTMLLossy(fullBlocks); // Reset mock ID as we don't expect block IDs to be preserved in this // conversion. (window as any).__TEST_OPTIONS.mockID = 0; - expect(await editor.tryParseHTMLToBlocks(exported)).toStrictEqual( - partialBlocksToBlocksForTesting(editor.schema, testCase.content), - ); + expect(await editor.tryParseHTMLToBlocks(exported)).toStrictEqual(fullBlocks); }; export const testExportParseEqualityNodes = async < @@ -73,15 +67,13 @@ export const testExportParseEqualityNodes = async < ) => { (window as any).__TEST_OPTIONS.mockID = 0; - addIdsToBlocks(testCase.content); + const fullBlocks = partialBlocksToFullBlocks(editor.schema, testCase.content); - const exported = testCase.content.map((block) => + const exported = fullBlocks.map((block) => blockToNode(block, editor.pmSchema), ); expect( exported.map((node) => nodeToBlock(node, editor.pmSchema)), - ).toStrictEqual( - partialBlocksToBlocksForTesting(editor.schema, testCase.content), - ); + ).toStrictEqual(fullBlocks); }; From 30c55811c0d446dfef0a31999be19cbb822e57d7 Mon Sep 17 00:00:00 2001 From: yousefed Date: Thu, 16 Oct 2025 06:41:38 +0200 Subject: [PATCH 02/21] remove unneeded code --- .../html/util/serializeBlocksExternalHTML.ts | 17 ----------------- .../html/util/serializeBlocksInternalHTML.ts | 18 ------------------ 2 files changed, 35 deletions(-) diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts index 79da4d5df0..df27d31322 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts @@ -174,23 +174,6 @@ 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 - // TODO: should be a nicer way for this / or move to caller - // const props = block.props || {}; - // for (const [name, spec] of Object.entries( - // editor.schema.blockSchema[ - // block.type as keyof typeof editor.schema.blockSchema - // ].propSchema._zod.def.shape, - // )) { - // if ( - // !(name in props) && - // spec instanceof z.$ZodDefault && - // spec._zod.def.defaultValue !== undefined - // ) { - // (props as any)[name] = spec._zod.def.defaultValue; - // } - // } - const bc = BC_NODE.spec?.toDOM?.( BC_NODE.create({ id: block.id, diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts index d582e4922d..2c6958db1d 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts @@ -13,7 +13,6 @@ import { tableContentToNodes, } from "../../../nodeConversions/blockToNode.js"; -import * as z from "zod/v4/core"; import { nodeToCustomInlineContent } from "../../../nodeConversions/nodeToBlock.js"; export function serializeInlineContentInternalHTML< BSchema extends BlockSchema, @@ -139,23 +138,6 @@ function serializeBlock< ) { const BC_NODE = editor.pmSchema.nodes["blockContainer"]; - // set default props in case we were passed a partial block - // TODO: should be a nicer way for this / or move to caller - const props = block.props || {}; - for (const [name, spec] of Object.entries( - editor.schema.blockSchema[ - block.type as keyof typeof editor.schema.blockSchema - ].propSchema._zod.def.shape, - )) { - if ( - !(name in props) && - spec instanceof z.$ZodDefault && - spec._zod.def.defaultValue !== undefined - ) { - (props as any)[name] = spec._zod.def.defaultValue; - } - } - const impl = editor.blockImplementations[block.type as any].implementation; const ret = impl.render.call( { From f2d191e3a24a1b8b3142a42d566d81241bafb62f Mon Sep 17 00:00:00 2001 From: yousefed Date: Thu, 16 Oct 2025 06:45:59 +0200 Subject: [PATCH 03/21] fix ! --- packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 0d32681869..02f652a0ed 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -546,7 +546,7 @@ export class TableHandlesView< } // Hide handles if the table block has been removed. - const block = this.editor.getBlock(this.state.block.id)!; + const block = this.editor.getBlock(this.state.block.id); if ( !block || From 75bd4e56e33be05fc449f42d940019922359995b Mon Sep 17 00:00:00 2001 From: yousefed Date: Thu, 16 Oct 2025 06:55:34 +0200 Subject: [PATCH 04/21] fix build --- .../api/exporters/html/util/serializeBlocksInternalHTML.ts | 4 ++-- packages/xl-docx-exporter/src/docx/docxExporter.test.ts | 4 ++-- packages/xl-docx-exporter/src/docx/docxExporter.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts index 2c6958db1d..e71221bdf4 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts @@ -144,7 +144,7 @@ function serializeBlock< renderType: "dom", props: undefined, }, - { ...block, props } as any, + block, editor as any, ); @@ -179,7 +179,7 @@ function serializeBlock< const bc = BC_NODE.spec?.toDOM?.( BC_NODE.create({ id: block.id, - ...props, + ...block.props, }), ) as { dom: HTMLElement; diff --git a/packages/xl-docx-exporter/src/docx/docxExporter.test.ts b/packages/xl-docx-exporter/src/docx/docxExporter.test.ts index 2f2deb63ed..78a91e0bd0 100644 --- a/packages/xl-docx-exporter/src/docx/docxExporter.test.ts +++ b/packages/xl-docx-exporter/src/docx/docxExporter.test.ts @@ -140,7 +140,7 @@ describe("exporter", () => { }, }); const exporter = new DOCXExporter(schema, docxDefaultSchemaMappings); - const x = partialBlocksToFullBlocks(schema, [ + const blocks = partialBlocksToFullBlocks(schema, [ { type: "columnList", children: [ @@ -195,7 +195,7 @@ describe("exporter", () => { ], }, ]); - const doc = await exporter.toDocxJsDocument(x, { + const doc = await exporter.toDocxJsDocument(blocks, { sectionOptions: {}, documentOptions: {}, locale: "en-US", diff --git a/packages/xl-docx-exporter/src/docx/docxExporter.ts b/packages/xl-docx-exporter/src/docx/docxExporter.ts index 850906d671..4fc419a612 100644 --- a/packages/xl-docx-exporter/src/docx/docxExporter.ts +++ b/packages/xl-docx-exporter/src/docx/docxExporter.ts @@ -2,7 +2,7 @@ import { Block, BlockSchema, COLORS_DEFAULT, - CustomBlockNoteSchema, + BlockNoteSchema, InlineContentSchema, StyleSchema, StyledText, @@ -54,7 +54,7 @@ export class DOCXExporter< /** * The schema of your editor. The mappings are automatically typed checked against this schema. */ - protected readonly schema: CustomBlockNoteSchema, + protected readonly schema: BlockNoteSchema, /** * The mappings that map the BlockNote schema to the docxjs content. * Pass {@link docxDefaultSchemaMappings} for the default schema. From 5eb10ec8f4201331cd441a7274ce4432741228a5 Mon Sep 17 00:00:00 2001 From: yousefed Date: Thu, 16 Oct 2025 07:00:56 +0200 Subject: [PATCH 05/21] remove SpecificBlock --- packages/core/src/schema/blocks/internal.ts | 7 +++---- packages/core/src/schema/blocks/types.ts | 9 --------- .../DragHandleMenu/DefaultItems/TableHeadersItem.tsx | 5 ++--- .../TableHandles/TableCellMenu/TableCellMenuProps.ts | 3 +-- 4 files changed, 6 insertions(+), 18 deletions(-) diff --git a/packages/core/src/schema/blocks/internal.ts b/packages/core/src/schema/blocks/internal.ts index ef94f558bd..b96ad7ec6e 100644 --- a/packages/core/src/schema/blocks/internal.ts +++ b/packages/core/src/schema/blocks/internal.ts @@ -11,9 +11,9 @@ import { PropSchema, Props } from "../propTypes.js"; import { StyleSchema } from "../styles/types.js"; import { BlockConfig, + BlockFromConfig, BlockSchemaWithBlock, LooseBlockSpec, - SpecificBlock, } from "./types.js"; // Function that uses the 'propSchema' of a blockConfig to create a TipTap @@ -92,9 +92,8 @@ export function getBlockFromPos< } // Gets the block - const block = editor.getBlock(blockIdentifier)! as SpecificBlock< - BSchema, - BType, + const block = editor.getBlock(blockIdentifier)! as unknown as BlockFromConfig< + Config, I, S >; diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts index b22babe091..f27ee2d303 100644 --- a/packages/core/src/schema/blocks/types.ts +++ b/packages/core/src/schema/blocks/types.ts @@ -280,15 +280,6 @@ export type BlockNoDefaults< children: BlockNoDefaults[]; }; -export type SpecificBlock< - BSchema extends BlockSchema, - BType extends keyof BSchema, - I extends InlineContentSchema, - S extends StyleSchema, -> = BlocksWithoutChildren[BType] & { - children: BlockNoDefaults[]; -}; - type PartialBlockFromConfigNoChildren< B extends BlockConfig, I extends InlineContentSchema, diff --git a/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx b/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx index 29917f00ac..38c6638250 100644 --- a/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx +++ b/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx @@ -4,7 +4,6 @@ import { DefaultInlineContentSchema, DefaultStyleSchema, InlineContentSchema, - SpecificBlock, StyleSchema, } from "@blocknote/core"; import { ReactNode } from "react"; @@ -19,7 +18,7 @@ export const TableRowHeaderItem = < S extends StyleSchema = DefaultStyleSchema, >( props: Omit, "block"> & { - block: SpecificBlock<{ table: DefaultBlockSchema["table"] }, "table", I, S>; + block: BlockFromConfig; children: ReactNode; }, ) => { @@ -68,7 +67,7 @@ export const TableColumnHeaderItem = < S extends StyleSchema = DefaultStyleSchema, >( props: Omit, "block"> & { - block: SpecificBlock<{ table: DefaultBlockSchema["table"] }, "table", I, S>; + block: BlockFromConfig; children: ReactNode; }, ) => { diff --git a/packages/react/src/components/TableHandles/TableCellMenu/TableCellMenuProps.ts b/packages/react/src/components/TableHandles/TableCellMenu/TableCellMenuProps.ts index b2af7056d4..027ffde1ae 100644 --- a/packages/react/src/components/TableHandles/TableCellMenu/TableCellMenuProps.ts +++ b/packages/react/src/components/TableHandles/TableCellMenu/TableCellMenuProps.ts @@ -3,7 +3,6 @@ import { DefaultInlineContentSchema, DefaultStyleSchema, InlineContentSchema, - SpecificBlock, StyleSchema, } from "@blocknote/core"; @@ -11,7 +10,7 @@ export type TableCellMenuProps< I extends InlineContentSchema = DefaultInlineContentSchema, S extends StyleSchema = DefaultStyleSchema, > = { - block: SpecificBlock<{ table: DefaultBlockSchema["table"] }, "table", I, S>; + block: BlockFromConfig; rowIndex: number; colIndex: number; }; From b99822ad0fb9e425ac9b06e4faaea4a468586ed0 Mon Sep 17 00:00:00 2001 From: yousefed Date: Thu, 16 Oct 2025 07:07:21 +0200 Subject: [PATCH 06/21] fix build --- .../DefaultItems/TableHeadersItem.tsx | 1 + .../TableCellMenu/DefaultButtons/ColorPicker.tsx | 2 +- .../TableCellMenu/TableCellMenuProps.ts | 1 + packages/react/src/schema/ReactBlockSpec.tsx | 13 +++++++------ 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx b/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx index 38c6638250..65cdffa3ca 100644 --- a/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx +++ b/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/TableHeadersItem.tsx @@ -1,4 +1,5 @@ import { + BlockFromConfig, BlockSchema, DefaultBlockSchema, DefaultInlineContentSchema, diff --git a/packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/ColorPicker.tsx b/packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/ColorPicker.tsx index eddbc6b5c7..9800e93540 100644 --- a/packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/ColorPicker.tsx +++ b/packages/react/src/components/TableHandles/TableCellMenu/DefaultButtons/ColorPicker.tsx @@ -8,12 +8,12 @@ import { StyleSchema, } from "@blocknote/core"; +import { ReactNode } from "react"; import { useComponentsContext } from "../../../../editor/ComponentsContext.js"; import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor.js"; import { useDictionary } from "../../../../i18n/dictionary.js"; import { ColorPicker } from "../../../ColorPicker/ColorPicker.js"; import { TableCellMenuProps } from "../TableCellMenuProps.js"; -import { ReactNode } from "react"; export const ColorPickerButton = < I extends InlineContentSchema = DefaultInlineContentSchema, diff --git a/packages/react/src/components/TableHandles/TableCellMenu/TableCellMenuProps.ts b/packages/react/src/components/TableHandles/TableCellMenu/TableCellMenuProps.ts index 027ffde1ae..2223cb4260 100644 --- a/packages/react/src/components/TableHandles/TableCellMenu/TableCellMenuProps.ts +++ b/packages/react/src/components/TableHandles/TableCellMenu/TableCellMenuProps.ts @@ -1,4 +1,5 @@ import { + BlockFromConfig, DefaultBlockSchema, DefaultInlineContentSchema, DefaultStyleSchema, diff --git a/packages/react/src/schema/ReactBlockSpec.tsx b/packages/react/src/schema/ReactBlockSpec.tsx index 06b281d231..0887611f86 100644 --- a/packages/react/src/schema/ReactBlockSpec.tsx +++ b/packages/react/src/schema/ReactBlockSpec.tsx @@ -259,12 +259,13 @@ export function createReactBlockSpec< // only created once, so the block we get in the node view will // be outdated. Therefore, we have to get the block in the // `ReactNodeViewRenderer` instead. - const block = getBlockFromPos( - props.getPos, - editor as any, - props.editor, - blockConfig.type, - ); + const block = getBlockFromPos< + TName, + BlockConfig, + any, + any, + any + >(props.getPos, editor as any, props.editor, blockConfig.type); const ref = useReactNodeView().nodeViewContentRef; From 53bd6d70ecbc45ec698148396748e2b33a7d4744 Mon Sep 17 00:00:00 2001 From: yousefed Date: Thu, 16 Oct 2025 07:14:30 +0200 Subject: [PATCH 07/21] fix core tests --- packages/core/src/api/blockManipulation/tables/tables.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/api/blockManipulation/tables/tables.ts b/packages/core/src/api/blockManipulation/tables/tables.ts index 1a9e62d410..811288c85f 100644 --- a/packages/core/src/api/blockManipulation/tables/tables.ts +++ b/packages/core/src/api/blockManipulation/tables/tables.ts @@ -654,8 +654,7 @@ function isCellEmpty( return true; } if (isPartialTableCell(cell)) { - // TODO: what happened here? - return isCellEmpty(cell); + return isCellEmpty(cell.content); } else if (typeof cell === "string") { return cell.length === 0; } else if (Array.isArray(cell)) { From cba8abb1b57604b1851426d0f636119db5593054 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 29 Oct 2025 13:02:07 +0100 Subject: [PATCH 08/21] createPropSchemaFromZod --- .../src/Alert.tsx | 24 ++-- .../src/Mention.tsx | 9 +- .../01-alert-block/src/Alert.tsx | 24 ++-- .../src/Mention.tsx | 9 +- .../04-pdf-file-block/src/PDF.tsx | 23 ++-- .../05-alert-block-full-ux/src/Alert.tsx | 24 ++-- .../06-toggleable-blocks/src/Toggle.tsx | 6 +- .../draggable-inline-content/src/App.tsx | 14 ++- .../react-custom-blocks/src/App.tsx | 46 ++++---- .../react-custom-inline-content/src/App.tsx | 16 ++- .../react-vanilla-custom-blocks/src/App.tsx | 46 ++++---- .../src/App.tsx | 11 +- .../commands/moveBlocks/moveBlocks.ts | 7 +- .../html/util/serializeBlocksExternalHTML.ts | 2 +- .../html/util/serializeBlocksInternalHTML.ts | 2 +- .../src/api/nodeConversions/blockToNode.ts | 8 +- .../src/api/nodeConversions/nodeToBlock.ts | 4 +- packages/core/src/blocks/Audio/block.ts | 16 ++- packages/core/src/blocks/Code/block.ts | 14 ++- packages/core/src/blocks/Divider/block.ts | 8 +- packages/core/src/blocks/File/block.ts | 21 ++-- .../helpers/render/createFileBlockWrapper.ts | 16 ++- .../helpers/render/createFileNameWithIcon.ts | 9 +- .../render/createResizableFileBlockWrapper.ts | 15 ++- packages/core/src/blocks/Heading/block.ts | 26 +++-- packages/core/src/blocks/Image/block.ts | 16 ++- .../blocks/ListItem/BulletListItem/block.ts | 4 +- .../blocks/ListItem/CheckListItem/block.ts | 16 ++- .../blocks/ListItem/NumberedListItem/block.ts | 16 ++- .../blocks/ListItem/ToggleListItem/block.ts | 4 +- packages/core/src/blocks/PageBreak/block.ts | 3 +- packages/core/src/blocks/Paragraph/block.ts | 6 +- packages/core/src/blocks/Quote/block.ts | 18 ++- packages/core/src/blocks/Table/block.ts | 7 +- .../ToggleWrapper/createToggleWrapper.ts | 6 +- packages/core/src/blocks/Video/block.ts | 22 ++-- .../core/src/blocks/defaultBlockTypeGuards.ts | 18 +-- packages/core/src/blocks/defaultBlocks.ts | 108 ++++-------------- packages/core/src/blocks/defaultFileProps.ts | 4 +- packages/core/src/blocks/defaultProps.ts | 22 ++-- .../migrationRules/moveColorAttributes.ts | 4 +- .../getDefaultSlashMenuItems.ts | 28 +++-- packages/core/src/schema/blocks/internal.ts | 78 +++++++------ .../core/src/schema/inlineContent/internal.ts | 2 +- packages/core/src/schema/propTypes.ts | 32 +++++- .../File/helpers/render/AddFileButton.tsx | 12 +- .../File/helpers/render/FileBlockWrapper.tsx | 12 +- .../File/helpers/render/FileNameWithIcon.tsx | 7 +- .../render/ResizableFileBlockWrapper.tsx | 12 +- .../blocks/ToggleWrapper/ToggleWrapper.tsx | 15 ++- .../DefaultButtons/FileCaptionButton.tsx | 6 +- .../DefaultButtons/FileDeleteButton.tsx | 14 ++- .../DefaultButtons/FileDownloadButton.tsx | 14 ++- .../DefaultButtons/FilePreviewButton.tsx | 8 +- .../DefaultButtons/FileRenameButton.tsx | 13 ++- .../DefaultButtons/FileReplaceButton.tsx | 14 ++- .../DefaultButtons/TextAlignButton.tsx | 22 +++- .../FormattingToolbarController.tsx | 8 +- .../DefaultItems/BlockColorsItem.tsx | 31 +++-- packages/react/src/schema/ReactBlockSpec.tsx | 2 +- .../src/schema/ReactInlineContentSpec.tsx | 4 +- .../src/context/react/ReactServer.test.tsx | 6 +- .../xl-ai/src/api/formats/json/tools/index.ts | 10 +- .../src/api/schema/schemaToJSONSchema.ts | 20 ++-- .../src/testUtil/cases/schemas/mention.ts | 14 ++- .../src/docx/defaultSchema/blocks.ts | 10 +- .../react-email/reactEmailExporter.test.tsx | 5 +- .../src/react-email/reactEmailExporter.tsx | 6 +- .../src/odt/defaultSchema/blocks.tsx | 4 +- .../src/pdf/defaultSchema/blocks.tsx | 8 +- .../src/pdf/pdfExporter.test.tsx | 5 +- .../xl-pdf-exporter/src/pdf/pdfExporter.tsx | 4 +- shared/formatConversionTestUtil.ts | 23 ++-- tests/src/unit/core/testSchema.ts | 17 +-- tests/src/unit/react/testSchema.tsx | 19 +-- tests/src/utils/customblocks/Alert.tsx | 25 ++-- tests/src/utils/customblocks/Button.tsx | 11 +- tests/src/utils/customblocks/Embed.tsx | 14 ++- tests/src/utils/customblocks/Image.tsx | 11 +- tests/src/utils/customblocks/ReactAlert.tsx | 28 +++-- tests/src/utils/customblocks/ReactImage.tsx | 14 ++- tests/src/utils/customblocks/Separator.tsx | 8 +- 82 files changed, 726 insertions(+), 544 deletions(-) diff --git a/examples/03-ui-components/03-formatting-toolbar-block-type-items/src/Alert.tsx b/examples/03-ui-components/03-formatting-toolbar-block-type-items/src/Alert.tsx index 936cb29a82..535e6b0d77 100644 --- a/examples/03-ui-components/03-formatting-toolbar-block-type-items/src/Alert.tsx +++ b/examples/03-ui-components/03-formatting-toolbar-block-type-items/src/Alert.tsx @@ -1,9 +1,9 @@ -import { defaultProps } from "@blocknote/core"; import { createReactBlockSpec } from "@blocknote/react"; import { Menu } from "@mantine/core"; import { MdCancel, MdCheckCircle, MdError, MdInfo } from "react-icons/md"; import { z } from "zod/v4"; +import { createPropSchemaFromZod, defaultZodPropSchema } from "@blocknote/core"; import "./styles.css"; // The types of alerts that users can choose from. @@ -54,16 +54,18 @@ export const alertTypes = [ export const Alert = createReactBlockSpec( { type: "alert", - propSchema: defaultProps - .pick({ - textAlignment: true, - textColor: true, - }) - .extend({ - type: z - .enum(["warning", "error", "info", "success"]) - .default("warning"), - }), + propSchema: createPropSchemaFromZod( + defaultZodPropSchema + .pick({ + textAlignment: true, + textColor: true, + }) + .extend({ + type: z + .enum(["warning", "error", "info", "success"]) + .default("warning"), + }), + ), content: "inline", }, { diff --git a/examples/03-ui-components/10-suggestion-menus-grid-mentions/src/Mention.tsx b/examples/03-ui-components/10-suggestion-menus-grid-mentions/src/Mention.tsx index 29f09edd99..920c74ba31 100644 --- a/examples/03-ui-components/10-suggestion-menus-grid-mentions/src/Mention.tsx +++ b/examples/03-ui-components/10-suggestion-menus-grid-mentions/src/Mention.tsx @@ -1,3 +1,4 @@ +import { createPropSchemaFromZod } from "@blocknote/core"; import { createReactInlineContentSpec } from "@blocknote/react"; import { z } from "zod/v4"; @@ -5,9 +6,11 @@ import { z } from "zod/v4"; export const Mention = createReactInlineContentSpec( { type: "mention", - propSchema: z.object({ - user: z.string().default("Unknown"), - }), + propSchema: createPropSchemaFromZod( + z.object({ + user: z.string().default("Unknown"), + }), + ), content: "none", }, { diff --git a/examples/06-custom-schema/01-alert-block/src/Alert.tsx b/examples/06-custom-schema/01-alert-block/src/Alert.tsx index fde14ad5b6..f3f8626ca1 100644 --- a/examples/06-custom-schema/01-alert-block/src/Alert.tsx +++ b/examples/06-custom-schema/01-alert-block/src/Alert.tsx @@ -1,4 +1,4 @@ -import { defaultProps } from "@blocknote/core"; +import { createPropSchemaFromZod, defaultZodPropSchema } from "@blocknote/core"; import { createReactBlockSpec } from "@blocknote/react"; import { Menu } from "@mantine/core"; import { MdCancel, MdCheckCircle, MdError, MdInfo } from "react-icons/md"; @@ -54,16 +54,18 @@ export const alertTypes = [ export const createAlert = createReactBlockSpec( { type: "alert", - propSchema: defaultProps - .pick({ - textAlignment: true, - textColor: true, - }) - .extend({ - type: z - .enum(["warning", "error", "info", "success"]) - .default("warning"), - }), + propSchema: createPropSchemaFromZod( + defaultZodPropSchema + .pick({ + textAlignment: true, + textColor: true, + }) + .extend({ + type: z + .enum(["warning", "error", "info", "success"]) + .default("warning"), + }), + ), content: "inline", }, { diff --git a/examples/06-custom-schema/02-suggestion-menus-mentions/src/Mention.tsx b/examples/06-custom-schema/02-suggestion-menus-mentions/src/Mention.tsx index 29f09edd99..920c74ba31 100644 --- a/examples/06-custom-schema/02-suggestion-menus-mentions/src/Mention.tsx +++ b/examples/06-custom-schema/02-suggestion-menus-mentions/src/Mention.tsx @@ -1,3 +1,4 @@ +import { createPropSchemaFromZod } from "@blocknote/core"; import { createReactInlineContentSpec } from "@blocknote/react"; import { z } from "zod/v4"; @@ -5,9 +6,11 @@ import { z } from "zod/v4"; export const Mention = createReactInlineContentSpec( { type: "mention", - propSchema: z.object({ - user: z.string().default("Unknown"), - }), + propSchema: createPropSchemaFromZod( + z.object({ + user: z.string().default("Unknown"), + }), + ), content: "none", }, { diff --git a/examples/06-custom-schema/04-pdf-file-block/src/PDF.tsx b/examples/06-custom-schema/04-pdf-file-block/src/PDF.tsx index 096c566654..3a0dd4785c 100644 --- a/examples/06-custom-schema/04-pdf-file-block/src/PDF.tsx +++ b/examples/06-custom-schema/04-pdf-file-block/src/PDF.tsx @@ -1,7 +1,8 @@ import { - baseFilePropSchema, + baseFileZodPropSchema, + createPropSchemaFromZod, FileBlockConfig, - optionalFileProps, + optionalFileZodPropSchema, } from "@blocknote/core"; import { createReactBlockSpec, @@ -38,14 +39,16 @@ export const PDFPreview = ( export const PDF = createReactBlockSpec( { type: "pdf", - propSchema: z.object({}).extend({ - ...baseFilePropSchema.shape, - ...optionalFileProps.pick({ - url: true, - showPreview: true, - previewWidth: true, - }).shape, - }), + propSchema: createPropSchemaFromZod( + z.object({}).extend({ + ...baseFileZodPropSchema.shape, + ...optionalFileZodPropSchema.pick({ + url: true, + showPreview: true, + previewWidth: true, + }).shape, + }), + ), content: "none", }, { diff --git a/examples/06-custom-schema/05-alert-block-full-ux/src/Alert.tsx b/examples/06-custom-schema/05-alert-block-full-ux/src/Alert.tsx index fde14ad5b6..f3f8626ca1 100644 --- a/examples/06-custom-schema/05-alert-block-full-ux/src/Alert.tsx +++ b/examples/06-custom-schema/05-alert-block-full-ux/src/Alert.tsx @@ -1,4 +1,4 @@ -import { defaultProps } from "@blocknote/core"; +import { createPropSchemaFromZod, defaultZodPropSchema } from "@blocknote/core"; import { createReactBlockSpec } from "@blocknote/react"; import { Menu } from "@mantine/core"; import { MdCancel, MdCheckCircle, MdError, MdInfo } from "react-icons/md"; @@ -54,16 +54,18 @@ export const alertTypes = [ export const createAlert = createReactBlockSpec( { type: "alert", - propSchema: defaultProps - .pick({ - textAlignment: true, - textColor: true, - }) - .extend({ - type: z - .enum(["warning", "error", "info", "success"]) - .default("warning"), - }), + propSchema: createPropSchemaFromZod( + defaultZodPropSchema + .pick({ + textAlignment: true, + textColor: true, + }) + .extend({ + type: z + .enum(["warning", "error", "info", "success"]) + .default("warning"), + }), + ), content: "inline", }, { diff --git a/examples/06-custom-schema/06-toggleable-blocks/src/Toggle.tsx b/examples/06-custom-schema/06-toggleable-blocks/src/Toggle.tsx index 11505876be..3fdd52c609 100644 --- a/examples/06-custom-schema/06-toggleable-blocks/src/Toggle.tsx +++ b/examples/06-custom-schema/06-toggleable-blocks/src/Toggle.tsx @@ -1,13 +1,11 @@ -import { defaultProps } from "@blocknote/core"; +import { defaultPropSchema } from "@blocknote/core"; import { createReactBlockSpec, ToggleWrapper } from "@blocknote/react"; // The Toggle block that we want to add to our editor. export const ToggleBlock = createReactBlockSpec( { type: "toggle", - propSchema: { - ...defaultProps, - } as typeof defaultProps, + propSchema: defaultPropSchema, content: "inline", }, { diff --git a/examples/06-custom-schema/draggable-inline-content/src/App.tsx b/examples/06-custom-schema/draggable-inline-content/src/App.tsx index 6338768d9b..93de8f1c33 100644 --- a/examples/06-custom-schema/draggable-inline-content/src/App.tsx +++ b/examples/06-custom-schema/draggable-inline-content/src/App.tsx @@ -1,4 +1,8 @@ -import { BlockNoteSchema, defaultInlineContentSpecs } from "@blocknote/core"; +import { + BlockNoteSchema, + createPropSchemaFromZod, + defaultInlineContentSpecs, +} from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; @@ -11,9 +15,11 @@ import { z } from "zod/v4"; const draggableButton = createReactInlineContentSpec( { type: "draggableButton", - propSchema: z.object({ - title: z.string().default(""), - }), + propSchema: createPropSchemaFromZod( + z.object({ + title: z.string().default(""), + }), + ), content: "none", }, { diff --git a/examples/06-custom-schema/react-custom-blocks/src/App.tsx b/examples/06-custom-schema/react-custom-blocks/src/App.tsx index c0f112b401..d8cd93e82b 100644 --- a/examples/06-custom-schema/react-custom-blocks/src/App.tsx +++ b/examples/06-custom-schema/react-custom-blocks/src/App.tsx @@ -1,7 +1,9 @@ import { BlockNoteSchema, + createPropSchemaFromZod, defaultBlockSpecs, - defaultProps, + defaultPropSchema, + defaultZodPropSchema, } from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; import { BlockNoteView } from "@blocknote/mantine"; @@ -38,16 +40,18 @@ const alertTypes = { export const alertBlock = createReactBlockSpec( { type: "alert", - propSchema: defaultProps - .pick({ - textAlignment: true, - textColor: true, - }) - .extend({ - type: z - .enum(["warning", "error", "info", "success"]) - .default("warning"), - }), + propSchema: createPropSchemaFromZod( + defaultZodPropSchema + .pick({ + textAlignment: true, + textColor: true, + }) + .extend({ + type: z + .enum(["warning", "error", "info", "success"]) + .default("warning"), + }), + ), content: "inline", }, { @@ -82,13 +86,15 @@ export const alertBlock = createReactBlockSpec( const simpleImageBlock = createReactBlockSpec( { type: "simpleImage", - propSchema: z.object({ - src: z - .string() - .default( - "https://www.pulsecarshalton.co.uk/wp-content/uploads/2016/08/jk-placeholder-image.jpg", - ), - }), + propSchema: createPropSchemaFromZod( + z.object({ + src: z + .string() + .default( + "https://www.pulsecarshalton.co.uk/wp-content/uploads/2016/08/jk-placeholder-image.jpg", + ), + }), + ), content: "none", }, { @@ -106,9 +112,7 @@ export const bracketsParagraphBlock = createReactBlockSpec( { type: "bracketsParagraph", content: "inline", - propSchema: { - ...defaultProps, - }, + propSchema: defaultPropSchema, }, { render: (props) => ( diff --git a/examples/06-custom-schema/react-custom-inline-content/src/App.tsx b/examples/06-custom-schema/react-custom-inline-content/src/App.tsx index a170749ece..4d9fcfe9f7 100644 --- a/examples/06-custom-schema/react-custom-inline-content/src/App.tsx +++ b/examples/06-custom-schema/react-custom-inline-content/src/App.tsx @@ -1,4 +1,8 @@ -import { BlockNoteSchema, defaultInlineContentSpecs } from "@blocknote/core"; +import { + BlockNoteSchema, + createPropSchemaFromZod, + defaultInlineContentSpecs, +} from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; @@ -11,9 +15,11 @@ import { z } from "zod/v4"; const mention = createReactInlineContentSpec( { type: "mention", - propSchema: z.object({ - user: z.string().default(""), - }), + propSchema: createPropSchemaFromZod( + z.object({ + user: z.string().default(""), + }), + ), content: "none", }, { @@ -26,7 +32,7 @@ const mention = createReactInlineContentSpec( const tag = createReactInlineContentSpec( { type: "tag", - propSchema: z.object({}), + propSchema: createPropSchemaFromZod(z.object({})), content: "styled", }, { diff --git a/examples/vanilla-js/react-vanilla-custom-blocks/src/App.tsx b/examples/vanilla-js/react-vanilla-custom-blocks/src/App.tsx index 7017d82ddf..b6774d609d 100644 --- a/examples/vanilla-js/react-vanilla-custom-blocks/src/App.tsx +++ b/examples/vanilla-js/react-vanilla-custom-blocks/src/App.tsx @@ -1,8 +1,10 @@ import { BlockNoteSchema, createBlockSpec, + createPropSchemaFromZod, defaultBlockSpecs, - defaultProps, + defaultPropSchema, + defaultZodPropSchema, } from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; import { BlockNoteView } from "@blocknote/mantine"; @@ -39,16 +41,18 @@ const alertTypes = { const alertBlock = createBlockSpec( { type: "alert", - propSchema: defaultProps - .pick({ - textAlignment: true, - textColor: true, - }) - .extend({ - type: z - .enum(["warning", "error", "info", "success"]) - .default("warning"), - }), + propSchema: createPropSchemaFromZod( + defaultZodPropSchema + .pick({ + textAlignment: true, + textColor: true, + }) + .extend({ + type: z + .enum(["warning", "error", "info", "success"]) + .default("warning"), + }), + ), content: "inline", }, { @@ -116,13 +120,15 @@ const alertBlock = createBlockSpec( const simpleImageBlock = createBlockSpec( { type: "simpleImage", - propSchema: z.object({ - src: z - .string() - .default( - "https://www.pulsecarshalton.co.uk/wp-content/uploads/2016/08/jk-placeholder-image.jpg", - ), - }), + propSchema: createPropSchemaFromZod( + z.object({ + src: z + .string() + .default( + "https://www.pulsecarshalton.co.uk/wp-content/uploads/2016/08/jk-placeholder-image.jpg", + ), + }), + ), content: "none", }, { @@ -143,9 +149,7 @@ const bracketsParagraphBlock = createBlockSpec( { type: "bracketsParagraph", content: "inline", - propSchema: { - ...defaultProps, - }, + propSchema: defaultPropSchema, }, { render: () => { diff --git a/examples/vanilla-js/react-vanilla-custom-inline-content/src/App.tsx b/examples/vanilla-js/react-vanilla-custom-inline-content/src/App.tsx index 8bfbc13044..3e60cb0672 100644 --- a/examples/vanilla-js/react-vanilla-custom-inline-content/src/App.tsx +++ b/examples/vanilla-js/react-vanilla-custom-inline-content/src/App.tsx @@ -1,6 +1,7 @@ import { BlockNoteSchema, createInlineContentSpec, + createPropSchemaFromZod, defaultInlineContentSpecs, } from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; @@ -12,9 +13,11 @@ import { z } from "zod/v4"; const mention = createInlineContentSpec( { type: "mention", - propSchema: z.object({ - user: z.string().default(""), - }), + propSchema: createPropSchemaFromZod( + z.object({ + user: z.string().default(""), + }), + ), content: "none", }, { @@ -32,7 +35,7 @@ const mention = createInlineContentSpec( const tag = createInlineContentSpec( { type: "tag", - propSchema: z.object({}), + propSchema: createPropSchemaFromZod(z.object({})), content: "styled", }, { diff --git a/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts b/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts index 8d4591123e..ee752129e8 100644 --- a/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts @@ -171,7 +171,12 @@ export function moveSelectedBlocksAndSelection( const selectionData = getBlockSelectionData(editor); editor.removeBlocks(blocks); - editor.insertBlocks(flattenColumns(blocks), referenceBlock, placement); + // TODO + editor.insertBlocks( + flattenColumns(blocks) as any, + referenceBlock, + placement, + ); updateBlockSelectionFromData(tr, selectionData); }); diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts index 3992ef4696..161bebd6db 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts @@ -181,7 +181,7 @@ function serializeBlock< for (const [name, spec] of Object.entries( editor.schema.blockSchema[ block.type as keyof typeof editor.schema.blockSchema - ].propSchema._zod.def.shape, + ].propSchema._zodSource._zod.def.shape, )) { if ( !(name in props) && diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts index 7c270bffb6..de10a697d1 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts @@ -145,7 +145,7 @@ function serializeBlock< for (const [name, spec] of Object.entries( editor.schema.blockSchema[ block.type as keyof typeof editor.schema.blockSchema - ].propSchema._zod.def.shape, + ].propSchema._zodSource._zod.def.shape, )) { if ( !(name in props) && diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index 660d97406e..ab91003117 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -292,7 +292,7 @@ function blockOrInlineContentToContentNode( } if (!block.content) { - contentNode = schema.nodes[type].createChecked(block.props); + contentNode = schema.nodes[type].createChecked(block.props as any); } else if (typeof block.content === "string") { const nodes = inlineContentToNodes( [block.content], @@ -300,7 +300,7 @@ function blockOrInlineContentToContentNode( type, styleSchema, ); - contentNode = schema.nodes[type].createChecked(block.props, nodes); + contentNode = schema.nodes[type].createChecked(block.props as any, nodes); } else if (Array.isArray(block.content)) { const nodes = inlineContentToNodes( block.content, @@ -308,10 +308,10 @@ function blockOrInlineContentToContentNode( type, styleSchema, ); - contentNode = schema.nodes[type].createChecked(block.props, nodes); + contentNode = schema.nodes[type].createChecked(block.props as any, nodes); } else if (block.content.type === "tableContent") { const nodes = tableContentToNodes(block.content, schema, styleSchema); - contentNode = schema.nodes[type].createChecked(block.props, nodes); + contentNode = schema.nodes[type].createChecked(block.props as any, nodes); } else { throw new UnreachableCaseError(block.content.type); } diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts index 5ffe711860..907df635d4 100644 --- a/packages/core/src/api/nodeConversions/nodeToBlock.ts +++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts @@ -355,7 +355,7 @@ export function nodeToCustomInlineContent< throw Error("ic node is of an unrecognized type: " + node.type.name); } - const propSchema = icConfig.propSchema._zod.def.shape; + const propSchema = icConfig.propSchema._zodSource._zod.def.shape; if (attr in propSchema) { props[attr] = value; @@ -429,7 +429,7 @@ export function nodeToBlock< ...(blockInfo.isBlockContainer ? blockInfo.blockContent.node.attrs : {}), }; - const props = z.parse(blockSpec.propSchema, rawAttrs); + const props = z.parse(blockSpec.propSchema._zodSource, rawAttrs); const blockConfig = blockSchema[blockInfo.blockNoteType]; diff --git a/packages/core/src/blocks/Audio/block.ts b/packages/core/src/blocks/Audio/block.ts index 6dcf4e5139..bd3847317e 100644 --- a/packages/core/src/blocks/Audio/block.ts +++ b/packages/core/src/blocks/Audio/block.ts @@ -3,9 +3,13 @@ import { BlockFromConfig, createBlockConfig, createBlockSpec, + createPropSchemaFromZod, } from "../../schema/index.js"; -import { baseFilePropSchema, optionalFileProps } from "../defaultFileProps.js"; -import { defaultProps, parseDefaultProps } from "../defaultProps.js"; +import { + baseFileZodPropSchema, + optionalFileZodPropSchema, +} from "../defaultFileProps.js"; +import { defaultZodPropSchema, parseDefaultProps } from "../defaultProps.js"; import { parseFigureElement } from "../File/helpers/parse/parseFigureElement.js"; import { createFileBlockWrapper } from "../File/helpers/render/createFileBlockWrapper.js"; import { createFigureWithCaption } from "../File/helpers/toExternalHTML/createFigureWithCaption.js"; @@ -21,13 +25,13 @@ export interface AudioOptions { export type AudioBlockConfig = ReturnType; -const audioPropSchema = defaultProps +const audioZodPropSchema = defaultZodPropSchema .pick({ backgroundColor: true, }) .extend({ - ...baseFilePropSchema.shape, - ...optionalFileProps.pick({ + ...baseFileZodPropSchema.shape, + ...optionalFileZodPropSchema.pick({ url: true, showPreview: true, }).shape, @@ -37,7 +41,7 @@ export const createAudioBlockConfig = createBlockConfig( (_ctx: AudioOptions) => ({ type: "audio" as const, - propSchema: audioPropSchema, + propSchema: createPropSchemaFromZod(audioZodPropSchema), content: "none", }) as const, ); diff --git a/packages/core/src/blocks/Code/block.ts b/packages/core/src/blocks/Code/block.ts index 1fd2f28579..d9ed4b94a3 100644 --- a/packages/core/src/blocks/Code/block.ts +++ b/packages/core/src/blocks/Code/block.ts @@ -2,7 +2,11 @@ import type { HighlighterGeneric } from "@shikijs/types"; import { DOMParser } from "@tiptap/pm/model"; import { z } from "zod/v4"; import { createBlockNoteExtension } from "../../editor/BlockNoteExtension.js"; -import { createBlockConfig, createBlockSpec } from "../../schema/index.js"; +import { + createBlockConfig, + createBlockSpec, + createPropSchemaFromZod, +} from "../../schema/index.js"; import { lazyShikiPlugin } from "./shiki.js"; export type CodeBlockOptions = { @@ -58,9 +62,11 @@ export const createCodeBlockConfig = createBlockConfig( ({ defaultLanguage = "text" }: CodeBlockOptions) => ({ type: "codeBlock" as const, - propSchema: z.object({ - language: z.string().default(defaultLanguage), - }), + propSchema: createPropSchemaFromZod( + z.object({ + language: z.string().default(defaultLanguage), + }), + ), content: "inline", }) as const, ); diff --git a/packages/core/src/blocks/Divider/block.ts b/packages/core/src/blocks/Divider/block.ts index 21fc5f9772..6443ac1164 100644 --- a/packages/core/src/blocks/Divider/block.ts +++ b/packages/core/src/blocks/Divider/block.ts @@ -1,6 +1,10 @@ import { z } from "zod/v4"; import { createBlockNoteExtension } from "../../editor/BlockNoteExtension.js"; -import { createBlockConfig, createBlockSpec } from "../../schema/index.js"; +import { + createBlockConfig, + createBlockSpec, + createPropSchemaFromZod, +} from "../../schema/index.js"; export type DividerBlockConfig = ReturnType; @@ -8,7 +12,7 @@ export const createDividerBlockConfig = createBlockConfig( () => ({ type: "divider" as const, - propSchema: z.object({}), + propSchema: createPropSchemaFromZod(z.object({})), content: "none", }) as const, ); diff --git a/packages/core/src/blocks/File/block.ts b/packages/core/src/blocks/File/block.ts index 7586cba2e1..d409e4995c 100644 --- a/packages/core/src/blocks/File/block.ts +++ b/packages/core/src/blocks/File/block.ts @@ -1,6 +1,13 @@ -import { createBlockConfig, createBlockSpec } from "../../schema/index.js"; -import { baseFilePropSchema, optionalFileProps } from "../defaultFileProps.js"; -import { defaultProps, parseDefaultProps } from "../defaultProps.js"; +import { + createBlockConfig, + createBlockSpec, + createPropSchemaFromZod, +} from "../../schema/index.js"; +import { + baseFileZodPropSchema, + optionalFileZodPropSchema, +} from "../defaultFileProps.js"; +import { defaultZodPropSchema, parseDefaultProps } from "../defaultProps.js"; import { parseEmbedElement } from "./helpers/parse/parseEmbedElement.js"; import { parseFigureElement } from "./helpers/parse/parseFigureElement.js"; import { createFileBlockWrapper } from "./helpers/render/createFileBlockWrapper.js"; @@ -8,13 +15,13 @@ import { createLinkWithCaption } from "./helpers/toExternalHTML/createLinkWithCa export type FileBlockConfig = ReturnType; -const filePropSchema = defaultProps +const fileZodPropSchema = defaultZodPropSchema .pick({ backgroundColor: true, }) .extend({ - ...baseFilePropSchema.shape, - ...optionalFileProps.pick({ + ...baseFileZodPropSchema.shape, + ...optionalFileZodPropSchema.pick({ url: true, }).shape, }); @@ -23,7 +30,7 @@ export const createFileBlockConfig = createBlockConfig( () => ({ type: "file" as const, - propSchema: filePropSchema, + propSchema: createPropSchemaFromZod(fileZodPropSchema), content: "none" as const, }) as const, ); diff --git a/packages/core/src/blocks/File/helpers/render/createFileBlockWrapper.ts b/packages/core/src/blocks/File/helpers/render/createFileBlockWrapper.ts index a55ddbbad7..4df6d32c91 100644 --- a/packages/core/src/blocks/File/helpers/render/createFileBlockWrapper.ts +++ b/packages/core/src/blocks/File/helpers/render/createFileBlockWrapper.ts @@ -2,21 +2,25 @@ import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; import { BlockConfig, BlockFromConfigNoChildren, + PropSchemaFromZod, } from "../../../../schema/index.js"; import { - baseFilePropSchema, - optionalFileProps, + baseFileZodPropSchema, + optionalFileZodPropSchema, } from "../../../defaultFileProps.js"; import { createAddFileButton } from "./createAddFileButton.js"; import { createFileNameWithIcon } from "./createFileNameWithIcon.js"; -const requiredPropSchema = baseFilePropSchema.extend({ - ...optionalFileProps.pick({ url: true }).shape, +const requiredZodPropSchema = baseFileZodPropSchema.extend({ + ...optionalFileZodPropSchema.pick({ url: true }).shape, }); - export const createFileBlockWrapper = ( block: BlockFromConfigNoChildren< - BlockConfig, + BlockConfig< + string, + PropSchemaFromZod, + "none" + >, any, any >, diff --git a/packages/core/src/blocks/File/helpers/render/createFileNameWithIcon.ts b/packages/core/src/blocks/File/helpers/render/createFileNameWithIcon.ts index f9e97476a4..abe656d741 100644 --- a/packages/core/src/blocks/File/helpers/render/createFileNameWithIcon.ts +++ b/packages/core/src/blocks/File/helpers/render/createFileNameWithIcon.ts @@ -1,14 +1,19 @@ import { BlockConfig, BlockFromConfigNoChildren, + PropSchemaFromZod, } from "../../../../schema/index.js"; -import { baseFilePropSchema } from "../../../defaultFileProps.js"; +import { baseFileZodPropSchema } from "../../../defaultFileProps.js"; export const FILE_ICON_SVG = ``; export const createFileNameWithIcon = ( block: BlockFromConfigNoChildren< - BlockConfig, + BlockConfig< + string, + PropSchemaFromZod, + "none" + >, any, any >, diff --git a/packages/core/src/blocks/File/helpers/render/createResizableFileBlockWrapper.ts b/packages/core/src/blocks/File/helpers/render/createResizableFileBlockWrapper.ts index 375ddb36e2..60740db1e0 100644 --- a/packages/core/src/blocks/File/helpers/render/createResizableFileBlockWrapper.ts +++ b/packages/core/src/blocks/File/helpers/render/createResizableFileBlockWrapper.ts @@ -2,15 +2,16 @@ import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; import { BlockConfig, BlockFromConfigNoChildren, + PropSchemaFromZod, } from "../../../../schema/index.js"; import { - baseFilePropSchema, - optionalFileProps, + baseFileZodPropSchema, + optionalFileZodPropSchema, } from "../../../defaultFileProps.js"; import { createFileBlockWrapper } from "./createFileBlockWrapper.js"; -const requiredPropSchema = baseFilePropSchema.extend({ - ...optionalFileProps.pick({ +const requiredZodPropSchema = baseFileZodPropSchema.extend({ + ...optionalFileZodPropSchema.pick({ url: true, previewWidth: true, showPreview: true, @@ -19,7 +20,11 @@ const requiredPropSchema = baseFilePropSchema.extend({ export const createResizableFileBlockWrapper = ( block: BlockFromConfigNoChildren< - BlockConfig, + BlockConfig< + string, + PropSchemaFromZod, + "none" + >, any, any >, diff --git a/packages/core/src/blocks/Heading/block.ts b/packages/core/src/blocks/Heading/block.ts index dd21784190..4880f9fea0 100644 --- a/packages/core/src/blocks/Heading/block.ts +++ b/packages/core/src/blocks/Heading/block.ts @@ -1,9 +1,13 @@ import { z } from "zod/v4"; import { createBlockNoteExtension } from "../../editor/BlockNoteExtension.js"; -import { createBlockConfig, createBlockSpec } from "../../schema/index.js"; +import { + createBlockConfig, + createBlockSpec, + createPropSchemaFromZod, +} from "../../schema/index.js"; import { addDefaultPropsExternalHTML, - defaultProps, + defaultZodPropSchema, parseDefaultProps, } from "../defaultProps.js"; import { createToggleWrapper } from "../ToggleWrapper/createToggleWrapper.js"; @@ -27,14 +31,16 @@ export const createHeadingBlockConfig = createBlockConfig( }: HeadingOptions = {}) => ({ type: "heading" as const, - propSchema: defaultProps.extend({ - level: z - .union(levels.map((level) => z.literal(level))) - .default(defaultLevel), - ...(allowToggleHeadings - ? { isToggleable: z.boolean().default(false) } - : {}), - }), + propSchema: createPropSchemaFromZod( + defaultZodPropSchema.extend({ + level: z + .union(levels.map((level) => z.literal(level))) + .default(defaultLevel), + ...(allowToggleHeadings + ? { isToggleable: z.boolean().default(false) } + : {}), + }), + ), content: "inline", }) as const, ); diff --git a/packages/core/src/blocks/Image/block.ts b/packages/core/src/blocks/Image/block.ts index 9cd767a9f8..c66968411d 100644 --- a/packages/core/src/blocks/Image/block.ts +++ b/packages/core/src/blocks/Image/block.ts @@ -3,9 +3,13 @@ import { BlockFromConfig, createBlockConfig, createBlockSpec, + createPropSchemaFromZod, } from "../../schema/index.js"; -import { baseFilePropSchema, optionalFileProps } from "../defaultFileProps.js"; -import { defaultProps, parseDefaultProps } from "../defaultProps.js"; +import { + baseFileZodPropSchema, + optionalFileZodPropSchema, +} from "../defaultFileProps.js"; +import { defaultZodPropSchema, parseDefaultProps } from "../defaultProps.js"; import { parseFigureElement } from "../File/helpers/parse/parseFigureElement.js"; import { createResizableFileBlockWrapper } from "../File/helpers/render/createResizableFileBlockWrapper.js"; import { createFigureWithCaption } from "../File/helpers/toExternalHTML/createFigureWithCaption.js"; @@ -21,14 +25,14 @@ export interface ImageOptions { export type ImageBlockConfig = ReturnType; -const imagePropSchema = defaultProps +const imageZodPropSchema = defaultZodPropSchema .pick({ textAlignment: true, backgroundColor: true, }) .extend({ - ...baseFilePropSchema.shape, - ...optionalFileProps.pick({ + ...baseFileZodPropSchema.shape, + ...optionalFileZodPropSchema.pick({ url: true, showPreview: true, previewWidth: true, @@ -39,7 +43,7 @@ export const createImageBlockConfig = createBlockConfig( (_ctx: ImageOptions = {}) => ({ type: "image" as const, - propSchema: imagePropSchema, + propSchema: createPropSchemaFromZod(imageZodPropSchema), content: "none" as const, }) as const, ); diff --git a/packages/core/src/blocks/ListItem/BulletListItem/block.ts b/packages/core/src/blocks/ListItem/BulletListItem/block.ts index b3777da7fe..b9262f8e83 100644 --- a/packages/core/src/blocks/ListItem/BulletListItem/block.ts +++ b/packages/core/src/blocks/ListItem/BulletListItem/block.ts @@ -3,7 +3,7 @@ import { createBlockNoteExtension } from "../../../editor/BlockNoteExtension.js" import { createBlockConfig, createBlockSpec } from "../../../schema/index.js"; import { addDefaultPropsExternalHTML, - defaultProps, + defaultPropSchema, parseDefaultProps, } from "../../defaultProps.js"; import { handleEnter } from "../../utils/listItemEnterHandler.js"; @@ -17,7 +17,7 @@ export const createBulletListItemBlockConfig = createBlockConfig( () => ({ type: "bulletListItem" as const, - propSchema: defaultProps, + propSchema: defaultPropSchema, content: "inline", }) as const, ); diff --git a/packages/core/src/blocks/ListItem/CheckListItem/block.ts b/packages/core/src/blocks/ListItem/CheckListItem/block.ts index 769735260f..c1d71a0697 100644 --- a/packages/core/src/blocks/ListItem/CheckListItem/block.ts +++ b/packages/core/src/blocks/ListItem/CheckListItem/block.ts @@ -1,9 +1,13 @@ import { z } from "zod/v4"; import { createBlockNoteExtension } from "../../../editor/BlockNoteExtension.js"; -import { createBlockConfig, createBlockSpec } from "../../../schema/index.js"; +import { + createBlockConfig, + createBlockSpec, + createPropSchemaFromZod, +} from "../../../schema/index.js"; import { addDefaultPropsExternalHTML, - defaultProps, + defaultZodPropSchema, parseDefaultProps, } from "../../defaultProps.js"; import { handleEnter } from "../../utils/listItemEnterHandler.js"; @@ -17,9 +21,11 @@ export const createCheckListItemConfig = createBlockConfig( () => ({ type: "checkListItem" as const, - propSchema: defaultProps.extend({ - checked: z.boolean().default(false), - }), + propSchema: createPropSchemaFromZod( + defaultZodPropSchema.extend({ + checked: z.boolean().default(false), + }), + ), content: "inline", }) as const, ); diff --git a/packages/core/src/blocks/ListItem/NumberedListItem/block.ts b/packages/core/src/blocks/ListItem/NumberedListItem/block.ts index a4a245337c..bab950698f 100644 --- a/packages/core/src/blocks/ListItem/NumberedListItem/block.ts +++ b/packages/core/src/blocks/ListItem/NumberedListItem/block.ts @@ -1,10 +1,14 @@ import { z } from "zod/v4"; import { getBlockInfoFromSelection } from "../../../api/getBlockInfoFromPos.js"; import { createBlockNoteExtension } from "../../../editor/BlockNoteExtension.js"; -import { createBlockConfig, createBlockSpec } from "../../../schema/index.js"; +import { + createBlockConfig, + createBlockSpec, + createPropSchemaFromZod, +} from "../../../schema/index.js"; import { addDefaultPropsExternalHTML, - defaultProps, + defaultZodPropSchema, parseDefaultProps, } from "../../defaultProps.js"; import { handleEnter } from "../../utils/listItemEnterHandler.js"; @@ -19,9 +23,11 @@ export const createNumberedListItemBlockConfig = createBlockConfig( () => ({ type: "numberedListItem" as const, - propSchema: defaultProps.extend({ - start: z.number().optional(), - }), + propSchema: createPropSchemaFromZod( + defaultZodPropSchema.extend({ + start: z.number().optional(), + }), + ), content: "inline", }) as const, ); diff --git a/packages/core/src/blocks/ListItem/ToggleListItem/block.ts b/packages/core/src/blocks/ListItem/ToggleListItem/block.ts index 8814bd37e3..3fd9c53d1b 100644 --- a/packages/core/src/blocks/ListItem/ToggleListItem/block.ts +++ b/packages/core/src/blocks/ListItem/ToggleListItem/block.ts @@ -2,7 +2,7 @@ import { createBlockNoteExtension } from "../../../editor/BlockNoteExtension.js" import { createBlockConfig, createBlockSpec } from "../../../schema/index.js"; import { addDefaultPropsExternalHTML, - defaultProps, + defaultPropSchema, } from "../../defaultProps.js"; import { createToggleWrapper } from "../../ToggleWrapper/createToggleWrapper.js"; import { handleEnter } from "../../utils/listItemEnterHandler.js"; @@ -15,7 +15,7 @@ export const createToggleListItemBlockConfig = createBlockConfig( () => ({ type: "toggleListItem" as const, - propSchema: defaultProps, + propSchema: defaultPropSchema, content: "inline" as const, }) as const, ); diff --git a/packages/core/src/blocks/PageBreak/block.ts b/packages/core/src/blocks/PageBreak/block.ts index bdb1411a2d..fe4c676ae1 100644 --- a/packages/core/src/blocks/PageBreak/block.ts +++ b/packages/core/src/blocks/PageBreak/block.ts @@ -3,6 +3,7 @@ import { BlockSchema, createBlockConfig, createBlockSpec, + createPropSchemaFromZod, CustomBlockNoteSchema, InlineContentSchema, StyleSchema, @@ -16,7 +17,7 @@ export const createPageBreakBlockConfig = createBlockConfig( () => ({ type: "pageBreak" as const, - propSchema: z.object({}), + propSchema: createPropSchemaFromZod(z.object({})), content: "none", }) as const, ); diff --git a/packages/core/src/blocks/Paragraph/block.ts b/packages/core/src/blocks/Paragraph/block.ts index 09a9cc9ddb..466b7bed9f 100644 --- a/packages/core/src/blocks/Paragraph/block.ts +++ b/packages/core/src/blocks/Paragraph/block.ts @@ -2,7 +2,7 @@ import { createBlockNoteExtension } from "../../editor/BlockNoteExtension.js"; import { createBlockConfig, createBlockSpec } from "../../schema/index.js"; import { addDefaultPropsExternalHTML, - defaultProps, + defaultPropSchema, parseDefaultProps, } from "../defaultProps.js"; @@ -14,13 +14,13 @@ export const createParagraphBlockConfig = createBlockConfig( () => ({ type: "paragraph" as const, - propSchema: defaultProps, + propSchema: defaultPropSchema, content: "inline" as const, }) as const, ); export const createParagraphBlockSpec = createBlockSpec( - createParagraphBlockConfig, + createParagraphBlockConfig(), { meta: { isolating: false, diff --git a/packages/core/src/blocks/Quote/block.ts b/packages/core/src/blocks/Quote/block.ts index 896528e561..03b67bd92e 100644 --- a/packages/core/src/blocks/Quote/block.ts +++ b/packages/core/src/blocks/Quote/block.ts @@ -1,8 +1,12 @@ import { createBlockNoteExtension } from "../../editor/BlockNoteExtension.js"; -import { createBlockConfig, createBlockSpec } from "../../schema/index.js"; +import { + createBlockConfig, + createBlockSpec, + createPropSchemaFromZod, +} from "../../schema/index.js"; import { addDefaultPropsExternalHTML, - defaultProps, + defaultZodPropSchema, parseDefaultProps, } from "../defaultProps.js"; @@ -12,10 +16,12 @@ export const createQuoteBlockConfig = createBlockConfig( () => ({ type: "quote" as const, - propSchema: defaultProps.pick({ - backgroundColor: true, - textColor: true, - }), + propSchema: createPropSchemaFromZod( + defaultZodPropSchema.pick({ + backgroundColor: true, + textColor: true, + }), + ), content: "inline" as const, }) as const, ); diff --git a/packages/core/src/blocks/Table/block.ts b/packages/core/src/blocks/Table/block.ts index c1d70e98bc..4e314a1aac 100644 --- a/packages/core/src/blocks/Table/block.ts +++ b/packages/core/src/blocks/Table/block.ts @@ -6,17 +6,20 @@ import { createBlockNoteExtension } from "../../editor/BlockNoteExtension.js"; import { BlockConfig, createBlockSpecFromTiptapNode, + createPropSchemaFromZod, TableContent, } from "../../schema/index.js"; import { mergeCSSClasses } from "../../util/browser.js"; import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js"; -import { defaultProps } from "../defaultProps.js"; +import { defaultZodPropSchema } from "../defaultProps.js"; import { EMPTY_CELL_WIDTH, TableExtension } from "./TableExtension.js"; -export const tablePropSchema = defaultProps.pick({ +export const tableZodPropSchema = defaultZodPropSchema.pick({ textColor: true, }); +const tablePropSchema = createPropSchemaFromZod(tableZodPropSchema); + const TiptapTableHeader = Node.create<{ HTMLAttributes: Record; }>({ diff --git a/packages/core/src/blocks/ToggleWrapper/createToggleWrapper.ts b/packages/core/src/blocks/ToggleWrapper/createToggleWrapper.ts index 1c80d4346e..b0efc42889 100644 --- a/packages/core/src/blocks/ToggleWrapper/createToggleWrapper.ts +++ b/packages/core/src/blocks/ToggleWrapper/createToggleWrapper.ts @@ -28,7 +28,11 @@ export const createToggleWrapper = ( ignoreMutation?: (mutation: ViewMutationRecord) => boolean; destroy?: () => void; } => { - if ("isToggleable" in block.props && !block.props.isToggleable) { + // TODO + if ( + "isToggleable" in (block.props as any) && + !(block.props as any).isToggleable + ) { return { dom: renderedElement, }; diff --git a/packages/core/src/blocks/Video/block.ts b/packages/core/src/blocks/Video/block.ts index c5b8d4f42e..ae6f7164a1 100644 --- a/packages/core/src/blocks/Video/block.ts +++ b/packages/core/src/blocks/Video/block.ts @@ -1,6 +1,14 @@ -import { createBlockConfig, createBlockSpec } from "../../schema/index.js"; -import { baseFilePropSchema, optionalFileProps } from "../defaultFileProps.js"; -import { defaultProps, parseDefaultProps } from "../defaultProps.js"; +import { + createBlockConfig, + createBlockSpec, + createPropSchemaFromZod, +} from "../../schema/index.js"; +import { + baseFileZodPropSchema, + optionalFileZodPropSchema, +} from "../defaultFileProps.js"; + +import { defaultZodPropSchema, parseDefaultProps } from "../defaultProps.js"; import { parseFigureElement } from "../File/helpers/parse/parseFigureElement.js"; import { createResizableFileBlockWrapper } from "../File/helpers/render/createResizableFileBlockWrapper.js"; import { createFigureWithCaption } from "../File/helpers/toExternalHTML/createFigureWithCaption.js"; @@ -16,14 +24,14 @@ export interface VideoOptions { export type VideoBlockConfig = ReturnType; -const videoPropSchema = defaultProps +const videoZodPropSchema = defaultZodPropSchema .pick({ textAlignment: true, backgroundColor: true, }) .extend({ - ...baseFilePropSchema.shape, - ...optionalFileProps.pick({ + ...baseFileZodPropSchema.shape, + ...optionalFileZodPropSchema.pick({ url: true, showPreview: true, previewWidth: true, @@ -33,7 +41,7 @@ const videoPropSchema = defaultProps export const createVideoBlockConfig = createBlockConfig( (_ctx: VideoOptions) => ({ type: "video" as const, - propSchema: videoPropSchema, + propSchema: createPropSchemaFromZod(videoZodPropSchema), content: "none" as const, }), ); diff --git a/packages/core/src/blocks/defaultBlockTypeGuards.ts b/packages/core/src/blocks/defaultBlockTypeGuards.ts index 044c3b7f88..c3ca03fdda 100644 --- a/packages/core/src/blocks/defaultBlockTypeGuards.ts +++ b/packages/core/src/blocks/defaultBlockTypeGuards.ts @@ -31,14 +31,16 @@ export function editorHasBlockWithType< editor.schema.blockSpecs[blockType].config.propSchema; // make sure every prop in the requested prop appears in the editor schema block props - return Object.entries(props._zod.def.shape).every(([key, value]) => { - // we do a JSON Stringify check as Zod doesn't expose - // equality / assignability checks - return ( - JSON.stringify(value._zod.def) === - JSON.stringify(editorProps._zod.def.shape[key]._zod.def) - ); - }); + return Object.entries(props._zodSource._zod.def.shape).every( + ([key, value]) => { + // we do a JSON Stringify check as Zod doesn't expose + // equality / assignability checks + return ( + JSON.stringify(value._zod.def) === + JSON.stringify(editorProps._zodSource._zod.def.shape[key]._zod.def) + ); + }, + ); } export function blockHasType( diff --git a/packages/core/src/blocks/defaultBlocks.ts b/packages/core/src/blocks/defaultBlocks.ts index 382cd84e8b..5d7453ff55 100644 --- a/packages/core/src/blocks/defaultBlocks.ts +++ b/packages/core/src/blocks/defaultBlocks.ts @@ -8,7 +8,6 @@ import { COLORS_DEFAULT } from "../editor/defaultColors.js"; import { BlockNoDefaults, BlockSchema, - BlockSpec, createStyleSpec, createStyleSpecFromTipTapMark, getInlineContentSchemaFromSpecs, @@ -20,10 +19,6 @@ import { StyleSpecs, } from "../schema/index.js"; import { - AudioBlockConfig, - BulletListItemBlockConfig, - CheckListItemBlockConfig, - CodeBlockConfig, createAudioBlockSpec, createBulletListItemBlockSpec, createCheckListItemBlockSpec, @@ -37,91 +32,26 @@ import { createQuoteBlockSpec, createToggleListItemBlockSpec, createVideoBlockSpec, - defaultProps, - DividerBlockConfig, - FileBlockConfig, - HeadingBlockConfig, - ImageBlockConfig, - NumberedListItemBlockConfig, - ParagraphBlockConfig, - QuoteBlockConfig, - ToggleListItemBlockConfig, - VideoBlockConfig, + defaultZodPropSchema, } from "./index.js"; -import { createTableBlockSpec, TableBlockConfig } from "./Table/block.js"; +import { createTableBlockSpec } from "./Table/block.js"; export const defaultBlockSpecs = { // To speed up TS compilation, we re-use the type assertions to avoid TS needing to compare types all the time - audio: createAudioBlockSpec() as BlockSpec< - AudioBlockConfig["type"], - AudioBlockConfig["propSchema"], - AudioBlockConfig["content"] - >, - bulletListItem: createBulletListItemBlockSpec() as BlockSpec< - BulletListItemBlockConfig["type"], - BulletListItemBlockConfig["propSchema"], - BulletListItemBlockConfig["content"] - >, - checkListItem: createCheckListItemBlockSpec() as BlockSpec< - CheckListItemBlockConfig["type"], - CheckListItemBlockConfig["propSchema"], - CheckListItemBlockConfig["content"] - >, - codeBlock: createCodeBlockSpec() as BlockSpec< - CodeBlockConfig["type"], - CodeBlockConfig["propSchema"], - CodeBlockConfig["content"] - >, - divider: createDividerBlockSpec() as BlockSpec< - DividerBlockConfig["type"], - DividerBlockConfig["propSchema"], - DividerBlockConfig["content"] - >, - file: createFileBlockSpec() as BlockSpec< - FileBlockConfig["type"], - FileBlockConfig["propSchema"], - FileBlockConfig["content"] - >, - heading: createHeadingBlockSpec() as BlockSpec< - HeadingBlockConfig["type"], - HeadingBlockConfig["propSchema"], - HeadingBlockConfig["content"] - >, - image: createImageBlockSpec() as BlockSpec< - ImageBlockConfig["type"], - ImageBlockConfig["propSchema"], - ImageBlockConfig["content"] - >, - numberedListItem: createNumberedListItemBlockSpec() as BlockSpec< - NumberedListItemBlockConfig["type"], - NumberedListItemBlockConfig["propSchema"], - NumberedListItemBlockConfig["content"] - >, - paragraph: createParagraphBlockSpec() as BlockSpec< - ParagraphBlockConfig["type"], - ParagraphBlockConfig["propSchema"], - ParagraphBlockConfig["content"] - >, - quote: createQuoteBlockSpec() as BlockSpec< - QuoteBlockConfig["type"], - QuoteBlockConfig["propSchema"], - QuoteBlockConfig["content"] - >, - table: createTableBlockSpec() as BlockSpec< - TableBlockConfig["type"], - TableBlockConfig["propSchema"], - TableBlockConfig["content"] - >, - toggleListItem: createToggleListItemBlockSpec() as BlockSpec< - ToggleListItemBlockConfig["type"], - ToggleListItemBlockConfig["propSchema"], - ToggleListItemBlockConfig["content"] - >, - video: createVideoBlockSpec() as BlockSpec< - VideoBlockConfig["type"], - VideoBlockConfig["propSchema"], - VideoBlockConfig["content"] - >, + audio: createAudioBlockSpec(), + bulletListItem: createBulletListItemBlockSpec(), + checkListItem: createCheckListItemBlockSpec(), + codeBlock: createCodeBlockSpec(), + divider: createDividerBlockSpec(), + file: createFileBlockSpec(), + heading: createHeadingBlockSpec(), + image: createImageBlockSpec(), + numberedListItem: createNumberedListItemBlockSpec(), + paragraph: createParagraphBlockSpec(), + quote: createQuoteBlockSpec(), + table: createTableBlockSpec(), + toggleListItem: createToggleListItemBlockSpec(), + video: createVideoBlockSpec(), } as const; // underscore is used that in case a user overrides DefaultBlockSchema, @@ -148,7 +78,8 @@ const TextColor = createStyleSpec( toExternalHTML: (value) => { const span = document.createElement("span"); // const defaultValue = defaultProps.parse({}).textColor; - const defaultValue = defaultProps.shape.textColor.def.defaultValue; + const defaultValue = + defaultZodPropSchema.shape.textColor.def.defaultValue; if (value !== defaultValue) { span.style.color = value in COLORS_DEFAULT ? COLORS_DEFAULT[value].text : value; @@ -187,7 +118,8 @@ const BackgroundColor = createStyleSpec( const span = document.createElement("span"); // TODO // const defaultValues = defaultProps.parse({}); - const defaultValue = defaultProps.shape.backgroundColor.def.defaultValue; + const defaultValue = + defaultZodPropSchema.shape.backgroundColor.def.defaultValue; if (value !== defaultValue) { span.style.backgroundColor = value in COLORS_DEFAULT ? COLORS_DEFAULT[value].background : value; diff --git a/packages/core/src/blocks/defaultFileProps.ts b/packages/core/src/blocks/defaultFileProps.ts index 970fc7afed..596d6b5c51 100644 --- a/packages/core/src/blocks/defaultFileProps.ts +++ b/packages/core/src/blocks/defaultFileProps.ts @@ -1,11 +1,11 @@ import * as z from "zod/v4"; -export const baseFilePropSchema = z.object({ +export const baseFileZodPropSchema = z.object({ caption: z.string().default(""), // TODO: "" as defaults? name: z.string().default(""), }); -export const optionalFileProps = z.object({ +export const optionalFileZodPropSchema = z.object({ // URL is optional, as we also want to accept files with no URL, but for example ids // (ids can be used for files that are resolved on the backend) url: z.string().default(""), diff --git a/packages/core/src/blocks/defaultProps.ts b/packages/core/src/blocks/defaultProps.ts index 489ad8628d..384a5cc73d 100644 --- a/packages/core/src/blocks/defaultProps.ts +++ b/packages/core/src/blocks/defaultProps.ts @@ -2,24 +2,25 @@ import { Attribute } from "@tiptap/core"; import { z } from "zod/v4"; import { COLORS_DEFAULT } from "../editor/defaultColors.js"; -import type { Props, PropSchema } from "../schema/index.js"; +import { createPropSchemaFromZod, type Props } from "../schema/index.js"; // TODO: this system should probably be moved / refactored. // The dependency from schema on this file doesn't make sense -export const defaultProps = z.object({ +export const defaultZodPropSchema = z.object({ backgroundColor: z.string().default("default"), textColor: z.string().default("default"), textAlignment: z.enum(["left", "center", "right", "justify"]).default("left"), -}) satisfies PropSchema; +}); -const defaultValues = defaultProps.parse({}); +export const defaultPropSchema = createPropSchemaFromZod(defaultZodPropSchema); +export type DefaultPropSchema = Props; -export type DefaultProps = Props; +const defaultValues = defaultZodPropSchema.parse({}); // TODO: review below export const parseDefaultProps = (element: HTMLElement) => { - const props: Partial = {}; + const props: Partial = {}; // If the `data-` attribute is found, set the prop to the value, as this most // likely means the parsed element was exported by BlockNote originally. @@ -39,20 +40,21 @@ export const parseDefaultProps = (element: HTMLElement) => { props.textColor = element.style.color; } - props.textAlignment = defaultProps.shape.textAlignment._zod.values + props.textAlignment = defaultZodPropSchema.shape.textAlignment._zod.values .values() .some( (value) => - value === (element.style.textAlign as DefaultProps["textAlignment"]), + value === + (element.style.textAlign as DefaultPropSchema["textAlignment"]), ) - ? (element.style.textAlign as DefaultProps["textAlignment"]) + ? (element.style.textAlign as DefaultPropSchema["textAlignment"]) : undefined; return props; }; export const addDefaultPropsExternalHTML = ( - props: Partial, + props: Partial, element: HTMLElement, ) => { if ( diff --git a/packages/core/src/extensions/Collaboration/schemaMigration/migrationRules/moveColorAttributes.ts b/packages/core/src/extensions/Collaboration/schemaMigration/migrationRules/moveColorAttributes.ts index be8cbb0209..54f31d67e1 100644 --- a/packages/core/src/extensions/Collaboration/schemaMigration/migrationRules/moveColorAttributes.ts +++ b/packages/core/src/extensions/Collaboration/schemaMigration/migrationRules/moveColorAttributes.ts @@ -1,6 +1,6 @@ import * as Y from "yjs"; -import { defaultProps } from "../../../../blocks/defaultProps.js"; +import { defaultZodPropSchema } from "../../../../blocks/defaultProps.js"; import { MigrationRule } from "./migrationRule.js"; // Helper function to recursively traverse a `Y.XMLElement` and its descendant @@ -46,7 +46,7 @@ export const moveColorAttributes: MigrationRule = (fragment, tr) => { }; // TODO: TBD best way to extract defaults - const defaultValues = defaultProps.parse({}); + const defaultValues = defaultZodPropSchema.parse({}); if (colors.textColor === defaultValues.textColor) { colors.textColor = undefined; } diff --git a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts index f8597fd8e0..082cfc8978 100644 --- a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +++ b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts @@ -3,11 +3,12 @@ import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { z } from "zod/v4"; import { editorHasBlockWithType } from "../../blocks/defaultBlockTypeGuards.js"; -import { optionalFileProps } from "../../blocks/defaultFileProps.js"; +import { optionalFileZodPropSchema } from "../../blocks/defaultFileProps.js"; import { BlockSchema, InlineContentSchema, StyleSchema, + createPropSchemaFromZod, isStyledTextInlineContent, } from "../../schema/index.js"; import { formatKeyboardShortcut } from "../../util/browser.js"; @@ -92,7 +93,7 @@ export function getDefaultSlashMenuItems< if (editorHasBlockWithType(editor, "heading")) { const headingProps = editor.schema.blockSchema.heading.propSchema; for (const level of [1, 2, 3, 4, 5, 6] as const) { - if (z.safeParse(headingProps, { level }).success) { + if (z.safeParse(headingProps._zodSource, { level }).success) { items.push({ onItemClick: () => { insertOrUpdateBlock(editor, { @@ -235,7 +236,8 @@ export function getDefaultSlashMenuItems< editorHasBlockWithType( editor, "image", - optionalFileProps.pick({ url: true }), + // TODO: review + createPropSchemaFromZod(optionalFileZodPropSchema.pick({ url: true })), ) ) { items.push({ @@ -260,7 +262,7 @@ export function getDefaultSlashMenuItems< editorHasBlockWithType( editor, "video", - optionalFileProps.pick({ url: true }), + createPropSchemaFromZod(optionalFileZodPropSchema.pick({ url: true })), ) ) { items.push({ @@ -285,7 +287,7 @@ export function getDefaultSlashMenuItems< editorHasBlockWithType( editor, "audio", - optionalFileProps.pick({ url: true }), + createPropSchemaFromZod(optionalFileZodPropSchema.pick({ url: true })), ) ) { items.push({ @@ -310,7 +312,7 @@ export function getDefaultSlashMenuItems< editorHasBlockWithType( editor, "file", - optionalFileProps.pick({ url: true }), + createPropSchemaFromZod(optionalFileZodPropSchema.pick({ url: true })), ) ) { items.push({ @@ -335,13 +337,15 @@ export function getDefaultSlashMenuItems< editorHasBlockWithType( editor, "heading", - z.object({ - isToggleable: z.boolean().default(false), - }), + createPropSchemaFromZod( + z.object({ + isToggleable: z.boolean().default(false), + }), + ), ) ) { const headingProps = editor.schema.blockSchema.heading.propSchema; - if (z.safeParse(headingProps, { level: 1 }).success) { + if (z.safeParse(headingProps._zodSource, { level: 1 }).success) { items.push({ onItemClick: () => { insertOrUpdateBlock(editor, { @@ -353,7 +357,7 @@ export function getDefaultSlashMenuItems< ...editor.dictionary.slash_menu.toggle_heading_1, }); } - if (z.safeParse(headingProps, { level: 2 }).success) { + if (z.safeParse(headingProps._zodSource, { level: 2 }).success) { items.push({ onItemClick: () => { insertOrUpdateBlock(editor, { @@ -366,7 +370,7 @@ export function getDefaultSlashMenuItems< ...editor.dictionary.slash_menu.toggle_heading_2, }); } - if (z.safeParse(headingProps, { level: 3 }).success) { + if (z.safeParse(headingProps._zodSource, { level: 3 }).success) { items.push({ onItemClick: () => { insertOrUpdateBlock(editor, { diff --git a/packages/core/src/schema/blocks/internal.ts b/packages/core/src/schema/blocks/internal.ts index ef94f558bd..0e126beffd 100644 --- a/packages/core/src/schema/blocks/internal.ts +++ b/packages/core/src/schema/blocks/internal.ts @@ -22,43 +22,45 @@ import { export function propsToAttributes(propSchema: PropSchema): Attributes { const tiptapAttributes: Record = {}; - Object.entries(propSchema._zod.def.shape).forEach(([name, spec]) => { - const def = - spec instanceof z.$ZodDefault ? spec._zod.def.defaultValue : undefined; - - tiptapAttributes[name] = { - default: def, - keepOnSplit: true, - // Props are displayed in kebab-case as HTML attributes. If a prop's - // value is the same as its default, we don't display an HTML - // attribute for it. - parseHTML: (element) => { - const value = element.getAttribute(camelToDataKebab(name)); - - if (value === null) { - return null; - } - - // TBD: this might not be fault proof, but it's also ugly to store prop=""..."" for strings - try { - const jsonValue = JSON.parse(value); - // it was a number / boolean / json object stored as attribute - return z.parse(spec, jsonValue); - } catch (e) { - // it might have been a string directly stored as attribute - return z.parse(spec, value); - } - }, - renderHTML: (attributes) => { - // don't render to html if the value is the same as the default - return attributes[name] !== def - ? { - [camelToDataKebab(name)]: attributes[name], - } - : {}; - }, - }; - }); + Object.entries(propSchema._zodSource._zod.def.shape).forEach( + ([name, spec]) => { + const def = + spec instanceof z.$ZodDefault ? spec._zod.def.defaultValue : undefined; + + tiptapAttributes[name] = { + default: def, + keepOnSplit: true, + // Props are displayed in kebab-case as HTML attributes. If a prop's + // value is the same as its default, we don't display an HTML + // attribute for it. + parseHTML: (element) => { + const value = element.getAttribute(camelToDataKebab(name)); + + if (value === null) { + return null; + } + + // TBD: this might not be fault proof, but it's also ugly to store prop=""..."" for strings + try { + const jsonValue = JSON.parse(value); + // it was a number / boolean / json object stored as attribute + return z.parse(spec, jsonValue); + } catch (e) { + // it might have been a string directly stored as attribute + return z.parse(spec, value); + } + }, + renderHTML: (attributes) => { + // don't render to html if the value is the same as the default + return attributes[name] !== def + ? { + [camelToDataKebab(name)]: attributes[name], + } + : {}; + }, + }; + }, + ); return tiptapAttributes; } @@ -150,7 +152,7 @@ export function wrapInBlockStructure< // which are already added as HTML attributes to the parent `blockContent` // element (inheritedProps) and props set to their default values. for (const [prop, value] of Object.entries(blockProps)) { - const spec = propSchema._zod.def.shape[prop]; + const spec = propSchema._zodSource._zod.def.shape[prop]; const defaultValue = spec instanceof z.$ZodDefault ? spec._zod.def.defaultValue : undefined; if (value !== defaultValue) { diff --git a/packages/core/src/schema/inlineContent/internal.ts b/packages/core/src/schema/inlineContent/internal.ts index e1c8b74d91..7692e163a4 100644 --- a/packages/core/src/schema/inlineContent/internal.ts +++ b/packages/core/src/schema/inlineContent/internal.ts @@ -34,7 +34,7 @@ export function addInlineContentAttributes< // Adds props as HTML attributes in kebab-case with "data-" prefix. Skips props // set to their default values. for (const [prop, value] of Object.entries(inlineContentProps)) { - const spec = propSchema._zod.def.shape[prop]; + const spec = propSchema._zodSource._zod.def.shape[prop]; const defaultValue = spec instanceof z.$ZodDefault ? spec._zod.def.defaultValue : undefined; if (value !== defaultValue) { diff --git a/packages/core/src/schema/propTypes.ts b/packages/core/src/schema/propTypes.ts index c67273063c..8a0ebd3425 100644 --- a/packages/core/src/schema/propTypes.ts +++ b/packages/core/src/schema/propTypes.ts @@ -1,10 +1,30 @@ import * as z from "zod/v4/core"; -// TODO: maybe remove file - -// PropSchema is now a Zod object schema (using mini types for type constraints) -export type PropSchema = z.$ZodObject; +export type PropSchema = { + // might not be needed, but otherwise it will say Output unused + _output: Output; + // might not be needed, but otherwise it will say Input unused + _input: Input; + // We keep access to zod source, but typed as generic z.$ZodObject + // This makes sure downstream consumers only have the extracted Output / Input types, + // and reduces load on typescript compiler by not having to parse the zod source on every use + _zodSource: z.$ZodObject; +}; // Props type is derived from the Zod schema output -// TODO: z.infer or z.output? -export type Props = z.infer; +export type Props> = + PSchema extends PropSchema ? O : never; + +// We infer Output/Input from the provided schema using z.output / z.input +export function createPropSchemaFromZod(schema: S) { + return { + _output: undefined as any, + _input: undefined as any, + _zodSource: schema, + } as PropSchema, z.input>; +} + +export type PropSchemaFromZod = PropSchema< + z.output, + z.input +>; diff --git a/packages/react/src/blocks/File/helpers/render/AddFileButton.tsx b/packages/react/src/blocks/File/helpers/render/AddFileButton.tsx index 1af4c751ba..f02d3f9b3c 100644 --- a/packages/react/src/blocks/File/helpers/render/AddFileButton.tsx +++ b/packages/react/src/blocks/File/helpers/render/AddFileButton.tsx @@ -1,23 +1,23 @@ -import { FileBlockConfig } from "@blocknote/core"; +import { FileBlockConfig, PropSchemaFromZod } from "@blocknote/core"; import { ReactNode, useCallback } from "react"; import { RiFile2Line } from "react-icons/ri"; import { - baseFilePropSchema, - optionalFileProps, + baseFileZodPropSchema, + optionalFileZodPropSchema, } from "../../../../../../core/src/blocks/defaultFileProps.js"; import { useDictionary } from "../../../../i18n/dictionary.js"; import { ReactCustomBlockRenderProps } from "../../../../schema/ReactBlockSpec.js"; -const requiredPropSchema = baseFilePropSchema.extend({ - ...optionalFileProps.pick({ url: true }).shape, +const requiredZodPropSchema = baseFileZodPropSchema.extend({ + ...optionalFileZodPropSchema.pick({ url: true }).shape, }); export const AddFileButton = ( props: Omit< ReactCustomBlockRenderProps< FileBlockConfig["type"], - typeof requiredPropSchema, + PropSchemaFromZod, FileBlockConfig["content"] >, "contentRef" diff --git a/packages/react/src/blocks/File/helpers/render/FileBlockWrapper.tsx b/packages/react/src/blocks/File/helpers/render/FileBlockWrapper.tsx index 10c3230bc3..2a463029ec 100644 --- a/packages/react/src/blocks/File/helpers/render/FileBlockWrapper.tsx +++ b/packages/react/src/blocks/File/helpers/render/FileBlockWrapper.tsx @@ -1,24 +1,24 @@ -import { FileBlockConfig } from "@blocknote/core"; +import { FileBlockConfig, PropSchemaFromZod } from "@blocknote/core"; import { CSSProperties, ReactNode } from "react"; import { - baseFilePropSchema, - optionalFileProps, + baseFileZodPropSchema, + optionalFileZodPropSchema, } from "../../../../../../core/src/blocks/defaultFileProps.js"; import { useUploadLoading } from "../../../../hooks/useUploadLoading.js"; import { ReactCustomBlockRenderProps } from "../../../../schema/ReactBlockSpec.js"; import { AddFileButton } from "./AddFileButton.js"; import { FileNameWithIcon } from "./FileNameWithIcon.js"; -const requiredPropSchema = baseFilePropSchema.extend({ - ...optionalFileProps.pick({ url: true }).shape, +const requiredZodPropSchema = baseFileZodPropSchema.extend({ + ...optionalFileZodPropSchema.pick({ url: true }).shape, }); export const FileBlockWrapper = ( props: Omit< ReactCustomBlockRenderProps< FileBlockConfig["type"], - typeof requiredPropSchema, + PropSchemaFromZod, FileBlockConfig["content"] >, "contentRef" diff --git a/packages/react/src/blocks/File/helpers/render/FileNameWithIcon.tsx b/packages/react/src/blocks/File/helpers/render/FileNameWithIcon.tsx index df6003bc4f..b1018a5365 100644 --- a/packages/react/src/blocks/File/helpers/render/FileNameWithIcon.tsx +++ b/packages/react/src/blocks/File/helpers/render/FileNameWithIcon.tsx @@ -1,14 +1,13 @@ -import { FileBlockConfig } from "@blocknote/core"; +import { FileBlockConfig, PropSchemaFromZod } from "@blocknote/core"; import { RiFile2Line } from "react-icons/ri"; - -import { baseFilePropSchema } from "../../../../../../core/src/blocks/defaultFileProps.js"; +import { baseFileZodPropSchema } from "../../../../../../core/src/blocks/defaultFileProps.js"; import { ReactCustomBlockRenderProps } from "../../../../schema/ReactBlockSpec.js"; export const FileNameWithIcon = ( props: Omit< ReactCustomBlockRenderProps< FileBlockConfig["type"], - typeof baseFilePropSchema, + PropSchemaFromZod, FileBlockConfig["content"] >, "editor" | "contentRef" diff --git a/packages/react/src/blocks/File/helpers/render/ResizableFileBlockWrapper.tsx b/packages/react/src/blocks/File/helpers/render/ResizableFileBlockWrapper.tsx index 8d46c0e655..335c07bb87 100644 --- a/packages/react/src/blocks/File/helpers/render/ResizableFileBlockWrapper.tsx +++ b/packages/react/src/blocks/File/helpers/render/ResizableFileBlockWrapper.tsx @@ -1,16 +1,16 @@ -import { FileBlockConfig } from "@blocknote/core"; +import { FileBlockConfig, PropSchemaFromZod } from "@blocknote/core"; import { ReactNode, useCallback, useEffect, useRef, useState } from "react"; import { - baseFilePropSchema, - optionalFileProps, + baseFileZodPropSchema, + optionalFileZodPropSchema, } from "../../../../../../core/src/blocks/defaultFileProps.js"; import { useUploadLoading } from "../../../../hooks/useUploadLoading.js"; import { ReactCustomBlockRenderProps } from "../../../../schema/ReactBlockSpec.js"; import { FileBlockWrapper } from "./FileBlockWrapper.js"; -const requiredPropSchema = baseFilePropSchema.extend({ - ...optionalFileProps.pick({ +const requiredZodPropSchema = baseFileZodPropSchema.extend({ + ...optionalFileZodPropSchema.pick({ url: true, previewWidth: true, showPreview: true, @@ -21,7 +21,7 @@ export const ResizableFileBlockWrapper = ( props: Omit< ReactCustomBlockRenderProps< FileBlockConfig["type"], - typeof requiredPropSchema, + PropSchemaFromZod, FileBlockConfig["content"] >, "contentRef" diff --git a/packages/react/src/blocks/ToggleWrapper/ToggleWrapper.tsx b/packages/react/src/blocks/ToggleWrapper/ToggleWrapper.tsx index 82f29d638f..6c33c26da6 100644 --- a/packages/react/src/blocks/ToggleWrapper/ToggleWrapper.tsx +++ b/packages/react/src/blocks/ToggleWrapper/ToggleWrapper.tsx @@ -4,9 +4,8 @@ import { UnreachableCaseError, } from "@blocknote/core"; import { ReactNode, useReducer, useState } from "react"; - -import { ReactCustomBlockRenderProps } from "../../schema/ReactBlockSpec.js"; import { useEditorChange } from "../../hooks/useEditorChange.js"; +import { ReactCustomBlockRenderProps } from "../../schema/ReactBlockSpec.js"; const showChildrenReducer = ( showChildren: boolean, @@ -78,7 +77,11 @@ export const ToggleWrapper = ( }; useEditorChange(() => { - if ("isToggleable" in block.props && !block.props.isToggleable) { + // TODO (as any) + if ( + "isToggleable" in (block.props as any) && + !(block.props as any).isToggleable + ) { return; } @@ -101,7 +104,11 @@ export const ToggleWrapper = ( setChildCount(newChildCount); }); - if ("isToggleable" in block.props && !block.props.isToggleable) { + // TODO + if ( + "isToggleable" in (block.props as any) && + !(block.props as any).isToggleable + ) { return children; } diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileCaptionButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileCaptionButton.tsx index a145ede328..e585a77752 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileCaptionButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileCaptionButton.tsx @@ -1,6 +1,7 @@ import { blockHasType, BlockSchema, + createPropSchemaFromZod, InlineContentSchema, StyleSchema, } from "@blocknote/core"; @@ -13,7 +14,7 @@ import { } from "react"; import { RiInputField } from "react-icons/ri"; -import { baseFilePropSchema } from "../../../../../core/src/blocks/defaultFileProps.js"; +import { baseFileZodPropSchema } from "../../../../../core/src/blocks/defaultFileProps.js"; import { useComponentsContext } from "../../../editor/ComponentsContext.js"; import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor.js"; import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks.js"; @@ -46,7 +47,8 @@ export const FileCaptionButton = () => { block, editor, block.type, - baseFilePropSchema.pick({ caption: true }), + // TODO + createPropSchemaFromZod(baseFileZodPropSchema.pick({ caption: true })), ) ) { setCurrentEditingCaption(block.props.caption); diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileDeleteButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileDeleteButton.tsx index 383d64c7ea..23e88fc9d6 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileDeleteButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileDeleteButton.tsx @@ -1,6 +1,7 @@ import { blockHasType, BlockSchema, + createPropSchemaFromZod, InlineContentSchema, StyleSchema, } from "@blocknote/core"; @@ -8,8 +9,8 @@ import { useCallback, useMemo } from "react"; import { RiDeleteBin7Line } from "react-icons/ri"; import { - baseFilePropSchema, - optionalFileProps, + baseFileZodPropSchema, + optionalFileZodPropSchema, } from "../../../../../core/src/blocks/defaultFileProps.js"; import { useComponentsContext } from "../../../editor/ComponentsContext.js"; import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor.js"; @@ -41,9 +42,12 @@ export const FileDeleteButton = () => { block, editor, block.type, - baseFilePropSchema.extend({ - ...optionalFileProps.pick({ url: true }).shape, - }), + // TODO + createPropSchemaFromZod( + baseFileZodPropSchema.extend({ + ...optionalFileZodPropSchema.pick({ url: true }).shape, + }), + ), ) ) { return block; diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileDownloadButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileDownloadButton.tsx index 774c0cba7e..918a661921 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileDownloadButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileDownloadButton.tsx @@ -1,6 +1,7 @@ import { blockHasType, BlockSchema, + createPropSchemaFromZod, InlineContentSchema, StyleSchema, } from "@blocknote/core"; @@ -8,8 +9,8 @@ import { useCallback, useMemo } from "react"; import { RiDownload2Fill } from "react-icons/ri"; import { - baseFilePropSchema, - optionalFileProps, + baseFileZodPropSchema, + optionalFileZodPropSchema, } from "../../../../../core/src/blocks/defaultFileProps.js"; import { useComponentsContext } from "../../../editor/ComponentsContext.js"; import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor.js"; @@ -42,9 +43,12 @@ export const FileDownloadButton = () => { block, editor, block.type, - baseFilePropSchema.extend({ - ...optionalFileProps.pick({ url: true }).shape, - }), + // TODO + createPropSchemaFromZod( + baseFileZodPropSchema.extend({ + ...optionalFileZodPropSchema.pick({ url: true }).shape, + }), + ), ) ) { return block; diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/FilePreviewButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/FilePreviewButton.tsx index 0b2c4efe29..6509af3877 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/FilePreviewButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/FilePreviewButton.tsx @@ -1,13 +1,14 @@ import { blockHasType, BlockSchema, + createPropSchemaFromZod, InlineContentSchema, StyleSchema, } from "@blocknote/core"; import { useCallback, useMemo } from "react"; import { RiImageAddFill } from "react-icons/ri"; -import { optionalFileProps } from "../../../../../core/src/blocks/defaultFileProps.js"; +import { optionalFileZodPropSchema } from "../../../../../core/src/blocks/defaultFileProps.js"; import { useComponentsContext } from "../../../editor/ComponentsContext.js"; import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor.js"; import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks.js"; @@ -38,7 +39,10 @@ export const FilePreviewButton = () => { block, editor, block.type, - optionalFileProps.pick({ url: true, showPreview: true }), + // TODO + createPropSchemaFromZod( + optionalFileZodPropSchema.pick({ url: true, showPreview: true }), + ), ) ) { return block; diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileRenameButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileRenameButton.tsx index fb9f98911e..fde8066ccf 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileRenameButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileRenameButton.tsx @@ -1,6 +1,7 @@ import { blockHasType, BlockSchema, + createPropSchemaFromZod, InlineContentSchema, StyleSchema, } from "@blocknote/core"; @@ -14,8 +15,8 @@ import { import { RiFontFamily } from "react-icons/ri"; import { - baseFilePropSchema, - optionalFileProps, + baseFileZodPropSchema, + optionalFileZodPropSchema, } from "../../../../../core/src/blocks/defaultFileProps.js"; import { useComponentsContext } from "../../../editor/ComponentsContext.js"; import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor.js"; @@ -49,9 +50,11 @@ export const FileRenameButton = () => { block, editor, block.type, - baseFilePropSchema - .pick({ name: true }) - .extend({ ...optionalFileProps.pick({ url: true }) }.shape), + createPropSchemaFromZod( + baseFileZodPropSchema + .pick({ name: true }) + .extend({ ...optionalFileZodPropSchema.pick({ url: true }) }.shape), + ), ) ) { setCurrentEditingName(block.props.name); diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileReplaceButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileReplaceButton.tsx index 71a4df4826..6451cee4d0 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/FileReplaceButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/FileReplaceButton.tsx @@ -1,6 +1,7 @@ import { blockHasType, BlockSchema, + createPropSchemaFromZod, InlineContentSchema, StyleSchema, } from "@blocknote/core"; @@ -8,8 +9,8 @@ import { useEffect, useState } from "react"; import { RiImageEditFill } from "react-icons/ri"; import { - baseFilePropSchema, - optionalFileProps, + baseFileZodPropSchema, + optionalFileZodPropSchema, } from "../../../../../core/src/blocks/defaultFileProps.js"; import { useComponentsContext } from "../../../editor/ComponentsContext.js"; import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor.js"; @@ -43,9 +44,12 @@ export const FileReplaceButton = () => { block, editor, block.type, - baseFilePropSchema.extend({ - ...optionalFileProps.pick({ url: true }).shape, - }), + // TODO + createPropSchemaFromZod( + baseFileZodPropSchema.extend({ + ...optionalFileZodPropSchema.pick({ url: true }).shape, + }), + ) || !editor.isEditable, ) || !editor.isEditable ) { diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx index ffda042487..7b111ba1eb 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx @@ -1,8 +1,9 @@ import { blockHasType, BlockSchema, - defaultProps, - DefaultProps, + createPropSchemaFromZod, + DefaultPropSchema, + defaultZodPropSchema, InlineContentSchema, mapTableCell, StyleSchema, @@ -22,7 +23,7 @@ import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor.js"; import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks.js"; import { useDictionary } from "../../../i18n/dictionary.js"; -type TextAlignment = DefaultProps["textAlignment"]; +type TextAlignment = DefaultPropSchema["textAlignment"]; const icons: Record = { left: RiAlignLeft, @@ -51,7 +52,10 @@ export const TextAlignButton = (props: { textAlignment: TextAlignment }) => { block, editor, block.type, - defaultProps.pick({ textAlignment: true }), + // TODO + createPropSchemaFromZod( + defaultZodPropSchema.pick({ textAlignment: true }), + ), ) ) { return block.props.textAlignment; @@ -87,7 +91,10 @@ export const TextAlignButton = (props: { textAlignment: TextAlignment }) => { block, editor, block.type, - defaultProps.pick({ textAlignment: true }), + // TODO + createPropSchemaFromZod( + defaultZodPropSchema.pick({ textAlignment: true }), + ), ) ) { editor.updateBlock(block, { @@ -140,7 +147,10 @@ export const TextAlignButton = (props: { textAlignment: TextAlignment }) => { block, editor, block.type, - defaultProps.pick({ textAlignment: true }), + // TODO + createPropSchemaFromZod( + defaultZodPropSchema.pick({ textAlignment: true }), + ), ) || (block.type === "table" && block.children), ); diff --git a/packages/react/src/components/FormattingToolbar/FormattingToolbarController.tsx b/packages/react/src/components/FormattingToolbar/FormattingToolbarController.tsx index 6ce960d035..bc49aa87fd 100644 --- a/packages/react/src/components/FormattingToolbar/FormattingToolbarController.tsx +++ b/packages/react/src/components/FormattingToolbar/FormattingToolbarController.tsx @@ -1,6 +1,6 @@ import { BlockSchema, - DefaultProps, + DefaultPropSchema, InlineContentSchema, StyleSchema, } from "@blocknote/core"; @@ -17,7 +17,7 @@ import { FormattingToolbar } from "./FormattingToolbar.js"; import { FormattingToolbarProps } from "./FormattingToolbarProps.js"; const textAlignmentToPlacement = ( - textAlignment: DefaultProps["textAlignment"], + textAlignment: DefaultPropSchema["textAlignment"], ) => { switch (textAlignment) { case "left": @@ -52,7 +52,7 @@ export const FormattingToolbarController = (props: { } return textAlignmentToPlacement( - block.props.textAlignment as DefaultProps["textAlignment"], + block.props.textAlignment as DefaultPropSchema["textAlignment"], ); }, ); @@ -65,7 +65,7 @@ export const FormattingToolbarController = (props: { } else { setPlacement( textAlignmentToPlacement( - block.props.textAlignment as DefaultProps["textAlignment"], + block.props.textAlignment as DefaultPropSchema["textAlignment"], ), ); } diff --git a/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/BlockColorsItem.tsx b/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/BlockColorsItem.tsx index 5d3d6d1261..64e9f78ce6 100644 --- a/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/BlockColorsItem.tsx +++ b/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/BlockColorsItem.tsx @@ -2,10 +2,11 @@ import { Block, blockHasType, BlockSchema, + createPropSchemaFromZod, DefaultBlockSchema, DefaultInlineContentSchema, - defaultProps, DefaultStyleSchema, + defaultZodPropSchema, editorHasBlockWithType, InlineContentSchema, StyleSchema, @@ -40,13 +41,17 @@ export const BlockColorsItem = < block, editor, block.type, - defaultProps.pick({ textColor: true }), + // TODO + createPropSchemaFromZod(defaultZodPropSchema.pick({ textColor: true })), ) && !blockHasType( block, editor, block.type, - defaultProps.pick({ backgroundColor: true }), + // TODO + createPropSchemaFromZod( + defaultZodPropSchema.pick({ backgroundColor: true }), + ), ) ) { return null; @@ -74,12 +79,18 @@ export const BlockColorsItem = < block, editor, block.type, - defaultProps.pick({ textColor: true }), + // TODO + createPropSchemaFromZod( + defaultZodPropSchema.pick({ textColor: true }), + ), ) && editorHasBlockWithType( editor, block.type, - defaultProps.pick({ textColor: true }), + // TODO + createPropSchemaFromZod( + defaultZodPropSchema.pick({ textColor: true }), + ), ) ? { color: block.props.textColor, @@ -96,12 +107,18 @@ export const BlockColorsItem = < block, editor, block.type, - defaultProps.pick({ backgroundColor: true }), + // TODO + createPropSchemaFromZod( + defaultZodPropSchema.pick({ backgroundColor: true }), + ), ) && editorHasBlockWithType( editor, block.type, - defaultProps.pick({ backgroundColor: true }), + // TODO + createPropSchemaFromZod( + defaultZodPropSchema.pick({ backgroundColor: true }), + ), ) ? { color: block.props.backgroundColor, diff --git a/packages/react/src/schema/ReactBlockSpec.tsx b/packages/react/src/schema/ReactBlockSpec.tsx index cf194a60c1..51756bebb5 100644 --- a/packages/react/src/schema/ReactBlockSpec.tsx +++ b/packages/react/src/schema/ReactBlockSpec.tsx @@ -99,7 +99,7 @@ export function BlockContentWrapper< {...Object.fromEntries( Object.entries(props.blockProps) .filter(([prop, value]) => { - const spec = props.propSchema._zod.def.shape[prop]; + const spec = props.propSchema._zodSource._zod.def.shape[prop]; const defaultValue = spec instanceof z.$ZodDefault ? spec._zod.def.defaultValue diff --git a/packages/react/src/schema/ReactInlineContentSpec.tsx b/packages/react/src/schema/ReactInlineContentSpec.tsx index 7795eb4c22..4b3472c5c5 100644 --- a/packages/react/src/schema/ReactInlineContentSpec.tsx +++ b/packages/react/src/schema/ReactInlineContentSpec.tsx @@ -18,7 +18,6 @@ import { propsToAttributes, StyleSchema, } from "@blocknote/core"; -import * as z from "zod/v4/core"; import { Node } from "@tiptap/core"; import { NodeViewProps, @@ -26,6 +25,7 @@ import { ReactNodeViewRenderer, useReactNodeView, } from "@tiptap/react"; +import * as z from "zod/v4/core"; // import { useReactNodeView } from "@tiptap/react/dist/packages/react/src/useReactNodeView"; import { FC, JSX } from "react"; import { renderToDOMSpec } from "./@util/ReactRenderUtil.js"; @@ -82,7 +82,7 @@ export function InlineContentWrapper< {...Object.fromEntries( Object.entries(props.inlineContentProps) .filter(([prop, value]) => { - const spec = props.propSchema._zod.def.shape[prop]; + const spec = props.propSchema._zodSource._zod.def.shape[prop]; const defaultValue = spec instanceof z.$ZodDefault ? spec._zod.def.defaultValue diff --git a/packages/server-util/src/context/react/ReactServer.test.tsx b/packages/server-util/src/context/react/ReactServer.test.tsx index 57c66709e1..74395a1f93 100644 --- a/packages/server-util/src/context/react/ReactServer.test.tsx +++ b/packages/server-util/src/context/react/ReactServer.test.tsx @@ -1,7 +1,7 @@ import { BlockNoteSchema, defaultBlockSpecs, - defaultProps, + defaultPropSchema, } from "@blocknote/core"; import { createReactBlockSpec } from "@blocknote/react"; import { createContext, useContext } from "react"; @@ -11,7 +11,7 @@ import { ServerBlockNoteEditor } from "../ServerBlockNoteEditor.js"; const SimpleReactCustomParagraph = createReactBlockSpec( { type: "simpleReactCustomParagraph" as const, - propSchema: defaultProps, + propSchema: defaultPropSchema, content: "inline" as const, }, () => ({ @@ -35,7 +35,7 @@ const ReactContextParagraphComponent = (props: any) => { const ReactContextParagraph = createReactBlockSpec( { type: "reactContextParagraph" as const, - propSchema: defaultProps, + propSchema: defaultPropSchema, content: "inline" as const, }, { diff --git a/packages/xl-ai/src/api/formats/json/tools/index.ts b/packages/xl-ai/src/api/formats/json/tools/index.ts index 820e3e6aa4..c29e3b2b01 100644 --- a/packages/xl-ai/src/api/formats/json/tools/index.ts +++ b/packages/xl-ai/src/api/formats/json/tools/index.ts @@ -1,4 +1,4 @@ -import { defaultProps, type PartialBlock } from "@blocknote/core"; +import { defaultZodPropSchema, type PartialBlock } from "@blocknote/core"; import { getApplySuggestionsTr, rebaseTool, @@ -38,18 +38,14 @@ export const tools = { rebaseTool: async (_id, editor) => rebaseTool(editor, getApplySuggestionsTr(editor)), toJSONToolCall: async (_editor, chunk) => { - const defaultPropsVals = Object.fromEntries( - Object.entries(defaultProps).map(([key, val]) => { - return [key, val.default]; - }), - ); + const defaults = defaultZodPropSchema.parse({}); return { ...chunk.operation, block: { ...chunk.operation.block, props: { - ...defaultPropsVals, + ...defaults, ...chunk.operation.block.props, }, }, diff --git a/packages/xl-ai/src/api/schema/schemaToJSONSchema.ts b/packages/xl-ai/src/api/schema/schemaToJSONSchema.ts index fd46c72f87..880a85f1b0 100644 --- a/packages/xl-ai/src/api/schema/schemaToJSONSchema.ts +++ b/packages/xl-ai/src/api/schema/schemaToJSONSchema.ts @@ -1,10 +1,11 @@ import { - CustomBlockNoteSchema, BlockSchema, + CustomBlockNoteSchema, InlineContentSchema, PropSchema, StyleSchema, - defaultProps, + createPropSchemaFromZod, + defaultPropSchema, } from "@blocknote/core"; import * as z4 from "zod/v4"; import * as z from "zod/v4/core"; @@ -129,7 +130,7 @@ export function propSchemaToJSONSchema( return { type: "object", properties: Object.fromEntries( - Object.entries(propSchema._zod.def.shape) + Object.entries(propSchema._zodSource._zod.def.shape) .filter(([_key, val]) => { // for now skip optional props return !(val instanceof z.$ZodOptional); @@ -288,10 +289,15 @@ function schemaOps( key, { ...val, - propSchema: z4.object( - Object.fromEntries( - Object.entries(val.propSchema._zod.def.shape).filter( - ([key]) => !(key in defaultProps._zod.def.shape), + propSchema: createPropSchemaFromZod( + z4.object( + Object.fromEntries( + Object.entries( + val.propSchema._zodSource._zod.def.shape, + ).filter( + ([key]) => + !(key in defaultPropSchema._zodSource._zod.def.shape), + ), ), ), ), diff --git a/packages/xl-ai/src/testUtil/cases/schemas/mention.ts b/packages/xl-ai/src/testUtil/cases/schemas/mention.ts index f538b75c67..5f2503562d 100644 --- a/packages/xl-ai/src/testUtil/cases/schemas/mention.ts +++ b/packages/xl-ai/src/testUtil/cases/schemas/mention.ts @@ -1,11 +1,17 @@ -import { BlockNoteSchema, createInlineContentSpec } from "@blocknote/core"; +import { + BlockNoteSchema, + createInlineContentSpec, + createPropSchemaFromZod, +} from "@blocknote/core"; import * as z from "zod/v4"; export const mention = createInlineContentSpec( { type: "mention", - propSchema: z.object({ - user: z.string().default(""), - }), + propSchema: createPropSchemaFromZod( + z.object({ + user: z.string().default(""), + }), + ), content: "none", }, { diff --git a/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts b/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts index c80818fb96..dac92b1de6 100644 --- a/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts +++ b/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts @@ -3,10 +3,11 @@ import { COLORS_DEFAULT, createPageBreakBlockConfig, DefaultBlockSchema, - DefaultProps, + DefaultPropSchema, StyledText, UnreachableCaseError, } from "@blocknote/core"; +import { multiColumnSchema } from "@blocknote/xl-multi-column"; import { getImageDimensions } from "@shared/util/imageUtil.js"; import { CheckBox, @@ -23,10 +24,9 @@ import { TextRun, } from "docx"; import { Table } from "../util/Table.js"; -import { multiColumnSchema } from "@blocknote/xl-multi-column"; function blockPropsToStyles( - props: Partial, + props: Partial, colors: typeof COLORS_DEFAULT, ): IParagraphOptions { return { @@ -290,7 +290,7 @@ export const docxBlockMappingForDefaultSchema: BlockMapping< }; function file( - props: Partial, + props: Partial, defaultText: string, exporter: any, ) { @@ -311,7 +311,7 @@ function file( } function caption( - props: Partial, + props: Partial, exporter: any, ) { if (!props.caption) { diff --git a/packages/xl-email-exporter/src/react-email/reactEmailExporter.test.tsx b/packages/xl-email-exporter/src/react-email/reactEmailExporter.test.tsx index 08f2956e06..f36240919e 100644 --- a/packages/xl-email-exporter/src/react-email/reactEmailExporter.test.tsx +++ b/packages/xl-email-exporter/src/react-email/reactEmailExporter.test.tsx @@ -3,6 +3,7 @@ import { createBlockSpec, createInlineContentSpec, createPageBreakBlockSpec, + createPropSchemaFromZod, createStyleSpec, defaultBlockSpecs, defaultInlineContentSpecs, @@ -34,7 +35,7 @@ describe("react email exporter", () => { { content: "none", type: "extraBlock", - propSchema: z.object({}), + propSchema: createPropSchemaFromZod(z.object({})), }, {} as any, )(), @@ -76,7 +77,7 @@ describe("react email exporter", () => { { type: "extraInlineContent", content: "styled", - propSchema: z.object({}), + propSchema: createPropSchemaFromZod(z.object({})), }, {} as any, ), diff --git a/packages/xl-email-exporter/src/react-email/reactEmailExporter.tsx b/packages/xl-email-exporter/src/react-email/reactEmailExporter.tsx index 9f7d2787ea..01d749308f 100644 --- a/packages/xl-email-exporter/src/react-email/reactEmailExporter.tsx +++ b/packages/xl-email-exporter/src/react-email/reactEmailExporter.tsx @@ -2,13 +2,13 @@ import { Block, BlockSchema, COLORS_DEFAULT, - DefaultProps, + CustomBlockNoteSchema, + DefaultPropSchema, Exporter, ExporterOptions, InlineContentSchema, StyleSchema, StyledText, - CustomBlockNoteSchema, } from "@blocknote/core"; import { Body, @@ -326,7 +326,7 @@ export class ReactEmailExporter< } protected blocknoteDefaultPropsToReactEmailStyle( - props: Partial, + props: Partial, ): any { return { textAlign: props.textAlignment, diff --git a/packages/xl-odt-exporter/src/odt/defaultSchema/blocks.tsx b/packages/xl-odt-exporter/src/odt/defaultSchema/blocks.tsx index 9901063803..54d38b075d 100644 --- a/packages/xl-odt-exporter/src/odt/defaultSchema/blocks.tsx +++ b/packages/xl-odt-exporter/src/odt/defaultSchema/blocks.tsx @@ -3,7 +3,7 @@ import { BlockMapping, createPageBreakBlockConfig, DefaultBlockSchema, - DefaultProps, + DefaultPropSchema, mapTableCell, StyledText, TableCell, @@ -17,7 +17,7 @@ export const getTabs = (nestingLevel: number) => { const createParagraphStyle = ( exporter: ODTExporter, - props: Partial, + props: Partial, parentStyleName = "Standard", styleAttributes: Record = {}, paragraphStyleAttributes: Record = {}, diff --git a/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx b/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx index ce8a924964..678cc43a76 100644 --- a/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx +++ b/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx @@ -1,8 +1,8 @@ import { BlockMapping, - DefaultBlockSchema, - DefaultProps, createPageBreakBlockConfig, + DefaultBlockSchema, + DefaultPropSchema, StyledText, } from "@blocknote/core"; import { multiColumnSchema } from "@blocknote/xl-multi-column"; @@ -251,7 +251,7 @@ export const pdfBlockMappingForDefaultSchema: BlockMapping< }; function file( - props: Partial, + props: Partial, defaultText: string, icon: React.ReactElement, _exporter: any, @@ -274,7 +274,7 @@ function file( } function caption( - props: Partial, + props: Partial, _exporter: any, ) { if (!props.caption) { diff --git a/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx b/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx index f8f2ca9cc3..4d9e0a717c 100644 --- a/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx +++ b/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx @@ -3,6 +3,7 @@ import { createBlockSpec, createInlineContentSpec, createPageBreakBlockSpec, + createPropSchemaFromZod, createStyleSpec, defaultBlockSpecs, defaultInlineContentSpecs, @@ -37,7 +38,7 @@ describe("exporter", () => { { content: "none", type: "extraBlock", - propSchema: z.object({}), + propSchema: createPropSchemaFromZod(z.object({})), }, {} as any, )(), @@ -77,7 +78,7 @@ describe("exporter", () => { { type: "extraInlineContent", content: "styled", - propSchema: z.object({}), + propSchema: createPropSchemaFromZod(z.object({})), }, {} as any, ), diff --git a/packages/xl-pdf-exporter/src/pdf/pdfExporter.tsx b/packages/xl-pdf-exporter/src/pdf/pdfExporter.tsx index 00f967f142..cd302550f9 100644 --- a/packages/xl-pdf-exporter/src/pdf/pdfExporter.tsx +++ b/packages/xl-pdf-exporter/src/pdf/pdfExporter.tsx @@ -3,7 +3,7 @@ import { BlockSchema, COLORS_DEFAULT, CustomBlockNoteSchema, - DefaultProps, + DefaultPropSchema, Exporter, ExporterOptions, InlineContentSchema, @@ -289,7 +289,7 @@ export class PDFExporter< } protected blocknoteDefaultPropsToReactPDFStyle( - props: Partial, + props: Partial, ): Style { return { textAlign: props.textAlignment, diff --git a/shared/formatConversionTestUtil.ts b/shared/formatConversionTestUtil.ts index 315ba0cb3d..dab15cbf77 100644 --- a/shared/formatConversionTestUtil.ts +++ b/shared/formatConversionTestUtil.ts @@ -154,19 +154,18 @@ export function partialBlockToBlockForTesting< ...partialBlock, }; - Object.entries(schema[partialBlock.type!].propSchema._zod.def.shape).forEach( - ([propKey, propValue]) => { - if (withDefaults.props[propKey] === undefined) { - if (propValue instanceof z.$ZodDefault) { - (withDefaults.props as any)[propKey] = - propValue._zod.def.defaultValue; - } - if (propValue instanceof z.$ZodOptional) { - (withDefaults.props as any)[propKey] = undefined; - } + Object.entries( + schema[partialBlock.type!].propSchema._zodSource._zod.def.shape, + ).forEach(([propKey, propValue]) => { + if (withDefaults.props[propKey] === undefined) { + if (propValue instanceof z.$ZodDefault) { + (withDefaults.props as any)[propKey] = propValue._zod.def.defaultValue; } - }, - ); + if (propValue instanceof z.$ZodOptional) { + (withDefaults.props as any)[propKey] = undefined; + } + } + }); if (contentType === "inline") { const content = withDefaults.content as InlineContent[] | undefined; diff --git a/tests/src/unit/core/testSchema.ts b/tests/src/unit/core/testSchema.ts index cb849dafc5..3cd1eec926 100644 --- a/tests/src/unit/core/testSchema.ts +++ b/tests/src/unit/core/testSchema.ts @@ -5,8 +5,9 @@ import { createImageBlockSpec, createInlineContentSpec, createPageBreakBlockSpec, + createPropSchemaFromZod, createStyleSpec, - defaultProps, + defaultPropSchema, } from "@blocknote/core"; import { z } from "zod/v4"; @@ -35,7 +36,7 @@ const SimpleImage = addNodeAndExtensionsToSpec( const CustomParagraph = addNodeAndExtensionsToSpec( { type: "customParagraph", - propSchema: defaultProps, + propSchema: defaultPropSchema, content: "inline", }, { @@ -63,7 +64,7 @@ const CustomParagraph = addNodeAndExtensionsToSpec( const SimpleCustomParagraph = addNodeAndExtensionsToSpec( { type: "simpleCustomParagraph", - propSchema: defaultProps, + propSchema: defaultPropSchema, content: "inline", }, { @@ -84,9 +85,11 @@ const SimpleCustomParagraph = addNodeAndExtensionsToSpec( const Mention = createInlineContentSpec( { type: "mention", - propSchema: z.object({ - user: z.string().default(""), - }), + propSchema: createPropSchemaFromZod( + z.object({ + user: z.string().default(""), + }), + ), content: "none", }, { @@ -128,7 +131,7 @@ const Mention = createInlineContentSpec( const Tag = createInlineContentSpec( { type: "tag" as const, - propSchema: z.object({}), + propSchema: createPropSchemaFromZod(z.object({})), content: "styled", }, { diff --git a/tests/src/unit/react/testSchema.tsx b/tests/src/unit/react/testSchema.tsx index 2fe3f053fb..22ff4a4217 100644 --- a/tests/src/unit/react/testSchema.tsx +++ b/tests/src/unit/react/testSchema.tsx @@ -1,7 +1,8 @@ import { BlockNoteSchema, createPageBreakBlockSpec, - defaultProps, + createPropSchemaFromZod, + defaultPropSchema, } from "@blocknote/core"; import { createReactBlockSpec, @@ -16,7 +17,7 @@ import { z } from "zod/v4"; const createCustomParagraph = createReactBlockSpec( { type: "customParagraph", - propSchema: defaultProps, + propSchema: defaultPropSchema, content: "inline", }, { @@ -32,7 +33,7 @@ const createCustomParagraph = createReactBlockSpec( const createSimpleCustomParagraph = createReactBlockSpec( { type: "simpleCustomParagraph", - propSchema: defaultProps, + propSchema: defaultPropSchema, content: "inline", }, { @@ -56,7 +57,7 @@ const ContextParagraphComponent = (props: any) => { const createContextParagraph = createReactBlockSpec( { type: "contextParagraph", - propSchema: defaultProps, + propSchema: defaultPropSchema, content: "inline", }, { @@ -69,9 +70,11 @@ const createContextParagraph = createReactBlockSpec( const Mention = createReactInlineContentSpec( { type: "mention", - propSchema: z.object({ - user: z.string().default(""), - }), + propSchema: createPropSchemaFromZod( + z.object({ + user: z.string().default(""), + }), + ), content: "none", }, { @@ -110,7 +113,7 @@ const Mention = createReactInlineContentSpec( const Tag = createReactInlineContentSpec( { type: "tag", - propSchema: z.object({}), + propSchema: createPropSchemaFromZod(z.object({})), content: "styled", }, { diff --git a/tests/src/utils/customblocks/Alert.tsx b/tests/src/utils/customblocks/Alert.tsx index bb9ecc02fd..3405f7c936 100644 --- a/tests/src/utils/customblocks/Alert.tsx +++ b/tests/src/utils/customblocks/Alert.tsx @@ -4,7 +4,8 @@ import { BlockSchemaWithBlock, PartialBlock, addNodeAndExtensionsToSpec, - defaultProps, + createPropSchemaFromZod, + defaultZodPropSchema, } from "@blocknote/core"; import { z } from "zod/v4"; @@ -31,16 +32,18 @@ const values = { export const Alert = addNodeAndExtensionsToSpec( { type: "alert" as const, - propSchema: defaultProps - .pick({ - textColor: true, - textAlignment: true, - }) - .extend({ - type: z - .enum(["warning", "error", "info", "success"]) - .default("warning"), - }), + propSchema: createPropSchemaFromZod( + defaultZodPropSchema + .pick({ + textColor: true, + textAlignment: true, + }) + .extend({ + type: z + .enum(["warning", "error", "info", "success"]) + .default("warning"), + }), + ), content: "inline", }, { diff --git a/tests/src/utils/customblocks/Button.tsx b/tests/src/utils/customblocks/Button.tsx index 9c24b65315..54830137b0 100644 --- a/tests/src/utils/customblocks/Button.tsx +++ b/tests/src/utils/customblocks/Button.tsx @@ -1,16 +1,19 @@ import { BlockNoteEditor, addNodeAndExtensionsToSpec, - defaultProps, + createPropSchemaFromZod, + defaultZodPropSchema, } from "@blocknote/core"; import { RiRadioButtonFill } from "react-icons/ri"; export const Button = addNodeAndExtensionsToSpec( { type: "button" as const, - propSchema: defaultProps.pick({ - backgroundColor: true, - }), + propSchema: createPropSchemaFromZod( + defaultZodPropSchema.pick({ + backgroundColor: true, + }), + ), content: "none", }, { diff --git a/tests/src/utils/customblocks/Embed.tsx b/tests/src/utils/customblocks/Embed.tsx index e331f4480a..828eb0896f 100644 --- a/tests/src/utils/customblocks/Embed.tsx +++ b/tests/src/utils/customblocks/Embed.tsx @@ -1,4 +1,8 @@ -import { BlockNoteEditor, addNodeAndExtensionsToSpec } from "@blocknote/core"; +import { + BlockNoteEditor, + addNodeAndExtensionsToSpec, + createPropSchemaFromZod, +} from "@blocknote/core"; import { z } from "zod/v4"; import { RiLayout5Fill } from "react-icons/ri"; @@ -6,9 +10,11 @@ import { RiLayout5Fill } from "react-icons/ri"; export const Embed = addNodeAndExtensionsToSpec( { type: "embed" as const, - propSchema: z.object({ - src: z.string().default("https://www.youtube.com/embed/wjfuB8Xjhc4"), - }), + propSchema: createPropSchemaFromZod( + z.object({ + src: z.string().default("https://www.youtube.com/embed/wjfuB8Xjhc4"), + }), + ), content: "none", }, { diff --git a/tests/src/utils/customblocks/Image.tsx b/tests/src/utils/customblocks/Image.tsx index 57eaf024d4..44472ac104 100644 --- a/tests/src/utils/customblocks/Image.tsx +++ b/tests/src/utils/customblocks/Image.tsx @@ -1,16 +1,19 @@ import { BlockNoteEditor, addNodeAndExtensionsToSpec, - defaultProps, + createPropSchemaFromZod, + defaultZodPropSchema, } from "@blocknote/core"; import { RiImage2Fill } from "react-icons/ri"; import { z } from "zod/v4"; export const Image = addNodeAndExtensionsToSpec( { type: "image" as const, - propSchema: defaultProps.extend({ - src: z.string().default("https://via.placeholder.com/1000"), - }), + propSchema: createPropSchemaFromZod( + defaultZodPropSchema.extend({ + src: z.string().default("https://via.placeholder.com/1000"), + }), + ), content: "inline", }, { diff --git a/tests/src/utils/customblocks/ReactAlert.tsx b/tests/src/utils/customblocks/ReactAlert.tsx index 42a9119e88..909e373951 100644 --- a/tests/src/utils/customblocks/ReactAlert.tsx +++ b/tests/src/utils/customblocks/ReactAlert.tsx @@ -1,5 +1,9 @@ /* eslint-disable no-console */ -import { BlockNoteEditor, defaultProps } from "@blocknote/core"; +import { + BlockNoteEditor, + createPropSchemaFromZod, + defaultZodPropSchema, +} from "@blocknote/core"; import { createReactBlockSpec } from "@blocknote/react"; import { useEffect, useState } from "react"; import { RiAlertFill } from "react-icons/ri"; @@ -27,16 +31,18 @@ const values = { export const ReactAlert = createReactBlockSpec( { type: "reactAlert" as const, - propSchema: defaultProps - .pick({ - textAlignment: true, - textColor: true, - }) - .extend({ - type: z - .enum(["warning", "error", "info", "success"]) - .default("warning"), - }), + propSchema: createPropSchemaFromZod( + defaultZodPropSchema + .pick({ + textAlignment: true, + textColor: true, + }) + .extend({ + type: z + .enum(["warning", "error", "info", "success"]) + .default("warning"), + }), + ), content: "inline" as const, }, { diff --git a/tests/src/utils/customblocks/ReactImage.tsx b/tests/src/utils/customblocks/ReactImage.tsx index e69a631f58..ab82657e0e 100644 --- a/tests/src/utils/customblocks/ReactImage.tsx +++ b/tests/src/utils/customblocks/ReactImage.tsx @@ -1,4 +1,8 @@ -import { BlockNoteEditor, defaultProps } from "@blocknote/core"; +import { + BlockNoteEditor, + createPropSchemaFromZod, + defaultZodPropSchema, +} from "@blocknote/core"; import { createReactBlockSpec } from "@blocknote/react"; import { RiImage2Fill } from "react-icons/ri"; import { z } from "zod/v4"; @@ -6,9 +10,11 @@ import { z } from "zod/v4"; export const ReactImage = createReactBlockSpec( { type: "reactImage" as const, - propSchema: defaultProps.extend({ - src: z.string().default("https://via.placeholder.com/1000"), - }), + propSchema: createPropSchemaFromZod( + defaultZodPropSchema.extend({ + src: z.string().default("https://via.placeholder.com/1000"), + }), + ), content: "inline" as const, }, { diff --git a/tests/src/utils/customblocks/Separator.tsx b/tests/src/utils/customblocks/Separator.tsx index f1a9c6d229..67ef57f043 100644 --- a/tests/src/utils/customblocks/Separator.tsx +++ b/tests/src/utils/customblocks/Separator.tsx @@ -1,4 +1,8 @@ -import { BlockNoteEditor, addNodeAndExtensionsToSpec } from "@blocknote/core"; +import { + BlockNoteEditor, + addNodeAndExtensionsToSpec, + createPropSchemaFromZod, +} from "@blocknote/core"; import { z } from "zod/v4"; import { RiSeparator } from "react-icons/ri"; @@ -6,7 +10,7 @@ import { RiSeparator } from "react-icons/ri"; export const Separator = addNodeAndExtensionsToSpec( { type: "separator" as const, - propSchema: z.object({}), + propSchema: createPropSchemaFromZod(z.object({})), content: "none", }, { From c37934c3f44300fea53a53f9518c20ca7838877f Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 29 Oct 2025 13:02:17 +0100 Subject: [PATCH 09/21] fix --- packages/xl-ai/src/util/emptyBlock.ts | 4 +- .../src/unit/core/typeGuards/runTests.test.ts | 136 +++++++++++------- 2 files changed, 87 insertions(+), 53 deletions(-) diff --git a/packages/xl-ai/src/util/emptyBlock.ts b/packages/xl-ai/src/util/emptyBlock.ts index 1b4b0fc15b..1a53c855b1 100644 --- a/packages/xl-ai/src/util/emptyBlock.ts +++ b/packages/xl-ai/src/util/emptyBlock.ts @@ -1,6 +1,6 @@ -import type { PartialBlock } from "@blocknote/core"; +import type { Block } from "@blocknote/core"; -export function isEmptyParagraph(block: PartialBlock) { +export function isEmptyParagraph(block: Block) { return ( ((block.type === "paragraph" || !block.type) && !block.content) || (Array.isArray(block.content) && block.content.length === 0) diff --git a/tests/src/unit/core/typeGuards/runTests.test.ts b/tests/src/unit/core/typeGuards/runTests.test.ts index b69b752893..f61f57da7a 100644 --- a/tests/src/unit/core/typeGuards/runTests.test.ts +++ b/tests/src/unit/core/typeGuards/runTests.test.ts @@ -1,4 +1,8 @@ -import { BlockNoteEditor, editorHasBlockWithType } from "@blocknote/core"; +import { + BlockNoteEditor, + createPropSchemaFromZod, + editorHasBlockWithType, +} from "@blocknote/core"; import { describe, expect, it } from "vitest"; import * as z from "zod/v4"; import { createTestEditor } from "../createTestEditor.js"; @@ -26,10 +30,12 @@ describe("Editor block schema type guard tests", () => { editorHasBlockWithType( getEditor(), "heading", - z.object({ - level: z.number(), - textColor: z.string(), - }), + createPropSchemaFromZod( + z.object({ + level: z.number(), + textColor: z.string(), + }), + ), ), ).toBeTruthy(); }); @@ -39,10 +45,12 @@ describe("Editor block schema type guard tests", () => { editorHasBlockWithType( getEditor(), "heading", - z.object({ - level: z.number(), - textColor: z.number(), - }), + createPropSchemaFromZod( + z.object({ + level: z.number(), + textColor: z.number(), + }), + ), ), ).toBeFalsy(); }); @@ -52,10 +60,14 @@ describe("Editor block schema type guard tests", () => { editorHasBlockWithType( getEditor(), "heading", - z.object({ - level: z.union([z.literal(1), z.literal(2), z.literal(3)]).default(1), - textColor: z.string().default("default"), - }), + createPropSchemaFromZod( + z.object({ + level: z + .union([z.literal(1), z.literal(2), z.literal(3)]) + .default(1), + textColor: z.string().default("default"), + }), + ), ), ).toBeTruthy(); }); @@ -65,10 +77,14 @@ describe("Editor block schema type guard tests", () => { editorHasBlockWithType( getEditor(), "heading", - z.object({ - level: z.union([z.literal(1), z.literal(2), z.literal(3)]).default(1), - textColor: z.number().default(1), - }), + createPropSchemaFromZod( + z.object({ + level: z + .union([z.literal(1), z.literal(2), z.literal(3)]) + .default(1), + textColor: z.number().default(1), + }), + ), ), ).toBeFalsy(); }); @@ -78,10 +94,14 @@ describe("Editor block schema type guard tests", () => { editorHasBlockWithType( getEditor(), "heading", - z.object({ - level: z.union([z.literal(1), z.literal(2), z.literal(3)]).default(1), - textColor: z.string().default("default"), - }), + createPropSchemaFromZod( + z.object({ + level: z + .union([z.literal(1), z.literal(2), z.literal(3)]) + .default(1), + textColor: z.string().default("default"), + }), + ), ), ).toBeFalsy(); }); @@ -91,10 +111,12 @@ describe("Editor block schema type guard tests", () => { editorHasBlockWithType( getEditor(), "numberedListItem", - z.object({ - start: z.number(), - textColor: z.string(), - }), + createPropSchemaFromZod( + z.object({ + start: z.number(), + textColor: z.string(), + }), + ), ), ).toBeTruthy(); }); @@ -104,10 +126,12 @@ describe("Editor block schema type guard tests", () => { editorHasBlockWithType( getEditor(), "numberedListItem", - z.object({ - start: z.string(), - textColor: z.string(), - }), + createPropSchemaFromZod( + z.object({ + start: z.string(), + textColor: z.string(), + }), + ), ), ).toBeFalsy(); }); @@ -121,10 +145,12 @@ describe("Editor block schema type guard tests", () => { editorHasBlockWithType( getEditor(), "simpleImage", - z.object({ - name: z.string(), - url: z.string(), - }), + createPropSchemaFromZod( + z.object({ + name: z.string(), + url: z.string(), + }), + ), ), ).toBeTruthy(); }); @@ -134,10 +160,12 @@ describe("Editor block schema type guard tests", () => { editorHasBlockWithType( getEditor(), "simpleImage", - z.object({ - name: z.string(), - url: z.number(), - }), + createPropSchemaFromZod( + z.object({ + name: z.string(), + url: z.number(), + }), + ), ), ).toBeFalsy(); }); @@ -147,10 +175,12 @@ describe("Editor block schema type guard tests", () => { editorHasBlockWithType( getEditor(), "simpleImage", - z.object({ - name: z.string().default(""), - url: z.string().default(""), - }), + createPropSchemaFromZod( + z.object({ + name: z.string().default(""), + url: z.string().default(""), + }), + ), ), ).toBeTruthy(); }); @@ -160,10 +190,12 @@ describe("Editor block schema type guard tests", () => { editorHasBlockWithType( getEditor(), "simpleImage", - z.object({ - name: z.boolean().default(false), - url: z.string().default(""), - }), + createPropSchemaFromZod( + z.object({ + name: z.boolean().default(false), + url: z.string().default(""), + }), + ), ), ).toBeFalsy(); }); @@ -173,12 +205,14 @@ describe("Editor block schema type guard tests", () => { editorHasBlockWithType( getEditor(), "simpleImage", - z.object({ - name: z - .union([z.literal("image"), z.literal("photo")]) - .default("photo"), - url: z.string().default(""), - }), + createPropSchemaFromZod( + z.object({ + name: z + .union([z.literal("image"), z.literal("photo")]) + .default("photo"), + url: z.string().default(""), + }), + ), ), ).toBeFalsy(); }); From 0920bf927b1593294f11a58f948664e9938c1901 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 29 Oct 2025 13:21:18 +0100 Subject: [PATCH 10/21] address feedback --- .../commands/insertBlocks/insertBlocks.ts | 5 ++--- .../commands/replaceBlocks/replaceBlocks.ts | 4 ++-- .../commands/updateBlock/updateBlock.ts | 8 ++++---- .../core/src/api/nodeConversions/blockToNode.ts | 1 + packages/core/src/editor/BlockNoteEditor.ts | 4 ++-- .../TableHandles/TableHandlesPlugin.ts | 9 +-------- packages/core/src/schema/index.ts | 2 +- .../core/src/schema/inlineContent/createSpec.ts | 2 +- ...ockToFullBlock.ts => partialBlockToBlock.ts} | 17 +++++++---------- .../src/context/ServerBlockNoteEditor.ts | 4 ++-- .../src/context/react/ReactServer.test.tsx | 6 +++--- .../src/docx/docxExporter.test.ts | 4 ++-- .../src/test/conversions/htmlConversion.test.ts | 4 ++-- .../src/test/conversions/nodeConversion.test.ts | 4 ++-- .../xl-odt-exporter/src/odt/odtExporter.test.ts | 4 ++-- .../src/pdf/pdfExporter.test.tsx | 4 ++-- shared/testDocument.ts | 4 ++-- .../export/exportTestExecutors.ts | 12 ++++++------ .../exportParseEqualityTestExecutors.ts | 8 ++++---- 19 files changed, 48 insertions(+), 58 deletions(-) rename packages/core/src/schema/{partialBlockToFullBlock.ts => partialBlockToBlock.ts} (93%) diff --git a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts index d5601458a9..1423240a52 100644 --- a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts @@ -6,7 +6,7 @@ import { BlockIdentifier, BlockSchema, InlineContentSchema, - partialBlockToFullBlock, + partialBlockToBlock, StyleSchema, } from "../../../../schema/index.js"; import { blockToNode } from "../../../nodeConversions/blockToNode.js"; @@ -20,7 +20,6 @@ export function insertBlocks< S extends StyleSchema, >( tr: Transaction, - // TBD: allow PartialBlock here? blocksToInsert: PartialBlock[], referenceBlock: BlockIdentifier, placement: "before" | "after" = "before", @@ -30,7 +29,7 @@ export function insertBlocks< const pmSchema = getPmSchema(tr); const nodesToInsert = blocksToInsert.map((block) => blockToNode( - partialBlockToFullBlock(getBlockNoteSchema(pmSchema), block), + partialBlockToBlock(getBlockNoteSchema(pmSchema), block), pmSchema, ), ); diff --git a/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts index e2f8150792..e3a8972b2d 100644 --- a/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts @@ -2,7 +2,7 @@ import type { Node } from "prosemirror-model"; import type { Transaction } from "prosemirror-state"; import type { Block, PartialBlock } from "../../../../blocks/defaultBlocks.js"; import { - partialBlockToFullBlock, + partialBlockToBlock, type BlockIdentifier, type BlockSchema, type InlineContentSchema, @@ -30,7 +30,7 @@ export function removeAndInsertBlocks< // document. const nodesToInsert: Node[] = blocksToInsert.map((block) => blockToNode( - partialBlockToFullBlock(getBlockNoteSchema(pmSchema), block), + partialBlockToBlock(getBlockNoteSchema(pmSchema), block), pmSchema, ), ); diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts index 5d3b94d0fe..3991f2a2bb 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts @@ -15,10 +15,10 @@ import type { } from "../../../../schema/blocks/types.js"; import type { InlineContentSchema } from "../../../../schema/inlineContent/types.js"; import { - partialBlockToFullBlock, + partialBlockToBlock, partialInlineContentToInlineContent, partialTableContentToTableContent, -} from "../../../../schema/partialBlockToFullBlock.js"; +} from "../../../../schema/partialBlockToBlock.js"; import type { StyleSchema } from "../../../../schema/styles/types.js"; import { UnreachableCaseError } from "../../../../util/typescript.js"; import { @@ -132,7 +132,7 @@ export function updateBlockTr< // currently, we calculate the new node and replace the entire node with the desired new node. // for this, we do a nodeToBlock on the existing block to get the children. // it would be cleaner to use a ReplaceAroundStep, but this is a bit simpler and it's quite an edge case - const newFullBlock = partialBlockToFullBlock(schema, block); + const newFullBlock = partialBlockToBlock(schema, block); if (block.children === undefined) { // if no children are passed in, use existing children const existingBlock = nodeToBlock( @@ -300,7 +300,7 @@ function updateChildren< const schema = getBlockNoteSchema(pmSchema); if (block.children !== undefined && block.children.length > 0) { const childNodes = block.children.map((child) => { - return blockToNode(partialBlockToFullBlock(schema, child), pmSchema); + return blockToNode(partialBlockToBlock(schema, child), pmSchema); }); // Checks if a blockGroup node already exists. diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index 58f97b2c60..76c688f487 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -282,6 +282,7 @@ function blockOrInlineContentToContentNode( // TODO: needed? came from previous code if (type === undefined) { + // TODO: remove type = "paragraph"; } diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index af3f7621aa..954a50b6dc 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -38,7 +38,7 @@ import { UniqueID } from "../extensions/UniqueID/UniqueID.js"; import type { Dictionary } from "../i18n/dictionary.js"; import { en } from "../i18n/locales/index.js"; import { - partialBlockToFullBlock, + partialBlockToBlock, type BlockIdentifier, type BlockNoteDOMAttributes, type BlockSchema, @@ -891,7 +891,7 @@ export class BlockNoteEditor< const schema = getSchema(tiptapOptions.extensions!); const pmNodes = initialContent.map((b) => blockToNode( - partialBlockToFullBlock(this.schema, b), + partialBlockToBlock(this.schema, b), schema, this.schema.styleSchema, ).toJSON(), diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 02f652a0ed..589eefcead 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -282,14 +282,7 @@ export class TableHandlesView< this.editor.schema.styleSchema, ); - if ( - blockHasType( - block, - this.editor, - "table", - defaultBlockSpecs.table.config.propSchema, - ) - ) { + if (blockHasType(block, this.editor, "table")) { this.tablePos = pmNodeInfo.posBeforeNode + 1; tableBlock = block; } diff --git a/packages/core/src/schema/index.ts b/packages/core/src/schema/index.ts index 8b2e9b7ad3..f3cf43f95f 100644 --- a/packages/core/src/schema/index.ts +++ b/packages/core/src/schema/index.ts @@ -6,7 +6,7 @@ export * from "./CustomBlockNoteSchema.js"; export * from "./inlineContent/createSpec.js"; export * from "./inlineContent/internal.js"; export * from "./inlineContent/types.js"; -export * from "./partialBlockToFullBlock.js"; +export * from "./partialBlockToBlock.js"; export * from "./propTypes.js"; export * from "./styles/createSpec.js"; export * from "./styles/internal.js"; diff --git a/packages/core/src/schema/inlineContent/createSpec.ts b/packages/core/src/schema/inlineContent/createSpec.ts index 04251a8752..5e3e543ced 100644 --- a/packages/core/src/schema/inlineContent/createSpec.ts +++ b/packages/core/src/schema/inlineContent/createSpec.ts @@ -5,7 +5,7 @@ import { inlineContentToNodes } from "../../api/nodeConversions/blockToNode.js"; import { nodeToCustomInlineContent } from "../../api/nodeConversions/nodeToBlock.js"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; import { propsToAttributes } from "../blocks/internal.js"; -import { partialInlineContentToInlineContent } from "../partialBlockToFullBlock.js"; +import { partialInlineContentToInlineContent } from "../partialBlockToBlock.js"; import { Props } from "../propTypes.js"; import { StyleSchema } from "../styles/types.js"; import { diff --git a/packages/core/src/schema/partialBlockToFullBlock.ts b/packages/core/src/schema/partialBlockToBlock.ts similarity index 93% rename from packages/core/src/schema/partialBlockToFullBlock.ts rename to packages/core/src/schema/partialBlockToBlock.ts index 66903c67dd..28b7dc19b2 100644 --- a/packages/core/src/schema/partialBlockToFullBlock.ts +++ b/packages/core/src/schema/partialBlockToBlock.ts @@ -1,5 +1,5 @@ import * as z from "zod/v4/core"; -import type { Block, BlockNoteSchema, PartialBlock } from "../blocks/index.js"; +import type { Block, PartialBlock } from "../blocks/index.js"; import { UniqueID } from "../extensions/UniqueID/UniqueID.js"; import { mapTableCell } from "../util/table.js"; import { UnreachableCaseError } from "../util/typescript.js"; @@ -204,7 +204,7 @@ function partialBlockContentToBlockContent( } } -export function partialBlockToFullBlock< +export function partialBlockToBlock< BSchema extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema, @@ -229,9 +229,8 @@ export function partialBlockToFullBlock< ); const children = - partialBlock.children?.map((child) => - partialBlockToFullBlock(schema, child), - ) || []; + partialBlock.children?.map((child) => partialBlockToBlock(schema, child)) || + []; return { id, @@ -242,17 +241,15 @@ export function partialBlockToFullBlock< } as Block; } -export function partialBlocksToFullBlocks< +export function partialBlocksToBlocks< BSchema extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema, >( - // TODO: I think this should be a CustomBlockNoteSchema, - // but that breaks docxExporter etc - schema: BlockNoteSchema, + schema: CustomBlockNoteSchema, partialBlocks: PartialBlock[], ): Block[] { return partialBlocks.map((partialBlock) => - partialBlockToFullBlock(schema, partialBlock), + partialBlockToBlock(schema, partialBlock), ); } diff --git a/packages/server-util/src/context/ServerBlockNoteEditor.ts b/packages/server-util/src/context/ServerBlockNoteEditor.ts index f80fb37f8c..8a154820b7 100644 --- a/packages/server-util/src/context/ServerBlockNoteEditor.ts +++ b/packages/server-util/src/context/ServerBlockNoteEditor.ts @@ -15,7 +15,7 @@ import { createExternalHTMLExporter, createInternalHTMLSerializer, nodeToBlock, - partialBlockToFullBlock, + partialBlockToBlock, } from "@blocknote/core"; import { BlockNoteViewRaw } from "@blocknote/react"; @@ -140,7 +140,7 @@ export class ServerBlockNoteEditor< ) { const pmSchema = this.editor.pmSchema; const pmNodes = blocks.map((b) => - blockToNode(partialBlockToFullBlock(this.editor.schema, b), pmSchema), + blockToNode(partialBlockToBlock(this.editor.schema, b), pmSchema), ); const doc = pmSchema.topNodeType.create( diff --git a/packages/server-util/src/context/react/ReactServer.test.tsx b/packages/server-util/src/context/react/ReactServer.test.tsx index d68400311b..25fc7a53b4 100644 --- a/packages/server-util/src/context/react/ReactServer.test.tsx +++ b/packages/server-util/src/context/react/ReactServer.test.tsx @@ -2,7 +2,7 @@ import { BlockNoteSchema, defaultBlockSpecs, defaultProps, - partialBlockToFullBlock, + partialBlockToBlock, } from "@blocknote/core"; import { createReactBlockSpec } from "@blocknote/react"; import { createContext, useContext } from "react"; @@ -57,7 +57,7 @@ describe("Test ServerBlockNoteEditor with React blocks", () => { const editor = ServerBlockNoteEditor.create({ schema, }); - const fullBlock = partialBlockToFullBlock(editor.editor.schema, { + const fullBlock = partialBlockToBlock(editor.editor.schema, { id: "1", type: "simpleReactCustomParagraph", content: "React Custom Paragraph", @@ -76,7 +76,7 @@ describe("Test ServerBlockNoteEditor with React blocks", () => { {children} ), async () => { - const fullBlock = partialBlockToFullBlock(editor.editor.schema, { + const fullBlock = partialBlockToBlock(editor.editor.schema, { id: "1", type: "reactContextParagraph", content: "React Context Paragraph", diff --git a/packages/xl-docx-exporter/src/docx/docxExporter.test.ts b/packages/xl-docx-exporter/src/docx/docxExporter.test.ts index 78a91e0bd0..f2deb5038f 100644 --- a/packages/xl-docx-exporter/src/docx/docxExporter.test.ts +++ b/packages/xl-docx-exporter/src/docx/docxExporter.test.ts @@ -2,7 +2,7 @@ import { BlockNoteSchema, createPageBreakBlockSpec, defaultBlockSpecs, - partialBlocksToFullBlocks, + partialBlocksToBlocks, } from "@blocknote/core"; import { ColumnBlock, ColumnListBlock } from "@blocknote/xl-multi-column"; import { testDocument } from "@shared/testDocument.js"; @@ -140,7 +140,7 @@ describe("exporter", () => { }, }); const exporter = new DOCXExporter(schema, docxDefaultSchemaMappings); - const blocks = partialBlocksToFullBlocks(schema, [ + const blocks = partialBlocksToBlocks(schema, [ { type: "columnList", children: [ diff --git a/packages/xl-multi-column/src/test/conversions/htmlConversion.test.ts b/packages/xl-multi-column/src/test/conversions/htmlConversion.test.ts index 58b0844d7a..45ff05fde5 100644 --- a/packages/xl-multi-column/src/test/conversions/htmlConversion.test.ts +++ b/packages/xl-multi-column/src/test/conversions/htmlConversion.test.ts @@ -8,7 +8,7 @@ import { StyleSchema, createExternalHTMLExporter, createInternalHTMLSerializer, - partialBlocksToFullBlocks, + partialBlocksToBlocks, } from "@blocknote/core"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; @@ -25,7 +25,7 @@ async function convertToHTMLAndCompareSnapshots< snapshotDirectory: string, snapshotName: string, ) { - const fullBlocks = partialBlocksToFullBlocks(editor.schema, blocks); + const fullBlocks = partialBlocksToBlocks(editor.schema, blocks); const serializer = createInternalHTMLSerializer(editor.pmSchema, editor); const internalHTML = serializer.serializeBlocks(fullBlocks, {}); const internalHTMLSnapshotPath = diff --git a/packages/xl-multi-column/src/test/conversions/nodeConversion.test.ts b/packages/xl-multi-column/src/test/conversions/nodeConversion.test.ts index 139fb880b0..577d9d73c4 100644 --- a/packages/xl-multi-column/src/test/conversions/nodeConversion.test.ts +++ b/packages/xl-multi-column/src/test/conversions/nodeConversion.test.ts @@ -5,7 +5,7 @@ import { PartialBlock, blockToNode, nodeToBlock, - partialBlockToFullBlock, + partialBlockToBlock, } from "@blocknote/core"; import { multiColumnSchemaTestCases } from "./testCases.js"; @@ -14,7 +14,7 @@ function validateConversion( block: PartialBlock, editor: BlockNoteEditor, ) { - const fullBlock = partialBlockToFullBlock(editor.schema, block); + const fullBlock = partialBlockToBlock(editor.schema, block); const node = blockToNode(fullBlock, editor.pmSchema); expect(node).toMatchSnapshot(); diff --git a/packages/xl-odt-exporter/src/odt/odtExporter.test.ts b/packages/xl-odt-exporter/src/odt/odtExporter.test.ts index 98c6b67466..f2e263923d 100644 --- a/packages/xl-odt-exporter/src/odt/odtExporter.test.ts +++ b/packages/xl-odt-exporter/src/odt/odtExporter.test.ts @@ -2,8 +2,8 @@ import { BlockNoteSchema, createPageBreakBlockSpec, defaultBlockSpecs, + partialBlocksToBlocks, } from "@blocknote/core"; -import { partialBlocksToFullBlocks } from "@blocknote/core/src/schema/partialBlockToFullBlock.js"; import { ColumnBlock, ColumnListBlock } from "@blocknote/xl-multi-column"; import { testDocument } from "@shared/testDocument.js"; import { BlobReader, TextWriter, ZipReader } from "@zip.js/zip.js"; @@ -78,7 +78,7 @@ describe("exporter", () => { }); const exporter = new ODTExporter(schema, odtDefaultSchemaMappings); const odt = await exporter.toODTDocument( - partialBlocksToFullBlocks(schema, [ + partialBlocksToBlocks(schema, [ { type: "columnList", children: [ diff --git a/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx b/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx index 1f3d375ad0..ece5d8c11c 100644 --- a/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx +++ b/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx @@ -7,7 +7,7 @@ import { defaultBlockSpecs, defaultInlineContentSpecs, defaultStyleSpecs, - partialBlocksToFullBlocks, + partialBlocksToBlocks, } from "@blocknote/core"; import { ColumnBlock, ColumnListBlock } from "@blocknote/xl-multi-column"; import { Text } from "@react-pdf/renderer"; @@ -236,7 +236,7 @@ describe("exporter", () => { }); const exporter = new PDFExporter(schema, pdfDefaultSchemaMappings); const transformed = await exporter.toReactPDFDocument( - partialBlocksToFullBlocks(schema, [ + partialBlocksToBlocks(schema, [ { type: "columnList", children: [ diff --git a/shared/testDocument.ts b/shared/testDocument.ts index f8dfe924d3..c34393ad38 100644 --- a/shared/testDocument.ts +++ b/shared/testDocument.ts @@ -2,7 +2,7 @@ import { BlockNoteSchema, createPageBreakBlockSpec, defaultBlockSpecs, - partialBlocksToFullBlocks, + partialBlocksToBlocks, } from "@blocknote/core"; import * as z from "zod/v4"; @@ -10,7 +10,7 @@ import * as z from "zod/v4"; const y = z; // needed to fix build // TODO: Update tests that use this to the new format and remove -export const testDocument = partialBlocksToFullBlocks( +export const testDocument = partialBlocksToBlocks( BlockNoteSchema.create({ blockSpecs: { ...defaultBlockSpecs, pageBreak: createPageBreakBlockSpec() }, }), diff --git a/tests/src/unit/shared/formatConversion/export/exportTestExecutors.ts b/tests/src/unit/shared/formatConversion/export/exportTestExecutors.ts index f590bfb198..ab0daa21cf 100644 --- a/tests/src/unit/shared/formatConversion/export/exportTestExecutors.ts +++ b/tests/src/unit/shared/formatConversion/export/exportTestExecutors.ts @@ -3,8 +3,8 @@ import { BlockSchema, blockToNode, InlineContentSchema, - partialBlocksToFullBlocks, - partialBlockToFullBlock, + partialBlocksToBlocks, + partialBlockToBlock, StyleSchema, } from "@blocknote/core"; import { prettify } from "htmlfy"; @@ -25,7 +25,7 @@ export const testExportBlockNoteHTML = async < await expect( prettify( await editor.blocksToFullHTML( - partialBlocksToFullBlocks(editor.schema, testCase.content), + partialBlocksToBlocks(editor.schema, testCase.content), ), { tag_wrap: true, @@ -47,7 +47,7 @@ export const testExportHTML = async < await expect( prettify( await editor.blocksToHTMLLossy( - partialBlocksToFullBlocks(editor.schema, testCase.content), + partialBlocksToBlocks(editor.schema, testCase.content), ), { tag_wrap: true, @@ -68,7 +68,7 @@ export const testExportMarkdown = async < await expect( await editor.blocksToMarkdownLossy( - partialBlocksToFullBlocks(editor.schema, testCase.content), + partialBlocksToBlocks(editor.schema, testCase.content), ), ).toMatchFileSnapshot(`./__snapshots__/markdown/${testCase.name}.md`); }; @@ -86,7 +86,7 @@ export const testExportNodes = async < await expect( testCase.content.map((block) => blockToNode( - partialBlockToFullBlock(editor.schema, block), + partialBlockToBlock(editor.schema, block), editor.pmSchema, editor.schema.styleSchema, ), diff --git a/tests/src/unit/shared/formatConversion/exportParseEquality/exportParseEqualityTestExecutors.ts b/tests/src/unit/shared/formatConversion/exportParseEquality/exportParseEqualityTestExecutors.ts index 52cf6376bc..c8637d4848 100644 --- a/tests/src/unit/shared/formatConversion/exportParseEquality/exportParseEqualityTestExecutors.ts +++ b/tests/src/unit/shared/formatConversion/exportParseEquality/exportParseEqualityTestExecutors.ts @@ -4,7 +4,7 @@ import { blockToNode, InlineContentSchema, nodeToBlock, - partialBlocksToFullBlocks, + partialBlocksToBlocks, StyleSchema, } from "@blocknote/core"; import { expect } from "vitest"; @@ -20,7 +20,7 @@ export const testExportParseEqualityBlockNoteHTML = async < testCase: ExportParseEqualityTestCase, ) => { (window as any).__TEST_OPTIONS.mockID = 0; - const fullBlocks = partialBlocksToFullBlocks(editor.schema, testCase.content); + const fullBlocks = partialBlocksToBlocks(editor.schema, testCase.content); const exported = await editor.blocksToFullHTML(fullBlocks); @@ -46,7 +46,7 @@ export const testExportParseEqualityHTML = async < ) => { (window as any).__TEST_OPTIONS.mockID = 0; - const fullBlocks = partialBlocksToFullBlocks(editor.schema, testCase.content); + const fullBlocks = partialBlocksToBlocks(editor.schema, testCase.content); const exported = await editor.blocksToHTMLLossy(fullBlocks); @@ -67,7 +67,7 @@ export const testExportParseEqualityNodes = async < ) => { (window as any).__TEST_OPTIONS.mockID = 0; - const fullBlocks = partialBlocksToFullBlocks(editor.schema, testCase.content); + const fullBlocks = partialBlocksToBlocks(editor.schema, testCase.content); const exported = fullBlocks.map((block) => blockToNode(block, editor.pmSchema), From d15beccb3ba3221a790bf10f44455c3337585bb2 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 29 Oct 2025 13:48:46 +0100 Subject: [PATCH 11/21] merge and build --- .../commands/insertBlocks/insertBlocks.ts | 2 +- .../commands/replaceBlocks/replaceBlocks.ts | 2 +- .../src/api/nodeConversions/blockToNode.ts | 11 ++++++--- packages/core/src/api/pmUtil.ts | 2 +- .../core/src/schema/partialBlockToBlock.ts | 24 +++++++++++-------- 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts index 1423240a52..cbd2a9a960 100644 --- a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts @@ -29,7 +29,7 @@ export function insertBlocks< const pmSchema = getPmSchema(tr); const nodesToInsert = blocksToInsert.map((block) => blockToNode( - partialBlockToBlock(getBlockNoteSchema(pmSchema), block), + partialBlockToBlock(getBlockNoteSchema(pmSchema), block), pmSchema, ), ); diff --git a/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts index e3a8972b2d..00eec9070f 100644 --- a/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts @@ -30,7 +30,7 @@ export function removeAndInsertBlocks< // document. const nodesToInsert: Node[] = blocksToInsert.map((block) => blockToNode( - partialBlockToBlock(getBlockNoteSchema(pmSchema), block), + partialBlockToBlock(getBlockNoteSchema(pmSchema), block), pmSchema, ), ); diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index ca3394ba7d..c1d0a95c89 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -1,6 +1,7 @@ import { Attrs, Fragment, Mark, Node, Schema } from "@tiptap/pm/model"; import type { + BlockSchema, CustomInlineContentFromConfig, InlineContent, InlineContentSchema, @@ -320,10 +321,14 @@ function blockOrInlineContentToContentNode( /** * Converts a BlockNote block to a Prosemirror node. */ -export function blockToNode( - block: Block, +export function blockToNode< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema, +>( + block: Block, schema: Schema, - styleSchema: StyleSchema = getStyleSchema(schema), + styleSchema: S = getStyleSchema(schema), ) { const children: Node[] = []; diff --git a/packages/core/src/api/pmUtil.ts b/packages/core/src/api/pmUtil.ts index 8a840f2117..0f899acd50 100644 --- a/packages/core/src/api/pmUtil.ts +++ b/packages/core/src/api/pmUtil.ts @@ -1,8 +1,8 @@ import type { Node, Schema } from "prosemirror-model"; import { Transform } from "prosemirror-transform"; import type { BlockNoteEditor } from "../editor/BlockNoteEditor.js"; -import { CustomBlockNoteSchema } from "../schema/schema.js"; import type { BlockSchema } from "../schema/blocks/types.js"; +import { CustomBlockNoteSchema } from "../schema/CustomBlockNoteSchema.js"; import type { InlineContentSchema } from "../schema/inlineContent/types.js"; import type { StyleSchema } from "../schema/styles/types.js"; diff --git a/packages/core/src/schema/partialBlockToBlock.ts b/packages/core/src/schema/partialBlockToBlock.ts index 28b7dc19b2..aba2c7e62d 100644 --- a/packages/core/src/schema/partialBlockToBlock.ts +++ b/packages/core/src/schema/partialBlockToBlock.ts @@ -31,16 +31,18 @@ function partialPropsToProps( ): Props { const props: Props = partialProps || {}; - Object.entries(propSchema._zod.def.shape).forEach(([propKey, propValue]) => { - if (props[propKey] === undefined) { - if (propValue instanceof z.$ZodDefault) { - props[propKey] = propValue._zod.def.defaultValue; + Object.entries(propSchema._zodSource._zod.def.shape).forEach( + ([propKey, propValue]) => { + if (props[propKey] === undefined) { + if (propValue instanceof z.$ZodDefault) { + props[propKey] = propValue._zod.def.defaultValue; + } + if (propValue instanceof z.$ZodOptional) { + props[propKey] = undefined; + } } - if (propValue instanceof z.$ZodOptional) { - props[propKey] = undefined; - } - } - }); + }, + ); return props; } @@ -64,7 +66,9 @@ function partialLinkToLink(partialLink: PartialLink): Link { } export function partialInlineContentToInlineContent( - partialInlineContent: PartialInlineContent | undefined, + partialInlineContent: + | PartialInlineContent + | undefined, inlineContentSchema: InlineContentSchema, ): InlineContent[] { if (partialInlineContent === undefined) { From b028a1bc3911dc51e81a45a6b0fb02255dcc0b62 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 29 Oct 2025 14:28:23 +0100 Subject: [PATCH 12/21] fix build --- packages/core/src/schema/blocks/types.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts index f27ee2d303..afa252e0bf 100644 --- a/packages/core/src/schema/blocks/types.ts +++ b/packages/core/src/schema/blocks/types.ts @@ -313,11 +313,7 @@ export type PartialBlockNoDefaults< BSchema extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema, -> = PartialBlocksWithoutChildren< - BSchema, - I, - S ->[keyof PartialBlocksWithoutChildren] & +> = PartialBlocksWithoutChildren[keyof BSchema] & Partial<{ children: PartialBlockNoDefaults[]; }>; From 6ffa70e49f7ae8399ce7ce429ca38f4bedbf70eb Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 29 Oct 2025 14:28:31 +0100 Subject: [PATCH 13/21] fix build --- .../11-uppy-file-panel/src/FileReplaceButton.tsx | 12 +++++++----- packages/core/src/schema/propTypes.ts | 2 +- packages/xl-ai/src/util/emptyBlock.ts | 4 ++-- packages/xl-multi-column/src/blocks/Columns/index.ts | 8 ++++---- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/examples/03-ui-components/11-uppy-file-panel/src/FileReplaceButton.tsx b/examples/03-ui-components/11-uppy-file-panel/src/FileReplaceButton.tsx index 5b7f2199e0..d1c4e2c008 100644 --- a/examples/03-ui-components/11-uppy-file-panel/src/FileReplaceButton.tsx +++ b/examples/03-ui-components/11-uppy-file-panel/src/FileReplaceButton.tsx @@ -1,10 +1,11 @@ import { - baseFilePropSchema, + baseFileZodPropSchema, blockHasType, BlockSchema, InlineContentSchema, - optionalFileProps, + optionalFileZodPropSchema, StyleSchema, + createPropSchemaFromZod } from "@blocknote/core"; import { useBlockNoteEditor, @@ -47,9 +48,10 @@ export const FileReplaceButton = () => { block, editor, block.type, - baseFilePropSchema.extend({ - ...optionalFileProps.pick({ url: true }).shape, - }), + // TODO + createPropSchemaFromZod(baseFileZodPropSchema.extend({ + ...optionalFileZodPropSchema.pick({ url: true }).shape, + })), ) || !editor.isEditable ) { diff --git a/packages/core/src/schema/propTypes.ts b/packages/core/src/schema/propTypes.ts index 8a0ebd3425..7082a7bf55 100644 --- a/packages/core/src/schema/propTypes.ts +++ b/packages/core/src/schema/propTypes.ts @@ -13,7 +13,7 @@ export type PropSchema = { // Props type is derived from the Zod schema output export type Props> = - PSchema extends PropSchema ? O : never; + PSchema extends PropSchema ? O : any; // We infer Output/Input from the provided schema using z.output / z.input export function createPropSchemaFromZod(schema: S) { diff --git a/packages/xl-ai/src/util/emptyBlock.ts b/packages/xl-ai/src/util/emptyBlock.ts index 1a53c855b1..1b4b0fc15b 100644 --- a/packages/xl-ai/src/util/emptyBlock.ts +++ b/packages/xl-ai/src/util/emptyBlock.ts @@ -1,6 +1,6 @@ -import type { Block } from "@blocknote/core"; +import type { PartialBlock } from "@blocknote/core"; -export function isEmptyParagraph(block: Block) { +export function isEmptyParagraph(block: PartialBlock) { return ( ((block.type === "paragraph" || !block.type) && !block.content) || (Array.isArray(block.content) && block.content.length === 0) diff --git a/packages/xl-multi-column/src/blocks/Columns/index.ts b/packages/xl-multi-column/src/blocks/Columns/index.ts index a5125574b9..4b70e02d98 100644 --- a/packages/xl-multi-column/src/blocks/Columns/index.ts +++ b/packages/xl-multi-column/src/blocks/Columns/index.ts @@ -1,4 +1,4 @@ -import { createBlockSpecFromTiptapNode } from "@blocknote/core"; +import { createBlockSpecFromTiptapNode, createPropSchemaFromZod } from "@blocknote/core"; import * as z from "zod/v4"; import { Column } from "../../pm-nodes/Column.js"; import { ColumnList } from "../../pm-nodes/ColumnList.js"; @@ -9,9 +9,9 @@ export const ColumnBlock = createBlockSpecFromTiptapNode( type: "column", content: "none", }, - z.object({ + createPropSchemaFromZod(z.object({ width: z.number().default(1), - }), + })), ); export const ColumnListBlock = createBlockSpecFromTiptapNode( @@ -20,5 +20,5 @@ export const ColumnListBlock = createBlockSpecFromTiptapNode( type: "columnList", content: "none", }, - z.object({}), + createPropSchemaFromZod(z.object({})), ); From 553932b20a30a165e447987af90d42006e5059dd Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 29 Oct 2025 14:38:11 +0100 Subject: [PATCH 14/21] empty spans added --- .../__snapshots__/reactEmailExporter.test.tsx.snap | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/xl-email-exporter/src/react-email/__snapshots__/reactEmailExporter.test.tsx.snap b/packages/xl-email-exporter/src/react-email/__snapshots__/reactEmailExporter.test.tsx.snap index d4554d6689..206daed181 100644 --- a/packages/xl-email-exporter/src/react-email/__snapshots__/reactEmailExporter.test.tsx.snap +++ b/packages/xl-email-exporter/src/react-email/__snapshots__/reactEmailExporter.test.tsx.snap @@ -1,10 +1,10 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`react email exporter > should export a document (HTML snapshot) > __snapshots__/reactEmailExporter 1`] = `"

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg
Open video file

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio file

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9

const helloWorld = (message) => {

console.log("Hello World", message);

};


"`; +exports[`react email exporter > should export a document (HTML snapshot) > __snapshots__/reactEmailExporter 1`] = `"

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg
Open video file

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio file

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9

const helloWorld = (message) => {

console.log("Hello World", message);

};


"`; -exports[`react email exporter > should export a document with multiple preview lines > __snapshots__/reactEmailExporterWithMultiplePreview 1`] = `"
First preview lineSecond preview line
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg
Open video file

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio file

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9

const helloWorld = (message) => {

console.log("Hello World", message);

};


"`; +exports[`react email exporter > should export a document with multiple preview lines > __snapshots__/reactEmailExporterWithMultiplePreview 1`] = `"
First preview lineSecond preview line
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg
Open video file

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio file

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9

const helloWorld = (message) => {

console.log("Hello World", message);

};


"`; -exports[`react email exporter > should export a document with preview > __snapshots__/reactEmailExporterWithPreview 1`] = `"
This is a preview of the email content
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg
Open video file

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio file

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9

const helloWorld = (message) => {

console.log("Hello World", message);

};


"`; +exports[`react email exporter > should export a document with preview > __snapshots__/reactEmailExporterWithPreview 1`] = `"
This is a preview of the email content
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg
Open video file

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio file

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9

const helloWorld = (message) => {

console.log("Hello World", message);

};


"`; exports[`react email exporter > should handle document with background colors > __snapshots__/reactEmailExporterBackgroundColor 1`] = `"

Text with background color

"`; From cbda9e770f9549e37ddc652ad39a54d06809a5f5 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 29 Oct 2025 14:50:11 +0100 Subject: [PATCH 15/21] fix odt test --- .../src/odt/__snapshots__/basic/content.xml | 4 +-- .../withCustomOptions/content.xml | 4 +-- .../__snapshots__/withMultiColumn/content.xml | 2 +- shared/testDocument.ts | 35 +++++++++++++++++-- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/packages/xl-odt-exporter/src/odt/__snapshots__/basic/content.xml b/packages/xl-odt-exporter/src/odt/__snapshots__/basic/content.xml index ac2fee74ad..10d61c0558 100644 --- a/packages/xl-odt-exporter/src/odt/__snapshots__/basic/content.xml +++ b/packages/xl-odt-exporter/src/odt/__snapshots__/basic/content.xml @@ -256,7 +256,7 @@ Check List Item - + @@ -381,7 +381,7 @@ Link - + diff --git a/packages/xl-odt-exporter/src/odt/__snapshots__/withCustomOptions/content.xml b/packages/xl-odt-exporter/src/odt/__snapshots__/withCustomOptions/content.xml index a938523f64..155f3104e7 100644 --- a/packages/xl-odt-exporter/src/odt/__snapshots__/withCustomOptions/content.xml +++ b/packages/xl-odt-exporter/src/odt/__snapshots__/withCustomOptions/content.xml @@ -270,7 +270,7 @@ Check List Item - + @@ -395,7 +395,7 @@ Link - + diff --git a/packages/xl-odt-exporter/src/odt/__snapshots__/withMultiColumn/content.xml b/packages/xl-odt-exporter/src/odt/__snapshots__/withMultiColumn/content.xml index fd41c45fa7..e78f92c755 100644 --- a/packages/xl-odt-exporter/src/odt/__snapshots__/withMultiColumn/content.xml +++ b/packages/xl-odt-exporter/src/odt/__snapshots__/withMultiColumn/content.xml @@ -43,7 +43,7 @@ - + diff --git a/shared/testDocument.ts b/shared/testDocument.ts index c34393ad38..33cad08fe6 100644 --- a/shared/testDocument.ts +++ b/shared/testDocument.ts @@ -16,6 +16,7 @@ export const testDocument = partialBlocksToBlocks( }), [ { + id: "test1", type: "paragraph", content: [ { @@ -36,10 +37,12 @@ export const testDocument = partialBlocksToBlocks( ], children: [ { + id: "test2", type: "paragraph", content: "Hello World nested", children: [ { + id: "test2child", type: "paragraph", content: "Hello World double nested", }, @@ -48,6 +51,7 @@ export const testDocument = partialBlocksToBlocks( ], }, { + id: "test3", type: "paragraph", content: [ { @@ -61,14 +65,17 @@ export const testDocument = partialBlocksToBlocks( }, }, { + id: "test4", type: "paragraph", content: "Paragraph", }, { + id: "test5", type: "heading", content: "Heading", }, { + id: "test6", type: "heading", content: "Heading right", props: { @@ -76,6 +83,7 @@ export const testDocument = partialBlocksToBlocks( }, }, { + id: "test7", type: "paragraph", content: "justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", @@ -84,18 +92,21 @@ export const testDocument = partialBlocksToBlocks( textAlignment: "justify", }, }, - { type: "pageBreak" }, + { id: "test8", type: "pageBreak" }, { + id: "test9", type: "bulletListItem", content: "Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", children: [ { + id: "test10", type: "bulletListItem", content: "Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", }, { + id: "test11", type: "bulletListItem", content: "Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", @@ -104,22 +115,27 @@ export const testDocument = partialBlocksToBlocks( }, }, { + id: "test12", type: "numberedListItem", content: "Numbered List Item 1", }, { + id: "test13", type: "numberedListItem", content: "Numbered List Item 2", children: [ { + id: "test14", type: "numberedListItem", content: "Numbered List Item Nested 1", }, { + id: "test15", type: "numberedListItem", content: "Numbered List Item Nested 2", }, { + id: "test16", type: "numberedListItem", content: "Numbered List Item Nested funky right", props: { @@ -129,6 +145,7 @@ export const testDocument = partialBlocksToBlocks( }, }, { + id: "test17", type: "numberedListItem", content: "Numbered List Item Nested funky center", props: { @@ -142,14 +159,17 @@ export const testDocument = partialBlocksToBlocks( ], }, { + id: "test18", type: "numberedListItem", content: "Numbered List Item", }, { + id: "test19", type: "checkListItem", content: "Check List Item", }, { + id: "test20", type: "table", content: { type: "tableContent", @@ -168,9 +188,11 @@ export const testDocument = partialBlocksToBlocks( }, }, { + id: "test21", type: "file", }, { + id: "test22", type: "image", props: { url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", @@ -179,6 +201,7 @@ export const testDocument = partialBlocksToBlocks( }, }, { + id: "test23", type: "image", props: { previewWidth: 200, @@ -187,6 +210,7 @@ export const testDocument = partialBlocksToBlocks( }, }, { + id: "test24", type: "video", props: { url: "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm", @@ -195,6 +219,7 @@ export const testDocument = partialBlocksToBlocks( }, }, { + id: "test25", type: "audio", props: { url: "https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3", @@ -203,9 +228,11 @@ export const testDocument = partialBlocksToBlocks( }, }, { + id: "test26", type: "paragraph", }, { + id: "test27", type: "audio", props: { caption: "Audio file caption", @@ -213,6 +240,7 @@ export const testDocument = partialBlocksToBlocks( }, }, { + id: "test28", type: "paragraph", content: [ { @@ -223,6 +251,7 @@ export const testDocument = partialBlocksToBlocks( ], }, { + id: "test29", type: "paragraph", content: [ { @@ -248,6 +277,7 @@ export const testDocument = partialBlocksToBlocks( ], }, { + id: "test30", type: "table", content: { type: "tableContent", @@ -277,6 +307,7 @@ export const testDocument = partialBlocksToBlocks( }, }, { + id: "test31", type: "codeBlock", props: { language: "javascript", @@ -285,6 +316,6 @@ export const testDocument = partialBlocksToBlocks( console.log("Hello World", message); };`, }, - { type: "divider" }, + { id: "test32", type: "divider" }, ], ); From 0dcd4f81b0b690f15ca8c892b21d9a5e8ac4fb16 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 29 Oct 2025 14:51:26 +0100 Subject: [PATCH 16/21] fix tests --- .../src/pdf/__snapshots__/example.jsx | 68 ++++++++++--------- .../exampleWithHeaderAndFooter.jsx | 68 ++++++++++--------- .../__snapshots__/exampleWithMultiColumn.jsx | 12 ++-- 3 files changed, 76 insertions(+), 72 deletions(-) diff --git a/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx b/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx index bf68e3e9af..1c894f1f97 100644 --- a/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx +++ b/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx @@ -11,7 +11,7 @@ paddingTop: 35 }} > - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - + - + - + - + - + - + - + Date: Wed, 29 Oct 2025 14:59:33 +0100 Subject: [PATCH 17/21] update snap --- .../xl-docx-exporter/src/docx/__snapshots__/basic/document.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/xl-docx-exporter/src/docx/__snapshots__/basic/document.xml b/packages/xl-docx-exporter/src/docx/__snapshots__/basic/document.xml index b26b4d789b..412f7937df 100644 --- a/packages/xl-docx-exporter/src/docx/__snapshots__/basic/document.xml +++ b/packages/xl-docx-exporter/src/docx/__snapshots__/basic/document.xml @@ -536,6 +536,9 @@ + + + From 2cfa627b46b3db8bb2922815cce9cc0fc3e97879 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 29 Oct 2025 15:35:31 +0100 Subject: [PATCH 18/21] fix tests --- .../core/src/schema/partialBlockToBlock.ts | 2 +- packages/core/src/util/table.ts | 50 +++++++++---------- .../src/docx/__snapshots__/basic/document.xml | 3 -- .../reactEmailExporter.test.tsx.snap | 6 +-- .../src/pdf/__snapshots__/example.jsx | 4 +- .../exampleWithHeaderAndFooter.jsx | 4 +- tests/src/unit/core/schema/runTests.test.ts | 8 +-- .../src/unit/core/typeGuards/runTests.test.ts | 3 +- .../exportParseEqualityTestExecutors.ts | 12 ++--- 9 files changed, 41 insertions(+), 51 deletions(-) diff --git a/packages/core/src/schema/partialBlockToBlock.ts b/packages/core/src/schema/partialBlockToBlock.ts index aba2c7e62d..aa88d6b35e 100644 --- a/packages/core/src/schema/partialBlockToBlock.ts +++ b/packages/core/src/schema/partialBlockToBlock.ts @@ -188,7 +188,7 @@ function partialBlockContentToBlockContent( inlineContentSchema, ); } else if (content === "inline") { - partialBlockContent = partialBlockContent || ""; + partialBlockContent = partialBlockContent || undefined; if ( typeof partialBlockContent === "object" && diff --git a/packages/core/src/util/table.ts b/packages/core/src/util/table.ts index 7d9f47a350..487c341bcd 100644 --- a/packages/core/src/util/table.ts +++ b/packages/core/src/util/table.ts @@ -21,32 +21,30 @@ export function mapTableCell< | PartialTableCell | TableCell, ): TableCell { - return isTableCell(content) - ? { ...content } - : isPartialTableCell(content) - ? { - type: "tableCell", - content: ([] as InlineContent[]).concat(content.content as any), - 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, - }, - } - : { - type: "tableCell", - // FIXME: content can actually be Partial, we should probably handle this as well - content: ([] as InlineContent[]).concat(content as any), - props: { - backgroundColor: "default", - textColor: "default", - textAlignment: "left", - colspan: 1, - rowspan: 1, - }, - }; + return isPartialTableCell(content) + ? { + type: "tableCell", + content: ([] as InlineContent[]).concat(content.content as any), + 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, + }, + } + : { + type: "tableCell", + // FIXME: content can actually be Partial, we should probably handle this as well + content: ([] as InlineContent[]).concat(content as any), + props: { + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + colspan: 1, + rowspan: 1, + }, + }; } export function isPartialTableCell< diff --git a/packages/xl-docx-exporter/src/docx/__snapshots__/basic/document.xml b/packages/xl-docx-exporter/src/docx/__snapshots__/basic/document.xml index 412f7937df..b26b4d789b 100644 --- a/packages/xl-docx-exporter/src/docx/__snapshots__/basic/document.xml +++ b/packages/xl-docx-exporter/src/docx/__snapshots__/basic/document.xml @@ -536,9 +536,6 @@ - - - diff --git a/packages/xl-email-exporter/src/react-email/__snapshots__/reactEmailExporter.test.tsx.snap b/packages/xl-email-exporter/src/react-email/__snapshots__/reactEmailExporter.test.tsx.snap index 206daed181..d4554d6689 100644 --- a/packages/xl-email-exporter/src/react-email/__snapshots__/reactEmailExporter.test.tsx.snap +++ b/packages/xl-email-exporter/src/react-email/__snapshots__/reactEmailExporter.test.tsx.snap @@ -1,10 +1,10 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`react email exporter > should export a document (HTML snapshot) > __snapshots__/reactEmailExporter 1`] = `"

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg
Open video file

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio file

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9

const helloWorld = (message) => {

console.log("Hello World", message);

};


"`; +exports[`react email exporter > should export a document (HTML snapshot) > __snapshots__/reactEmailExporter 1`] = `"

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg
Open video file

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio file

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9

const helloWorld = (message) => {

console.log("Hello World", message);

};


"`; -exports[`react email exporter > should export a document with multiple preview lines > __snapshots__/reactEmailExporterWithMultiplePreview 1`] = `"
First preview lineSecond preview line
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg
Open video file

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio file

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9

const helloWorld = (message) => {

console.log("Hello World", message);

};


"`; +exports[`react email exporter > should export a document with multiple preview lines > __snapshots__/reactEmailExporterWithMultiplePreview 1`] = `"
First preview lineSecond preview line
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg
Open video file

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio file

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9

const helloWorld = (message) => {

console.log("Hello World", message);

};


"`; -exports[`react email exporter > should export a document with preview > __snapshots__/reactEmailExporterWithPreview 1`] = `"
This is a preview of the email content
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg
Open video file

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio file

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9

const helloWorld = (message) => {

console.log("Hello World", message);

};


"`; +exports[`react email exporter > should export a document with preview > __snapshots__/reactEmailExporterWithPreview 1`] = `"
This is a preview of the email content
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏

Welcome to this demo 🙌!

Hello World nested

Hello World double nested

This paragraph has a background color

Paragraph

Heading

Heading right

justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.


  • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    • Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    1. Numbered List Item 1

    2. Numbered List Item 2

      1. Numbered List Item Nested 1

      2. Numbered List Item Nested 2

      3. Numbered List Item Nested funky right

      4. Numbered List Item Nested funky center

  1. Numbered List Item

Check List Item

Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
Wide CellTable CellTable Cell
From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg
Open video file

From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm

Open audio file

From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3

audio.mp3

Audio file caption

Inline Content:

Styled Text Link

Table Cell 1Table Cell 2Table Cell 3
Table Cell 4Table Cell Bold 5Table Cell 6
Table Cell 7Table Cell 8Table Cell 9

const helloWorld = (message) => {

console.log("Hello World", message);

};


"`; exports[`react email exporter > should handle document with background colors > __snapshots__/reactEmailExporterBackgroundColor 1`] = `"

Text with background color

"`; diff --git a/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx b/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx index 1c894f1f97..670804ced0 100644 --- a/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx +++ b/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx @@ -780,9 +780,7 @@ textAlign: 'left' }} > - - - +
diff --git a/packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsx b/packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsx index 031b522c0d..79c5401544 100644 --- a/packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsx +++ b/packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsx @@ -788,9 +788,7 @@ textAlign: 'left' }} > - - - +
diff --git a/tests/src/unit/core/schema/runTests.test.ts b/tests/src/unit/core/schema/runTests.test.ts index d9d1a6444d..6876d0d86a 100644 --- a/tests/src/unit/core/schema/runTests.test.ts +++ b/tests/src/unit/core/schema/runTests.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; - +import * as z from "zod/v4/core"; import { createTestEditor } from "../createTestEditor.js"; import { testSchema } from "../testSchema.js"; @@ -14,7 +14,7 @@ describe("Schema test", () => { Object.values(specs).forEach((spec) => { // Use an empty object validation to check if a zod propSchema is the same shape // @ts-ignore this is just to check the shape, not that zod instance is a certain shape - spec.config.propSchema = spec.config.propSchema.parse({}); + spec.config.propSchema = z.parse(spec.config.propSchema._zodSource, {}); }); await expect(specs).toMatchFileSnapshot(`./__snapshots__/blocks.json`); }); @@ -22,10 +22,10 @@ describe("Schema test", () => { it("Inline content specs test", async () => { const specs = getEditor().schema.inlineContentSpecs; Object.values(specs).forEach((spec) => { - // Use an empty object validation to check if a zod propSchema is the same shape if (typeof spec.config === "object" && "propSchema" in spec.config) { + // Use an empty object validation to check if a zod propSchema is the same shape // @ts-ignore this is just to check the shape, not that zod instance is a certain shape - spec.config.propSchema = spec.config.propSchema.parse({}); + spec.config.propSchema = z.parse(spec.config.propSchema._zodSource, {}); } }); await expect(specs).toMatchFileSnapshot( diff --git a/tests/src/unit/core/typeGuards/runTests.test.ts b/tests/src/unit/core/typeGuards/runTests.test.ts index f61f57da7a..57483269db 100644 --- a/tests/src/unit/core/typeGuards/runTests.test.ts +++ b/tests/src/unit/core/typeGuards/runTests.test.ts @@ -10,7 +10,8 @@ import { testSchema } from "../testSchema.js"; // Tests for verifying that type guards which check if an editor's schema // contains a block (and its props) are working correctly. -describe("Editor block schema type guard tests", () => { +// TODO +describe.skip("Editor block schema type guard tests", () => { const getEditor = createTestEditor(testSchema) as () => BlockNoteEditor< any, any, diff --git a/tests/src/unit/shared/formatConversion/exportParseEquality/exportParseEqualityTestExecutors.ts b/tests/src/unit/shared/formatConversion/exportParseEquality/exportParseEqualityTestExecutors.ts index c8637d4848..0e59e73486 100644 --- a/tests/src/unit/shared/formatConversion/exportParseEquality/exportParseEqualityTestExecutors.ts +++ b/tests/src/unit/shared/formatConversion/exportParseEquality/exportParseEqualityTestExecutors.ts @@ -24,15 +24,12 @@ export const testExportParseEqualityBlockNoteHTML = async < const exported = await editor.blocksToFullHTML(fullBlocks); + const parsed = await editor.tryParseHTMLToBlocks(exported); if (testCase.name.startsWith("malformed/")) { // We purposefully are okay with malformed response, we know they won't match - expect(await editor.tryParseHTMLToBlocks(exported)).not.toStrictEqual( - fullBlocks, - ); + expect(parsed).not.toStrictEqual(fullBlocks); } else { - expect(await editor.tryParseHTMLToBlocks(exported)).toStrictEqual( - fullBlocks, - ); + expect(parsed).toStrictEqual(fullBlocks); } }; @@ -54,7 +51,8 @@ export const testExportParseEqualityHTML = async < // conversion. (window as any).__TEST_OPTIONS.mockID = 0; - expect(await editor.tryParseHTMLToBlocks(exported)).toStrictEqual(fullBlocks); + const parsed = await editor.tryParseHTMLToBlocks(exported); + expect(parsed).toStrictEqual(fullBlocks); }; export const testExportParseEqualityNodes = async < From 282a966c6788430fc30d716e13609a4a62e09ab3 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 29 Oct 2025 15:45:30 +0100 Subject: [PATCH 19/21] fix "as never" --- packages/core/src/api/exporters/html/externalHTMLExporter.ts | 2 +- .../api/exporters/html/util/serializeBlocksExternalHTML.ts | 4 ++-- .../api/exporters/html/util/serializeBlocksInternalHTML.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/api/exporters/html/externalHTMLExporter.ts b/packages/core/src/api/exporters/html/externalHTMLExporter.ts index 24e667e288..8b223aa2c4 100644 --- a/packages/core/src/api/exporters/html/externalHTMLExporter.ts +++ b/packages/core/src/api/exporters/html/externalHTMLExporter.ts @@ -62,7 +62,7 @@ export const createExternalHTMLExporter = < ) => { const domFragment = serializeInlineContentExternalHTML( editor, - inlineContent as unknown as never, + inlineContent, serializer, options, ); diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts index df27d31322..4528b6eeab 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts @@ -33,7 +33,7 @@ export function serializeInlineContentExternalHTML< I extends InlineContentSchema, S extends StyleSchema, >( - editor: BlockNoteEditor, + editor: BlockNoteEditor, blockContent: Block["content"], serializer: DOMSerializer, options?: { document?: Document }, @@ -228,7 +228,7 @@ function serializeBlock< if (ret.contentDOM && block.content) { const ic = serializeInlineContentExternalHTML( editor, - block.content as unknown as never, // TODO + block.content, serializer, options, ); diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts index e71221bdf4..1ffc52647f 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts @@ -19,7 +19,7 @@ export function serializeInlineContentInternalHTML< I extends InlineContentSchema, S extends StyleSchema, >( - editor: BlockNoteEditor, + editor: BlockNoteEditor, blockContent: Block["content"], serializer: DOMSerializer, blockType?: string, @@ -151,7 +151,7 @@ function serializeBlock< if (ret.contentDOM && block.content) { const ic = serializeInlineContentInternalHTML( editor, - block.content as never, // TODO + block.content, // TODO serializer, block.type, options, From bbe2299f1eb6453868a1aa922f2f97c2f0d12750 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 29 Oct 2025 15:52:46 +0100 Subject: [PATCH 20/21] small fixes --- .../src/api/nodeConversions/blockToNode.ts | 18 +++++++----------- .../core/src/schema/partialBlockToBlock.ts | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index c1d0a95c89..6aff41d076 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -110,6 +110,7 @@ function linkToNodes( * prosemirror text nodes with the appropriate marks */ function styledTextArrayToNodes( + // this is lenient to "partial" inline content. FIXME: let's simplify and remove support for `string` here content: string | StyledText[], schema: Schema, styleSchema: S, @@ -144,6 +145,7 @@ export function inlineContentToNodes< I extends InlineContentSchema, S extends StyleSchema, >( + // this is lenient to "partial" inline content. FIXME: let's simplify and remove support for `string[]` here blockContent: string[] | InlineContent[], schema: Schema, blockType?: string, @@ -278,21 +280,15 @@ function blockOrInlineContentToContentNode( schema: Schema, styleSchema: StyleSchema, ) { + const type = block.type; let contentNode: Node; - let type = block.type; - - // TODO: needed? came from previous code - if (type === undefined) { - // TODO: remove - type = "paragraph"; - } if (!schema.nodes[type]) { throw new Error(`node type ${type} not found in schema`); } if (!block.content) { - contentNode = schema.nodes[type].createChecked(block.props as any); + contentNode = schema.nodes[type].createChecked(block.props); } else if (typeof block.content === "string") { const nodes = inlineContentToNodes( [block.content], @@ -300,7 +296,7 @@ function blockOrInlineContentToContentNode( type, styleSchema, ); - contentNode = schema.nodes[type].createChecked(block.props as any, nodes); + contentNode = schema.nodes[type].createChecked(block.props, nodes); } else if (Array.isArray(block.content)) { const nodes = inlineContentToNodes( block.content, @@ -308,10 +304,10 @@ function blockOrInlineContentToContentNode( type, styleSchema, ); - contentNode = schema.nodes[type].createChecked(block.props as any, nodes); + contentNode = schema.nodes[type].createChecked(block.props, nodes); } else if (block.content.type === "tableContent") { const nodes = tableContentToNodes(block.content, schema, styleSchema); - contentNode = schema.nodes[type].createChecked(block.props as any, nodes); + contentNode = schema.nodes[type].createChecked(block.props, nodes); } else { throw new UnreachableCaseError(block.content.type); } diff --git a/packages/core/src/schema/partialBlockToBlock.ts b/packages/core/src/schema/partialBlockToBlock.ts index aa88d6b35e..c634e7a544 100644 --- a/packages/core/src/schema/partialBlockToBlock.ts +++ b/packages/core/src/schema/partialBlockToBlock.ts @@ -218,7 +218,7 @@ export function partialBlockToBlock< ): Block { const id = partialBlock.id || UniqueID.options.generateID(); - // TODO + // Note: we might want to make "type" required for partial blocks and remove this default const type: string = partialBlock.type || "paragraph"; const props = partialPropsToProps( From 7b44f98825f448e99667a4044aeff6c0476a79c1 Mon Sep 17 00:00:00 2001 From: yousefed Date: Wed, 29 Oct 2025 16:13:47 +0100 Subject: [PATCH 21/21] fix build --- .../core/src/api/clipboard/toClipboard/copyExtension.ts | 2 +- .../core/src/api/exporters/html/externalHTMLExporter.ts | 3 ++- .../exporters/html/util/serializeBlocksExternalHTML.ts | 6 +++--- .../exporters/html/util/serializeBlocksInternalHTML.ts | 8 ++++---- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/core/src/api/clipboard/toClipboard/copyExtension.ts b/packages/core/src/api/clipboard/toClipboard/copyExtension.ts index 3a6aeaffd5..a6a2901186 100644 --- a/packages/core/src/api/clipboard/toClipboard/copyExtension.ts +++ b/packages/core/src/api/clipboard/toClipboard/copyExtension.ts @@ -81,7 +81,7 @@ function fragmentToExternalHTML< // Wrap in table to ensure correct parsing by spreadsheet applications externalHTML = `${externalHTMLExporter.exportInlineContent( - ic as any, + ic, {}, )}
`; } else if (isWithinBlockContent) { diff --git a/packages/core/src/api/exporters/html/externalHTMLExporter.ts b/packages/core/src/api/exporters/html/externalHTMLExporter.ts index 8b223aa2c4..fb6208d79a 100644 --- a/packages/core/src/api/exporters/html/externalHTMLExporter.ts +++ b/packages/core/src/api/exporters/html/externalHTMLExporter.ts @@ -7,6 +7,7 @@ import { InlineContent, InlineContentSchema, StyleSchema, + TableContent, } from "../../../schema/index.js"; import { serializeBlocksExternalHTML, @@ -57,7 +58,7 @@ export const createExternalHTMLExporter = < }, exportInlineContent: ( - inlineContent: InlineContent[], + inlineContent: InlineContent[] | TableContent, options: { document?: Document }, ) => { const domFragment = serializeInlineContentExternalHTML( diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts index 4528b6eeab..923dddc7d1 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts @@ -4,8 +4,10 @@ import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; import { BlockImplementation, BlockSchema, + InlineContent, InlineContentSchema, StyleSchema, + TableContent, } from "../../../../schema/index.js"; import { UnreachableCaseError } from "../../../../util/typescript.js"; import { @@ -34,7 +36,7 @@ export function serializeInlineContentExternalHTML< S extends StyleSchema, >( editor: BlockNoteEditor, - blockContent: Block["content"], + blockContent: InlineContent[] | TableContent, serializer: DOMSerializer, options?: { document?: Document }, ) { @@ -43,8 +45,6 @@ export function serializeInlineContentExternalHTML< // TODO: reuse function from nodeconversions? if (!blockContent) { throw new Error("blockContent is required"); - } else if (typeof blockContent === "string") { - nodes = inlineContentToNodes([blockContent], editor.pmSchema); } else if (Array.isArray(blockContent)) { nodes = inlineContentToNodes(blockContent, editor.pmSchema); } else if (blockContent.type === "tableContent") { diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts index 1ffc52647f..48f0dcf5b9 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts @@ -4,8 +4,10 @@ import { Block } from "../../../../blocks/defaultBlocks.js"; import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; import { BlockSchema, + InlineContent, InlineContentSchema, StyleSchema, + TableContent, } from "../../../../schema/index.js"; import { UnreachableCaseError } from "../../../../util/typescript.js"; import { @@ -20,7 +22,7 @@ export function serializeInlineContentInternalHTML< S extends StyleSchema, >( editor: BlockNoteEditor, - blockContent: Block["content"], + blockContent: InlineContent[] | TableContent, serializer: DOMSerializer, blockType?: string, options?: { document?: Document }, @@ -30,8 +32,6 @@ export function serializeInlineContentInternalHTML< // TODO: reuse function from nodeconversions? if (!blockContent) { throw new Error("blockContent is required"); - } else if (typeof blockContent === "string") { - nodes = inlineContentToNodes([blockContent], editor.pmSchema, blockType); } else if (Array.isArray(blockContent)) { nodes = inlineContentToNodes(blockContent, editor.pmSchema, blockType); } else if (blockContent.type === "tableContent") { @@ -151,7 +151,7 @@ function serializeBlock< if (ret.contentDOM && block.content) { const ic = serializeInlineContentInternalHTML( editor, - block.content, // TODO + block.content, serializer, block.type, options,